這篇將會介紹 bind() 的一些使用範例和實作一個簡單版本的 bind()。
傳入參數和 call() 一樣,但是 bind() 會回傳一個新的函式。
const character = {
name: "Simon",
getIntro(introSentence) {
return `${introSentence} ${this.name}.`;
}
};
const getBindChar = character.getIntro.bind(character, 'My name is');
console.log(getBindChar()); // Simon
另外 bind() 也蠻適合搭配 currying、Partial application 等觀念一起使用,不過這內容較多,將會放在明天的文章做解說。
透過實作 bind(),幫助自己更了解這個函式的運作及特性。
首先會針對兩個重點去思考:
像以下使用了原生的 bind(),this 就指向了 person 物件,並且回傳一個函式 result;
function showName(...args) {
console.log(`${args} ${this.name}.`);
}
const person = { name: 'Harry' };
const result = showName.bind(person, 'My name is');
result(); // 'My name is Harry.'
根據上面思考的兩點,我們可以寫出初步版本的 bind():
Function.prototype.myBind = function (thisArg, ...args) {
const that = this;
return function () {
return that.call(thisArg, ...args);
// 等於 showName.call(person, "My name is");,this 指向 thisArg,也就是 person 物件
};
};
同時這個版本的 bind() 也能處理下面兩點:
const result = showName.myBind(null, 'My name is');
result(); // My name is .
// 在 Codepen 上則是出現 "My name is CodePen."
const isNumber = (obj) => Object.prototype.toString.myBind(obj)() === '[object Number]';
console.log(isNumber(2)); // true
接著要考慮的是因為 bind() 會回傳一個函式,若那個回傳的函式被當作建構函式使用時,bind 綁定的 this 會依照 new 關鍵字調用 this(Day 8 文章中 this 指向情境的第四點)。
以下是用原生 bind() 執行後的結果:
const person = { name: "Harry" };
function createPerson(...args) {
this.skill = "programming";
console.log(...args); // 'My name is' 24 172
console.log(this); // { skill: 'programming' }
}
createPerson.prototype.race = "Asian";
const createPersonConstructor = createPerson.bind(person, "My name is");
const member = new createPersonConstructor(24, 172);
console.log(member); // { skill: 'programming' }
console.log(member.race); // 'Asian'
但是當前第一個版本的 myBind() 函式印出的結果如下:
const person = { name: "Harry" };
function createPerson(...args) {
this.skill = "programming";
console.log(...args); // 'My name is'
console.log(this); // { name: 'Harry', skill: 'programming' }
}
createPerson.prototype.race = "Asian";
Function.prototype.myBind = function (thisArg, ...args) {
const that = this;
return function () {
return that.call(thisArg, ...args);
};
};
const createPersonConstructor = createPerson.myBind(person, "My name is");
const member = new createPersonConstructor(24, 172);
console.log(member); // {}
console.log(member.race); // undefined
這個結果顯然和原生 bind() 不一樣!所以要進行一些調整。
這個版本將 returnFn 函式回傳的程式碼改寫成 return that.call(this instanceof returnFn ? this : thisArg, ...args);
,透過 instanceof 去判斷回傳的函式是被當作建構函式還是一般的函式。
this instanceof returnFn
為 true,所以就維持原樣,this 做為第一個參數this instanceof returnFn
為 false,將傳入 bind() 的第一個參數 thisArg 做為 this 的指向調整後如下:
Function.prototype.myBind = function (thisArg, ...args) {
const that = this;
const returnFn = function () {
return that.call(this instanceof returnFn ? this : thisArg, ...args);
};
return returnFn;
};
經過上個版本的調整後,會發現印出來的值不太一樣了,createPerson 函式內的 this 指向和使用 bind() 時一樣,console.log(member);
印出的結果也和使用 bind() 時一樣。
不過 createPerson 函式內 console.log(...args);
不一樣,原因是因為沒有處理到建構函式傳入的參數,以範例來說就是 new createPersonConstructor(24, 172);
裡面的兩個參數。
const person = { name: "Harry" };
function createPerson(...args) {
this.skill = "programming";
console.log(...args); // 'My name is'
console.log(this); // { skill: 'programming' }
}
createPerson.prototype.race = "Asian";
Function.prototype.myBind = function (thisArg, ...args) {
const that = this;
const returnFn = function () {
return that.call(this instanceof returnFn ? this : thisArg, ...args);
};
return returnFn;
};
const createPersonConstructor = createPerson.myBind(person, "My name is");
const member = new createPersonConstructor(24, 172);
console.log(member); // { skill: 'programming' }
console.log(member.race); // undefined
所以我們來做些調整,使用 concat 將 bind() 的參數和建構函式的參數做合併。
Function.prototype.myBind = function (thisArg, ...args) {
const that = this;
const returnFn = function (...constructorArgs) {
return that.call(
this instanceof returnFn ? this : thisArg,
...args.concat([...constructorArgs])
);
};
return returnFn;
};
這樣在 createPerson 就可以取到兩個函式的參數。
最後就是處理原型的問題,因為返回函式的原型 createPersonConstructor 不是 createPerson,所以在 member 物件查找 race 屬性會找不到。
createPerson.prototype.race = "Asian";
// 第三個版本的 myBind() 函式
console.log(member.race); // undefined
// 原生 bind():
console.log(member.race); // 'Asian'
這裡將 returnFn 的 prototype 修改為綁定函式 createPerson 的 prototype,returnFn 就可以透過原型鏈找到 race 屬性。
Function.prototype.myBind = function (thisArg, ...args) {
const that = this;
const returnFn = function (...constructorArgs) {
return that.call(
this instanceof returnFn ? this : thisArg,
...args.concat([...constructorArgs])
);
};
returnFn.prototype = Object.create(this.prototype);
return returnFn;
};
bind() 的實作就到這邊,不過實作完我相信還有可以更優化的地方,所以也歡迎讀者不吝的提出,謝謝!
這篇也提到了一些繼承的觀念,包括 instanceof 和 Object.create() 函式,在後面的篇章也會介紹到~敬請期待!
Implement your own — call(), apply() and bind() method in JavaScript
英雄所見略同? 你可能不知道的Function.prototype.bind()
我也有嘗試實現一個myBind()
或許可以相互參考~
我來看看哩~