bind,call,apply的實現
阿新 • • 發佈:2020-11-24
apply、call 和 bind 實現
在前面的文章中介紹過了,apply 和bind 的作用就是顯示改變函式執行的this的繫結。
apply 和 call 是執行函式並該改變this,二者的引數有所區別
而bind則是 返回一個待執行的新函式, 當新函式執行的時候改變了this的指向。
所以,bind的情況會與apply和call不同。
- 我們知道在js裡函式是可以被new的 因此情況會比 apply 和 call 多一種情況。
- bind 是可以預設引數,
相同情況是:
這三個函式對於沒有引數 都指向的window 對於原始型別 都會包裝成物件的形式
apply的實現
Function.prototype.myapply = function(context){ context = context ? Object(context) : window; // 確定物件 var fn = Symbol(); context[fn] = this; // 繫結到當前物件上 let result; let arr = [...arguments].slice(1); // 獲取引數 // 執行函式 if (!arr.length) { result = context[fn](); } else { result = context[fn](arr); } delete context[fn]; // 刪除繫結 return result; }
call的實現
Function.prototype.mycall = function(context){ context = context ? Object(context) : window; let fn = Symbol(); // 繫結到當前物件上 context[fn] = this; // 獲取引數 let args = [...arguments].slice(1); // 執行函式 let result = context[fn](...args); // 刪除繫結 delete context[fn] return result; }
bind的實現
如果參照 apply 和 call 的想法,實現方式如下:
Function.prototype.mybind = function(context, ...perAgrs){
context = context ? Object(context) : window;
let fn = this; // 記錄函式
let fnBound = function(...args){
fn.apply(context, perAgrs.concat(args) )
}
return fnBound;
}
此時我們正常的繫結函式是沒有問題的。
但是在 new 繫結後的函式時候就會出現問題
function fn(a){
this.a = a;
}
let t1 = {a:2};
let t2 = {a:2};
let mybindFn = fn.mybind(t1);
let bindFn = fn.bind(t2);
let objmybindFn = new mybindFn(1);
let objbindFn = new bindFn(1);
let objn = new fn(1);
console.log(objmybindFn); // fn {a: 1}
console.log(objbindFn); // fnBound{}
我們會發現這兩結果是不一樣的,
objmybindFn是 fnBound例項化後的物件。
objbindFn則是 fn例項化後的物件。
其實對於objmybindFn的結果,我們也很容易理解,建構函式就是fnBound。而在fnBound的函式體內執行語句是fn.apply(context, perAgrs.concat(args) )
所以new值設定在t2上了。
可是我們發現原生bind的實現並不是這樣的。他使用的建構函式是fn, 而且他也不會改變t1。你可以認為是直接new fn(2).
到此,我們需要解決兩個問題。
- 判斷 new 還是 函式執行,從而確定
fn.apply(context, perAgrs.concat(args) )
的context是誰。 - 原型物件的改寫。
解決方法: instanceof
可以做原型鏈的檢查, 判斷當前物件是都 new 出來的。 用於確定 context是誰。- 重寫 fnBound 的原型物件(方法很多)
- 直接讓
fnBound.prototype = fn.prototype
, 這樣在改寫fnBound.prototype
時候會影響fn.prototype
fnBound.prototype = new fn()
,fnBound.prototype = Object.create(fn.prototype)
其實是 2和3 是在 1的中間多加了一個物件, 但是原型鏈卻相連線,這樣在改寫fnBound.prototype
的時候只會改寫創建出來的物件,但是訪問的時候卻可以通過原型鏈訪問到fn.prototype
。
因此注意構建出來的物件,不能覆蓋fn.prototype
上的屬性和方法。
- 直接讓
實現
Function.prototype.mybind = function(context, ...perAgrs){
context = context ? Object(context) : window;
let fn = this; // 記錄函式
let fnBound = function(...args){
fn.apply( this instanceof fnBound ? this : context, perAgrs.concat(args) )
}
fnBound.prototype = Object.create(fn.prototype);
return fnBound;
}
依然存在問題
function fn(a){
this.a = a;
}
let mybindFn = fn.mybind({a:2});
let bindFn = fn.bind({a:2});
let objmybindFn = new mybindFn(1);
let objbindFn = new bindFn(1);
let objn = new fn(1);
console.log(mybindFn.prototype) // {}
console.log(bindFn.prototype) // undefined
這裡你就會發現,其實我們實現的結果和bind還是有所差異。