Month: November 2016

JavaScript Corners – Part 5

JavaScript Corners – Part 5

Here’s a quick one:

let f = function() {};
console.log(f.name); // prints f

This was quite unexpected to me. It’s the only time I’ve ever seen where the left-hand side of an assignment can affect the right-hand side.

This only happens once. Once the anonymous function has a name, it can’t be re-named:

let f = function() {};
let g = f;
console.log(g.name); // prints f

Interestingly, this doesn’t seem to work with destructuring:

let [f] = [function(){}];
console.log(f.name); // prints ''

This implies that somewhere between when the anonymous function is instantiated, and when it added to the array literal, the anonymous function acquires a name. It’s not the destructuring itself that suppresses the name, as can be seen in the following example:

let [f = function(){}] = [];
console.log(f); // prints f

It also doesn’t seem to work with anonymous functions passed as parameters:

(function(f) {
  console.log(f.name);
})(function() {});

It also doesn’t seem to work with anonymous functions evaluated from more complicated expressions:

let foo = (42, function() {});
console.log(foo.name); // prints ''

 

JavaScript Corners – Part 4

JavaScript Corners – Part 4

Here’s another quick one. It relates to the scoping rules with parameter expressions.

const x = 'a';
const y = 'b';
const z = 'c';
function foo(x, f = () => [ x, y, z ], y = 2) {
  var x = 10;
  var y = 11;
  const z = 3;
  return f;
}
const f = foo(1);
console.log(f());

Function foo has three parameters: x, f, and y. The key thing here is that the default value for f is a function that encloses the local lexical scope surrounding the function, and we’ve designed the function in such a way that when we call it it “tells” us the values of x, y, and z in the surrounding scope.

So the question is, which values of x, y, and z does f close over? There are global variables x, y, and z, there are parameters x and y, and there are local variables x, y and z. The parameter x is intentionally ordered before the function f is initialized, while parameter y is positioned to be after the function f is initialized.

I’ll spare you the time of running the code yourself. On my machine, in node.js 7.0.0, the output is [ 1, 2, 'c']. Clearly no local variables have been used, and f is capable of seeing parameter y even though it’s declared after f. The output is the same whether or not the code is executed in strict mode.

One of the most interesting things here, to me, is that the local variable x is not an alias for the parameter x.  This is strange when you consider the following code:

function foo(x) {
  var x;
  return x;
}
console.log(foo(42));

Given that local variables are apparently not aliases of the parameters with the same name, you would expect the output of the above to be undefined, since variable x is not initialized, even though parameter x is initializedHowever, the output is actually 42.

Where did we go wrong in our logic? Let’s see if we can get an example that explores what’s going on:

function foo(x, y, f = () => [ x, y ] ) {
 var x;
 var y = undefined;
 return { x, y, f };
}
const { x, y, f } = foo(42, 43);
console.log([x, y]);
console.log(f());

The output of this is:

[ 42, undefined ]
[ 42, 43 ]

This clearly tells me that variable x is not an alias of parameter x, but rather a separate variable that is initialized to the same value as parameter x.

Also, as a side note, it tells me that there is a subtle semantic distinction between initializing a variable to undefined and not initializing a variable at all, which is very interesting.

I should note that we’ve explored this empirically, not deducing the behavior from the spec. When I run the same experiment in Firefox I get a different result — both instances of y are undefined.

Which is correct?

I believe the V8 (node.js) implementation is more accurate in this case. There’s a note in the ECMAScript specification1 that says:

NOTE A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body.

I think this note is self explanatory, and tell us that the closure function in the parameter list is not meant to be able to see the local variables of its parent function.

P.S.

I’m going add one last investigation to this post. The above quote applies only if there are parameter expressions (if some parameters have default values). If there are no parameter expressions, then apparently this second “environment” is not created, and so the variable x should be an alias for the parameter x.

At first I thought that there was no way that we could ever test this distinction. Can you think of a way to test this difference?

Spoiler alert, I did think of a way. If the code is not executing in strict mode, then the arguments variable holds aliases for the parameters, which makes it possible to mutate parameters without referring to them by name. This is important, because once you have a local variable with the same name, there is no way to know if the local variable is a copy of the parameter, or an alias of the parameter, based purely on code that identifies it by name.

Take a look at the following experiment:

function foo(x, y) {
 var x;
 console.log(x);        // outputs "1"
 arguments[0] = 2;
 console.log(x);        // outputs "2"
}
foo(1, 42);

First note that y is not used in this code sample, but you’ll see why I added it in a moment.

The first log output of “1” shows that the variable x is either a copy of parameter x or an alias of parameter x. Then we continue by modifying the parameter x, without touching the variable x. Then we log the value of the variable x and see that it’s also changed — because in fact the variable and parameter x are both symbols for the same binding.

But now look at this slight modification to the code:

function foo(x, y = 42) {
  var x;
  console.log(x);      // outputs "1"
  arguments[0] = 2;
  console.log(x);      // outputs "1" again!
}
foo(1, 42);

Note that again y is not used. In fact, to keep this a controlled experiment, y is even initialized to the same value that is passed as a parameter. The contents of the value initializer is not relevant though, because the initializer is never evaluated. What is important in this case is that there is an initializer expression at all, regardless of whether it executes or not.

The surprising thing, as I noted in the comment on the snippet, is that the second output is now 1 instead of 2, resulting from the fact that the mutation of the argument does not change the variable.

This is not a bug in node.js, but just an interesting quirk of the spec, in another corner of JavaScript.

 


  1. https://tc39.github.io/ecma262/#sec-functiondeclarationinstantiation