1. 问题描述
js
function fn(name) {
this.name = name;
}
const obj = {}
const fn1 = fn.bind(obj);
const instance = new fn1("abc");
console.log(instance.name);
console.log(obj.name);
上面这段代码的输出结果是:
js
abc
undefined
在这段代码中我们首先声明了一个函数 fn, 函数内部将参数 name 赋值给 this 的 name 属性;随后将变量 fn 通过 bind 函数绑定了一个空对象 obj;之后通过 new fn1("abc") 创建了实例 instance; 通过打印,可以发现 instance 的 name 属性是实例化时传入的参数 "abc",而 obj 的 name 属性不存在。
根据这个现象我们可以推断,在 fn 中获取到的 this 是 instance,而不是通过 bind 函数绑定的 obj。因此我们可以得到结论,new 绑定对于 this 指向的优先级要高于 bind 函数。
2. 原因解释
想要知道其中的原因,必然要知道 bind 的实现原理。参考 core-js 对 bind 函数的 polyfill(github.com/zloirock/co... ),我们可以对 bind 函数实现原理有一个大致认识,以下是部分简化后的代码:
js
function bind(that /* , ...args */) {
var F = aCallable(this);
var Prototype = F.prototype;
var partArgs = arraySlice(arguments, 1);
var boundFunction = function bound(/* args... */) {
var args = concat(partArgs, arraySlice(arguments));
return this instanceof boundFunction ? construct(F, args.length, args) : F.apply(that, args);
};
if (isObject(Prototype)) boundFunction.prototype = Prototype;
return boundFunction;
};
代码中 aCallable 就是判断传入参数是否可调用,如果是则返回参数本身。construct 函数是将传入的 F 实例化。其它如 arraySlice 、concat、isObject 函数的作用结合函数名都比较好理解。
这里就可以很清晰的看到,bind 函数最终返回了 boundFunction,而在这个函数内部对 this 进行了判断,这里的 this 的值根据我们调用方式的不同就会有差别:
- 通过
fn.bind(obj)(xxx)直接调用
此时 bind 函数获取到的 that 参数为 obj,而 bind 函数中的 this 对象则是 fn。fn.bind(obj) 返回了 boundFunction 函数,相当于调用 boundFunction(xxx),这时 boundFunction 函数没有显示的调用对象,因此 this 指向全局对象。
- 通过
new fn.bind(obj)(xxx)形式调用
fn.bind(obj) 函数返回的是函数 boundFunction,这里通过 new 调用,所以相当于 new boundFunction(xxx),那么显然在 boundFunction 中获取到的 this 就是 boundFunction 实例化后的对象。
我们在前面的代码中执行了 new fn.bind(obj)("abc"),也就相当于执行 new boundFunction(obj)("abc")。
我们继续看 boundFunction 中 return 的内容。这一部分代码首先对 this instanceof boundFunction 进行了判断,又可以分两种情况讨论:
-
如果我们是直接调用的话
this指向全局对象,那么显然判断条件不成立,因此会走入F.apply(that, args),这里的that根据前文分析是传入bind的参数obj对象,这种形式就是我们将函数F的this显式绑定了对象obj。 -
如果我们通过
new进行调用,this指向boundFunction的实例对象,那么判断条件成立,执行construct(F, args.length, args),相当于new F(...args),这里的F就是bind函数获取到的this,也就是fn函数。因此最终我们执行的实际上是new fn(xxx),很明显此时我们在fn函数中获取到的this就是fn实例化后的对象,而非我们在bind中传递的参数obj。
3. 小结
我们在同时用 new 和 bind 函数去改变 this 指向时,new 对 this 的影响优先级要高于 bind 函数。这是由于在 bind 函数中我们返回了一个新函数 boundFunction,并且在这个函数中对 this instanceof boundFunction 进行了判断。调用方式的不同会导致判断结果的不同,最终导致了 new 对 this 指向的影响优先级高于 bind 函数。