1、簡單說一下bind、call、apply的區別
三者都是用於改變函式體內this的指向,但是bind與apply和call的最大的區別是:bind不會立即呼叫,而是返回一個新函式,稱為繫結函式,其內的this指向為建立它時傳入bind的第一個引數,而傳入bind的第二個及以後的引數作為原函式的引數來呼叫原函式。
var obj = {};
function test() {
console.log(this === obj);
}
test(); //false
var testObj = test.bind(obj);
testObj(); //true
apply和call都是為了改變某個函式執行時的上下文而存在的(就是為了改變函式內部this的指向);apply和call的呼叫返回函式執行結果;
如果使用apply或call方法,那麼this指向他們的第一個引數,apply的第二個引數是一個引數陣列,call的第二個及其以後的引數都是陣列裡面的元素,就是說要全部列舉出來;
以下是MDN文件:
bind語法:
func.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg 當繫結函式被呼叫時,該引數會作為原函式執行時的this指向。當使用new 操作符呼叫繫結函式時,該引數無效。
arg1, arg2, ... 當繫結函式被呼叫時,這些引數將置於實參之前傳遞給被繫結的方法。
call語法:
fun.call(thisArg, arg1, arg2, ...)
thisArg::在fun函式執行時指定的this值。需要注意的是,指定的this值並不一定是該函式執行時真正的this值,如果這個函式處於非嚴格模式下,則指定為null和undefined的this值會自動指向全域性物件(瀏覽器中就是window物件),同時值為原始值(數字,字串,布林值)的this會指向該原始值的自動包裝物件。
arg1, arg2, ... 指定的引數列表。
apply語法:
fun.apply(thisArg, [argsArray])
thisArg: 在 fun 函式執行時指定的 this 值。需要注意的是,指定的 this 值並不一定是該函式執行時真正的 this 值,如果這個函式處於非嚴格模式下,則指定為 null 或 undefined 時會自動指向全域性物件(瀏覽器中就是window物件),同時值為原始值(數字,字串,布林值)的 this 會指向該原始值的自動包裝物件。
argsArray: 一個陣列或者類陣列物件,其中的陣列元素將作為單獨的引數傳給 fun 函式。如果該引數的值為null 或 undefined,則表示不需要傳入任何引數。從ECMAScript 5 開始可以使用類陣列物件。
區別總結:
當我們使用一個函式需要改變this指向的時候才會用到call,apply,bind
如果你要傳遞的引數不多,則可以使用fn.call(thisObj, arg1, arg2 ...)
如果你要傳遞的引數很多,則可以用陣列將引數整理好呼叫fn.apply(thisObj, [arg1, arg2 ...])
如果你想生成一個新的函式長期繫結某個函式給某個物件使用,則可以使用
const newFn = fn.bind(thisObj);
newFn(arg1, arg2...)
2、bind、call、apply的實現
myBind:
Function.prototype.myBind = function() {
var _this = this;
var context = [].shift.call(arguments);// 儲存需要繫結的this上下文
var args = [].slice.call(arguments); //剩下引數轉為陣列
console.log(_this, context, args);
return function() {
return _this.apply(context, [].concat.call(args, [].slice.call(arguments)));
}
};
myCall:
/**
* 每個函式都可以呼叫call方法,來改變當前這個函式執行的this關鍵字,並且支援傳入引數
*/
Function.prototype.myCall = function(context) {
//第一個引數為呼叫call方法的函式中的this指向
var context = context || global;
//將this賦給context的fn屬性
context.fn = this;//此處this是指呼叫myCall的function
var arr = [];
for (var i=0,len=arguments.length;i<len;i++) {
arr.push("arguments[" + i + "]");
}
//執行這個函式,並返回結果
var result = eval("context.fn(" + arr.toString() + ")");
//將this指向銷燬
delete context.fn;
return result;
}
myApply:
/**
* apply函式傳入的是this指向和引數陣列
*/
Function.prototype.myApply = function(context, arr) {
var context = context || global;
context.fn = this;
var result;
if (!arr) {
result = context.fn(); //直接執行
} else {
var args = [];
for (var i=0,len=arr.length;i<len;i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn([" + args.toString() + "])");
}
//將this指向銷燬
delete context.fn;
return result;
}
以上是bind、apply、和call的模擬實現
注意:繫結函式(bind函式返回的新函式)不可以再通過apply和call改變其this指向,即當繫結函式呼叫apply和call改變其this指向時,並不能達到預期效果。
var obj = {};
function test() {
console.log(this === obj);
}
var testObj = test.bind(obj);
testObj(); //true
var objTest = {
"作者": "chengbo"
};
/**
* 預期返回false, 但是testObj是個繫結函式,所以不能改變其this指向
*/
testObj.apply(objTest); //true
testObj.call(objTest); //true
歡迎留言。
原文連結:《bind、call、apply的區別與實現原理》