今天來實現JavaScript的bind函式。
首先看MDN的bind函式描述:
從上面可以看出來,var A = B.bind(this)函式其實幹了這幾件事情:
- 返回一個函式,且這個函式後面執行時的this就是bind(this)傳入的this
- 接收引數,這些引數(如果有的話)作為bind()的第二個引數跟在this(或其他物件)後面,之後它們會被插入到目標函式的引數列表的開始位置,傳遞給繫結函式的引數會跟在它們的後面
- 使用new操作bind函式返回的函式時,之前傳入的this會被忽略,也就是說new的優先順序高於bind
第一步
首先實現第一步:
Function.prototype.Zbind = function (othis) {
var originFunc = this;
return function () {
originFunc.apply(othis);
}
}
var obj = {
}
function createAgumon() {
this.name = "agumon";
}
var createAgumonBind = createAgumon.Zbind(obj);
createAgumonBind();
obj;// {name: "agumon"}
複製程式碼
第二步
第二步考慮傳參的問題,首先看看原生的bind函式是如何傳參的:
var obj = {
}
function createAgumon(gender, age) {
this.name = "agumon";
this.gender = gender;
this.age = age;
}
var createAgumonBind = createAgumon.bind(obj, `female`);
createAgumonBind(22);
複製程式碼
可以看出來在bind函式中能先傳部分引數,執行bind返回的函式時可以再傳入部分引數。
自己實現:
Function.prototype.Zbind = function (othis) {
var originFunc = this;
var partArgs = [].slice.call(arguments, 1);
var func = function() {};
var boundFunc = function () {
var finalArgs = partArgs.concat([].slice.call(arguments));
return originFunc.apply(othis, finalArgs);
}
return boundFunc;
}
var obj = {
}
function createAgumon(gender, age) {
this.name = "agumon";
this.gender = gender;
this.age = age;
}
var createAgumonBind = createAgumon.Zbind(obj, `female`);
createAgumonBind(22);
obj;// {name: "agumon", gender: "female", age: 22}
複製程式碼
第三步
使用new來呼叫bind返回的函式時,會忽略bind傳入的this
new操作和普通的函式呼叫有哪些區別?
粗略的來講,例如new F()這樣的呼叫,有以下幾個步驟:
- 新建一個物件,var o = new Object()
- 設定原型鏈,o.proto = F.prototype
- 把F函式體內的this繫結為o,並且執行F函式的程式碼
- 判斷F的返回型別:
如果是值型別,則返回o
如果是引用型別,則返回該引用型別物件
開始實現:
Function.prototype.Zbind = function (othis) {
var originFunc = this;
var partArgs = [].slice.call(arguments, 1);
var func = function() {};
var boundFunc = function () {
var finalArgs = partArgs.concat([].slice.call(arguments));
return originFunc.apply(this instanceof boundFunc ? this : othis, finalArgs);
}
return boundFunc;
}
var obj = {}
function createAgumon(gender, age) {
this.name = "agumon";
this.gender = gender;
this.age = age;
}
var createAgumonBind = createAgumon.Zbind(obj, `female`);
var newObj = new createAgumonBind(22);
obj // {}
newObj // {name: "agumon", gender: "female", age: 22}
複製程式碼
關鍵的地方在於這裡:this instanceof boundFunc ? this : othis,如果是new操作的話,此時this的__proto__已經指向了boundFunc,所以使用instanceof可以檢測出是否在使用new操作
小細節
原型丟失
剛剛實現的Zbind方法有個小問題:
Function.prototype.Zbind = function (othis) {
var originFunc = this;
var partArgs = [].slice.call(arguments, 1);
var func = function() {};
var boundFunc = function () {
var finalArgs = partArgs.concat([].slice.call(arguments));
return originFunc.apply(this instanceof boundFunc ? this : othis, finalArgs);
}
return boundFunc;
}
var obj = {
}
function createAgumon(gender, age) {
this.name = "agumon";
this.gender = gender;
this.age = age;
}
createAgumon.prototype.college = `THU`
var createAgumonBind = createAgumon.Zbind(obj, `female`);
var newObj = new createAgumonBind(22);
console.log(newObj.college)// undefined
複製程式碼
可以看出來原型鏈丟失了,newObj.college得是`THU`才行
修改:
Function.prototype.Zbind = function (othis) {
var originFunc = this;
var partArgs = [].slice.call(arguments, 1);
var func = function() {};
var boundFunc = function () {
var finalArgs = partArgs.concat([].slice.call(arguments));
return originFunc.apply(this instanceof boundFunc ? this : othis, finalArgs);
}
func.prototype = originFunc.prototype;
boundFunc.prototype = new func();
return boundFunc;
}
var obj = {
}
function createAgumon(gender, age) {
this.name = "agumon";
this.gender = gender;
this.age = age;
}
createAgumon.prototype.college = `THU`
var createAgumonBind = createAgumon.Zbind(obj, `female`);
var newObj = new createAgumonBind(22);
console.log(newObj.college)// `THU`
複製程式碼
為什麼要使用func.prototype = originFunc.prototype;boundFunc.prototype = new func();,而不是直接用**boundFunc.prototype = originFunc.prototype;**是因為這樣寫的話,修改boundFunc.prototype會影響到原函式的prototype。
that`all