JavaScript 中的 apply、call、bind

2023-02-23 12:00:36

一、常規

在 JavaScript 中,apply、call、bind 是三個與函數呼叫相關的方法,它們都允許你在呼叫函數時手動設定函數的上下文(即 this 指向)。

1、apply 方法:apply 方法允許你呼叫一個函數,並且手動設定函數的上下文(即 this 指向)以及傳遞一個引數陣列。其語法如下:

function.apply(thisArg, [argsArray])

其中,thisArg 表示函數要繫結的上下文,argsArray 是一個可選的陣列,其中包含將傳遞給函數的參數列。

例如,以下程式碼會將陣列 [1, 2, 3] 作為引數傳遞給函數 foo,並且將函數的上下文設定為物件 obj:

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

var obj = {
  x: 1,
  y: 2,
  z: 3
};

foo.apply(obj, [1, 2, 3]); // 輸出 6

2、call 方法:call 方法與 apply 方法類似,也是允許你呼叫一個函數,並且手動設定函數的上下文(即 this 指向),但是它需要你手動傳遞一個參數列,而不是一個引數陣列。其語法如下:

function.call(thisArg, arg1, arg2, ...)

其中,thisArg 表示函數要繫結的上下文,arg1、arg2、... 是將傳遞給函數的參數列。

例如,以下程式碼會將引數 1、2、3 傳遞給函數 foo,並且將函數的上下文設定為物件 obj:

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

var obj = {
  x: 1,
  y: 2,
  z: 3
};

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

3、bind 方法:bind 方法與 apply、call 方法不同,它並不會立即呼叫函數,而是會返回一個新的函數,並且這個新函數的上下文(即 this 指向)被永久地繫結到了指定的物件上。其語法如下:

function.bind(thisArg, arg1, arg2, ...)

其中,thisArg 表示函數要繫結的上下文,arg1、arg2、... 是一些可選的引數,這些引數將在呼叫新函數時作為參數列傳遞給原函數。

例如,以下程式碼會將函數 foo 的上下文繫結到物件 obj 上,並返回一個新函數 newFoo,當呼叫 newFoo 時,它的引數將被傳遞給原函數 foo:

function foo(a, b, c) {
  console.log(a + b + c + this.x + this.y + this.z);
}

var obj = {
  x: 1,
  y: 2,
  z: 3
};

var newFoo = foo.bind(obj, 1, 2, 3);
newFoo(); // 輸出 12

 

下面是一個範例,演示了使用 apply、call、bind 方法的一些特性:

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

var obj1 = {
  x: 1,
  y: 2
};

var obj2 = {
  x: 3,
  y: 4
};

// 使用 apply 方法呼叫函數 foo,並將 obj1 作為上下文
foo.apply(obj1, [3, 4]); // 輸出 10

// 使用 call 方法呼叫函數 foo,並將 obj2 作為上下文
foo.call(obj2, 5, 6); // 輸出 18

// 使用 bind 方法系結函數 foo 的上下文為 obj1,並建立一個新函數 newFoo
var newFoo = foo.bind(obj1, 7);
newFoo(8); // 輸出 18

// 使用 bind 方法系結函數 foo 的上下文為 obj2,並建立一個新函數 newFoo2
var newFoo2 = foo.bind(obj2);
newFoo2(9, 10); // 輸出 26

二、特殊

1、如果在使用 apply、call、bind 方法時沒有傳遞 thisArg 引數或者傳遞了 null 或 undefined,那麼預設的上下文將是全域性物件(在瀏覽器環境中,通常是 window 物件)。

function foo() {
  console.log(this === window); // 在瀏覽器環境中,輸出 true
}

// 使用 apply 方法呼叫函數 foo,沒有傳遞 thisArg 引數
foo.apply(); // 輸出 true

// 使用 call 方法呼叫函數 foo,傳遞了 null 作為 thisArg 引數
foo.call(null); // 輸出 true

// 使用 bind 方法系結函數 foo 的上下文為 null,並建立一個新函數 newFoo
var newFoo = foo.bind(null);
newFoo(); // 輸出 true

2、如果使用 bind 方法系結了函數的上下文後,再使用 apply 或 call 方法呼叫這個函數,那麼繫結的上下文將會被忽略,仍然使用傳遞給 bind 方法的上下文。

var obj1 = {
  x: 1,
  y: 2
};

var obj2 = {
  x: 3,
  y: 4
};

function foo() {
  console.log(this.x + this.y);
}

// 使用 bind 方法系結函數 foo 的上下文為 obj1,並建立一個新函數 newFoo
var newFoo = foo.bind(obj1);

// 使用 apply 方法呼叫新函數 newFoo,並將 obj2 作為上下文
newFoo.apply(obj2); // 輸出 3

// 使用 call 方法呼叫新函數 newFoo,並將 obj2 作為上下文
newFoo.call(obj2); // 輸出 3

在這個範例中,先使用 bind 方法將函數 foo 的上下文繫結為 obj1,並建立了一個新函數 newFoo。然後,分別使用 apply 和 call 方法呼叫新函數 newFoo,並將 obj2 作為上下文。由於使用了 bind 方法系結了上下文,無論傳遞了什麼引數,都不會改變繫結的上下文。這就是繫結的上下文被忽略的情況。最終輸出的結果為 3,而不是 7。

3、如果使用 bind 方法系結了函數的上下文後,再使用 new 操作符建立範例,那麼繫結的上下文將被忽略,而是建立一個新的物件作為 this,並且原函數中的 this 將會指向這個新物件。

function Foo() {
  this.x = 1;
  this.y = 2;
}

var obj = {
  x: 3,
  y: 4
};

// 使用 bind 方法系結函數 Foo 的上下文為 obj,並建立一個新函數 NewFoo
var NewFoo = Foo.bind(obj);

// 使用 new 操作符建立範例,此時會忽略繫結的上下文 obj,而是建立一個新物件作為 this
var newObj = new NewFoo();

console.log(newObj.x); // 輸出 1
console.log(newObj.y); // 輸出 2

console.log(obj.x); // 輸出 3
console.log(obj.y); // 輸出 4

在這個範例中,先使用 bind 方法將函數 Foo 的上下文繫結為 obj,並建立了一個新函數 NewFoo。然後,使用 new 操作符建立範例,此時會忽略繫結的上下文 obj,而是建立一個新物件作為 this。因此,範例 newObj 中繼承了繫結函數 Foo 中定義的屬性 x 和 y,輸出 1 和 2。同時,原函數中的 this 指向了新建立的物件 newObj,因此在原函數中定義的屬性 x 和 y 也被新增到了新物件上。

 

綜上所述,apply、call、bind 方法都是用於手動設定函數的上下文(即 this 指向),在實際開發中也經常被使用。對於這三個方法,需要了解它們的使用方法、語法和一些注意事項,以便在開發中更加靈活地使用它們。