淺談JavaScript中的apply、call和bind

是小小白啊發表於2018-12-21

摘要

  • 三種方法均可改變函式this關鍵字的指向。
  • apply()接受一引數陣列,返回函式執行的結果。
  • call()接受一組引數,返回函式執行的結果。
  • bind()接受一組引數,返回函式體。需在bind()後加小括號才能執行函式。
  • 箭頭函式的this繫結後無論使用apply()call()還是bind()都不可修改。

淺析this關鍵字

  • JavaScript中的函式存在定義上下文執行上下文,通過call()apply()bind()可以改變this的指向。
  • this總指向執行上下文

定義上下文

定義上下文更準確的名稱應該叫詞法作用域,它指函式的定義部分所形成的作用域。

function fun1 () {
	// 函式fun1的詞法作用域
	// 函式定義
	// ....
}
複製程式碼

執行上下文

函式在呼叫時會產生一個呼叫記錄,其中包含函式在哪裡被呼叫、傳入函式的引數等資訊。該記錄也被稱為執行上下文。一個函式的this總指向函式的執行上下文。當fun1fun2中呼叫時,fun1this指向fun2的定義上下文(詞法作用域)。

function fun2 () {
    // fun1的this指向該作用域
    fun1();
    // 函式定義
    // ...
}
複製程式碼

this的指向

this是執行上下文的一個屬性,因此常說this指向函式的執行上下文
this是在函式呼叫時被繫結的,與函式的宣告位置(即詞法作用域)沒有任何關係。


call()apply()

call()

call()方法的第一個引數為要指定的this物件,第二個引數及以後為函式執行所需的引數列表。

let obj = {
    a: 1
}

function fun1 (num1, num2) {
    console.log(this); // obj {a: 1}
    console.log(num1 + num2); // 3
    console.log(this.a); // 1
}

fun1.call(obj, 1, 2);
複製程式碼

上述程式碼展示了call()的兩種能力。一方面它改變了fun1呼叫時的this關鍵字,讓其指向obj,並通過this關鍵字來訪問obj中的屬性。另一方面它將自己接受到的引數傳入fun1

apply()

call()apply()本質上並無太大差別,唯一的區別在於call()接受的是引數列表,而apply()接受的是一個引數陣列。

    // ··· 同上
    fun1.apply(obk, [1, 2]); // 效果和fun1.call(obj, 1, 2)相同
複製程式碼

bind()

bind()建立一個新的函式, 當這個新函式被呼叫時this鍵值為其提供的值,其引數列表前幾項值為建立時指定的引數序列。          ----- MDN

bind()的使用方法和call()十分類似,它的第一個引數是需要繫結的this物件,之後的引數為函式執行所需的引數列表。下面讓我們來試驗一下。

let obj = {
    a: 1
}

function fun1 (num1, num2) {
    console.log(this);
    console.log(num1 + num2);
    console.log(this.a);
}

fun1.bind(obj, 1, 2); // fun1 { ··· }
複製程式碼

不同於apply()call()bind()返回的並不是fun1執行完畢的返回值,而是更改了this並初始化引數之後的fun1的函式定義。因此,要執行fun1,需在bind()後面再加一對括號:

fun1.bind(obj, 1, 2)(); // 改變this並傳入引數後執行fun1
複製程式碼

apply()call()bind()的比較

相同點

  • 均可改變函式this關鍵字的指向。

不同點

  • apply()接受一引數陣列,返回函式執行的結果。
  • call()接受一組引數,返回函式執行的結果。
  • bind()接受一組引數,返回函式體。需在bind()後加小括號才能執行函式。

箭頭函式的this繫結

ES6中新加入了箭頭函式,它的繫結完全繼承自呼叫它的作用域。繫結後無論使用apply()call()還是bind()都不可修改

相關文章