试图理解 ECMAScript-6 Function.prototype.bind() 的实际作用

An attempt at understanding what ECMAScript-6 Function.prototype.bind() actually does

提问人:kuroi neko 提问时间:7/28/2021 最后编辑:kuroi neko 更新时间:7/29/2021 访问量:48

问:

这个问题是这个问题的后续。出于某种原因,我在 7 年后回到 JS,男孩,我几乎认不出这只亲爱的老野兽。

纯粹出于教育目的,我决定重写各种允许做的事情的幼稚实现。这只是一个练习,试图理解正在发生的事情并引发一些问题。Function.prototype.bind()

我很乐意纠正我的例子和评论中的所有错误和误解。

另外,这段代码不是我的。我从博客中得到这个想法,只是稍微调整了一下,但不幸的是,我忘记了来源。如果有人认出原件,我很乐意给予应有的信任。同时,我为这个错误道歉。

朴素绑定

最初的想法只是简单地执行 lambda 演算精明的人显然称之为“部分应用程序”,即固定函数的第一个参数的值,该参数也接受隐式的“this”第一个参数,如下所示:

Function.prototype.naive_bind = function (fixed_this, ...fixed_args) {
    const fun = this; // close on the fixed args and the bound function
    return function(...free_args) { // leave the rest of the parameters free
        return fun.call(fixed_this, ...fixed_args, ...free_args);
    }
}

绑定构造函数(第一轮)

class class1 {
    constructor (p1, p2) {
        this.p1 = p1;
        this.p2 = p2;
    }
}

var innocent_bystander = { "huh?":"what?" }

var class2 = class1.naive_bind(innocent_bystander);
class2 (1,2)                 // exception: class constructor must be invoked with new (as expected)
console.log(new class2(1,2)) // exception: class constructor must be invoked with new (Rats!)

function pseudoclass1 (p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
}
var pseudoclass2 = pseudoclass1.naive_bind(innocent_bystander);
pseudoclass2 (1,2)                 // same exception (as expected)
console.log(new pseudoclass2(1,2)) // same again (at least it's consistent...)

Tonnerre de Brest!显然,运行时对基于 . 似乎真正的是添加一团秘密酱汁,为生成的函数提供适当的“构造函数”风味(显然,ECMA 262 规范所说的“构造函数”并不意味着类构造函数,而是任何可以使用“new”调用并使用“this”来填充新创建的对象的属性和方法的函数)Function.prototype.call()bind()

绑定其他函数

var purefunction1 = console.log
var purefunction2 = purefunction1.naive_bind (null, "Sure,", "but")
purefunction2 ("you can still", "bind pure", "functions")
// sure, but you can still bind pure functions

// ...and make animals talk (by binding simple methods)
var cat = { speech: "meow" }
var dog = { speech: "woof" }
var fish= { speech: "-" }

var talk = function(count) { console.log ((this.speech+", ").repeat(count-1) + this.speech + "!") }

talk.naive_bind (cat,1)(); // meow! 
talk.naive_bind (dog,1)(); // woof!
talk.naive_bind (cat)(3) // meow, meow, meow!
talk.naive_bind (fish)(10) // -, -, -, -, -, -, -, -, -, -!

// and bind arrow functions
// ("this" is wasted on them, but their parameters can still be bound)

var surprise = (a,b,c) => console.log (this.surprise, a,b,c)
var puzzlement = surprise.naive_bind(innocent_bystander, "don't", "worry");

// "this" refers to window, so we get our own function as first output.
surprise ("where", "am", "I?")  // function surprise(a, b, c) where am I?
// the bound value of this is ignored, but the other arguments are working fine
puzzlement("dude")              // function surprise(a, b, c) don't worry dude        

显然,一切都按预期进行。还是我错过了什么?

绑定构造函数(第 2 轮)

我们显然无法将包装器传递给 ,但我们可以尝试直接调用。 由于 的值由 提供,因此构造专用包装器只需要担心实际的构造函数参数。newnewthisnew

 Function.prototype.constructor_bind = function (...fixed_args) {
    const fun = this;
    return function(...free_args) {
        return new fun (...fixed_args, ...free_args);
    }
}

var class1_ctor = class1.constructor_bind(1);
console.log (class1_ctor(2)) // class1 { p1:1, p2:2 }

var monster = (a,b) => console.log ("boooh", a, b)
var abomination = monster.constructor_bind(1);
console.log (abomination(2)) // exception: fun is not a constructor (as expected)

嗯,这似乎可以削减它。我想 real 更安全、更快捷,但至少我们可以重现基本功能,即:bind()

  • 提供 to 方法的固定值this
  • 在任何合法函数上执行部分应用程序,尽管构造函数需要特定的变体

编辑:删除了问题以符合 SO 政策。

取而代之的是一个,与这篇文章的标题相匹配的那个,我试图通过提供一个幼稚且可能错误的等价物来探索我认为真正的功能:

请帮助我了解本机方法的工作原理,根据 ECMAScript 规范的 6.0 版。Function.prototype.bind()

ecmascript-6 绑定

评论

0赞 VLAZ 7/28/2021
请每个问题一个问题。
0赞 kuroi neko 7/28/2021
好吧,如果我得到元素来关闭最终列表,我计划编辑帖子。
1赞 VLAZ 7/28/2021
1. 也许吧。但可能不是。是的。3.对于不使用的函数,可以只作为部分分配。中几乎没有任何特定于 OOP 的内容。4.性能介于什么和具体什么之间?另请参阅:哪个更快。5. 提出多个和/或开放式问题不在 SO 的范围内。请参阅帮助中心this.bind().bind()
0赞 kuroi neko 7/28/2021
好的,我删除了 alove my life 的结尾问题。现在能做到吗?
1赞 Bergi 7/28/2021
"请帮助我了解此功能的工作原理。- 哪个功能?那个本地人,你的,还是你的?你对他们有什么不了解的地方?看来是你自己写的,而且它们按预期工作,所以真的不清楚你在要求什么。bindnaive_bindconstructor_bind

答:

1赞 Bergi 7/29/2021 #1

您唯一缺少的一点是在 ES6 中引入了 new.target,它 a) 可以区分 a 中的 [[call]] 和 [[construct]],并且 b) 需要在调用中转发。functionnew

因此,更完整的 polyfill 可能如下所示:

Function.prototype.bind = function (fixed_this, ...fixed_args) {
    const fun = this;
    return function(...free_args) {
        return new.target != null
            ? Reflect.construct(fun, [...fixed_args, ...free_args], new.target)
            : fun.call(fixed_this, ...fixed_args, ...free_args);
    }
}

其他一些细节将涉及被断言为函数对象,并且返回的绑定函数具有特殊的 , 准确的 和 没有 。您可以在规范中找到这些东西,显然您已经在阅读了。fun.name.length.prototype

评论

0赞 kuroi neko 7/29/2021
谢谢你,亲爱的先生。这就是我所说的有用的知识。我完全忽略了一个你可以读取的实际值,我以为它只是像其他代码一样的伪代码。现在我要去看看这个 Reflect 应该做什么:)new.target
0赞 Bergi 7/29/2021
Reflect.construct[[construct]] 的紧密包装器。就像允许你做但指定一个参数一样,它允许你做但指定一个 newTarget。.callfun(...fixed_args, ...free_args)thisnew fun(...fixed_args, ...free_args)
0赞 kuroi neko 7/29/2021
美妙。我会试一试的......嗯。。。有趣的规格只是为了确保,但这似乎是完全合乎逻辑的。有没有机会获得关于效率方面的额外建议?我不是在那里劈头盖脸,我只是想要一个明智的意见,以确保这些传播运算符和浅层副本(可能还有其他我不知道的隐藏的东西)不会效率低下,以至于使这些简单的替代方案变得不切实际。
1赞 Bergi 7/29/2021
不,速度和内存消耗在涉及的值数量上都是线性的,就像在本机实现中一样。它可能会因一些(可能很小的)常数因素而变慢,但仅此而已。
0赞 kuroi neko 7/29/2021
再次感谢您的善意和耐心。