如何`new`操作符能够在Function.prototype.bind(..)中覆盖硬绑定

问题描述 投票:14回答:3

这是一个纯粹的理论问题。我正在从'你不知道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.a2变成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操作符覆盖硬绑定?

javascript new-operator
3个回答
8
投票

首先,重要的是要理解对象的原型(表示规范为[[Prototype]],可通过函数Object.getPrototypeOf或已弃用的__proto__属性表示)与名称为prototype的函数的属性之间的区别。每个函数都有一个名为prototype的属性,该函数在使用new调用函数时使用。

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

{ [[Prototype]]: Foo.prototype }

让我们简要地讨论一下变量:

  • qazxsw poi是受约束的功能:对于qazxsw poi,qazxsw poi是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


1
投票

new优先于绑定的fToBind值,因为这就是语言的定义方式。

首先你有一个功能。然后绑定一个this值,并正常调用它。正如所料,new的值是约束值。

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

语言实现只是一个程序。和任何其他程序一样,它遵循规则。因此,在这种情况下的规则是,new可以决定new的价值而不管任何约束价值。


1
投票

我想你只是问他们如何让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值。

© www.soinside.com 2019 - 2024. All rights reserved.