This chapter gives advice on how to properly use entities you can call (via function calls, method calls, etc.) in ES6.
Sections in this chapter:
nameproperty of functions
In ES5, a single construct, the (traditional) function, played three roles:
In ES6, there is more specialization. The three duties are now handled as follows (a class definition is either one of the two constructs for creating classes – a class declaration or a class expression):
This list is a simplification. There are quite a few libraries that use
this as an implicit parameter for callbacks. Then you have to use traditional functions.
Note that I distinguish:
Even though their behaviors differ (as explained later), all of these entities are functions. For example:
Some calls can be made anywhere, others are restricted to specific locations.
Three kinds of calls can be made anywhere in ES6:
For function calls, it is important to remember that most ES6 code will be contained in modules and that module bodies are implicitly in strict mode.
superare restricted to specific locations
Two kinds of calls can be made via the
super keyword; their use is restricted to specific locations:
constructor()inside a derived class definition.
The difference between non-method functions and methods is becoming more pronounced in ECMAScript 6. There are now special entities for both and things that only they can do:
this(and other variables) from their surrounding scopes (“lexical
super, to refer to super-properties and to make super-method calls.
This section gives tips for using callable entities: When it’s best to use which entity; etc.
As callbacks, arrow functions have two advantages over traditional functions:
thisis lexical and therefore safer to use.
thisas an implicit parameter
this as an implicit argument for their callbacks, which prevents you from using arrow functions. For example: The
this in line B is an implicit argument of the function in line A.
This pattern is less explicit and prevents you from using arrow functions.
This is easy to fix, but requires the API to change:
We have turned the API from an implicit parameter
this into an explicit parameter
api. I like this kind of explicitness.
thisin some other way
In some APIs, there are alternate ways to get to the value of
this. For example, the following code uses
But the target of the event can also be accessed via
As stand-alone functions (versus callbacks), I prefer function declarations:
The benefits are:
functionis an advantage – you want the construct to stand out.
There is one caveat: Normally, you don’t need
this in stand-alone functions. If you use it, you want to access the
this of the surrounding scope (e.g. a method which contains the stand-alone function). Alas, function declarations don’t let you do that – they have their own
this, which shadows the
this of the surrounding scope. Therefore, you may want to let a linter warn you about
this in function declarations.
Another option for stand-alone functions is assigning arrow functions to variables. Problems with
this are avoided, because it is lexical.
Method definitions are the only way to create methods that use
super. They are the obvious choice in object literals and classes (where they are the only way to define methods), but what about adding a method to an existing object? For example:
The following is a quick way to do the same thing in ES6 (caveat:
Object.assign() doesn’t move methods with
For more information and caveats, consult the section on
There is a subtle difference between an object with methods and an object with callbacks.
this of a method is the receiver of the method call (e.g.
obj if the method call is
For example, you can use the WHATWG streams API as follows:
obj is an object whose properties
cancel are methods. Accordingly, these methods can use
this to access object-local state (line *) and to call each other (line **).
this of an arrow function is the
this of the surrounding scope (lexical
this). Arrow functions make great callbacks, because that is the behavior you normally want for callbacks (real, non-method functions). A callback shouldn’t have its own
this that shadows the
this of the surrounding scope.
If the properties
cancel are arrow functions then they pick up the
surroundingMethod() (their surrounding scope):
If the output in line * surprises you then consider the following code:
o.p work the same, because both arrow functions have the same surrounding lexical scope,
bar(). The latter arrow function being surrounded by an object literal does not change that.
This section gives tips for avoiding IIFEs in ES6.
In ES5, you had to use an IIFE if you wanted to keep a variable local:
In ECMAScript 6, you can simply use a block and a
In ECMAScript 5 code that doesn’t use modules via libraries (such as RequireJS, browserify or webpack), the revealing module pattern is popular, and based on an IIFE. Its advantage is that it clearly separates between what is public and what is private:
This module pattern produces a global variable and is used as follows:
In ECMAScript 6, modules are built in, which is why the barrier to adopting them is low:
This module does not produce a global variable and is used as follows:
There is one use case where you still need an immediately-invoked function in ES6: Sometimes you only can produce a result via a sequence of statements, not via a single expression. If you want to inline those statements, you have to immediately invoke a function. In ES6, you can use immediately-invoked arrow functions if you want to:
Note that you must parenthesize as shown (the parens are around the arrow function, not around the complete function call). Details are explained in the chapter on arrow functions.
In ES5, constructor functions where the mainstream way of creating factories for objects (but there were also many other techniques, some arguably more elegant). In ES6, classes are the mainstream way of implementing constructor functions. Several frameworks support them as alternatives to their custom inheritance APIs.
This section starts with a cheat sheet, before describing each ES6 callable entity in detail.
|Func decl/Func expr||Arrow||Class||Method|
|Func decl||Func expr||Arrow||Class||Method|
|Inner name (2)||×||✔||✔||×|
|Func decl||Func expr||Arrow||Class (3)||Method|
Abbreviations in cells:
Function.prototypefor base classes. The details are explained in the chapter on classes.
What about generator functions and methods? Those work like their non-generator counterparts, with two exceptions:
(GeneratorFunction)is an internal object, see diagram in Sect. “Inheritance within the iteration API (including generators)”).
|FC strict||FC sloppy||MC||
Abbreviations in column titles:
Abbreviations in cells:
These are the functions that you know from ES5. There are two ways to create them:
undefinedin strict mode and the global object in sloppy mode.
thisis the receiver of the method call (or the first argument of
thisis the newly created instance.
Generator functions are explained in the chapter on generators. Their syntax is similar to traditional functions, but they have an extra asterisk:
The rules for
this are as follows. Note that it never refers to the generator object.
thisis handled like it is with traditional functions. The results of such calls are generator objects.
TypeErroris thrown if you do.
Method definitions can appear inside object literals:
As you can see, you must separate method definitions in an object literal with commas, but there are no separators between them in a class definition. The former is necessary to keep the syntax consistent, especially with regard to getters and setters.
Method definitions are the only place where you can use
super to refer to super-properties. Only method definitions that use
super produce functions that have the property
[[HomeObject]], which is required for that feature (details are explained in the chapter on classes).
Inside class definitions, methods whose name is
constructor are special, as explained later.
Generator methods are explained in the chapter on generators. Their syntax is similar to method definitions, but they have an extra asterisk:
superas you would in normal method definitions.
Arrow functions are explained in their own chapter:
The following variables are lexical inside an arrow function (picked up from the surrounding scope):
thiscontinues to be lexical and does not refer to the receiver of a method call.
Classes are explained in their own chapter.
constructor is special, because it “becomes” the class. That is, classes are very similar to constructor functions:
thisrefers to it. A derived class receives its instance from its superclass, which is why it needs to call
superbefore it can access
someFunc.call(thisValue, arg0, arg1)
This section explains how these two work and why you will rarely call methods directly in ECMAScript 6. Before we get started, I’ll refresh your knowledge w.r.t. to prototype chains.
['a', 'b'] looks as follows:
Array.prototype, the properties provided by the
Object.prototype, the properties provided by the
null(the end of the chain, so not really a member of it)
You can examine the chain via
Properties in “earlier” objects override properties in “later” objects. For example,
Array.prototype provides an Array-specific version of the
toString() method, overriding
If you look at the method call
arr.toString() you can see that it actually performs two steps:
arr, retrieve the value of the first property whose name is
thisto the receiver
arrof the method invocation.
You can make the two steps explicit by using the
call() method of functions:
Function.prototype.call(thisValue, arg0?, arg1?, ···)
call and method
apply are invoked on functions. They are different from normal function calls in that you specify a value for
call provides the arguments of the method call via individual parameters,
apply provides them via an Array.
With a dispatched method call, the receiver plays two roles: It is used to find the method and it is an implicit parameter. A problem with the first role is that a method must be in the prototype chain of an object if you want to invoke it. With a direct method call, the method can come from anywhere. That allows you to borrow a method from another object. For example, you can borrow
Object.prototype.toString and thus apply the original, un-overridden implementation of
toString to an Array
The Array version of
toString() produces a different result:
Object.prototype (which have to work with all objects and are thus implicitly generic).
This section covers use cases for direct method calls. Each time, I’ll first describe the use case in ES5 and then how it changes with ES6 (where you’ll rarely need direct method calls).
Some functions accept multiple values, but only one value per parameter. What if you want to pass the values via an Array?
push() lets you destructively append several values to an Array:
But you can’t destructively append a whole Array. You can work around that limitation by using
Math.min() only work for single values:
apply(), you can use them for Arrays:
...) mostly replaces
Making a direct method call via
apply() only because you want to turn an Array into arguments is clumsy, which is why ECMAScript 6 has the spread operator (
...) for this. It provides this functionality even in dispatched method calls.
As a bonus, spread also works with the
apply() can’t be used with
new – the above feat can only be achieved via a complicated work-around in ECMAScript 5.
First, the special variable
arguments of functions is Array-like. It has a
length and indexed access to elements.
arguments isn’t an instance of
Array and does not have the method
Second, the DOM method
document.querySelectorAll() returns an instance of
Thus, for many complex operations, you need to convert Array-like objects to Arrays first. That is achieved via
Array.prototype.slice(). This method copies the elements of its receiver into a new Array:
If you call
slice() directly, you can convert a
NodeList to an Array:
And you can convert
arguments to an Array:
On one hand, ECMAScript 6 has
Array.from(), a simpler way of converting Array-like objects to Arrays:
On the other hand, you won’t need the Array-like
arguments, because ECMAScript 6 has rest parameters (declared via a triple dot):
obj.hasOwnProperty('prop') tells you whether
obj has the own (non-inherited) property
hasOwnProperty via dispatch can cease to work properly if
Object.prototype.hasOwnProperty is overridden.
hasOwnProperty may also be unavailable via dispatch if
Object.prototype is not in the prototype chain of an object.
In both cases, the solution is to make a direct call to
hasOwnProperty() is mostly used to implement Maps via objects. Thankfully, ECMAScript 6 has a built-in
Map data structure, which means that you’ll need
Applying an Array method such as
join() to a string normally involves two steps:
Strings are Array-like and can become the
this value of generic Array methods. Therefore, a direct call lets you avoid step 1:
Similarly, you can apply
map() to a string either after you split it or via a direct method call:
Note that the direct calls may be more efficient, but they are also much less elegant. Be sure that they are really worth it!
Array.from() can convert and map in a single step, if you provide it with a callback as the second argument.
As a reminder, the two step solution is:
You can access the methods of
Object.prototype via an empty object literal (whose prototype is
Object.prototype). For example, the following two direct method calls are equivalent:
The same trick works for
This pattern has become quite popular. It does not reflect the intention of the author as clearly as the longer version, but it’s much less verbose. Speed-wise, there isn’t much of a difference between the two versions.
nameproperty of functions
name property of a function contains its name:
This property is useful for debugging (its value shows up in stack traces) and some metaprogramming tasks (picking a function by name etc.).
Prior to ECMAScript 6, this property was already supported by most engines. With ES6, it becomes part of the language standard and is frequently filled in automatically.
The following sections describe how
name is set up automatically for various programming constructs.
Functions pick up names if they are created via variable declarations:
But even with a normal assignment,
name is set up properly:
With regard to names, arrow functions are like anonymous function expressions:
From now on, whenever you see an anonymous function expression, you can assume that an arrow function works the same way.
If a function is a default value, it gets its name from its variable or parameter:
Function declarations and function expression are function definitions. This scenario has been supported for a long time: a function definition with a name passes it on to the
For example, a function declaration:
The name of a named function expression also sets up the
Because it comes first, the function expression’s name
baz takes precedence over other names (e.g. the name
bar provided via the variable declaration):
However, as in ES5, the name of a function expression is only a variable inside the function expression:
If a function is the value of a property, it gets its name from that property. It doesn’t matter if that happens via a method definition (line A), a traditional property definition (line B), a property definition with a computed property key (line C) or a property value shorthand (line D).
The names of getters are prefixed with
'get', the names of setters are prefixed with
The naming of methods in class definitions is similar to object literals:
Getters and setters again have the name prefixes
In ES6, the key of a method can be a symbol. The
name property of such a method is still a string:
Remember that class definitions create functions. Those functions also have their property
name set up correctly:
All of the following statements set
new Function()produces functions whose
'anonymous'. A webkit bug describes why that is necessary on the web.
func.bind(···)produces a function whose
functionFactory() is assigned in line A and not changed by the declaration in line B.
One could, in theory, check for each assignment whether the right-hand side evaluates to a function and whether that function doesn’t have a name, yet. But that would incur a significant performance penalty.
Function names are subject to minification, which means that they will usually change in minified code. Depending on what you want to do, you may have to manage function names via strings (which are not minified) or you may have to tell your minifier what names not to minify.
These are the attributes of property
The property not being writable means that you can’t change its value via assignment:
The property is, however, configurable, which means that you can change it by re-defining it:
If the property
name already exists then you can omit the descriptor property
configurable, because missing descriptor properties mean that the corresponding attributes are not changed.
If the property
name does not exist yet then the descriptor property
configurable ensures that
name remains configurable (the default attribute values are all
namein the spec
SetFunctionName()sets up the property
name. Search for its name in the spec to find out where that happens.
namecan be seen by looking at their runtime semantics:
SetFunctionName(). That operation is not invoked for anonymous function expressions.
SetFunctionName()is not invoked).
=>) in ES6, but no “thin” arrow functions (
ECMAScript 6 has syntax for functions with a lexical
this, so-called arrow functions. However, it does not have arrow syntax for functions with dynamic
this. That omission was deliberate; method definitions cover most of the use cases for thin arrows. If you really need dynamic
this, you can still use a traditional function expression.
ES6 has a new protocol for subclassing, which is explained in the chapter on classes. Part of that protocol is the meta-property
new.target, which refers to the first element in a chain of constructor calls (similar to
this in a chain for supermethod calls). It is
undefined if there is no constructor call. We can use that to enforce that a function must be invoked via
new or that it must not be invoked via it. This is an example for the latter:
In ES5, this was usually checked like this: