今天来实现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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。