三種改變函式內部 this 的方式:apply()、call()、bind()

三種改變函式內部 this 的方式:apply()、call()、bind()

在 JavaScript 中,改變函數內部 this 的指向常見的方式有 call()apply()bind(),而這三種方式又存在些微差異。


call()

call() 方法會呼叫(執行)一個函數,且可以同時改變函數內部的 this 指向。

語法:

1
fun.call(thisArg ,arg1, arg2, ...);

參數說明:

  • thisArg:在函式運行時的 this 指向,若省略則僅執行函式而不改變 this 指向。
  • arg1, arg2, …:傳遞的參數(可省略)。

例如:

在非嚴格模式下,函式 foo 中的 this 指向的原本是 window,因在此例子中,foo 其實是屬於 window 物件的一個方法。(若在嚴格模式下,則為 undefined)

1
2
3
4
5
6
7
8
9
var obj = {
name: 'yachen'
}

function foo(){
console.log(this);
}

foo(); // window,其實這裡的 foo() 是 window.foo()

剛提到,call() 會呼叫(執行)函式,但若無給第一個參數,則僅會呼叫函式而不會改變 this 指向,所以除了用 foo() 執行 foo 函式之外,也可以透過 call() 來呼叫(當然不需要多此一舉)。

1
foo.call();  // window

現在除了呼叫 foo 函式,同時想要將函式 foo 裡的 this 指向改為 obj,可以使用 call() 來達成。

1
foo.call(obj);  // obj

還可以傳遞一些參數。

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
name: 'yachen'
}

function foo(a, b){
console.log(this);
console.log(a + b);
}

foo.call(obj, 1, 2);
// obj
// 3

實現繼承

call() 也可以用來實現繼承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name, age){
this.name = name;
this.age = age;
this.say = function(){
console.log(`${this.name} is ${this.age} years old`);
}};

function Taiwanese(name, age){
// this 指向 me
Person.call(this, name, age);
}

var me = new Taiwanese('yachen', 18);

me.say(); // "yachen is 18 years old"


apply()

使用 apply() 會呼叫一個函數,同時可以改變函數內部 this 指向,apply()call() 幾乎一樣,最大的不同是 call() 接受一連串的參數,而 apply() 接受一組陣列(或類陣列)形式的參數。


語法:

1
fun.apply(thisArg, [argsArray]);

參數說明:

  • thisArg:在函式運行時的 this 指向。
  • [argsArray]:傳遞的參數必須為陣列或類陣列(可省略)。


bind()

bind() 能改變 this 指向,但不會呼叫(執行)函數,會 copy 並返回一個新的函數。


語法:

1
fun.bind(thisArg, arg1, arg2, ...)

參數說明:

  • thisArg:在 fun 函數運行時指定的 this 值。
  • arg1, arg2, …:傳遞其他參數(可省略)。

例如:

使用 bind() 將函式 foo 裡的 this 指向改為 obj,同時傳遞 12 兩個引數,有別於 call()apply()bind() 並不會呼叫執行 foo 函式,僅會返回一個新的函式。

1
2
3
4
5
6
7
8
9
10
var obj = {
name: 'yachen'
}

function foo(a, b){
console.log(this);
console.log(a + b);
}

foo.bind(obj, 1, 2); // 並未執行,而使返回一個新的函式

bind() 並不會呼叫執行 foo 函式,而是返回一個新的函式,若想要同時執行 foo,則需手動加上 ()。

1
2
3
foo.bind(obj, 1, 2)();  
// obj
// 3

現在來看看返回的新函式長什麼樣子,可以打開瀏覽器,並輸入以下代碼。

1
2
3
4
5
6
7
8
9
10
11
var obj = {
name: 'yachen'
}

function foo(a, b){
console.log(this);
console.log(a + b);
}

var newFoo = foo.bind(obj, 1, 2); // 並未執行,而使返回一個新的函式
console.log(newFoo);

印出 newFoo


咦? newFoo 不就是 foo 嗎?

再試著輸入以下代碼,事實證明 newFoo 不等於 foo

1
console.log(newFoo === foo);   // false


在開發中,最常使用的應該是 bind(),因為很多情況下我們只是想改變 this,不想同時執行函數。

例如現在有一個按鈕,想要在用戶點擊後,禁用此按鈕 3 秒。

1
<button>按鈕</button>

若直接在定時器裡使用匿名 function(){} 形式的 callback,定時器裡的 this 指向的是 window

1
2
3
4
5
6
7
8
9
10
11
12
const btn = document.querySelector('button');
btn.addEventListener('click', eventHandler);

function eventHandler(){
// this 指向按鈕 btn
this.disabled = true;

setTimeout(function(){
// this 指向 window
this.disabled = false;
},3000)
}

這時候 bind() 就可以派上用場了,因為不想要在改變 this 的同時執行 this.disabled = false,而是希望透過定時器 3 秒後執行,此時就不適用 call()apply()

1
2
3
4
5
6
7
8
9
10
11
const btn = document.querySelector('button');
btn.addEventListener('click', eventHandler);

function eventHandler(){
// this 指向按鈕 btn
this.disabled = true;

setTimeout(function(){
this.disabled = false;
}.bind(this),3000) // this 改指向按鈕 btn
}

當然,也可以直接將定時器裡的 callback function 改成箭頭函式(arrow function)的形式來解決。

1
2
3
4
5
6
7
8
9
10
11
12
const btn = document.querySelector('button');
btn.addEventListener('click', eventHandler);

function eventHandler(){
this.disabled = true;

// 改為箭頭函式
setTimeout(()=>{
// this 指向 btn
this.disabled = false;
},3000)
}


總結

call()apply()bind() 皆可以改變函數內部的 this ,三者的差異在於:

自動呼叫函數 參數(可省略)
call() ⭕️ arg1, arg2, …
apply() ⭕️ [arg1, arg2, …]
bind() arg1, arg2, …





參考資料:

  1. MDN - Function.prototype.call
  2. MDN - Function.prototype.apply()
  3. MDN - Function.prototype.bind()
  4. MDN - setTimeout

評論

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×