In JavaScript, one aspect of creating a function inside a method is difficult to get right: handling the special variable this. ECMAScript.next will make things easy by introducing two constructs: arrow functions and method definitions. This blog posts explains what they are and how they help.
var obj = { myMethod: function () { setTimeout(function () { ... }, 0); } }myMethod is a method, the first argument of setTimeout() is a subroutine. Both are implemented by functions. Before we can explain the implications of that, we need to make a few more definitions. Whenever a function is called, it is in two kinds of scopes (or contexts): Its lexical scopes are the syntactic constructs that surround it (a trait of the source code or the lexicon). Its dynamic scopes are the function that called it, the function that called that function, etc. Note the nesting that occurs in both cases. Free variables are variables that aren’t declared within a function. If such variables are read or written, JavaScript looks for them in the surrounding lexical scopes.
Functions work well as method implementations: They have a special variable called this that refers to the object via which the method has been invoked. In contrast to other free variables, this isn’t looked up in the surrounding lexical scopes, it is handed to the function via the invocation. As the function receives this dynamically, it is called dynamic this.
Functions don’t work well as implementations of subroutines, because this is still dynamic. The subroutine call sets it to undefined in strict mode [1] and to the global object, otherwise. That is unfortunate, because the subroutine has no use for its own this, but it shadows the this of the surrounding method, making it inaccessible. For example:
var jane = { name: "Jane", logHello: function (friends) { var that = this; // (*) friends.forEach(function (friend) { console.log(that.name + " says hello to " + friend) }); } }The argument of forEach is a subroutine. You need the assignment at (*) so that it can access logHello’s this. Clearly, subroutines should have lexical this, meaning that this should be treated the same as other free variables and looked up in the enclosing lexical scopes. that = this is a good work-around. It simulates lexical this, if you will. Another work-around is to use bind:
var jane = { name: "Jane", logHello: function (friends) { friends.forEach(function (friend) { console.log(this.name + " says hello to " + friend) }.bind(this)); } }Now the argument of forEach has a fixed value for this. I can’t be changed, not even via call or apply. There are three problems with any simulation of lexical this:
let jane = { name: "Jane", logHello: function (friends) { friends.forEach(friend => { console.log(this.name + " says hello to " + friend) }); } }The “fat” arrow => (as opposed to the thin arrow ->) was chosen to be compatible with CoffeeScript, whose fat arrow functions are very similar.
Specifying arguments:
() => { ... } // no argument x => { ... } // one argument (x, y) => { ... } // several argumentsSpecifying a body:
x => { return x * x } // block x => x * x // expression, equivalent to previous lineThe statement block behaves like a normal function body. For example, you need return to give back a value. With an expression body, the expression is always implicitly returned. Having a block body in addition to an expression body means that if you want the expression to be an object literal, you have to put it in parentheses.
Note how much an arrow function with an expression body can reduce verbosity. Compare:
let squares = [ 1, 2, 3 ].map(function (x) { return x * x }); let squares = [ 1, 2, 3 ].map(x => x * x);
x => x + this.yis mostly syntactic sugar for
function (x) { return x + this.y }.bind(this)That expression creates two functions: First, the original anonymous function with the parameter x and dynamic this. Second, the bound function that is the result of bind. While an arrow function behaves as if it had been created via bind, it consumes less memory: Only a single entity is created, a specialized function where this is directly bound to the this of the surrounding function.
Apart from these simplifications, there is no observable difference between an arrow function and a normal function. For example, typeof and instanceof can be used as before:
> typeof () => {} 'function' > () => {} instanceof Function true
=> { ... }With JavaScript’s automatic semicolon insertion [2], there is a risk of such an expression being wrongly considered as continuing a previous line. Take, for example, the following code.
var x = 3 + a => 5These two lines are interpreted as
var x = 3 + (a => 5);However, arrow functions will usually appear in expression context, nested inside a statement. Hence, I wouldn’t expect semicolon insertion to be much of a problem. If JavaScript had significant newlines [3] (like CoffeeScript) then the problem would go away completely.
x =>That’s a function with a single parameter that always returns undefined. It is a synonym for the void operator [4]. I’m not sure how useful that is.
let fac = me(n) => { if (n <= 0) { return 1; } else { return n * me(n-1); } } console.log(me); // ReferenceError: me is not defined
(x, y, z) (x, y, z) => {}The first expression is the comma operator applied to three variables, in parentheses. The second expression is an arrow function. If you want to distinguish them at the beginning (at the opening parenthesis), you have to look head many tokens, until you either encounter the arrow or not.
To parse both of the above with a limited look-ahead, one uses a trick called cover grammar: One creates a grammar rule that covers both use cases, parses and then performs post-processing. If an arrow follows the closing parenthesis, some previously parsed things will raise an error and the parsed construct is used as the formal parameter list of an arrow function. If no arrow follows, other previously parsed things will raise an error and the parsed construct is an expression in parentheses. Some things can only be done in a parenthesized expression:
(foo(123))Other things can only be done in a parameter list. For example, declaring a rest parameter:
(a, b, ...rest)Several things can be done in both contexts:
(title = "no title")The above is an assignment in expression context and a declaration of a default parameter value in arrow function context.
$(".someCssClass").each(function (i) { console.log(this) });You currently cannot write each’s argument as an arrow function, because call and apply cannot override the arrow function’s bound value for this. You would need to switch to dynamic this to do so:
$(".someCssClass").each((this, i) => { console.log(this) });Normally, no parameter can have the name this, so the above is a good marker for an arrow function with dynamic this.
Shouldn’t there be a simpler solution for optional dynamic this? Alas, two seemingly simpler approaches won’t work.
Another problem with switching between the two kinds of this is security-related: You can’t control how a function you write will be used by clients, opening the door to inadvertently exposed secrets. Example: Let’s pretend there are “thin arrow functions” (defined via ->) that switch between dynamic and lexical this, on the fly.
let objectCreator = { create: function (secret) { return { secret: secret, getSecret: () -> { return this.secret; } }; }, secret: "abc" }This is how one would normally use obj:
let obj = objectCreator.create("xyz"); // dynamic this: console.log(obj.getSecret()); // xyzThis is how an attacker could get access to objectCreator.secret:
let obj = objectCreator.create("xyz"); let func = obj.getSecret; // lexical this: console.log(func()); // abc
$(".someCssClass").each(function (i, ele) { console.log(ele) });Another option is to use a normal function. Or you can wrap a normal function around an arrow function and pass this from the former to the latter, as an additional (prefixed) parameter. In the following code, method curryThis performs such wrapping.
$(".someCssClass").each( (that, ele) { console.log(that) }.curryThis() );curryThis can be implemented as follows [5]:
Function.prototype.curryThis = function () { var f = this; return function () { var a = Array.prototype.slice.call(arguments); a.unshift(this); return f.apply(null, a); }; };
class Point { constructor(x, y) { this.x = x; this.y = y; } // Method definition: dist() { return Math.sqrt((this.x*this.x)+(this.y*this.y)); } }If accepted, class declarations would be great for newcomers and for tool support. It would also hopefully make it easier to share code between JavaScript frameworks (which currently tend to have different inheritance APIs). The foundations don’t change, a class declaration will internally be translated to a constructor function. dist is a method definition for Point.prototype.dist. As you can see, there is less to type and you won’t be tempted to incorrectly use an arrow function.
let jane = { name: "Jane", // Method definition: logHello(friends) { friends.forEach(friend => { console.log(this.name + " says hello to " + friend) }); } }Again, you don’t see that logHello is a function, underneath. It looks like a method and will have dynamic this. That leaves us with one more use case: What if you want to add methods to an existing object, say:
obj.method1 = function (...) { ... }; obj.method2 = function (...) { ... };Here, you still see old-school functions. However, ECMAScript.next will allow you to use an object literal, instead:
Object.assign(obj, { method1(...) { ... }, method2(...) { ... } });
Arrow function candidates (AFCs) | 55.7% |
Expression body (return expr) | 8.9% |
Object literal body (return {...}) | 0.14% |
Block with single statement | 20% |
Block with multiple statements | 26.7% |
Methods (in object literal, with this) | 35.77% |
AFCs or methods | 91.46% |
Functions with this (outside object literal) | 8.54% |
Terminology:
function (...) { ... }.bind(this)Obviously, those are actually AFCs.
These findings mean that JavaScript most urgently needs easy ways to define subroutines (with lexical this) and methods. ECMAScript.next therefore makes the right choices. Another implication is that arrow functions with dynamic this are not that important, even for current JavaScript code. Lastly, having to parenthesize an object literal in an expression body is rarely an issue: only 0.14% of the function expressions return one.