# 实现bind函数
实现bind之前,我们首先要知道它做了哪些事情。
- 返回一个新函数
this
值是传递给bind()
的第一个参数- 第二个参数以后的为新函数的参数
- 可以使用new操作符来创建对象
# 返回函数
关于指定 this 的指向,我们可以使用 call 或者 apply 实现
// 第一版
Function.prototype.bind2 = function (context) {
var self = this;
return function () {
return self.apply(context);
}
}
此外,之所以 return self.apply(context)
,是考虑到绑定函数可能是有返回值的,依然是这个例子:
var foo = {
value: 1
}
function bar() {
return this.value;
}
var bindFoo = bar.bind2(foo);
console.log(bindFoo()); // 1
# 传参
// 第二版
Function.prototype.bind2 = function (context) {
var self = this;
// 获取bind2函数从第二个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指bind返回的函数传入的参数
// 即 return function() 的参数
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(bindArgs));
}
}
var value = 2;
var foo = {
value: 1
}
function bar(name, age) {
return {
value: this.value,
name: name,
age: age
}
}
var bindFoo = bar.bind2(foo, 'Libai')
console.log(bindFoo(20))
// {
// value: 1,
// name: "Libai",
// age: 20,
// }
# 构造函数效果
完成了这两点,最难的部分到啦!因为 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 = 'Libai';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// Libai
上面例子中,运行结果this.value
输出为 undefined
,这不是全局value
也不是foo
对象中的value
,这说明 bind
的 this
对象失效了,new 的实现中生成一个新的对象,这个时候的this
指向的是obj
。
这里可以通过修改返回函数的原型来实现,代码如下:
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 注释1
return self.apply(
this instanceof fBound ? this : context,
args.concat(bindArgs)
);
}
// 注释2
fBound.prototype = this.prototype;
return fBound;
}
注释1:
- 当作为构造函数时,
this
指向实例,此时this instanceof fBound
结果为true
,可以让实例获得来自绑定函数的值,即上例中实例会具有habit
属性。 - 当作为普通函数时,
this
指向window
,此时结果为false
,将绑定函数的this
指向context
- 当作为构造函数时,
注释2: 修改返回函数的
prototype
为绑定函数的prototype
,实例就可以继承绑定函数的原型中的值,即上例中obj
可以获取到bar
原型上的friend
。
# 构造函数优化
但是在这个写法中,我们直接将 fBound.prototype = this.prototype
,我们直接修改 fBound.prototype
的时候,也会直接修改 this.prototype
。
测试一下:
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 = 'Libai';
var bindFoo = bar.bind2(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// Libai
obj.__proto__.friend = "123123" // 修改原型
console.log(bar.prototype.friend) // 123123
把fBound.prototype = this.prototype
, 改换为原型继承的方式, 就ok了
// 原型式继承
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
到这里其实已经差不多了,但有一个问题是调用 bind 的不是函数,这时候需要抛出异常。
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
# 最终完美版代码
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}