接上篇文章JavaScript重识bind、call、apply
1、 先看一段代码:
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 ); // obj1.a === 2
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); // 3
复制代码
bar
被硬绑定到 obj1
上,但是 new bar(3)
没有将obj1.a
修改为 3。相反,new
修改了硬绑定(到 obj1
的)调用 bar(..)
中的 this
。因为使用了 new
绑定,我们得到了一个名字为 baz
的新对象,并且 baz.a
的值是 3。
2、手动实现的bind代码
if (!Function.prototype.bindNew) {
Function.prototype.bindNew = function(oThis) {
//一个函数去调用,也就是说bind,call,apply的this是个函数;
//然后再去改变这个函数里面的this;
if (typeof this !== "function") {
// 与 ECMAScript 5 最接近的
// 内部 IsCallable 函数
throw new TypeError(
"Function.prototype.bind - what is trying " +
"to be bound is not callable"
);
}
//这里将初始化的参数缓存起来;
var aArgs = Array.prototype.slice.call( arguments, 1 ),
// ftoBind 指向要bind的函数;
fToBind = this,
// 返回一个新函数
fNOP = function(){},
fBound = function(){
//fToBind.apply 改变绑定this;
// 执行的时候判断,当前this等于fNOP并且传入oThis,就设置成当前this,不然就改变成初始化传入的oThis;
return fToBind.apply(
(this instanceof fNOP && oThis ? this : oThis ),
aArgs.concat(Array.prototype.slice.call( arguments ) )
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
复制代码
(后面会介绍为什么要在 new 中使用硬绑定函 数)
3、解释new操作符
1️⃣使用bindNew来模拟bind内部机制
下面是 new 修改 this 的相关代码:
this instanceof fNOP &&
oThis ? this : oThis ;
// ... 以及:
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
复制代码
这段代码会判断硬绑定函数是否是被 new 调用,如果是的话就会使用新创建 的 this 替换硬绑定的 this。 如果你这样子调用:
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" };
var obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
var dd = foo.bindNew(obj2);
var dj = new dd();// name:undefined; 而不是name:obj2
复制代码
因为new操作修改了this的指向;this绑定的就是是新创建的对象-dj。 详细解释一下:
- 1、dd 是foo.bindNew(obj2)执行后,返回的一个函数
- 2、dd这个函数是:
// ftoBind 指向要bind的函数; 这里是foo;
fToBind = this,
// 返回一个新函数
fNOP = function(){},
fBound = function(){
//fToBind.apply 改变绑定this;
// 执行的时候判断,当前this等于fNOP并且传入oThis,就设置成当前this,不然就改变成初始化传入的oThis;
return fToBind.apply(
(this instanceof fNOP && oThis ? this : oThis ),
aArgs.concat(Array.prototype.slice.call( arguments ) )
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
复制代码
注意 :
// fNOP的原型指向this的原型,this此时指向foo;
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
复制代码
这个代码使 fBound 为 fNOP 的实例;
- 3、new dd()后,就执行fBound这个函数 里面的代码:
return fToBind.apply(
(this instanceof fNOP && oThis ? this : oThis ),
aArgs.concat(Array.prototype.slice.call( arguments ) )
复制代码
此时的
fToBind
,是之前执行bindNew
指向的foo
;
此时
this
,就是指向的new dd()
后返回的新实例;this instanceof fNOP === true
(this instanceof fNOP && oThis ? this : oThis )
这个就返回this
; 那么这个新对象上面是没有obj
这个属性的,foo.apply
,执行foo
后,就打印出name:undefined
;
2️⃣使用bind
上面是手写bind然后来剖析bind内部的绑定机制;那么我们实际检测也会等到同样的结果; 就是本文最开始的代码:
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 ); // obj1.a === 2
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); // 3
复制代码
这样就明白了为什么baz.a
是3,而不是2了,因为new
后,改变了bar
的this
指向;使其新new
的实例 baz
; foo
的this.a = something;
就将 baz.a = 3
了; 这里也可以得出结论,new
操作改变this
绑定的优先级高于硬绑定(bind,apply,call
);
3️⃣new和bind的特性的应用
如果 new 中使用硬绑定函数,就可以预先设置函数的一些参数,这样在使用 new 进行初始化时就可以只传入其余的参数。bind(..) 的功能之一就是可以把除了第一个 参数(第一个参数用于绑定 this)之外的其他参数都传给下层的函数(这种技术称为“部 分应用”,是“柯里化”的一种)。举例来说:
function foo(p1,p2) {
this.val = p1 + p2;
}
// 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么
// 反正使用 new 时 this 会被修改
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" );
baz.val; // p1p2
复制代码