本文共 1100 字,讀完只需 4 分鐘
概述
前一篇文章我們嘗試模擬實現了 call 和 apply 方法,其實 bind 函式也可以用來改變 this 的指向。bind 和 call和 apply 兩者的區別在於,bind 會返回一個被改變了 this 指向的函式。
本文介紹如何模擬實現 bind 函式:
首先觀察 bind 函式有什麼特點:
var person = {
name: 'jayChou'
}function say(age, sex) {
console.log(this.name, age, sex);
}var foo = say.bind(person, '男', 39);
foo();
// jayChou 男 39複製程式碼
- 返回一個函式
- 函式引數以逗號的形式傳入
- 改變了 this 的指向
一、call 簡單實現
既然 bind 內部也要用改變 this 指向,我們可以用現成的 call 函式來實現這一功能。
Function.prototype.newBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
} var self = this;
return function () {
return self.call(context)
}
}複製程式碼
說明一下:
if 判斷是為了校驗,只能讓函式來呼叫此方法,並丟擲錯誤。第二個 return
是為了讓返回的函式有返回值的功能。
測試一下:
var person = {
name: 'jayChou'
};
var say = function() {
console.log(this.name);
}var foo = say.newBind(person);
foo();
// jayChou複製程式碼
成功啦。
二、傳遞引數
剛才的函式是沒有傳遞引數,當然不行,所以我們想辦法把函式的引數也傳遞進去。
bind 函式有個特點,就是在繫結的時候可以傳參,返回的函式還可以繼續傳參。
var person = {
name: 'jayChou'
};
var say = function(p1, p2) {
console.log(this.name, p1, p2);
}var foo = say.bind(person, 18);
foo(20);
// jayChou 18 20複製程式碼
還可以寫成:
say.bind(person, 18)(20);
// jayChou 18 20複製程式碼
好,進入正題,考慮傳參的事。在前面的文章,我們講過 arguments 類陣列物件的一些特性,不能直接呼叫陣列的方法,但是可以用原型方法間接來呼叫,我們採用 apply 的方式來傳遞引數。
Function.prototype.newBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
} var self = this;
var args = Array.prototype.slice.call(arguments, 1);
// 間接呼叫陣列方法,獲取第一次傳的引數 return function () {
var innerArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(innerArgs))
}
}var person = {
name: 'jayChou'
};
var say = function(p1, p2) {
console.log(this.name, p1, p2);
}var foo = say.newBind(person, 18);
// 第一次傳參foo(20);
// 第二次傳參複製程式碼
最後輸出:
jayChou 18 20
結果正確,以上就完成了 bind 函式基本功能的實現。
但是!
三、作為建構函式時?
bind 函式其實還有一個非常重要的特點:
bind返回的函式如果作為建構函式,搭配new關鍵字出現的話,我們的繫結this就需要“被忽略”。
意思就是指:當使用 nuw 關鍵字把 bind 返回的函式作為建構函式,之前改變了指向的 this 就失效了。返回的函式的 this 就關聯到了建構函式的例項物件上。
針對這個特點,需要再做一些修改:
Function.prototype.newBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
} var self = this;
var args = Array.prototype.slice.call(arguments, 1);
// 間接呼叫陣列方法,獲取第一次傳的引數 let tempFn = function {
};
// 利用一個空函式作為中轉 tempFn.prototype = this.prototype;
// 修改返回函式的 prototype 為繫結函式的 prototype,例項就可以繼承繫結函式的原型中的值 var resultFn = function () {
var innerArgs = Array.prototype.slice.call(arguments);
if (this instanceof tempFn) {
// 如果 返回函式被當做建構函式後,生成的物件是 tempFn 的例項,此時應該將 this 的指向指向建立的例項。 return self.apply(this, args.concat(innerArgs));
} else {
return self.apply(context, args.concat(innerArgs))
}
} resultFn = new tempFn();
return resultFn;
}複製程式碼
總結
本文嘗試模擬實現了 bind 函式,bind 函式與 call,apply 函式的區別在於,bind 函式返回一個指定了 this 的函式,函式並未執行。其次,當返回的函式作為建構函式時,之前繫結的 this 會失效。
歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。