JavaScript:探索bind和new之间的关系
JavaScript: 手写 bind 和探索 bind 与 new 之间的关系
首先了解一下 bind
的定义
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
也就是说 bind
和 apply
、call
不一样,bind
是返回一个待执行函数,并且可以传入参数。来看一看 bind
的例子
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
// 返回了一个函数
var bindFoo = bar.bind(foo);
bindFoo(); // 1
// bindFoo() 相当于 bar.bind(foo)();
所以 bind
返回的是一个函数(这也是为什么 new
可以改变 bind
后函数的 this
指向的原因)并且返回的函数可以继续传入参数。
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18
所以要对参数进行处理。
// 第二版
Function.prototype.myBind = function (context) {
var self = this;
// 获取myBind第二个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指bind返回的函数传入的参数,这个arguments和上面的不是同一个
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(bindArgs));
}
}
到这里 bind
的基本功能差不多完成了,但是 bind
还有一个特点,那就是和 new
配合使用的时候,bind
时指定的 this
值会失效,但传入的参数依然生效。下面这个例子就可以说明。
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'game';
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.myBind(foo, 'hcb');
var obj = new bindFoo('18'); // 这里bind绑定的this值已经失效了
// undefined
// hcb
// 18
所以我们要通过修改返回的函数的原型来实现
特别要注意下面的代码执行完只是返回 fBound
,函数里面的语句还未执行,所以这就是为什么这里能判断 fBound
里面的 this
到底指向普通函数还是构造函数
Function.prototype.myBind = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
// 这里有同学就问了,这里怎么判定this是属于构造函数还是普通函数的呢?
// 因为bind返回的是一个函数的定义,在new中这个函数被调用,所以这里的this是到调用的时候才确定的
//
return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值,因为js中对象是引用传递
fBound.prototype = this.prototype;
return fBound;
}
但是在这个写法中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。为什么也会直接修改呢?因为这里是引用类型,这个时候,我们可以通过一个空函数来进行中转。
Function.prototype.myBind = function (context) {
// 只有函数才能调用bind,所以要加这个判断
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;
}
来看一下 new
的手写实现:
function myNew4(fn, ...args) {
if (typeof fn !== 'function') {
throw 'fn must be a function';
}
let obj = {};
obj.__proto__ = fn.prototype;
// 如果fn返回值是一个对象的话,那么就返回这个对象里面的东西
// 如果不是的话,那么就返回构造出来的对象
// console.log(obj);
// 这里的fn可以看作bind里面return出来的fBound,所以到这里fBound的this就被改变了
let res = fn.apply(obj, args); // obj变成fn这个函数的this
// console.log(obj);
return res instanceof Object ? res : obj;
}
------------- 本文结束 感谢阅读 -------------