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
函数。