这是一个纯粹的理论问题。我正在从'你不知道js'学习javascript,而且我被困在JS中的bind
函数的实现上。考虑以下代码:
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
在上面的片段中,我们将foo()
绑定到obj1
,所以this
中的foo()
属于obj1
,这就是为什么当我们称obj1.a
时2
变成bar(2)
。但new
算子能够优先,obj1.a
即使用bar(3)
调用new
也不会改变。
以下是bind(..)
的MDN页面提供的polyfill:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
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 aArgs = Array.prototype.slice.call( arguments, 1 ),
fToBind = this,
fNOP = function(){},
fBound = function(){
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;
};
}
根据这本书允许新覆盖的部分是:
this instanceof fNOP &&
oThis ? this : oThis
// ... and:
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
所以,现在的重点。根据这本书:“我们实际上不会深入解释这种技巧是如何工作的(这很复杂,超出了我们的范围),但实际上该实用程序确定是否使用new调用了硬绑定函数(导致新构造的对象就是它的这个),如果是这样的话,它会使用新创建的对象,而不是先前为此指定的硬绑定。“
bind()
函数中的逻辑如何允许new
操作符覆盖硬绑定?
首先,重要的是要理解对象的原型(表示规范为[[Prototype]]
,可通过函数Object.getPrototypeOf
或已弃用的__proto__
属性表示)与名称为prototype
的函数的属性之间的区别。每个函数都有一个名为prototype
的属性,该函数在使用new
调用函数时使用。
当您使用new
调用函数时,该函数将提供一个this
值,该值设置为新构造的对象,其原型(即[[Prototype]]
)设置为被调用函数的prototype
属性。也就是说,当你调用new Foo()
时,那么当运行Foo
中的代码时,this
值将成为表单的对象
{ [[Prototype]]: Foo.prototype }
让我们简要地讨论一下变量:
fToBind
。foo.bind(...)
是foo
的绑定版本;它是fToBind
操作的返回值。 fBound
就像原来的fToBind
函数的看门人一样,并决定bind
在调用fBound
时得到的值。fToBind
是第一个提供给this
的参数,即对象被绑定到函数的fToBind
。oThis
是一个函数,其bind
属性设置为this
fNOP
使这些成为现实:
prototype
当用fToBind.prototype
调用fBound.prototype = new fNOP()
时,提供给Object.getPrototypeOf(fBound.prototype) === fNOP.prototype
Object.getPrototypeOf(fBound.prototype) === fToBind.prototype
的fBound
的形式是
new
和this
是形式的对象
fBound
制作完整形式的{ [[Prototype]]: fBound.prototype }
相当于
fBound.prototype
因此,当{ [[Prototype]]: fNOP.prototype }
与this
一起调用时,{ [[Prototype]]: { [[Prototype]]: fNOP.prototype } }
处于新创建的fNOP.prototype
对象的原型链中。这正是this
操作测试的:
fBound
算子在new
的原型链中测试了object instanceof constructor
的存在。
instanceof
和三元之间的操作顺序如下:
constructor.prototype
如果object
在其原型链中有&&
并且原始的(this instanceof fNOP && oThis) ? this : oThis
调用被赋予了与该函数绑定的第一个参数,那么当使用this
调用时,使用提供给fNOP.prototype
的天然创建的bind
并将其提供给this
而不是绑定fBound
。
new
优先于绑定的fToBind
值,因为这就是语言的定义方式。
首先你有一个功能。然后绑定一个this
值,并正常调用它。正如所料,new
的值是约束值。
然后你用this
调用相同的函数,你的值被覆盖。为什么?因为使用this
的调用是由语言设计指导的,因此通过语言实现来指示,忽略绑定的this
值,并将其替换为正在构造的新对象。
语言实现只是一个程序。和任何其他程序一样,它遵循规则。因此,在这种情况下的规则是,new
可以决定new
的价值而不管任何约束价值。
我想你只是问他们如何让polyfill工作。
在那个polyfill中,this
是一个无操作函数(在调用时什么都不做),它只是用于将new
插入到返回的this
函数的原型链中。这是在这里完成的:
fNOP
因此,当调用.prototype
函数(返回到fBound
调用者的函数)时,fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
运算符可以检查fBound
函数内的.bind()
值,以查看该值是否为instanceof
的实例。如果是的话,它推断使用了this
。
这样做(有点)因为fBound
将从它左侧给出的对象开始,并搜索原型链以查看它们中的任何一个是否与右侧函数的fNOP
对象是同一个对象。因此,如果调用new
,则instanceof
值将是一个新对象,其原型链中具有.prototype
,因为如上所示执行了设置。
但是,这不是一种完美的测试方法。例如,new
方法可用于将调用的this
值设置为fNOP.prototype
函数的某个其他实例。因此,即使不使用.call()
也会看起来像这样,因此,this
的绑定值不会被用作原始函数调用的fBound
值。