【问题标题】:How is `new` operator able to override hard-binding, in the Function.prototype.bind(..)`new` 运算符如何在 Function.prototype.bind(..) 中覆盖硬绑定
【发布时间】:2017-10-02 14:55:19
【问题描述】:

这是一个纯粹的理论问题。 我正在从“你不知道 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

在上面的sn-p中,我们将foo()绑定到obj1,所以foo()中的this属于obj1,这就是为什么当我们调用obj1.aobj1.a变成2bar(2) .但是new 运算符可以优先,并且obj1.a 不会改变,即使bar(3)new 一起调用也是如此。

下面是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();

所以,现在是重点。根据书: “我们实际上不会深入解释这个诡计是如何工作的(它很复杂,超出了我们的范围),但本质上,该实用程序确定是否使用 new 调用了硬绑定函数(导致新构造的对象是它的this),如果是这样,它使用新创建的 this 而不是之前为 this 指定的硬绑定。”

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

【问题讨论】:

  • 您可能会发现this answer 很有用。
  • 语言具有定义和实现的编译时和运行时语义。在解析和编译 JS 程序时,会找到带有 new 运算符的调用。已知此运算符用于调用函数以构造新对象。因此,遵循规范为这种情况定义的特定算法。与任何呼叫一样,其中一部分是将this 设置为正确的值。没有真正的魔法。语言实现只是遵循规则。
  • 另外,请记住,polyfill 是纯粹用 JS 编写的“尽力而为”的实现。他们无法访问语言实现的内部结构,因此有时无法完美地代表规范的要求。
  • 这是一个简单的例子function Foo(x) { console.log(this instanceof Foo); } -> Foo(4) = false new Foo(4) = true。
  • @Utkanos 这真的很有帮助,但仍然没有解释 new 如何能够优先于硬绑定

标签: javascript new-operator


【解决方案1】:

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

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

{ [[Prototype]]: Foo.prototype }

让我们简要介绍一下变量的类型:

  • fToBind 是被绑定的函数:对于foo.bind(...)foofToBind
  • fBoundfToBind 的绑定版本;它是bind 操作的返回值。 fBound 充当原始 fToBind 函数的看门人,并决定 thisfToBind 在调用时得到什么。
  • 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 操作测试的内容:

instanceof 运算符测试constructor.prototypeobject 的原型链中的存在。

这里&&和三元之间的运算顺序是:

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

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

【讨论】:

  • 这个:fBound.prototype.__proto__ === fNOP.prototype 和这个:fBound.prototype.__proto__ === fToBind.prototype 不可能都是真的。也许第二个你的意思是fNOP.prototype === fToBind.prototype
  • "在代码中表示为 __proto__" - 请不要再在任何地方使用它 - 它已被弃用,需要死!请改用Object.create/Object.getPrototypeOf/Object.setPrototypeOf
  • @Bergi 我不反对!您是否愿意在此答案中使用 [[Prototype]] 内部插槽表示法来表达对象的原型?还是您只是警告不要在实际代码中使用它(我完全赞同的立场)?
  • @apsillers 两者,我想。
  • @Bergi 灭绝完成:)
【解决方案2】:

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

首先你有一个函数。然后绑定一个this 值,并正常调用它。正如所料,this 的值是绑定值。

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

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

【讨论】:

  • 我可以看到您已经发布了两个答案(这可能吗?)您可以将另一个答案与此合并
  • 是的,您可以有多个回答者,我实际上是在回答两个截然不同的问题,具体取决于帖子的解释方式,因此我更愿意将它们分开。
【解决方案3】:

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

在那个 polyfill 中,fNOP 是一个无操作函数(调用时不执行任何操作),它仅用于将其 .prototype 插入到返回的 fBound 函数的原型链中。在这里完成:

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

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

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

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

【讨论】:

    猜你喜欢
    • 2013-05-08
    • 2014-01-05
    • 2011-04-12
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 2010-12-29
    相关资源
    最近更新 更多