很显然,默认绑定 在四种规则中优先权最低的。所以我们先把它放在一边。
隐含绑定 和 明确绑定 哪一个更优先呢?我们来测试一下:
所以, 明确绑定 的优先权要高于 隐含绑定,这意味着你应当在考察 隐含绑定 之前 首先 考察 明确绑定 是否适用。
现在,我们只需要搞清楚 new 绑定 的优先级位于何处。
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4
好了,new 绑定 的优先级要高于 隐含绑定。那么你觉得 new 绑定 的优先级较之于 明确绑定 是高还是低呢?
注意: new
和 call
/apply
不能同时使用,所以 new foo.call(obj1)
是不允许的,也就是不能直接对比测试 new 绑定 和 明确绑定。但是我们依然可以使用 硬绑定 来测试这两个规则的优先级。
在我们进入代码中探索之前,回想一下 硬绑定 物理上是如何工作的,也就是 Function.prototype.bind(..)
创建了一个新的包装函数,这个函数被硬编码为忽略它自己的 this
绑定(不管它是什么),转而手动使用我们提供的。
因此,这似乎看起来很明显,硬绑定(明确绑定的一种)的优先级要比 new 绑定 高,而且不能被 new
覆盖。
我们检验一下:
如果你回头看看我们的“山寨”绑定帮助函数,这很令人吃惊:
function bind(fn, obj) {
return function() {
fn.apply( obj, arguments );
}
如果你推导这段帮助代码如何工作,会发现对于 new
操作符调用来说没有办法去像我们观察到的那样,将绑定到 obj
的硬绑定覆盖。
但是 ES5 的内建 Function.prototype.bind(..)
更加精妙,实际上十分精妙。这里是 MDN 网页上为 bind(..)
提供的(稍稍格式化后的)polyfill(低版本兼容填补工具):
注意: 就将与 new
一起使用的硬绑定函数(参照下面来看为什么这有用)而言,上面的 bind(..)
polyfill 与 ES5 中内建的 bind(..)
是不同的。因为 polyfill 不能像内建工具那样,没有 .prototype
就能创建函数,这里使用了一些微妙而间接的方法来近似模拟相同的行为。如果你打算将硬绑定函数和 new
一起使用而且依赖于这个 polyfill,应当多加小心。
允许 new
进行覆盖的部分是这里:
this instanceof fNOP &&
oThis ? this : oThis
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
我们不会实际深入解释这个花招儿是如何工作的(这很复杂而且超出了我们当前的讨论范围),但实质上这个工具判断硬绑定函数是否是通过 new
被调用的(导致一个新构建的对象作为它的 this
),如果是,它就用那个新构建的 this
而非先前为 this
指定的 硬绑定。
为什么 new
可以覆盖 硬绑定 这件事很有用?
这种行为的主要原因是,创建一个实质上忽略 this
的 硬绑定 而预先设置一部分或所有的参数的函数(这个函数可以与 new
一起使用来构建对象)。bind(..)
的一个能力是,任何在第一个 this
绑定参数之后被传入的参数,默认地作为当前函数的标准参数(技术上这称为“局部应用(partial application)”,是一种“柯里化(currying)”)。
例如:
函数是通过
new
被调用的吗(new 绑定)?如果是,this
就是新构建的对象。var bar = new foo()
函数是通过
call
或apply
被调用(明确绑定),甚至是隐藏在bind
硬绑定 之中吗?如果是,this
就是那个被明确指定的对象。var bar = foo.call( obj2 )
函数是通过环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,
this
就是那个环境对象。var bar = obj1.foo()
以上,就是理解对于普通的函数调用来说的 this
绑定规则 所需的全部。是的……几乎是全部。