最近看到一道很有意思的面试题,问输出是多少:
var name = "global";
var obj = {
name: "local",
foo: function () {
this.name = "foo";
}.bind(window)
};
var bar = new obj.foo();
setTimeout(function () {
console.log(window.name);
}, 0);
console.log(bar.name);
var bar3 = bar2 = bar;
bar2.name = "foo2";
console.log(bar3.name);
我直呼刁钻,这个里面考的东西属实有点多,运算符和优先级、this的指向和绑定优先级、事件队列、new运算符……不过这里我打算说的是new的事情,并不打算在别的地方纠缠,所以不会在非关键点进行解释,还请读者包涵。
首先,三条语句的执行顺序是很明显的:
console.log(bar.name); // foo
console.log(bar3.name); // foo2
console.log(window.name); // global
那么就牵扯到一个问题,也是这里的关键问题:bar
是什么?我们先看看new的调用过程(来自于MDN,由本人翻译):
- 创建一个空的简单JavaScript对象(即
{}
); - 设置该对象的constructor属性值,指向它的构造函数;
- 将步骤1新创建的对象作为
this
的上下文 ; - 如果该函数没有返回对象,则返回
this
。
所以,毫无疑问,这个bar
应该是这样一个对象:
{ name: "foo" }
但是,按理来说,此处已经使用了bind
将this绑定到了window,调用时的this.name = "foo";
应该会修改window的name属性值。但由于绑定优先级,new运算符的优先级是高于bind的,所以此处的bind导致的this修改是被覆盖的。至于这一点,可以通过下面的代码验证(其实跟题目里的场景很类似):
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3
但是,为什么会引起覆盖呢?因为我们并不太能深入具体的native code(比如V8的具体实现),所以用MDN上的bind函数的polyfill来举例:
if (!Function.prototype.bind)
(function () {
var ArrayPrototypeSlice = Array.prototype.slice;
Function.prototype.bind = function (otherThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
var baseArgs = ArrayPrototypeSlice.call(arguments, 1),
baseArgsLength = baseArgs.length,
fToBind = this,
fNOP = function () {},
fBound = function () {
baseArgs.length = baseArgsLength; // reset to default base arguments
baseArgs.push.apply(baseArgs, arguments);
return fToBind.apply(
fNOP.prototype.isPrototypeOf(this) ? this : otherThis,
baseArgs
);
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
})();
重点其实是这一句:
fNOP.prototype.isPrototypeOf(this) ? this : otherThis
言下之意,如果是通过new关键字创建的,this指向并不会改变。