为什么 `obj.foo = function() { };` 不会将名称 `foo` 赋给函数?

56
自从ES2015(ES6)开始,函数有了正确的名称(包括官方的name属性),并且在创建函数时以各种方式分配名称,除了明显的函数声明和命名函数表达式外,还可以将名称分配给变量(函数的名称设置为变量的名称)、将名称分配给对象属性(函数的名称设置为属性的名称),甚至是函数参数的默认值(函数的名称设置为参数的名称)。但是在现有对象上分配属性(例如不在对象初始化中)不会将该属性的名称分配给函数。为什么呢?肯定有一个特定的原因不希望或不可能这样做。那是什么原因呢?
明确一下:我不是在询问如何解决此问题。我想知道是什么阻止了处理这种看似明显的情况,而其他许多情况(包括默认参数值!)都能够被处理。肯定有一个很好的原因。

请不要猜测或推理。 TC39没有包含它的原因。我对那个原因很感兴趣。我已经查看了TC39会议记录,但还没有找到。到目前为止,我找到的最接近的是Allen Wirfs-Brock 回复Bergi说,由于“各种反对意见”,没有就那种形式达成共识。但遗憾的是,他没有说出这些反对意见是什么。

细节:

以下所有内容都将名称foo分配给兼容的浏览器中的函数:

// Requires a compliant browser

// Assigning to a variable or constant...
// ...whether in the initializer...
{
    let foo = function() { };
    console.log("1:", foo.name); // "foo"
}
{
    const foo = function() { };
    console.log("2:", foo.name); // "foo"
}
// ...or later...
{
    let foo;
    foo = function() { };
    console.log("3:", foo.name); // "foo"
}
// As an initializer for an object property
{
    const obj = {
        foo: function() { }
    };
    console.log("4:", obj.foo.name); // "foo"
}
// Or as a method
{
    const obj = {
        foo() { }
    };
    console.log("5:", obj.foo.name); // "foo"
}
// Even if it's a computed property name
{
    let name = "f";
    const obj = {
        [name + "o" + "o"]() { }
    };
    console.log("6:", obj.foo.name); // "foo"
}
// As a default value for a parameter
(function(foo = function() { }) {
    console.log("7:", foo.name); // "foo"
})();
// ...and a bunch of others

但是在对象初始化器之外对现有对象的属性进行赋值不行:

const obj = {};
obj.foo = function() { };
console.log("Nope:", obj.foo.name);

据我所知,这在规范的此节中有所涉及,明确仅当LeftHandSideExpressionIsIdentifierRef为true时(对于属性引用而言,它不是)才设置函数名。
因此,重申上文:为什么不这样做呢?肯定有一个具体的原因,不可取/不可能。是什么原因呢?

7
我曾在esdiscuss上提过这个问题,但没有给出原因。 - Bergi
3
@Bergi:谢谢。那个...有点让人不满意。很遗憾AWB从未告诉你“各种反对意见”的具体内容。 :-( - T.J. Crowder
8
obj.prop1obj.prop2都被赋值为function() {},应该为此赋予什么名称?不做任何归属很方便地避免回答这类有争议的问题。 - artem
2
现在,使用var x = y = function() ...,我看到的第一个名称是var x,因此函数被命名为y可能会令人困惑,并且可能被视为一个错误。我认为在创建函数时分配名称的正确方法是使用明确的命名函数语法:var x = y = function y() {} - artem
4
这段话的意思是:x = y = function() { }; 这个语句在语言运作上完全合理且一致。它会先评估 y = function() { }(创建函数,给它命名,把它分配给 y),然后将这个结果值(一个已存在的函数引用)分配给 x。做其他任何事情都会违反标准表达式语义,并需要极其复杂的机制才能实现。 - T.J. Crowder
显示剩余20条评论
3个回答

16

Allen Wirfs-Brock在es-discuss邮件列表上回复了有关obj.foo = function() { }格式未达成TC39共识的反对意见:

...for

cache[getUserSecret(user)] = function() {};

it would leak the secret user info as the value of name

and for

obj[someSymbol] = function() {}

it would leak the Symbol value as the value of name

and for

 table[n]=function() {}

name would likely be a numeric string

有对这些反对意见的回应(尤其是最后一个,非常薄弱;函数自动分配数字字符串名称的方式还有很多其他方法),但这不是重点;关键是这些都是被提出的反对意见。
他还补充说,需要IsPropertyReference操作(目前只有IsIdentifierRef)...

...是一项运行时操作,而新语义要求在运行时确定名称值。这是所有额外的运行时工作,可能会减慢出现在循环内部的函数闭包的创建速度。

所以总的来说,显然在当时做出决定时,这些反对意见起了决定性作用(现在也很可能如此),这就是为什么这种形式不会自动命名函数,而许多其他形式会这样做的原因。

-1

我仔细阅读了Allen Wirfs-Brock的答案,他明确谈到了可能存在的安全问题。我个人同意他的看法。

也可能存在安全问题。name属性可能通过函数对象泄漏最初分配给它的变量的名称。但是除了源函数之外,没有太多人可以使用局部变量名称。但是泄漏的属性名称可能具有更大的能力。

似乎他所谈论的反对意见与此有关。 如果TC39没有进一步解释其决定,那么很难找出为什么他们会这样做:)。

很抱歉我无法为您提供更多帮助。


不过此项异议已经被解决;它同样适用于所有其它形式(好吧,除了默认参数值形式)。既然其他形式都被采纳了,这就不能(有意地)成为这一形式没有被采纳的原因。 - T.J. Crowder
1
尝试这里:http://www.2ality.com/2015/09/function-names-es6.html。有关“函数名称在创建时始终被赋值”的内容。你可能比我更理解它,我不是ES6专家: D - Gabriel

-2

我不确定是否有具体的原因。

obj.foo = function (){};

首先在 obj 中创建函数表达式的引用,然后将 foo 绑定到这个引用上,该引用已经具有(只读)名称。

所以:

obj.fooC = (new Function ());
console.log(obj.fooC.name);//'anonymous'

obj.fooFE = function (){};
console.log(obj.fooFE.name);//''

obj.fooNFE = function a_name (){};
console.log(obj.fooNFE.name);//'a_name'

这是正常行为。

写代码时有任何限制吗:

obj.foo = (function foo(){});

1
“我不确定是否有特定的原因。” Allen Wirfs-Brock说有一些“不同的反对意见”,因此没有就那种形式达成共识,所以被省略了。 “写作有任何限制吗?” 当然没有 :-) - T.J. Crowder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接