Why does `obj.foo = function() { };` not assign the name `foo` to the function?
Asked Answered
V

3

56

As of ES2015 (ES6), functions have proper names (including an official name property), and names are assigned when the function is created in a variety of ways in addition to the obvious function declaration and named function expression, such as assigning to variables (function's name is set to the variable's name), assigning to object properties (function's name is set to the property's name), even default values for function parameters (function's name is set to the parameter's name). But assigning to a property on an existing object (e.g., not in an object initializer) doesn't assign that property's name to the function. Why not? Surely there must be a specific reason it was not desirable/possible. What was it?

To be clear: I'm not asking how to work around it. I'm asking what prevents this seemingly-obvious case from being handled when so many others (including default parameter values!) are. There must be a good reason.

Please don't speculate or theorize. TC39 had a reason for not including it. I'm interested in what that reason was. I've been through the TC39 meeting notes but haven't found it yet. The closest I've found so far is Allen Wirfs-Brock replying to Bergi to say there was no consensus for doing it for that form because of "various objections," but sadly he didn't say what those objections were.

Details:

All of the following assign the name foo to the function on a compliant browser:

// 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

But assigning to a property on an existing object, outside an object initializer, does not:

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

As far as I can tell, this is covered by this section of the specification, which explicitly only sets the function name if the IsIdentifierRef of the LeftHandSideExpression is true (which apparently it isn't for property references).

So reiterating from above: Why not? Surely there must be a specific reason it was not desirable/possible. What was it?

Vergara answered 12/12, 2016 at 18:51 Comment(25)
Wonder if it has to do with the fact that foo["2for1"] = function... is totally valid but "2for1" is not a valid function name....although that would apply equally to the computed example.Swirly
@JaredSmith: 2for1 is a valid function name: jsfiddle.net/Lx825mgf Function names, like property names, don't have to be valid identifiers. :-) (Edit: Heh, just saw your comment edit.)Vergara
Your fiddle has a function being assigned to the name of an object property using an opaque string, foo.2for1 = function... and function 2for1() {} both throw, because JS identifiers can't start with numbers.Swirly
@JaredSmith: Yes. Function names, like property names, don't have to be valid identifiers. But of course, if they aren't valid identifiers, you can't use them as identifiers.Vergara
I asked this at esdiscuss, but no reasons were mentioned.Stentorian
@Bergi: Thank you. How...dissatisfying. Unfortunate that AWB never came back to you with what the "various objections" were. :-(Vergara
No, there is nothing "opinion based" here. The question is clear about asking for hard information, not opinion.Vergara
@T.J.Crowder yes, that seems rather hand-wavey for not implementing name assignments for the case MemberExpression[Expression] = FunctionExpression.Swirly
I've been consistently surprised that the member expression form fails to name functions too. Maybe worth a new thread or pinging that old one?Coaler
@loganfsmyth: I'm digging through the meeting notes. If I don't find it, I'll do exactly that.Vergara
Which name should be assigned for obj.prop1 = obj.prop2 = function() {} ? Not assigning anything is very convenient way to avoid answering controversial questions like this.Mclyman
@Mclyman It would be prop2. Identical behavior to var x = y = function();. function name == y. No ambiguity there, the function name is assigned when the function is created. In fact, because obj.prop1 = obj.prop2 doesn't work while var x = y does is in itself confusing.Mihe
Now, with var x = y = function() ..., the first name that I see is var x, so the fact that function is named y is arguably confusing and might be considered a bug. I think the proper way to assign a name when the function is created is to use unambiguous named function syntax: var x = y = function y() {}Mclyman
@artem: It makes perfect sense and is entirely consistent with how the language works. x = y = function() { }; is evaluated as y = function() { } (creating the function, which gives it its name, assigning to y), then that resulting value (an existing function reference) is assigned to x. Doing anything else would violate standard expression semantics and require extremely complicated mechanisms in the spec to achieve.Vergara
I'd say that assigning a name to a function like this always violates assignment expression semantics. a = b assigns value of b to a, normally b is not affected by a in any way. Making special case when b is a function for deriving its name from a seems arbitrary and unnecessary, given that there existed a way for a long time to give a name to any function.Mclyman
@artem: That's fine, you're entitled to disagree with TC39. (I happen to agree with them that this was a good idea.) But this isn't the place to discuss issues you may have with the specification.Vergara
Not sure it helps at all but const obj = {}; Object.defineProperty(obj, 'foo', {value:function(){}}); obj.foo.name; returns 'value'... which is consistent but probably not intended.Biggerstaff
@Biggerstaff Good example. That's the thing - there are so many edge cases here (and differing opinions about how a function should be implicitly named) that the only thing that can be achieved is consistency - and this is where I take issue with the current implementationMihe
In my opinion, this is by design related to the keyword (and concept) const. "The value of a constant cannot change through re-assignment, and it can't be redeclared." Don't use const for mutable variables.Detent
@TravisJ: It has nothing to do with const (you get exactly the same results with let), and I'm not using const for mutable variables -- you can't, on a compliant browser. I am using const for constant variables referring to mutable objects, which is an entirely separate (and normal, in JS) thing. JavaScript doesn't currently have good support for deep-immutability (Object.freeze and such still leave holes), and while immutability is fantastic and useful, it's not the only way to do things.Vergara
I deleted my answer explaining how the syntax in question works but basically this is the takeaway: The syntax doesn't name functions.You're asking why they didn't program in a special case for when the function's name is "". Why do you think there is an explicit answer out there? The syntax does the same thing regardless of what the function's name is. In other words, it behaves consistently.Indifference
@ChrisRollins: No, I'm asking why they left out a special case. And yes, the syntax does name the functions. As for why I think there's a specific reason, it's because A) The spec isn't written at random, gaps like this have explanations, and B) Allen Wirfs-Brock said so.Vergara
The syntax does not name functions. foo.bar = function(){} is not the same syntax as foo={bar:function(){}} the latter names functions the former does not. they have demonstrably different behaviors. In all cases the former will not change the name property of the function. If it only changed it in the case of the property being equal to "", that would be a special case. That is what you're asking about. It would be an inconsistent behavior so why would a designer have to explicitly justify not doing it?Indifference
@ChrisRollins: Correct, foo.bar = function(){} does not name the function. foo = function(){} does. That's the whole point of the question. There are something like a half-dozen syntaxes using formerly-anonymous expressions which are then assigned to something (a variable, object property during object initialization, default parameter value) which do, as of ES2015, assign names. There's one glaring omission. AWB referred to "objections" without elaboration. I want to know what they are (or were).Vergara
Those are also different syntax. a variable is a totally different thing from an object property. But I guess given the information you have the only way to find out what those objections were is to figure out who made them and contact those people?Indifference
V
16

Allen Wirfs-Brock has replied on the es-discuss list with the objections that prevented the TC39 consensus on the obj.foo = function() { } form:

...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

There are counters to those objections (particularly the last one, which is extremely weak; there are many other ways a function is automatically assigned a numeric string name), but that's not the point; the point is that those were the objections raised.

He also added that the IsPropertyReference operation that would be required (where currently there's just an IsIdentifierRef)...

...is a runtime operation and the new semantics require runtime determination of the name value. This is all extra runtime work that may slow down the creation of function closures that appear within loops.

So all in all, apparently at the time the decision was taken, those objections carried the day (and quite possibly would now as well), which is why this form doesn't automatically name functions whereas so many others do.

Vergara answered 28/1, 2017 at 16:28 Comment(0)
O
-1

I went through reading the Allen Wirfs-Brock answers, and he explicitly talks about possible security issues. I personally agree with him.

There may also be security concerns. The name property potentially leaks via the function object the name of the variable it is initially assigned to. But there isn't much someone could do with a local variable name, outside of the originating function. But a leaked property name potentially carries a greater capability.

It seems like the objections he talks about have something to do with it. If TC39 didn't explained further it's decision, it's going hard to find out why did they leave it like that :).

I'mn sorry I can't help you further.

Orthman answered 20/12, 2016 at 10:22 Comment(2)
This objection was put to bed, though; it would apply equally to all the other forms (well, not to the default parameter value form). Since the other forms went forward, this can't be (intentionally) why this one form didn't.Vergara
Try here: 2ality.com/2015/09/function-names-es6.html. There's something about "the name of a function is always assigned during creation". You may understand it better than me, I'm not an ES6 guru :DOrthman
D
-2

I'm not sure there is a specific reason.

obj.foo = function (){};

first creates a reference for the function expression in obj, then binds foo to this reference which has already a (readonly) name.

So:

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'

is normal behaviour.

Is there any restriction to write :

obj.foo = (function foo(){});
Diarrhea answered 12/1, 2017 at 10:23 Comment(1)
"I'm not sure there is a specific reason." Allen Wirfs-Brock said there was, that there were "various objections" and therefore there was "no consensus" on that form and thus it was left out. "Is there any restriction to write" No, of course not. :-)Vergara

© 2022 - 2024 — McMap. All rights reserved.