javascript 原生bind方法实现
bind方法可以用来给一个方法绑定上下文环境对象,以及重新给方法传参数。
bind的另一个简单使用是使一个函数拥有预设的初始参数。我们称为偏函数
function list() {
return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);
var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
由于bind方法在并不是在所有的浏览器上都支持,因此我们考虑自己实现bind方法。
首先我们可以给目标函数指定作用域来简单实现bind
Function.prototype.bind = function(context){
self = this;
return function(){
return self.apply(context, arguments);
}
}
这样实现的bind方法就只能接受一个上下文环境变量的参数,不能同时接受参数。因此我们修改一下。
Function.prototype.bind = function(context){
var slice = Array.prototype.slice,
_args = slice.call(arguments,1),
self = this;
return function(){
var _inargs = slice.call(arguments);
return self.apply(context, _args.concat(_inargs));
}
}
现在bind可以绑定对象,同时也能在绑定对象时传递参数。
但是bind还有一个特点:
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
并看不懂什么意思 = = 其实就是bind返回的函数还能用做构造函数。bind 时指定的 this 值会失效,但传入的参数依然生效。
举个例子:
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
我们可以通过修改函数的返回原型来实现,代码如下:
Function.prototype.bind = function(context){
var slice = Array.prototype.slice,
_args = slice.call(arguments,1),
self = this,
fBound = function(){
var _inargs = slice.call(arguments);
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
// 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return self.apply((this instanceof fBound ? this : context), _args.concat(_inargs));
};
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
fBound.prototype = self.prototype;
return fBound;
}
bound.prototype = this.prototype
这么写的话,修改返回函数原型对象(bound.prototype)的同时把绑定函数的原型对象(this.prototype也同时修改了。因此用匿名函数做中转,this.protptype 就安全了。
还有几个小问题的解决:
- 调用bind不是函数
- bind兼容性的问题
最终的完整代码如下:
fakeBind = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var slice = Array.prototype.slice,
_args = slice.call(arguments, 1),
self = this,
F = function () {},
bound = function () {
var _inargs = slice.call(arguments);
return self.apply((this instanceof F ? this : context), _args.concat(_inargs));
};
F.prototype = self.prototype;
bound.prototype = new F();
return bound;
}
Function.prototype.bind = Function.prototype.bind || fakeBind;
ES6版实现
Function.prototype.fakeBindES6 = function(context, ...rest) {
if (typeof this !== "function") {
throw new Error("Bind must be called on a function");
}
var self = this;
return function inner(...args) {
if (this instanceof inner) {
// 当返回的内层函数作为构造函数使用,bind 时绑定的 this 失效。
// 即此处直接执行绑定函数,而不使用 apply 对 this 进行绑定
return new self(...rest, ...args);
}
// 当作为普通函数调用,this 指向传入的对象
return self.apply(context, rest.concat(args));
};
};
Github: https://github.com/Vxee/articles/issues/7