手寫call、apply、bind
關於this
的指向性和執行上下文等問題本人也寫過相關文章對其進行詳細的解析,但是這些文章中好像遺忘了什麼?對,那就是如何改變this
指向的作用域,所以也就引出今天的主角——call
、apply
、bind
;這「三兄弟」是Function
原型上的三個改變調用函數作用域/上下文的函數,具體如何使用可以直接查看MDN,今天只研究如何實現這三種方法。
一、call的實現
call()
方法:讓call()中的對象調用當前對象所擁有的function。例如:test.call(obj,arg1,arg2,···) 等價於 obj.test(arg1,arg2,···)
;在手寫實現call()
方法前我們先進行分析,test
調用call
方法可以看作將test
方法作為obj
的一個屬性(方法)調用,等obj.test()
執行完畢後,再從obj
屬性上刪除test
方法:
- 1、將函數設置為對象的屬性;
- 2、處理傳入的參數;
- 3、執行對象上設置的函數;
- 4、刪除對象上第一步設置的函數;
myCall:
function test(a, b) {
console.log(a);
console.log(b);
console.log(this.c);
}
let obj = {
c: "hello",
};
//myCall
Function.prototype.myCall = function () {
//聲明傳入上下文為傳入的第一個參數,如果沒有傳參默認為global(node環境),如果是瀏覽器環境則為 window;
let context = arguments[0] || global;
//將調用myCall方法函數(this)設置為 聲明的傳入上下文中的fn函數;
context.fn = this;
//對函數參數進行處理
var args = [];
for (let index = 0; index < arguments.length; index++) {
index > 0 && args.push(arguments[index]);
}
//執行fn,也就是調用myCall方法的函數
context.fn(...args);
//執行完畢後刪除傳入上下文的fn,不改變原來的對象
delete context.fn;
};
test.myCall(obj, "a", 123);
console.log(obj)
列印的結果:
a
123
hello
{ c: 'hello' }
從結果可以看出:test
中this.c
輸出為hello
,說明this
為obj
;最後輸出的obj
也沒有改變。
二、apply的實現
apply()
方法作用和call()
完全一樣,只是apply
的參數第一個為需要指向的對象,第二個參數以數組形式傳入。例如:test.apply(obj,[arg1,arg2,···]) 等價於 obj.test(arg1,arg2,···)
;
myApply:
//myApply
Function.prototype.myApply = function(){
let context = arguments[0] || global;
context.fn = this;
var args = arguments.length > 1 ? arguments[1] : [];
context.fn(...args);
delete context.fn;
}
test.myApply(obj, ["world", 123]);
console.log(obj)
列印的結果:
world
123
hello
{ c: 'hello' }
三、bind的實現
bind
方法:創建一個新的函數, 當被調用時,將其this關鍵字設置為提供的值,在調用新函數時,在任何提供之前提供一個給定的參數序列。例如:let fn = test.bind(obj,arg1,arg2,···); fn() 等價於 let fn = obj.test; fn(arg1,arg2,···)
;實現思路為:
- 1、將函數設置為對象的屬性;
- 2、處理傳入的參數;
- 3、返回函數的定義/引用;
- 4、外層執行接收的函數;
- 5、刪除對象上第一步設置的函數;
myBind:
Function.prototype.myBind = function(){
//1.1、聲明傳入上下文為傳入的第一個參數,如果沒有傳參默認為global(node環境),如果是瀏覽器環境則為 window;
let context = arguments[0] || global;
//1.2、將調用myBind方法函數(this)設置為 聲明的傳入上下文中的fn函數;
context.fn = this;
//2.1、對調用myBind的函數參數進行處理
var args = [];
for (let index = 0; index < arguments.length; index++) {
index > 0 && args.push(arguments[index]);
}
//3、聲明和定義函數變數F,用於返回給外層
let F = function (){
//2.2、對再次傳入的參數進行處理,追加到
for (let index = 0; index < arguments.length; index++) {
args.push(arguments[index]);
}
//4.2、執行實際的調用myBind方法函數
context.fn(...args);
//5、執行完畢後刪除傳入上下文的fn,不改變原來的對象
delete context.fn;
}
return F;
}
var f = test.myBind(obj, "a")
//4.1、執行返回的函數
f(9527);
列印的結果:
a
9527
hello
{ c: 'hello' }
四、總結
關於實現js中一些常見的方法屬於面試中的常問問題,可能剛開始接觸的時候會一籌莫展。知道和理解其中的原理能夠在日常開發中更如魚得水,面對面試也不成問題。另外,學會以目的(實現的功能)為導向一層一層反推,總結出實現的思路就能按照步驟直接實現或者曲線實現。