在 Function.prototype.bind(..)

How is `new` operator able to override hard-binding, in the Function.prototype.bind(..)

这是一个纯理论问题。 我从“你不懂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,因此 foo() 中的 this 属于 obj1,这就是为什么 obj1.a 当我们调用 bar(2) 时变成 2。但是 new 运算符能够优先,并且 obj1.a 不会更改,即使在使用 new 调用 bar(3) 时也是如此。

下面是 MDN 页面为 bind(..) 提供的 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();

那么,现在是重点。根据这本书: "We won't actually dive into explaining how this trickery works (it's complicated and beyond our scope here), but essentially the utility determines whether or not the hard-bound function has been called with new (resulting in a newly constructed object being its this), and if so, it uses that newly created this rather than the previously specified hard binding for this."

bind() 函数中的逻辑如何允许 new 运算符覆盖硬绑定?

new 优先于绑定 this 值,因为语言就是这样定义的。

首先你有一个函数。然后你绑定一个 this 值,然后正常调用它。正如预期的那样,this 的值是绑定值。

然后您使用 new 调用相同的函数,您的值将被覆盖。为什么?因为使用 new 的调用是由语言设计指示的,因此由语言实现指示忽略绑定的 this 值,并将其替换为正在构造的新对象。

语言实现只是一个程序。和任何其他程序一样,它遵循规则。因此,在这种情况下,规则是 new 决定 this 的值,而不考虑任何绑定值。

我猜你只是在问他们是如何让 polyfill 工作的。

在那个 polyfill 中,fNOP 是一个无操作函数(调用时什么都不做),它只是用来将其 .prototype 插入到返回的 [=13= 的原型链中] 功能。这是在这里完成的:

fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();

那么当调用 fBound 函数(返回给 .bind() 的调用者的函数)时,instanceof 运算符可以检查 this 值在 fBound 函数中查看该值是否是 fNOP 的实例。如果是,则推断使用了 new

这(有点)有效,因为 instanceof 将从它在左侧给出的对象开始,并搜索原型链以查看它们中是否有任何一个与 .prototype 右侧的函数对象。因此,如果 new 被调用,this 值将是一个在其原型链中具有 fNOP.prototype 的新对象,因为已执行如上所示的设置。

但是,这不是一个完美的测试方法。例如,.call() 方法可能已用于将调用的 this 值设置为 fBound 函数的某个其他实例。所以它看起来像 new 被使用,即使它没有被使用,因此,this 的绑定值将不会用作调用原始值的 this 值功能。

首先,了解对象原型(表示规范 [[Prototype]] 和可通过函数 Object.getPrototypeOf 或已弃用的 __proto__ 属性) 和名称为 prototype 的函数上的 属性。每个函数都有一个名为 prototype 的 属性,当使用 new.

调用函数时会使用它

当您使用 new 调用一个函数时,该函数被提供一个 this 值设置为一个新构造的对象,其原型(即 [[Prototype]])被设置为被调用函数的 prototype 属性。也就是说,当你调用 new Foo() 时,那么当 Foo 中的代码是 运行 时,this 值将是一个形式为

的对象
{ [[Prototype]]: Foo.prototype }

让我们简单认识一下变量的转换:

  • fToBind 是被绑定的函数:对于 foo.bind(...)foofToBind.
  • fBoundfToBind的绑定版本;它是 bind 操作的返回值。 fBound 充当原始 fToBind 函数的看门人,并决定调用时 fToBind 获得的 this 值。
  • oThis 是提供给 bind 的第一个参数,即绑定到函数的 this.
  • 的对象
  • fNOP 是一个函数,其 prototype 属性 设置为 fToBind.prototype
  • fBound.prototype = new fNOP() 使这些为真:

    Object.getPrototypeOf(fBound.prototype) === fNOP.prototype
    Object.getPrototypeOf(fBound.prototype) === fToBind.prototype
    

当使用 new 调用 fBound 时,提供给 fBoundthis 的形式为

{ [[Prototype]]: fBound.prototype }

并且 fBound.prototype 是一个形式为

的对象
{ [[Prototype]]: fNOP.prototype }

使 this 的完整形式等同于

{ [[Prototype]]: { [[Prototype]]: fNOP.prototype } }

所以,当fBoundnew调用时,fNOP.prototype在新创建的this对象的原型链中。这正是 object instanceof constructor 操作测试的内容:

The instanceof operator tests the presence of constructor.prototype in object's prototype chain.

这里的&&和三进制的运算顺序是:

(this instanceof fNOP && oThis) ? this : oThis

如果 this 在它的原型链中有 fNOP.prototype 原始的 bind 调用被赋予一个真实的第一个参数来绑定到函数,然后在使用 new 调用时使用提供给 fBound 的自然创建的 this 并将其提供给 fToBind 而不是绑定 this.