bind
、call
和 apply
都是 JavaScript 中用於改變函式執行上下文(即函式內部的 this
指向)的方法,它們的主要區別在於:
call
: 立即呼叫函式,並接受引數列表作為後續引數。apply
: 立即呼叫函式,並接受一個引數陣列作為引數。bind
: 建立一個新的函式,該函式的this
值被繫結到指定的值,並可以預先設定一些引數。它不會立即執行原函式,而是返回一個新的函式,可以在之後呼叫。
更詳細的解釋:
-
call(thisArg, arg1, arg2, ...)
: 第一個引數thisArg
是要繫結到函式的this
值。後續引數arg1
,arg2
等是傳遞給函式的引數。 -
apply(thisArg, [arg1, arg2, ...])
: 第一個引數thisArg
同call
一樣。第二個引數是一個陣列(或類陣列物件),包含傳遞給函式的引數。 -
bind(thisArg, arg1, arg2, ...)
: 第一個引數thisArg
同call
和apply
。後續引數arg1
,arg2
等是預先設定給函式的引數。bind
返回一個新的函式,當這個新函式被呼叫時,預先設定的引數會先於新函式呼叫時傳入的引數傳遞給原函式。
手寫實現 bind
方法:
Function.prototype.myBind = function(thisArg, ...boundArgs) {
const originalFunction = this; // 儲存原函式
// 返回一個新函式
return function(...args) {
// 合併預設引數和呼叫時傳入的引數
const allArgs = boundArgs.concat(args);
// 使用 apply 呼叫原函式,並設定 this 值和引數
return originalFunction.apply(thisArg, allArgs);
};
};
// 測試用例:
function greet(greeting, name) {
console.log(`${greeting}, ${name}! I am ${this.title}`);
}
const person = {
title: "Professor"
};
const greetPerson = greet.myBind(person, "Hello");
greetPerson("John"); // 輸出: Hello, John! I am Professor
const greetPerson2 = greet.myBind(person, "Hi", "Jane");
greetPerson2(); // 輸出: Hi, Jane! I am Professor
const greetPerson3 = greet.myBind(person);
greetPerson3("Good morning", "David"); // 輸出: Good morning, David! I am Professor
關鍵點解釋:
-
儲存原函式:
const originalFunction = this;
儲存對原函式的引用,以便在新函式內部呼叫。 -
返回新函式:
bind
的核心是返回一個新函式,這個新函式會在之後被呼叫。 -
合併引數:
boundArgs.concat(args)
將預設引數boundArgs
和呼叫新函式時傳入的引數args
合併成一個陣列allArgs
。 -
使用
apply
呼叫原函式:originalFunction.apply(thisArg, allArgs)
使用apply
方法呼叫原函式,並將this
值設定為thisArg
,引數設定為合併後的引數陣列allArgs
。
這個手寫實現涵蓋了 bind
的核心功能。一些更完善的實現還會處理一些邊緣情況,例如:
new
運算子: 當使用new
運算子呼叫繫結函式時,繫結的this
值會被忽略。- 返回函式的
prototype
: 需要正確設定返回函式的prototype
屬性。
這個更完善的版本,可以參考MDN的polyfill:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to throwing a TypeError in IE6-8
// since it doesn't support accessing the 'caller' property
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis || this,