Exploring Es6
Exploring Es6
Exploring Es6
Axel Rauschmayer
This book is for sale at http://leanpub.com/exploring-es6
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools
and many iterations to get reader feedback, pivot until you have the right book and build
traction once you do.
Short TOC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . x
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
I Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1. About ECMAScript 6 (ES6) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1 TC39 (Ecma Technical Committee 39) . . . . . . . . . . . . . . . . . . . . . . 2
1.2 How ECMAScript 6 was designed . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2.1 The design process after ES6 . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 JavaScript versus ECMAScript . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Upgrading to ES6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
CONTENTS
2. FAQ: ECMAScript 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.1 How can I use ES6 today? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Isn’t ECMAScript 6 now called ECMAScript 2015? . . . . . . . . . . . . . . . 9
2.3 How do I migrate my ECMAScript 5 code to ECMAScript 6? . . . . . . . . . . 9
2.4 Does it still make sense to learn ECMAScript 5? . . . . . . . . . . . . . . . . . 9
2.5 Is ES6 bloated? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.6 Isn’t the ES6 specification very big? . . . . . . . . . . . . . . . . . . . . . . . 10
2.7 Does ES6 have array comprehensions? . . . . . . . . . . . . . . . . . . . . . . 10
2.8 Is ES6 statically typed? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
II Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5. New number and Math features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.1.1 New integer literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.1.2 New Number properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.1.3 New Math methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.2 New integer literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.2.1 Use case for octal literals: Unix-style file permissions . . . . . . . . . . . 41
5.2.2 Number.parseInt() and the new integer literals . . . . . . . . . . . . . . 42
5.3 New static Number properties . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.3.1 Previously global functions . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.3.2 Number.EPSILON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.3.3 Number.isInteger(number) . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.3.4 Safe integers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.4 Math . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.4.1 Various numerical functionality . . . . . . . . . . . . . . . . . . . . . . . 49
5.4.2 Using 0 instead of 1 with exponentiation and logarithm . . . . . . . . . . 50
5.4.3 Logarithms to base 2 and 10 . . . . . . . . . . . . . . . . . . . . . . . . . 51
5.4.4 Support for compiling to JavaScript . . . . . . . . . . . . . . . . . . . . . 52
5.4.5 Bitwise operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.4.6 Trigonometric methods . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.5 FAQ: numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.5.1 How can I use integers beyond JavaScript’s 53 bit range? . . . . . . . . . 53
CONTENTS
7. Symbols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
7.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
7.1.1 Use case 1: unique property keys . . . . . . . . . . . . . . . . . . . . . . 61
7.1.2 Use case 2: constants representing concepts . . . . . . . . . . . . . . . . 61
7.1.3 Pitfall: you can’t coerce symbols to strings . . . . . . . . . . . . . . . . . 62
7.1.4 Which operations related to property keys are aware of symbols? . . . . 63
7.2 A new primitive type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7.2.1 Symbols as property keys . . . . . . . . . . . . . . . . . . . . . . . . . . 64
7.2.2 Enumerating own property keys . . . . . . . . . . . . . . . . . . . . . . 64
7.3 Using symbols to represent concepts . . . . . . . . . . . . . . . . . . . . . . . 65
7.4 Symbols as keys of properties . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.4.1 Symbols as keys of non-public properties . . . . . . . . . . . . . . . . . . 67
7.4.2 Symbols as keys of meta-level properties . . . . . . . . . . . . . . . . . . 68
7.4.3 Examples of name clashes in JavaScript’s standard library . . . . . . . . 68
7.5 Converting symbols to other primitive types . . . . . . . . . . . . . . . . . . 69
7.5.1 Pitfall: coercion to string . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.5.2 Making sense of the coercion rules . . . . . . . . . . . . . . . . . . . . . 69
7.5.3 Explicit and implicit conversion in the spec . . . . . . . . . . . . . . . . 70
7.6 Wrapper objects for symbols . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
7.6.1 Accessing properties via [ ] and wrapped keys . . . . . . . . . . . . . . 73
7.7 Crossing realms with symbols . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.8 FAQ: symbols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7.8.1 Can I use symbols to define private properties? . . . . . . . . . . . . . . 75
7.8.2 Are symbols primitives or objects? . . . . . . . . . . . . . . . . . . . . . 76
7.8.3 Do we really need symbols? Aren’t strings enough? . . . . . . . . . . . . 76
7.8.4 Are JavaScript’s symbols like Ruby’s symbols? . . . . . . . . . . . . . . . 77
7.9 The spelling of well-known symbols: why Symbol.iterator and not Sym-
bol.ITERATOR (etc.)? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.10 The symbol API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.10.1 The function Symbol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.10.2 Methods of symbols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.10.3 Converting symbols to other values . . . . . . . . . . . . . . . . . . . . . 77
CONTENTS
8. Template literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
8.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
8.2 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
8.2.1 Template literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
8.2.2 Escaping in template literals . . . . . . . . . . . . . . . . . . . . . . . . . 81
8.2.3 Line terminators in template literals are always LF (\n) . . . . . . . . . . 82
8.2.4 Tagged template literals . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
8.3 Examples of using tagged template literals . . . . . . . . . . . . . . . . . . . . 83
8.3.1 Raw strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
8.3.2 Shell commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8.3.3 Byte strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8.3.4 HTTP requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8.3.5 More powerful regular expressions . . . . . . . . . . . . . . . . . . . . . 85
8.3.6 Query languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
8.3.7 React JSX via tagged templates . . . . . . . . . . . . . . . . . . . . . . . 86
8.3.8 Facebook GraphQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
8.3.9 Text localization (L10N) . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
8.3.10 Text templating via untagged template literals . . . . . . . . . . . . . . . 89
8.3.11 A tag function for HTML templating . . . . . . . . . . . . . . . . . . . . 91
8.4 Implementing tag functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.4.1 Number of template strings versus number of substitutions . . . . . . . . 93
8.4.2 Escaping in tagged template literals: cooked versus raw . . . . . . . . . . 93
8.4.3 Example: String.raw . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
8.4.4 Example: implementing a tag function for HTML templating . . . . . . . 95
8.4.5 Example: assembling regular expressions . . . . . . . . . . . . . . . . . . 98
8.5 FAQ: template literals and tagged template literals . . . . . . . . . . . . . . . 99
8.5.1 Where do template literals and tagged template literals come from? . . . 99
8.5.2 What is the difference between macros and tagged template literals? . . . 99
8.5.3 Can I load a template literal from an external source? . . . . . . . . . . . 100
8.5.4 Why are backticks the delimiters for template literals? . . . . . . . . . . 100
8.5.5 Weren’t template literals once called template strings? . . . . . . . . . . 100
IV Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
VI Miscellaneous . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512
• Quick start: Begin with the chapter “Core ES6 features”. Additionally, almost every
chapter starts with a section giving an overview of what’s in the chapter. The last chapter
collects all of these overview sections in a single location.
• Solid foundation: Each chapter always starts with the essentials and then increasingly
goes into details. The headings should give you a good idea of when to stop reading, but I
also occasionally give tips in sidebars w.r.t. how important it is to know something.
¹http://speakingjs.com/
What you need to know about this book iv
Glossary
'use strict';
If you put this line at the beginning of a file, all code in it is in strict mode. If you make this line
the first line of a function, only that function is in strict mode.
Using a directive to switch on strict mode is not very user friendly and was one of the reasons
why strict mode was not nearly as popular in ES5 as it should be. However, ES6 modules and
classes are implicitly in strict mode. Given that most ES6 code will live in modules, strict mode
becomes the de-facto default for ES6.
Protocol
The term protocol has various meanings in computing. In the context of programming languages
and API design, I’m using it as follows:
A protocol defines interfaces (signatures for methods and/or functions) and rules for
using them.
The idea is to specify how a service is to be performed. Then anyone can perform the service
and anyone can request it and they are guaranteed to work together well.
Note that the definition given here is different from viewing a protocol as an interface (as, for
example, Objective C does), because this definition includes rules.
You can see that parseInt() expects a string and a number and returns a number. If the type of
a parameter is clear, I often omit the type annotation.
Internal slots
The ES6 language specification uses internal slots to store internal data. In the spec, internal slots
are accessed as if they were properties whose names are in square brackets:
What you need to know about this book vi
O.[[GetPrototypeOf]]()
• They are not read via “get” operations and written via “set” operations.
• They are only known to the spec and not accessible from JavaScript. For example: the link
between an object and its prototype is the internal slot [[Prototype]]. The value of that
slot cannot be read directly via JavaScript, but you can use Object.getPrototypeOf() to
do so.
How exactly internal slots are stored is left unspecified. Some may not even exist in actual
JavaScript implementations.
Destructive operations
Destructive operations (methods, functions) modify their parameters or their receivers. For
example, push() modifies its receiver arr:
In contrast, concat() creates a new Array and does not change its receiver arr:
Conventions
Documenting classes
The API of a class C is usually documented as follows:
• C constructor
• Static C methods
• C.prototype methods
What you need to know about this book vii
Capitalization
In English, I capitalize JavaScript terms as follows:
• The names of primitive entities are not capitalized: a boolean value, a number value,
a symbol, a string. One reason why I’m doing this is because TypeScript and Flow
distinguish:
– The type String: its members are objects, instances of String.
– The type string: its members are primitive values, strings.
• The data structure Map is capitalized. Rationale: distinguish from the Array method map().
• The data structure Set is capitalized. Rationale: distinguish from the verb set.
• Array and Promise are capitalized. Rationale: easy to confuse with English words.
• Not capitalized (for now): object, generator, proxy.
• async-examples⁹
• babel-on-node¹⁰
• demo_promise¹¹
• generator-examples¹²
• node-es6-demo¹³
• promise-examples¹⁴
• webpack-es6-demo¹⁵
Sidebars
Sidebars are boxes of text marked with icons. They complement the normal content.
Code on GitHub
Tells you where you can download demo code shown in this book.
⁹https://github.com/rauschma/async-examples
¹⁰https://github.com/rauschma/babel-on-node
¹¹https://github.com/rauschma/demo_promise
¹²https://github.com/rauschma/generator-examples
¹³https://github.com/rauschma/node-es6-demo
¹⁴https://github.com/rauschma/promise-examples
¹⁵https://github.com/rauschma/webpack-es6-demo
What you need to know about this book viii
Information
General information.
Question
Asks and answers a question, in FAQ style.
Warning
Things you need to be careful about.
External material
Points to related material hosted somewhere on the web.
Footnotes
Occasionally, I refer to (publicly available) external material via footnotes. Two sources are
marked with a prefix in square brackets:
Feel free to skip this part, you won’t miss anything that is essential w.r.t. the features
of ES6.
1. About ECMAScript 6 (ES6)
It took a long time to finish it, but ECMAScript 6, the next version of JavaScript, is finally a
reality:
The next sections explain concepts that are important in the world of ES6.
• Standard: If the proposal continues to prove itself and is accepted by TC39, it will
eventually be included in an edition of the ECMAScript standard. At this point, it is a
standard feature.
These groups have remarkably little control over each other. That’s why upgrading a web
language is so challenging.
On one hand, upgrading engines is challenging, because they are confronted with all kinds of
code on the web, some of which is very old. You also want engine upgrades to be automatic and
unnoticeable for users. Therefore, ES6 is a superset of ES5, nothing is removed⁹. ES6 upgrades the
⁷http://tc39wiki.calculist.org/about/harmony/
⁸https://github.com/tc39/ecma262
⁹This is not completely true: there are a few minor breaking changes that don’t affect code on the web. These are detailed in section D.1
and section E.1 of the ES6 specification.
About ECMAScript 6 (ES6) 4
language without introducing versions or modes. It even manages to make strict mode the de-
facto default (via modules), without increasing the rift between it and sloppy mode. The approach
that was taken is called “One JavaScript” and explained in a separate chapter.
On the other hand, upgrading code is challenging, because your code must run on all JavaScript
engines that are used by your target audience. Therefore, if you want to use ES6 in your code,
you only have two choices: You can either wait until no one in your target audience uses a non-
ES6 engine, anymore. That will take years; mainstream audiences were at that point w.r.t. ES5
when ES6 became a standard in June 2015. And ES5 was standardized in December 2009! Or you
can compile ES6 to ES5 and use it now. More information on how to do that is given in the book
“Setting up ES6¹⁰”, which is free to read online.
Goals and requirements clash in the design of ES6:
i. complex applications;
ii. libraries (possibly including the DOM) shared by those applications;
iii. code generators targeting the new edition.
Sub-goal (i) acknowledges that applications written in JavaScript have grown huge. A key ES6
feature fulfilling this goal is built-in modules.
Modules are also an answer to goal (ii). As an aside, the DOM is notoriously difficult to implement
in JavaScript. ES6 Proxies should help here.
Several features were mainly added to make it easier to compile to JavaScript. Two examples are:
They are both useful for, e.g., compiling C/C++ to JavaScript via Emscripten¹².
¹⁰https://leanpub.com/setting-up-es6
¹¹http://wiki.ecmascript.org/doku.php?id=harmony:harmony
¹²https://github.com/kripken/emscripten
About ECMAScript 6 (ES6) 5
• Better syntax for features that already exist (e.g. via libraries). For example:
– Classes
– Modules
• New functionality in the standard library. For example:
– New methods for strings and Arrays
– Promises
– Maps, Sets
• Completely new features. For example:
– Generators
– Proxies
– WeakMaps
About ECMAScript 6 (ES6) 6
• ECMAScript 4 was designed by Adobe, Mozilla, Opera, and Google and was a massive
upgrade. Its planned feature sets included:
– Programming in the large (classes, interfaces, namespaces, packages, program units,
optional type annotations, and optional static type checking and verification)
– Evolutionary programming and scripting (structural types, duck typing, type defini-
tions, and multimethods)
– Data structure construction (parameterized types, getters and setters, and meta-level
methods)
– Control abstractions (proper tail calls, iterators, and generators)
– Introspection (type meta-objects and stack marks)
• ECMAScript 3.1 was designed by Microsoft and Yahoo. It was planned as a subset of ES4
and an incremental upgrade of ECMAScript 3, with bug fixes and minor new features.
ECMAScript 3.1 eventually became ECMAScript 5.
¹³http://www.adaptivepath.com/ideas/ajax-new-approach-web-applications/
About ECMAScript 6 (ES6) 7
The two groups disagreed on the future of JavaScript and tensions between them continued to
increase.
It’s no secret that the JavaScript standards body, Ecma’s Technical Committee 39,
has been split for over a year, with some members favoring ES4 […] and others
advocating ES3.1 […]. Now, I’m happy to report, the split is over.
The agreement that was worked out at the meeting consisted of four points:
Thus: The ES4 group agreed to make Harmony less radical than ES4, the rest of TC39 agreed to
keep moving things forward.
The next versions of ECMAScript are:
• ECMAScript 5 (December 2009). This is the version of ECMAScript that most browsers
support today. It brings several enhancements to the standard library and updated
language semantics via a strict mode.
• ECMAScript 5.1 (June 2011). ES5 was submitted as an ISO standard. In the process, minor
corrections were made. ES5.1 contains those corrections. It is the same text as ISO/IEC
16262:2011.
• ECMAScript 6 (June 2015). This version went through several name changes:
¹⁴http://www.ecmascript.org/es4/spec/overview.pdf
¹⁵http://ejohn.org/blog/ecmascript-harmony/
¹⁶https://mail.mozilla.org/pipermail/es-discuss/2008-August/006837.html
About ECMAScript 6 (ES6) 8
– ECMAScript Harmony: was the initial code name for JavaScript improvements after
ECMAScript 5.
– ECMAScript.next: It became apparent that the plans for Harmony were too ambitious
for a single version, so its features were split into two groups: The first group of
features had highest priority and was to become the next version after ES5. The code
name of that version was ECMAScript.next, to avoid prematurely comitting to a
version number, which proved problematic with ES4. The second group of features
had time until after ECMAScript.next.
– ECMAScript 6: As ECMAScript.next matured, its code name was dropped and
everybody started to call it ECMAScript 6.
– ECMAScript 2015: In late 2014, TC39 decided to change the official name of EC-
MAScript 6 to ECMAScript 2015, in light of upcoming yearly spec releases. However,
given how established the name “ECMAScript 6” already is and how late TC39
changed their minds, I expect that that’s how everybody will continue to refer to
that version.
• ECMAScript 2016 was previously called ECMAScript 7. Starting with ES2016, the lan-
guage standard will see smaller yearly releases.
2. FAQ: ECMAScript 6
This chapter answers a few frequently asked questions about ECMAScript 6.
• There are several ECMAScript 6 features that kind of replace ECMAScript 5 features, but
still use them as their foundations. It is important to understand those foundations. Two
examples: classes are internally translated to constructors and methods are still functions
(as they have always been).
• As long as ECMAScript 6 is compiled to ECMAScript 5, it is useful to understand the
output of the compilation process. And you’ll have to compile to ES5 for a while (probably
years), until you can rely on ES6 being available in all relevant browsers.
• It’s important to be able to understand legacy code.
• It may be possible to create comprehensions that work for arbitrary datatypes (think
Microsoft’s LINQ).
⁴Source: Tweet by Allen Wirfs-Brock. https://twitter.com/awbjs/status/574649464687734785
FAQ: ECMAScript 6 11
• It may also be possible that methods for iterators are a better way to achieve what
comprehensions do.
• Microsoft TypeScript: is basically ES6 plus optional type annotations. At the moment, it
is compiled to ES5 and throws away the type information while doing so. Optionally, it can
also make that information available at runtime, for type introspection and for runtime
type checks.
• Facebook Flow: is a type checker for ECMAScript 6 that is based on flow analysis. As
such, it only adds optional type annotations to the language and infers and checks types.
It does not help with compiling ES6 to ES5.
• It allows you to detect a certain category of errors earlier, because the code is analyzed
statically (during development, without running code). As such, static typing is comple-
mentary to testing and catches different errors.
• It helps IDEs with auto-completion.
Both TypeScript and Flow are using the same notation. Type annotations are optional, which
makes this approach relatively lightweight. Even without annotations, types can often be
inferred. Therefore, this kind of type checking is even useful for completely unannotated code,
as a consistency check.
3. One JavaScript: avoiding
versioning in ECMAScript 6
What is the best way to add new features to a language? This chapter describes the approach
taken by ECMAScript 6. It is called One JavaScript, because it avoids versioning.
3.1 Versioning
In principle, a new version of a language is a chance to clean it up, by removing outdated
features or by changing how features work. That means that new code doesn’t work in older
implementations of the language and that old code doesn’t work in a new implementation. Each
piece of code is linked to a specific version of the language. Two approaches are common for
dealing with versions being different.
First, you can take an “all or nothing” approach and demand that, if a code base wants to use the
new version, it must be upgraded completely. Python took that approach when upgrading from
Python 2 to Python 3. A problem with it is that it may not be feasible to migrate all of an existing
code base at once, especially if it is large. Furthermore, the approach is not an option for the web,
where you’ll always have old code and where JavaScript engines are updated automatically.
Second, you can permit a code base to contain code in multiple versions, by tagging code with
versions. On the web, you could tag ECMAScript 6 code via a dedicated Internet media type¹.
Such a media type can be associated with a file via an HTTP header:
Content-Type: application/ecmascript;version=6
It can also be associated via the type attribute of the <script> element (whose default value² is
text/javascript):
<script type="application/ecmascript;version=6">
···
</script>
This specifies the version out of band, externally to the actual content. Another option is to
specify the version inside the content (in-band). For example, by starting a file with the following
line:
¹http://en.wikipedia.org/wiki/Internet_media_type
²http://www.w3.org/TR/html5/scripting-1.html#attr-script-type
One JavaScript: avoiding versioning in ECMAScript 6 13
use version 6;
Both ways of tagging are problematic: out-of-band versions are brittle and can get lost, in-band
versions add clutter to code.
A more fundamental issue is that allowing multiple versions per code base effectively forks a
language into sub-languages that have to be maintained in parallel. This causes problems:
• Engines become bloated, because they need to implement the semantics of all versions.
The same applies to tools analyzing the language (e.g. style checkers such as JSLint).
• Programmers need to remember how the versions differ.
• Code becomes harder to refactor, because you need to take versions into consideration
when you move pieces of code.
Therefore, versioning is something to avoid, especially for JavaScript and the web.
• let-declarations are difficult to add to non-strict mode, because let is not a reserved word
in that mode. The only variant of let that looks like valid ES5 code is:
let[x] = arr;
Research yielded that no code on the web uses a variable let in non-strict mode in this
manner. That enabled TC39 to add let to non-strict mode. Details of how this was done
are described later in this chapter.
• Function declarations do occasionally appear in non-strict blocks, which is why the ES6
specification describes measures that web browsers can take to ensure that such code
doesn’t break. Details are explained later.
One JavaScript: avoiding versioning in ECMAScript 6 14
'use strict';
• Syntactic changes: some previously legal syntax is forbidden in strict mode. For example:
– The with statement is forbidden. It lets users add arbitrary objects to the chain of
variable scopes, which slows down execution and makes it tricky to figure out what
a variable refers to.
– Deleting an unqualified identifier (a variable, not a property) is forbidden.
– Functions can only be declared at the top level of a scope.
– More identifiers are reserved⁴: implements interface let package private pro-
tected public static yield
• More errors. For example:
– Assigning to an undeclared variable causes a ReferenceError. In non-strict mode, a
global variable is created in this case.
– Changing read-only properties (such as the length of a string) causes a TypeError.
In non-strict mode, it simply has no effect.
• Different semantics: Some constructs behave differently in strict mode. For example:
– arguments doesn’t track the current values of parameters, anymore.
– this is undefined in non-method functions. In non-strict mode, it refers to the
global object (window), which meant that global variables were created if you called
a constructor without new.
Strict mode is a good example of why versioning is tricky: Even though it enables a cleaner
version of JavaScript, its adoption is still relatively low. The main reasons are that it breaks
some existing code, can slow down execution and is a hassle to add to files (let alone interactive
command lines). I love the idea of strict mode and don’t nearly use it often enough.
In strict ECMAScript 6, you get an exception in line 1, because you are using the reserved word
let as a variable name. And the statement in line 2 is interpreted as a let variable declaration
(that uses destructuring).
In sloppy ECMAScript 6, the first line does not cause an exception, but the second line is still
interpreted as a let declaration. This way of using the identifier let is so rare on the web that ES6
can afford to make this interpretation. Other ways of writing let declarations can’t be mistaken
for sloppy ES5 syntax:
Second, the global object (window in browsers) shouldn’t be in the scope chain of variables. But
it is also much too late to change that now. At least, one won’t be in global scope in modules and
let never creates properties of the global object, not even when used in global scope.
3.4 Conclusion
One JavaScript means making ECMAScript 6 completely backward-compatible. It is great that
that succeeded. Especially appreciated is that modules (and thus most of our code) are implicitly
in strict mode.
⁶http://www.ecma-international.org/ecma-262/6.0/#sec-corrections-and-clarifications-in-ecmascript-2015-with-possible-compatibility-
impact
⁷http://www.ecma-international.org/ecma-262/6.0/#sec-additions-and-changes-that-introduce-incompatibilities-with-prior-editions
One JavaScript: avoiding versioning in ECMAScript 6 17
In the short term, adding ES6 constructs to both strict mode and sloppy mode is more work
when it comes to writing the language specification and to implementing it in engines. In the
long term, both the spec and engines profit from the language not being forked (less bloat etc.).
Programmers profit immediately from One JavaScript, because it makes it easier to get started
with ECMAScript 6.
var x = 3;
function func(randomize) {
if (randomize) {
var x = Math.random(); // (A) scope: whole function
return x;
}
return x; // accesses the x from line A
}
func(false); // undefined
That func() returns undefined may be surprising. You can see why if you rewrite the code so
that it more closely reflects what is actually going on:
var x = 3;
function func(randomize) {
var x;
if (randomize) {
x = Math.random();
return x;
}
return x;
}
func(false); // undefined
In ES6, you can additionally declare variables via let and const. Such variables are block-scoped,
their scopes are the innermost enclosing blocks. let is roughly a block-scoped version of var.
const works like let, but creates variables whose values can’t be changed.
let and const behave more strictly and throw more exceptions (e.g. when you access their
variables inside their scope before they are declared). Block-scoping helps with keeping the
Core ES6 features 19
effects of code fragments more local (see the next section for a demonstration). And it’s
more mainstream than function-scoping, which eases moving between JavaScript and other
programming languages.
If you replace var with let in the initial version, you get different behavior:
let x = 3;
function func(randomize) {
if (randomize) {
let x = Math.random();
return x;
}
return x;
}
func(false); // 3
That means that you can’t blindly replace var with let or const in existing code; you have to
be careful during refactoring.
My advice is:
• Prefer const. You can use it for all variables whose values never change.
• Otherwise, use let – for variables whose values do change.
• Avoid var.
console.log(tmp); // ReferenceError
In ECMAScript 6, you can simply use a block and a let declaration (or a const declaration):
Core ES6 features 20
{ // open block
let tmp = ···;
···
} // close block
console.log(tmp); // ReferenceError
function printCoord(x, y) {
console.log('('+x+', '+y+')');
}
function printCoord(x, y) {
console.log(`(${x}, ${y})`);
}
var HTML5_SKELETON =
'<!doctype html>\n' +
'<html>\n' +
'<head>\n' +
' <meta charset="UTF-8">\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n' +
'</body>\n' +
'</html>\n';
If you escape the newlines via backslashes, things look a bit nicer (but you still have to explicitly
add newlines):
Core ES6 features 21
const HTML5_SKELETON = `
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
</body>
</html>`;
(The examples differ in how much whitespace is included, but that doesn’t matter in this case.)
More information: chapter “Template literals and tagged templates”.
function UiComponent() {
var _this = this; // (A)
var button = document.getElementById('myButton');
button.addEventListener('click', function () {
console.log('CLICK');
_this.handleClick(); // (B)
});
}
UiComponent.prototype.handleClick = function () {
···
};
In ES6, you can use arrow functions, which don’t shadow this (line A):
Core ES6 features 22
function UiComponent() {
var button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('CLICK');
this.handleClick(); // (A)
});
}
(In ES6, you also have the option of using a class instead of a constructor function. That is
explored later.)
Arrow functions are especially handy for short callbacks that only return results of expressions.
In ES5, such callbacks are relatively verbose:
When defining parameters, you can even omit parentheses if the parameters are just a single
identifier. Thus: (x) => x * x and x => x * x are both allowed.
More information: chapter “Arrow functions”.
var matchObj =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
var year = matchObj[1];
var month = matchObj[2];
var day = matchObj[3];
The empty slot at the beginning of the Array pattern skips the Array element at index zero.
In ES5, you have the option of using the Array method forEach():
arr.forEach(function (elem) {
console.log(elem);
});
A for loop has the advantage that you can break from it, forEach() has the advantage of
conciseness.
In ES6, the for-of loop combines both advantages:
If you want both index and value of each array element, for-of has got you covered, too, via
the new Array method entries() and destructuring:
function foo(x, y) {
x = x || 0;
y = y || 0;
···
}
An added benefit is that in ES6, a parameter default value is only triggered by undefined, while
it is triggered by any falsy value in the previous ES5 code.
More information: section “Parameter default values”.
Two advantages of this approach are: Code becomes more self-descriptive and it is easier to omit
arbitrary parameters.
In ES5, you can implement selectEntries() as follows:
function selectEntries(options) {
var start = options.start || 0;
var end = options.end || -1;
var step = options.step || 1;
···
}
In ES6, you can use destructuring in parameter definitions and the code becomes simpler:
function selectEntries(options) {
options = options || {}; // (A)
var start = options.start || 0;
var end = options.end || -1;
var step = options.step || 1;
···
}
function logAllArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
In ES6, you can declare a rest parameter (args in the example below) via the ... operator:
function logAllArguments(...args) {
for (const arg of args) {
console.log(arg);
}
}
Rest parameters are even nicer if you are only interested in trailing parameters:
function format(pattern) {
var args = [].slice.call(arguments, 1);
···
}
Rest parameters make code easier to read: You can tell that a function has a variable number of
parameters just by looking at its parameter definitions.
More information: section “Rest parameters”.
4.10.1 Math.max()
Math.max() returns the numerically greatest of its arguments. It works for an arbitrary number
of arguments, but not for Arrays.
ES5 – apply():
4.10.2 Array.prototype.push()
Array.prototype.push() appends all of its arguments as elements to its receiver. There is no
method that destructively appends an Array to another one.
ES5 – apply():
arr1.push.apply(arr1, arr2);
// arr1 is now ['a', 'b', 'c', 'd']
arr1.push(...arr2);
// arr1 is now ['a', 'b', 'c', 'd']
console.log(arr1.concat(arr2, arr3));
// [ 'a', 'b', 'c', 'd', 'e' ]
var obj = {
foo: function () {
···
},
bar: function () {
this.foo();
}, // trailing comma is legal in ES5
}
const obj = {
foo() {
···
},
bar() {
this.foo();
},
}
function Person(name) {
this.name = name;
}
Person.prototype.describe = function () {
return 'Person called '+this.name;
};
In ES6, classes provide slightly more convenient syntax for constructor functions:
Core ES6 features 30
class Person {
constructor(name) {
this.name = name;
}
describe() {
return 'Person called '+this.name;
}
}
Note the compact syntax for method definitions – no keyword function needed. Also note that
there are no commas between the parts of a class.
ES6 has built-in support for subclassing, via the extends clause:
function MyError() {
// Use Error as a function
var superInstance = Error.apply(null, arguments);
copyOwnPropertiesFrom(this, superInstance);
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
In ES6, all built-in constructors can be subclassed, which is why the following code achieves
what the ES5 code can only simulate:
The following ES5 code contains the function countWords that uses the object dict as a map:
Core ES6 features 32
In ES6, you can use the built-in data structure Map and don’t have to escape keys. As a downside,
incrementing values inside Maps is less convenient.
Another benefit of Maps is that you can use arbitrary values as keys, not just strings.
More information:
• Section “The dict Pattern: Objects Without Prototypes Are Better Maps¹” in “Speaking
JavaScript”
• Chapter “Maps and Sets”
From join to repeat (the ES5 way of repeating a string is more of a hack):
arr.indexOf(NaN); // -1
arr.findIndex(x => Number.isNaN(x)); // 1
As an aside, the new Number.isNaN() provides a safe way to detect NaN (because it doesn’t coerce
non-numbers to numbers):
> isNaN('abc')
true
> Number.isNaN('abc')
false
If a value is iterable (as all Array-like DOM data structure are by now), you can also use the
spread operator (...) to convert it to an Array:
fill() is even more convenient if you want to create an Array that is filled with an arbitrary
value:
// ES5
var arr3 = Array.apply(null, new Array(2))
.map(function (x) { return 'x' });
// ['x', 'x']
// ES6
const arr4 = new Array(2).fill('x');
// ['x', 'x']
fill() replaces all Array elements with the given value. Holes are treated as if they were
elements.
More information: Sect. “Creating Arrays filled with values”
Core ES6 features 35
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
Alternatively, you can import the whole module as an object and access square and diag via it:
In ES6, multiple exports are called named exports and handled like this:
²http://christianheilmann.com/2007/08/22/again-with-the-module-pattern-reveal-something-to-the-world/
Core ES6 features 36
The syntax for importing modules as objects looks as follows (line A):
Node.js extends CommonJS and lets you export single values from modules, via module.exports:
In ES6, the same thing is done via a so-called default export (declared via export default):
Core ES6 features 37
• Number.EPSILON for comparing floating point numbers with a tolerance for rounding
errors.
• Number.isInteger(num) checks whether num is an integer (a number without a decimal
fraction):
> Number.isInteger(1.05)
false
> Number.isInteger(1)
true
> Number.isInteger(-3.1)
false
> Number.isInteger(-3)
true
• A method and constants for determining whether a JavaScript integer is safe (within the
signed 53 bit range in which there is no loss of precision):
– Number.isSafeInteger(number)
– Number.MIN_SAFE_INTEGER
– Number.MAX_SAFE_INTEGER
• Number.isNaN(num) checks whether num is the value NaN. In contrast to the global function
isNaN(), it doesn’t coerce its argument to a number and is therefore safer for non-numbers:
New number and Math features 40
> isNaN('???')
true
> Number.isNaN('???')
false
• Three additional methods of Number are mostly equivalent to the global functions with the
same names: Number.isFinite, Number.parseFloat, Number.parseInt.
> Math.sign(-8)
-1
> Math.sign(0)
0
> Math.sign(3)
1
> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3
> Math.log10(100)
2
Math.hypot() Computes the square root of the sum of the squares of its arguments (Pythagoras’
theorem):
> Math.hypot(3, 4)
5
> 0x9
9
> 0xA
10
> 0x10
16
> 0xFF
255
> 0b11
3
> 0b100
4
• Octal literals have the prefix 0o or 0O (that’s a zero followed by the capital letter O; the
first variant is safer):
> 0o7
7
> 0o10
8
Remember that the Number method toString(radix) can be used to see numbers in a base other
than 10:
> 255..toString(16)
'ff'
> 4..toString(2)
'100'
> 8..toString(8)
'10'
(The double dots are necessary so that the dot for property access isn’t confused with a decimal
dot.)
That means that permissions can be represented by 9 bits (3 categories with 3 permissions each):
That means that octal numbers are a compact representation of all permissions, you only need
3 digits, one digit per category of users. Two examples:
• 755 = 111,101,101: I can change, read and execute; everyone else can only read and execute.
• 640 = 110,100,000: I can read and write; group members can read; everyone can’t access at
all.
Number.parseInt(string, radix?)
Number.parseInt() provides special support for the hexadecimal literal notation – the prefix 0x
(or 0X) of string is removed if:
New number and Math features 43
• radix is missing or 0. Then radix is set to 16. As a rule, you should never omit the radix.
• radix is 16.
For example:
> Number.parseInt('0xFF')
255
> Number.parseInt('0xFF', 0)
255
> Number.parseInt('0xFF', 16)
255
In all other cases, digits are only parsed until the first non-digit:
However, Number.parseInt() does not have special support for binary or octal literals!
> Number.parseInt('0b111')
0
> Number.parseInt('0b111', 2)
0
> Number.parseInt('111', 2)
7
> Number.parseInt('0o10')
0
> Number.parseInt('0o10', 8)
0
> Number.parseInt('10', 8)
8
If you want to parse these kinds of literals, you need to use Number():
> Number('0b111')
7
> Number('0o10')
8
Number.parseInt() works fine with numbers that have a different base, as long as there is no
special prefix and the parameter radix is provided:
New number and Math features 44
> Number.parseInt('111', 2)
7
> Number.parseInt('10', 8)
8
5.3.1.1 Number.isFinite(number)
> Number.isFinite(Infinity)
false
> Number.isFinite(-Infinity)
false
> Number.isFinite(NaN)
false
> Number.isFinite(123)
true
The advantage of this method is that it does not coerce its parameter to number (whereas the
global function does):
> Number.isFinite('123')
false
> isFinite('123')
true
5.3.1.2 Number.isNaN(number)
However, this function coerces non-numbers to numbers and returns true if the result is NaN
(which is usually not what you want):
> isNaN('???')
true
The new method Number.isNaN() does not exhibit this problem, because it does not coerce its
arguments to numbers:
> Number.isNaN('???')
false
The following two methods work exactly like the global functions with the same names. They
were added to Number for completeness sake; now all number-related functions are available
there.
• Number.parseFloat(string)²
• Number.parseInt(string, radix)³
5.3.2 Number.EPSILON
Especially with decimal fractions, rounding errors can become a problem in JavaScript⁴. For
example, 0.1 and 0.2 can’t be represented precisely, which you notice if you add them and
compare them to 0.3 (which can’t be represented precisely, either).
Number.EPSILON specifies a reasonable margin of error when comparing floating point numbers.
It provides a better way to compare floating point values, as demonstrated by the following
function.
²[Speaking JS] parseFloat() in (“Speaking JavaScript”).
³[Speaking JS] parseInt() in (“Speaking JavaScript”).
⁴[Speaking JS] The details of rounding errors are explained in “Speaking JavaScript”.
New number and Math features 46
function epsEqu(x, y) {
return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true
5.3.3 Number.isInteger(number)
JavaScript has only floating point numbers (doubles). Accordingly, integers are simply floating
point numbers without a decimal fraction.
Number.isInteger(number) returns true if number is a number and does not have a decimal
fraction.
> Number.isInteger(-17)
true
> Number.isInteger(33)
true
> Number.isInteger(33.1)
false
> Number.isInteger('33')
false
> Number.isInteger(NaN)
false
> Number.isInteger(Infinity)
false
• Number.isSafeInteger(number)
• Number.MIN_SAFE_INTEGER
• Number.MAX_SAFE_INTEGER
The notion of safe integers centers on how mathematical integers are represented in JavaScript.
In the range (−2⁵³, 2⁵³) (excluding the lower and upper bounds), JavaScript integers are safe: there
is a one-to-one mapping between them and the mathematical integers they represent.
Beyond this range, JavaScript integers are unsafe: two or more mathematical integers are
represented as the same JavaScript integer. For example, starting at 2⁵³, JavaScript can represent
only every second mathematical integer:
New number and Math features 47
> 9007199254740992
9007199254740992
> 9007199254740993
9007199254740992
> 9007199254740994
9007199254740994
> 9007199254740995
9007199254740996
> 9007199254740996
9007199254740996
> 9007199254740997
9007199254740996
Therefore, a safe JavaScript integer is one that unambiguously represents a single mathematical
integer.
The two static Number properties specifying the lower and upper bound of safe integers could be
defined as follows:
For a given value n, this function first checks whether n is a number and an integer. If both
checks succeed, n is safe if it is greater than or equal to MIN_SAFE_INTEGER and less than or equal
to MAX_SAFE_INTEGER.
How can we make sure that results of computations with integers are correct? For example, the
following result is clearly not correct:
New number and Math features 48
> 9007199254740990 + 3
9007199254740992
> Number.isSafeInteger(9007199254740990)
true
> Number.isSafeInteger(3)
true
> Number.isSafeInteger(9007199254740992)
false
> 9007199254740995 - 10
9007199254740986
This time, the result is safe, but one of the operands isn’t:
> Number.isSafeInteger(9007199254740995)
false
> Number.isSafeInteger(10)
true
> Number.isSafeInteger(9007199254740986)
true
Therefore, the result of applying an integer operator op is guaranteed to be correct only if all
operands and the result are safe. More formally:
5.4 Math
The global object Math has several new methods in ECMAScript 6.
⁵https://mail.mozilla.org/pipermail/es-discuss/2013-August/032991.html
New number and Math features 49
5.4.1.1 Math.sign(x)
Math.sign(x) returns:
Examples:
> Math.sign(-8)
-1
> Math.sign(3)
1
> Math.sign(0)
0
> Math.sign(NaN)
NaN
> Math.sign(-Infinity)
-1
> Math.sign(Infinity)
1
5.4.1.2 Math.trunc(x)
Math.trunc(x) removes the decimal fraction of x. Complements the other rounding methods
Math.floor(), Math.ceil() and Math.round().
> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3
function trunc(x) {
return Math.sign(x) * Math.floor(Math.abs(x));
}
5.4.1.3 Math.cbrt(x)
> Math.cbrt(8)
2
Precision-wise, the important quantity here is the capacity of the mantissa, as measured in
significant digits. That’s why (A) gives you higher precision than (B).
Additionally, JavaScript represents numbers close to zero (e.g. small fractions) with higher
precision.
5.4.2.1 Math.expm1(x)
> Math.expm1(1e-10)
1.00000000005e-10
> Math.exp(1e-10)-1
1.000000082740371e-10
The former is the better result, which you can verify by using a library (such as decimal.js⁷) for
floating point numbers with arbitrary precision (“bigfloats”):
⁷https://github.com/MikeMcl/decimal.js/
New number and Math features 51
5.4.2.2 Math.log1p(x)
Therefore, this method lets you specify parameters that are close to 1 with a higher precision.
The following examples demonstrate why that is.
The following two calls of log() produce the same result:
> Math.log1p(1e-16)
1e-16
> Math.log1p(0)
0
The reason for the higher precision of Math.log1p() is that the correct result for 1 + 1e-16 has
more significant digits than 1e-16:
5.4.3.1 Math.log2(x)
> Math.log2(8)
3
5.4.3.2 Math.log10(x)
> Math.log10(100)
2
5.4.4.1 Math.fround(x)
Math.fround(x) rounds x to a 32 bit floating point value (float). Used by asm.js to tell an engine
to internally use a float value.
5.4.4.2 Math.imul(x, y)
Math.imul(x, y) multiplies the two 32 bit integers x and y and returns the lower 32 bits of the
result. This is the only 32 bit basic math operation that can’t be simulated by using a JavaScript
operator and coercing the result back to 32 bits. For example, idiv could be implemented as
follows:
function idiv(x, y) {
return (x / y) | 0;
}
In contrast, multiplying two large 32 bit integers may produce a double that is so large that lower
bits are lost.
⁸https://github.com/kripken/emscripten
⁹http://asmjs.org/
New number and Math features 53
> Math.clz32(0b01000000000000000000000000000000)
1
> Math.clz32(0b00100000000000000000000000000000)
2
> Math.clz32(2)
30
> Math.clz32(1)
31
Why is this interesting? Quoting “Fast, Deterministic, and Portable Counting Leading Zeros¹⁰”
by Miro Samek:
• Math.hypot(...values)
Computes the square root of the sum of the squares of its arguments (Pythagoras’ theorem):
> Math.hypot(3, 4)
5
At the moment, the only way around that limitation is to use a library for higher-precision
numbers (bigints or bigfloats). One such library is decimal.js¹¹.
Plans to support larger integers in JavaScript exist, but may take a while to come to fruition.
¹¹https://github.com/MikeMcl/decimal.js/
6. New string features
6.1 Overview
New string methods:
> 'hello'.startsWith('hell')
true
> 'hello'.endsWith('ello')
true
> 'hello'.includes('ell')
true
> 'doo '.repeat(3)
'doo doo doo '
// Template literals also let you create strings with multiple lines
const multiLine = `
This is
a string
with multiple
lines`;
const multiLine = `
This is
a string
with multiple
lines`;
Third, template literals are “raw” if you prefix them with the tag String.raw – the backslash is
not a special character and escapes such as \n are not interpreted:
And you can use the spread operator (...) to turn strings into Arrays:
New string features 57
> [...'x\uD83D\uDE80y'].length
3
This method works well when combined with iteration over strings:
¹https://github.com/mathiasbynens/esrever
New string features 59
> 'hello'.startsWith('hell')
true
> 'hello'.endsWith('ello')
true
> 'hello'.includes('ell')
true
Each of these methods has a position as an optional second parameter, which specifies where the
string to be searched starts or ends:
> 'hello'.startsWith('ello', 1)
true
> 'hello'.endsWith('hell', 4)
true
> 'hello'.includes('ell', 1)
true
> 'hello'.includes('ell', 2)
false
• String.prototype.match(regexp) calls
regexp[Symbol.match](this).
• String.prototype.replace(searchValue, replaceValue) calls
searchValue[Symbol.replace](this, replaceValue).
• String.prototype.search(regexp) calls
regexp[Symbol.search](this).
• String.prototype.split(separator, limit) calls
separator[Symbol.split](this, limit).
The parameters don’t have to be regular expressions, anymore. Any objects with appropriate
methods will do.
New string features 60
Finding strings:
Repeating strings:
• String.prototype.repeat(count) : string
Returns the receiver, concatenated count times.
²http://unicode.org/faq/normalization.html
7. Symbols
7.1 Overview
Symbols are a new primitive type in ECMAScript 6. They are created via a factory function:
Every time you call the factory function, a new and unique symbol is created. The optional
parameter is a descriptive string that is shown when printing the symbol (it has no other purpose):
> mySymbol
Symbol(mySymbol)
const iterableObject = {
[Symbol.iterator]() { // (A)
···
}
}
for (const x of iterableObject) {
console.log(x);
}
// Output:
// hello
// world
In line A, a symbol is used as the key of the method. This unique marker makes the object iterable
and enables us to use the for-of loop.
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_ORANGE:
return COLOR_BLUE;
case COLOR_YELLOW:
return COLOR_VIOLET;
case COLOR_GREEN:
return COLOR_RED;
case COLOR_BLUE:
return COLOR_ORANGE;
case COLOR_VIOLET:
return COLOR_YELLOW;
default:
throw new Exception('Unknown color: '+color);
}
}
Every time you call Symbol('Red'), a new symbol is created. Therefore, COLOR_RED can never be
mistaken for another value. That would be different if it were the string 'Red'.
Forbidding coercion prevents some errors, but also makes working with symbols more compli-
cated.
Symbols 63
• Reflect.ownKeys()
• Property access via []
• Object.assign()
• Object.keys()
• Object.getOwnPropertyNames()
• for-in loop
Symbol() has an optional string-valued parameter that lets you give the newly created Symbol
a description. That description is used when the symbol is converted to a string (via toString()
or String()):
Every symbol returned by Symbol() is unique, every symbol has its own identity:
You can see that symbols are primitive if you apply the typeof operator to one of them – it will
return a new symbol-specific result:
Symbols 64
obj[MY_KEY] = 123;
console.log(obj[MY_KEY]); // 123
Classes and object literals have a feature called computed property keys: You can specify the key
of a property via an expression, by putting it in square brackets. In the following object literal,
we use a computed property key to make the value of MY_KEY the key of a property.
Let’s examine the APIs for enumerating own property keys by first creating an object.
Symbols 65
const obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Object.defineProperty(obj,
'nonEnum', { enumerable: false });
> Object.getOwnPropertyNames(obj)
['enum', 'nonEnum']
> Object.getOwnPropertySymbols(obj)
[Symbol(my_key)]
> Reflect.ownKeys(obj)
[Symbol(my_key), 'enum', 'nonEnum']
> Object.keys(obj)
['enum']
The name Object.keys clashes with the new terminology (only string keys are listed). Ob-
ject.names or Object.getEnumerableOwnPropertyNames would be a better choice now.
However, strings are not as unique as we’d like them to be. To see why, let’s look at the following
function.
Symbols 66
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_ORANGE:
return COLOR_BLUE;
case COLOR_YELLOW:
return COLOR_VIOLET;
case COLOR_GREEN:
return COLOR_RED;
case COLOR_BLUE:
return COLOR_ORANGE;
case COLOR_VIOLET:
return COLOR_YELLOW;
default:
throw new Exception('Unknown color: '+color);
}
}
It is noteworthy that you can use arbitrary expressions as switch cases, you are not limited in
any way. For example:
function isThree(x) {
switch (x) {
case 1 + 1 + 1:
return true;
default:
return false;
}
}
We use the flexibility that switch offers us and refer to the colors via our constants (COLOR_RED
etc.) instead of hard-coding them ('RED' etc.).
Interestingly, even though we do so, there can still be mix-ups. For example, someone may define
a constant for a mood:
Now the value of BLUE is not unique anymore and MOOD_BLUE can be mistaken for it. If you use
it as a parameter for getComplement(), it returns 'ORANGE' where it should throw an exception.
Let’s use symbols to fix this example. Now we can also use the ES6 feature const, which lets us
declare actual constants (you can’t change what value is bound to a constant, but the value itself
may be mutable).
Symbols 67
Each value returned by Symbol is unique, which is why no other value can be mistaken for BLUE
now. Intriguingly, the code of getComplement() doesn’t change at all if we use symbols instead
of strings, which shows how similar they are.
For usability’s sake, public properties usually have string keys. But for private properties with
string keys, accidental name clashes can become a problem. Therefore, symbols are a good choice.
For example, in the following code, symbols are used for the private properties _counter and
_action.
counter--;
this[_counter] = counter;
if (counter === 0) {
this[_action]();
}
}
}
Note that symbols only protect you from name clashes, not from unauthorized access, be-
cause you can find out all own property keys – including symbols – of an object via Re-
flect.ownKeys(). If you want protection there, as well, you can use one of the approaches listed
in Sect. “Private data for classes”.
const obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
···
}
};
The iterability of obj enables you to use the for-of loop and similar JavaScript features:
// Output:
// hello
// world
• When the new method Array.prototype.values() was created, it broke existing code
where with was used with an Array and shadowed a variable values in an outer scope
(bug report 1¹, bug report 2²). Therefore, a mechanism was introduced to hide properties
from with (Symbol.unscopables).
• String.prototype.contains clashed with a method added by MooTools and had to be
renamed to String.prototype.includes (bug report³).
• The ES2016 method Array.prototype.contains also clashed with a method added by
MooTools and had to be renamed to Array.prototype.includes (bug report⁴).
In contrast, adding iterability to an object via the property key Symbol.iterator can’t cause
problems, because that key doesn’t clash with anything.
Coercion to boolean is always allowed, mainly to enable truthiness checks in if statements and
other locations:
if (value) { ··· }
param = param || 0;
Symbols are special property keys, which is why you want to avoid accidentally converting them
to strings, which are a different kind of property keys. This could happen if you use the addition
operator to compute the name of a property:
myObject['__' + value]
You also don’t want to accidentally turn symbols into Array indices. The following is code where
that could happen if value is a symbol:
myArray[1 + value]
To explicitly convert a symbol to boolean, you call Boolean()⁵, which returns true for symbols:
Boolean() computes its result via the internal operation ToBoolean()⁶, which returns true for
symbols and other truthy values.
Coercion also uses ToBoolean():
⁵http://www.ecma-international.org/ecma-262/6.0/#sec-boolean-constructor-boolean-value
⁶http://www.ecma-international.org/ecma-262/6.0/#sec-toboolean
Symbols 71
> !sym
false
Number() computes its result via the internal operation ToNumber()⁸, which throws a TypeError
for symbols.
Coercion also uses ToNumber():
> +sym
TypeError: can't convert symbol to number
If the parameter of String() is a symbol then it handles the conversion to string itself and
returns the string Symbol() wrapped around the description that was provided when creating
the symbol. If no description was given, the empty string is used:
> String(Symbol())
'Symbol()'
The toString() method returns the same string as String(), but neither of these two operations
calls the other one, they both call the same internal operation SymbolDescriptiveString()¹⁰.
> Symbol('hello').toString()
'Symbol(hello)'
Coercion is handled via the internal operation ToString()¹¹, which throws a TypeError for
symbols. One method that coerces its parameter to string is Number.parseInt():
⁷http://www.ecma-international.org/ecma-262/6.0/#sec-number-constructor-number-value
⁸http://www.ecma-international.org/ecma-262/6.0/#sec-tonumber
⁹http://www.ecma-international.org/ecma-262/6.0/#sec-string-constructor-string-value
¹⁰http://www.ecma-international.org/ecma-262/6.0/#sec-symboldescriptivestring
¹¹http://www.ecma-international.org/ecma-262/6.0/#sec-tostring
Symbols 72
> Number.parseInt(Symbol())
TypeError: can't convert symbol to string
7.5.3.4 Not allowed: converting via the binary addition operator (+)
Coercion to either string or number throws an exception, which means that you can’t (directly)
use the addition operator for symbols:
There is still a way to create wrapper objects, instances of Symbol: Object, called as a function,
converts all values to objects, including symbols.
¹²http://www.ecma-international.org/ecma-262/6.0/#sec-addition-operator-plus
Symbols 73
Like any other value not related to symbols, a wrapped string is converted to a string by the
square bracket operator:
The operator for getting and setting properties uses the internal operation ToPropertyKey()¹³,
which works as follows:
¹³http://www.ecma-international.org/ecma-262/6.0/#sec-topropertykey
Symbols 74
• Convert the operand to a primitive via ToPrimitive()¹⁴ with the preferred type String:
– A primitive value is returned as it is.
– Otherwise, the operand is an object. If it has a method [@@toPrimitive](), that
method is used to convert it to a primitive value. Symbols have such a method, which
returns the wrapped symbol.
– Otherwise, the operand is converted to a primitive via toString() – if it returns
a primitive value. Otherwise, valueOf() is used – if it returns a primitive value.
Otherwise, a TypeError is thrown. The preferred type String determines that
toString() is called first, valueOf() second.
• If the result of the conversion is a symbol, return it.
• Otherwise, coerce the result to string via ToString()¹⁵.
A code realm (short: realm) is a context in which pieces of code exist. It includes global variables,
loaded modules and more. Even though code exists “inside” exactly one realm, it may have access
to code in other realms. For example, each frame in a browser has its own realm. And execution
can jump from one frame to another, as the following HTML demonstrates.
<head>
<script>
function test(arr) {
var iframe = frames[0];
// This code and the iframe’s code exist in
// different realms. Therefore, global variables
// such as Array are different:
console.log(Array === iframe.Array); // false
console.log(arr instanceof Array); // false
console.log(arr instanceof iframe.Array); // true
¹⁴http://www.ecma-international.org/ecma-262/6.0/#sec-toprimitive
¹⁵http://www.ecma-international.org/ecma-262/6.0/#sec-tostring
Symbols 75
</iframe>
</body>
The problem is that each realm has its own global variables where each variable Array points to
a different object, even though they are all essentially the same object. Similarly, libraries and
user code are loaded once per realm and each realm has a different version of the same object.
Objects are compared by identity, but booleans, numbers and strings are compared by value.
Therefore, no matter in which realm a number 123 originated, it is indistinguishable from all
other 123s. That is similar to the number literal 123 always producing the same value.
Symbols have individual identities and thus don’t travel across realms as smoothly as other
primitive values. That is a problem for symbols such as Symbol.iterator that should work across
realms: If an object is iterable in one realm, it should be iterable in all realms. All built-in symbols
are managed by the JavaScript engine, which makes sure that, e.g., Symbol.iterator is the same
value in each realm. If a library wants to provide cross-realm symbols, it has to rely on extra
support, which comes in the form of the global symbol registry: This registry is global to all
realms and maps strings to symbols. For each symbol, the library needs to come up with a string
that is as unique as possible. To create the symbol, it doesn’t use Symbol(), it asks the registry
for the symbol that the string is mapped to. If the registry already has an entry for the string, the
associated symbol is returned. Otherwise, entry and symbol are created first.
You ask the registry for a symbol via Symbol.for() and retrieve the string associated with a
symbol (its key) via Symbol.keyFor():
Cross-realm symbols, such as Symbol.iterator, that are provided by the JavaScript engine, are
not in the registry:
> Symbol.keyFor(Symbol.iterator)
undefined
• On one hand, you want a proxy to be able to completely isolate its target (for membranes)
and to intercept all MOP operations applied to its target.
Symbols 76
• On the other hand, proxies should not be able to extract private data from an object; private
data should remain private.
These two goals are at odds. The chapter on classes explains your options for managing private
data. Symbols is one of these options, but you don’t get the same amount of safety that you’d get
from private symbols, because it’s possible to determine the symbols used as an object’s property
keys, via Object.getOwnPropertySymbols() and Reflect.ownKeys().
• Symbols are like strings (primitive values) w.r.t. what they are used for: as representations
of concepts and as property keys.
• Symbols are like objects in that each symbol has its own identity.
What are symbols then – primitive values or objects? In the end, they were turned into primitives,
for two reasons.
First, symbols are more like strings than like objects: They are a fundamental value of the
language, they are immutable and they can be used as property keys. Symbols having unique
identities doesn’t necessarily contradict them being like strings: UUID algorithms produce strings
that are quasi-unique.
Second, symbols are most often used as property keys, so it makes sense to optimize the JavaScript
specification and implementations for that use case. Then symbols don’t need many abilities of
objects:
Symbols not having these abilities makes life easier for the specification and the implementations.
The V8 team has also said that when it comes to property keys, it is easier to make a primitive
type a special case than certain objects.
:foo == :foo
The JavaScript function Symbol() is a factory for symbols – each value it returns is unique:
Well-known symbols are stored in properties whose names start with lowercase characters and
are camel-cased. In a way, these properties are constants and it is customary for constants to
have all-caps names (Math.PI etc.). But the reasoning for their spelling is different: Well-known
symbols are used instead of normal property keys, which is why their “names” follow the rules
for property keys, not the rules for constants.
Symbol is can’t be used as a constructor – an exception is thrown if you invoke it via new.
• Customizing basic language operations (explained in Chap. “New OOP features besides
classes”):
– Symbol.hasInstance (method)
Lets an object C customize the behavior of x instanceof C.
– Symbol.toPrimitive (method)
Lets an object customize how it is converted to a primitive value. This is the first step
whenever something is coerced to a primitive type (via operators etc.).
– Symbol.toStringTag (string)
Called by Object.prototype.toString() to compute the default string description
of an object obj: ‘[object ‘+obj[Symbol.toStringTag]+’]’.
– Symbol.unscopables (Object)
Lets an object hide some properties from the with statement.
• Iteration (explained in the chapter on iteration):
– Symbol.iterator (method)
A method with this key makes an object iterable (its contents can be iterated over
by language constructs such as the for-of loop and the spread operator (...)). The
method returns an iterator. Details: chapter “Iterables and iterators”.
• Forwarding string methods: The following string methods are forwarded to methods of
their parameters (usually regular expressions).
– String.prototype.match(x, ···) is forwarded to x[Symbol.match](···).
– String.prototype.replace(x, ···) is forwarded to x[Symbol.replace](···).
– String.prototype.search(x, ···) is forwarded to x[Symbol.search](···).
– String.prototype.split(x, ···) is forwarded to x[Symbol.split](···).
The details are explained in Sect. “String methods that delegate regular expression work
to their parameters” in the chapter on strings.
• Miscellaneous:
– Symbol.species (method)
Configures how built-in methods (such as Array.prototype.map()) create objects
that are similar to this. The details are explained in the chapter on classes.
¹⁶http://www.ecma-international.org/ecma-262/6.0/#sec-well-known-symbols
Symbols 79
– Symbol.isConcatSpreadable (boolean)
Configures whether Array.prototype.concat() adds the indexed elements of an
object to its result (“spreading”) or the object as a single element (details are explained
in the chapter on Arrays).
• Symbol.for(str) : symbol
Returns the symbol whose key is the string str in the registry. If str isn’t in the registry
yet, a new symbol is created and filed in the registry under the key str.
• Symbol.keyFor(sym) : string
returns the string that is associated with the symbol sym in the registry. If sym isn’t in the
registry, this method returns undefined. This method can be used to serialize symbols (e.g.
to JSON).
8. Template literals
8.1 Overview
ES6 has two new kinds of literals: template literals and tagged template literals. These two literals
have similar names and look similar, but they are quite different. It is therefore important to
distinguish:
Template literals are string literals that can stretch across multiple lines and include interpolated
expressions (inserted via ${···}):
// Output:
// Hello Jane!
// How are you
// today?
Tagged template literals (short: tagged templates) are created by mentioning a function before a
template literal:
Tagged templates are function calls. In the previous example, the method String.raw is called
to produce the result of the tagged template.
8.2 Introduction
Literals are syntactic constructs that produce values. Examples include string literals (which
produce strings) and regular expression literals (which produce regular expression objects).
ECMAScript 6 has two new literals:
Template literals 81
• Template literals are string literals with support for interpolation and multiple lines.
• Tagged template literals (short: tagged templates): are function calls whose parameters are
provided via template literals.
It is important to keep in mind that the names of template literals and tagged templates are
slightly misleading. They have nothing to do with templates, as often used in web development:
text files with blanks that can be filled in via (e.g.) JSON data.
// Output:
// Hello Jane!
// How are you
// today?
The literal itself is delimited by backticks (`), the interpolated expressions inside the literal are
delimited by ${ and }. Template literals always produce strings.
> `\``
'`'
> `$` // OK
'$'
> `${`
SyntaxError
> `\${`
'${'
> `\${}`
'${}'
> `\\`
'\\'
> `\n`
'\n'
> `\u{58}`
'X'
• Line feed (LF, \n, U+000A): used by Unix (incl. current macOS)
• Carriage return (CR, \r, U+000D): used by the old Mac OS.
• CRLF (\r\n): used by Windows.
All of these line terminators are normalized to LF in template literals. That is, the following code
logs true on all platforms:
Putting a template literal after an expression triggers a function call, similar to how a parameter
list (comma-separated values in parentheses) triggers a function call. The previous code is
equivalent to the following function call (in reality, first parameter is more than just an Array,
but that is explained later).
¹http://www.ecma-international.org/ecma-262/6.0/#sec-static-semantics-tv-and-trv
Template literals 83
Thus, the name before the content in backticks is the name of a function to call, the tag function.
The tag function receives two different kinds of data:
Template strings are known statically (at compile time), substitutions are only known at runtime.
The tag function can do with its parameters as it pleases: It can completely ignore the template
strings, return values of any type, etc.
Additionally, tag functions get two versions of each template string:
• A “raw” version in which backslashes are not interpreted (`\n` becomes '\\n', a string
of length 2)
• A “cooked” version in which backslashes are special (`\n` becomes a string with just a
newline in it).
²http://wiki.ecmascript.org/doku.php?id=harmony:quasis
Template literals 84
This is useful whenever you need to create strings that have backslashes in them. For example:
function createNumberRegExp(english) {
const PERIOD = english ? String.raw`\.` : ','; // (A)
return new RegExp(`[0-9]+(${PERIOD}[0-9]+)?`);
}
{ "foo": ${foo},
"bar": ${bar}}
`
(myOnReadyStateChangeHandler);
XRegExp is highly recommended if you are working with regular expressions. You
get many advanced features, but there is only a small performance penalty – once at
creation time – because XRegExp compiles its input to native regular expressions.
console.log(parts.year); // 2015
We can see that XRegExp gives us named groups (year, month, title) and the x flag. With that
flag, most whitespace is ignored and comments can be inserted.
There are two reasons that string literals don’t work well here. First, we have to type every
regular expression backslash twice, to escape it for the string literal. Second, it is cumbersome to
enter multiple lines.
Instead of adding strings, you can also continue a string literal in the next line if you end the
current line with a backslash. But that still involves much visual clutter, especially because you
still need the explicit newline via \n at the end of each line.
Problems with backslashes and multiple lines go away with tagged templates:
⁶https://gist.github.com/4222600
⁷http://xregexp.com
Template literals 86
Additionally, tagged templates let you insert values v via ${v}. I’d expect a regular expression
library to escape strings and to insert regular expressions verbatim. For example:
$`a.${className}[href*='//${domain}/']`
This is a DOM query that looks for all <a> tags whose CSS class is className and whose target
is a URL with the given domain. The tag function $ ensures that the arguments are correctly
escaped, making this approach safer than manual string concatenation.
⁸https://facebook.github.io/react/
Template literals 87
t7.module(function(t7) {
function MyWidget(props) {
return t7`
<div>
<span>I'm a widget ${ props.welcome }</span>
</div>
`;
}
t7.assign('Widget', MyWidget);
t7`
<div>
<header>
<Widget welcome="Hello world" />
</header>
</div>
`;
});
In “Why not Template Literals?⁹”, the React team explains why they opted not to use template
literals. One challenge is accessing components inside tagged templates. For example, MyWidget
is accessed from the second tagged template in the previous example. One verbose way of doing
so would be:
Instead, t7.js uses a registry which is filled via t7.assign(). That requires extra configuration,
but the template literals look nicer; especially if there is both an opening and a closing tag.
⁹https://facebook.github.io/jsx/#why-not-template-literals
¹⁰https://facebook.github.io/relay/
¹¹https://facebook.github.io/relay/
Template literals 88
The objects starting in line A and line B define fragments, which are defined via callbacks that
return queries. The result of fragment tea is put into this.props.tea. The result of fragment
store is put into this.props.store.
const STORE = {
teas: [
{name: 'Earl Grey Blue Star', steepingTime: 5},
···
],
};
This data is wrapped in an instance of GraphQLSchema, where it gets the name Store (as
mentioned in fragment on Store).
const data = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];
A template is basically a function: data in, text out. And that description gives us a clue how we
can turn a template literal into an actual template. Let’s implement a template tmpl as a function
that maps an Array addrs to a string:
The outer template literal provides the bracketing <table> and </table>. Inside, we are
embedding JavaScript code that produces a string by joining an Array of strings. The Array
is created by mapping each address to two table rows. Note that the plain text pieces <Jane> and
<Croft> are not properly escaped. How to do that via a tagged template is explained in the next
section.
This is a useful quick solution for smaller templating tasks. For larger tasks, you may want more
powerful solutions such as the templating engine Handlebars.js¹² or the JSX syntax used in React.
Acknowledgement: This approach to text templating is based on an idea¹³ by Claus Reinke.
¹²http://handlebarsjs.com/
¹³https://mail.mozilla.org/pipermail/es-discuss/2012-August/024328.html
Template literals 91
• They can escape characters for us if we prefix ${} with an exclamation mark. That is
needed for the names, which contain characters that need to be escaped (<Jane>).
• They can automatically join() Arrays for us, so that we don’t have to call that method
ourselves.
Then the code for the template looks as follows. The name of the tag function is html:
Note that the angle brackets around Jane and Croft are escaped, whereas those around tr and
td aren’t.
If you prefix a substitution with an exclamation mark (!${addr.first}) then it will be HTML-
escaped. The tag function checks the text preceding a substitution in order to determine whether
to escape or not.
An implementation of html is shown later.
__templateMap__[716] = templateObject;
}
There are two kinds of input that the tag function receives:
1. Template strings (first parameter): the static parts of tagged templates that don’t change
(e.g. ' lit2 '). A template object stores two versions of the template strings:
• Cooked: with escapes such as \n interpreted. Stored in templateObject[0] etc.
• Raw: with uninterpreted escapes. Stored in templateObject.raw[0] etc.
2. Substitutions (remaining parameters): the values that are embedded inside template literals
via ${} (e.g. subst1). Substitutions are dynamic, they can change with each invocation.
The idea behind a global template object is that the same tagged template might be executed
multiple times (e.g. in a loop or a function). The template object enables the tag function to
cache data from previous invocations: It can put data it derived from input kind #1 (template
strings) into the object, to avoid recomputing it. Caching happens per realm (think frame in a
browser). That is, there is one template object per call site and realm.
¹⁴http://www.ecma-international.org/ecma-262/6.0/#sec-tagged-templates
¹⁵http://www.ecma-international.org/ecma-262/6.0/#sec-template-literals-runtime-semantics-argumentlistevaluation
Template literals 93
The number of template strings is always one plus the number of substitutions. In other words:
every substitution is always surrounded by two template strings.
> tagFunc`${'subst'}xyz`
{ templateObject: [ '', 'xyz' ], substs: [ 'subst' ] }
> tagFunc`abc${'subst'}`
{ templateObject: [ 'abc', '' ], substs: [ 'subst' ] }
> tagFunc``
{ templateObject: [ '' ], substs: [] }
• In both cooked and raw interpretation, a backslash (\) in front of ${ prevents it from being
interpreted as starting a substitution.
• In both cooked and raw interpretation, backticks are also escaped via backslashes.
• However, every single backslash is mentioned in the raw interpretation, even the ones that
escape substitutions and backticks.
> describe`${3+3}`
{ Cooked: '6', Raw: '6' }
> describe`\${3+3}`
{ Cooked: '${3+3}', Raw: '\\${3+3}' }
> describe`\\${3+3}`
{ Cooked: '\\6', Raw: '\\\\6' }
> describe`\``
{ Cooked: '`', Raw: '\\`' }
As you can see, whenever the cooked interpretation has a substitution or a backtick then so does
the raw interpretation. However, all backslashes from the literal appear in the raw interpretation.
Other occurrences of the backslash are interpreted as follows:
For example:
Template literals 95
> describe`\\`
{ Cooked: '\\', Raw: '\\\\' }
> describe`\n`
{ Cooked: '\n', Raw: '\\n' }
> describe`\u{58}`
{ Cooked: 'X', Raw: '\\u{58}' }
To summarize: The only effect the backslash has in raw mode is that it escapes substitutions and
backticks.
¹⁶http://www.ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components
¹⁷http://www.ecma-international.org/ecma-262/6.0/#sec-static-semantics-tv-and-trv
Template literals 96
substs.forEach((subst, i) => {
// Retrieve the template string preceding
// the current substitution
let lit = raw[i];
subst = subst.join('');
}
return result;
}
There is always one more template string than substitutions, which is why we need to append
the last template string in line A.
The following is a simple implementation of htmlEscape().
function htmlEscape(str) {
return str.replace(/&/g, '&') // first!
.replace(/>/g, '>')
.replace(/</g, '<')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/`/g, '`');
}
There are more things you can do with this approach to templating:
• This approach isn’t limited to HTML, it would work just as well for other kinds of text.
Obviously, escaping would have to be adapted.
• if-then-else inside the template can be done via the ternary operator (cond?then:else) or
via the logical Or operator (||):
• Dedenting: Some of the leading whitespace in each line can be removed if the first non-
whitespace character defines in which column the text starts. For example:
Template literals 98
The first non-whitespace characters are <div>, which means that the text starts in column
4 (the leftmost column is column 0). The tag function html could automatically remove all
preceding columns. Then the previous tagged template would be equivalent to:
const theHtml =
html`<div>
Hello!
</div>`;
// Without destructuring
${addrs.map((person) => html`
<tr><td>!${person.first}</td></tr>
<tr><td>!${person.last}</td></tr>
`)}
// With destructuring
${addrs.map(({first,last}) => html`
<tr><td>!${first}</td></tr>
<tr><td>!${last}</td></tr>
`)}
If you use the latter, it is because you have to wait until runtime so that all necessary ingredients
are available. You are creating the regular expression by concatenating three kinds of pieces:
1. Static text
2. Dynamic regular expressions
3. Dynamic text
For #3, special characters (dots, square brackets, etc.) have to be escaped, while #1 and #2 can be
used verbatim. A regular expression tag function regex can help with this task:
Template literals 99
While macros are much more powerful for implementing sub-languages than tagged templates,
they depend on the tokenization of the language. Therefore, tagged templates are complementary,
because they specialize on text content.
9.1.1 let
let works similarly to var, but the variable it declares is block-scoped, it only exists within the
current block. var is function-scoped.
In the following code, you can see that the let-declared variable tmp only exists inside the block
that starts in line A:
function order(x, y) {
if (x > y) { // (A)
let tmp = x;
x = y;
y = tmp;
}
console.log(tmp===x); // ReferenceError: tmp is not defined
return [x, y];
}
9.1.2 const
const works like let, but the variable you declare must be immediately initialized, with a value
that can’t be changed afterwards.
const foo;
// SyntaxError: missing = in const declaration
Since for-of creates one binding (storage space for a variable) per loop iteration, it is OK to
const-declare the loop variable:
Variables and scoping 102
function func() {
if (true) {
const tmp = 123;
}
console.log(tmp); // ReferenceError: tmp is not defined
}
function func() {
if (true) {
var tmp = 123;
}
console.log(tmp); // 123
}
Block scoping means that you can shadow variables within a function:
¹https://twitter.com/kangax/status/567330097603284992
Variables and scoping 103
function func() {
const foo = 5;
if (···) {
const foo = 10; // shadows outer `foo`
console.log(foo); // 10
}
console.log(foo); // 5
}
Constants, variables created by const, are immutable – you can’t assign different values to them:
If you want the value of obj to be immutable, you have to take care of it, yourself. For example,
by freezing it⁴:
Keep in mind that Object.freeze() is shallow, it only freezes the properties of its argument, not
the objects stored in its properties. For example, the object obj is frozen:
function logArgs(...args) {
for (const [index, elem] of args.entries()) { // (A)
const message = index + '. ' + elem; // (B)
console.log(message);
}
}
logArgs('Hello', 'everyone');
// Output:
// 0. Hello
// 1. everyone
There are two const declarations in this code, in line A and in line B. And during each loop
iteration, their constants have different values.
⁴http://speakingjs.com/es5/ch17.html#freezing_objects
Variables and scoping 105
• When the scope (its surrounding function) of a var variable is entered, storage space (a
binding) is created for it. The variable is immediately initialized, by setting it to undefined.
• When the execution within the scope reaches the declaration, the variable is set to the
value specified by the initializer (an assignment) – if there is one. If there isn’t, the value
of the variable remains undefined.
• When the scope (its surrounding block) of a let variable is entered, storage space (a
binding) is created for it. The variable remains uninitialized.
• Getting or setting an uninitialized variable causes a ReferenceError.
• When the execution within the scope reaches the declaration, the variable is set to the
value specified by the initializer (an assignment) – if there is one. If there isn’t then the
value of the variable is set to undefined.
const variables work similarly to let variables, but they must have an initializer (i.e., be set to
a value immediately) and can’t be changed.
9.4.3 Examples
Within a TDZ, an exception is thrown if a variable is got or set:
Variables and scoping 106
tmp = 123;
console.log(tmp); // 123
}
console.log(tmp); // true
If there is an initializer then the TDZ ends after the initializer was evaluated and the result was
assigned to the variable:
The following code demonstrates that the dead zone is really temporal (based on time) and not
spatial (based on location):
if (true) {
console.log(typeof foo); // ReferenceError (TDZ)
console.log(typeof aVariableThatDoesntExist); // 'undefined'
let foo;
}
Why? The rationale is as follows: foo is not undeclared, it is uninitialized. You should be aware
of its existence, but aren’t. Therefore, being warned seems desirable.
Furthermore, this kind of check is only useful for conditionally creating global variables. That is
something that you don’t need to do in normal programs.
Variables and scoping 107
This option only works in global scope (and therefore not inside ES6 modules).
Option 2 – window:
if (!('someGlobal' in window)) {
window.someGlobal = { ··· };
}
• To catch programming errors: Being able to access a variable before its declaration is
strange. If you do so, it is normally by accident and you should be warned about it.
• For const: Making const work properly is difficult. Quoting Allen Wirfs-Brock⁵: “TDZs …
provide a rational semantics for const. There was significant technical discussion of that
topic and TDZs emerged as the best solution.” let also has a temporal dead zone so that
switching between let and const doesn’t change behavior in unexpected ways.
• Future-proofing for guards: JavaScript may eventually have guards, a mechanism for
enforcing at runtime that a variable has the correct value (think runtime type check). If the
value of a variable is undefined before its declaration then that value may be in conflict
with the guarantee given by its guard.
• for
• for-in
• for-of
To make a declaration, you can use either var, let or const. Each of them has a different effect,
as I’ll explain next.
Every i in the bodies of the three arrow functions refers to the same binding, which is why they
all return the same value.
If you let-declare a variable, a new binding is created for each loop iteration:
This time, each i refers to the binding of one specific iteration and preserves the value that was
current at that time. Therefore, each arrow function returns a different value.
const works like var, but you can’t change the initial value of a const-declared variable:
Getting a fresh binding for each iteration may seem strange at first, but it is very useful whenever
you use loops to create functions that refer to loop variables, as explained in a later section.
let also creates one binding per iteration, but the bindings it creates are mutable.
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="content"></div>
<script>
const entries = [
['yes', 'ja'],
['no', 'nein'],
['perhaps', 'vielleicht'],
];
const content = document.getElementById('content');
for (const [source, target] of entries) { // (A)
¹⁰http://www.ecma-international.org/ecma-262/6.0/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-
labelset
¹¹http://www.ecma-international.org/ecma-262/6.0/#sec-runtime-semantics-bindinginstantiation
¹²http://www.ecma-international.org/ecma-262/6.0/#sec-initializereferencedbinding
¹³http://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements-runtime-semantics-bindinginitialization
¹⁴http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-binding-patterns-runtime-semantics-bindinginitialization
Variables and scoping 111
content.insertAdjacentHTML('beforeend',
`<div><a id="${source}" href="">${source}</a></div>`);
document.getElementById(source).addEventListener(
'click', (event) => {
event.preventDefault();
alert(target); // (B)
});
}
</script>
</body>
</html>
What is displayed depends on the variable target (line B). If we had used var instead of const
in line A, there would be a single binding for the whole loop and target would have the value
'vielleicht', afterwards. Therefore, no matter what link you click on, you would always get
the translation 'vielleicht'.
Thankfully, with const, we get one binding per loop iteration and the translations are displayed
correctly.
9.6 Parameters
function func(arg) {
let arg; // static error: duplicate declaration of `arg`
}
function func(arg) {
{
let arg; // shadows parameter `arg`
}
}
In contrast, var-declaring a variable that has the same name as a parameter does nothing, just
like re-declaring a var variable within the same scope does nothing.
Variables and scoping 112
function func(arg) {
var arg; // does nothing
}
function func(arg) {
{
// We are still in same `var` scope as `arg`
var arg; // does nothing
}
}
9.6.3 Parameter default values don’t see the scope of the body
The scope of parameter default values is separate from the scope of the body (the former
surrounds the latter). That means that methods or functions defined “inside” parameter default
values don’t see the local variables of the body:
• All properties of the global object are global variables. In global scope, the following
declarations create such properties:
– var declarations
– Function declarations
• But there are now also global variables that are not properties of the global object. In global
scope, the following declarations create such variables:
– let declarations
– const declarations
– Class declarations
Note that the bodies of modules are not executed in global scope, only scripts are. Therefore, the
environments for various variables form the following chain.
Class declarations…
• are block-scoped.
• don’t create properties on the global object.
• are not hoisted.
Classes not being hoisted may be surprising, because, under the hood, they create functions. The
rationale for this behavior is that the values of their extends clauses are defined via expressions
and those expressions have to be executed at the appropriate times.
1. Prefer const. You can use it whenever a variable never changes its value. In other words:
the variable should never be the left-hand side of an assignment or the operand of ++ or
--. Changing an object that a const variable refers to is allowed:
You can even use const in a for-of loop, because one (immutable) binding is created per
loop iteration:
Variables and scoping 115
3. Avoid var.
If you follow these rules, var will only appear in legacy code, as a signal that careful refactoring
is required.
var does one thing that let and const don’t: variables declared via it become properties of the
global object. However, that’s generally not a good thing. You can achieve the same effect by
assigning to window (in browsers) or global (in Node.js).
// Variable declarations:
const [x] = ['a'];
let [x] = ['a'];
var [x] = ['a'];
// Assignments:
[x] = ['a'];
// Parameter definitions:
function f([x]) { ··· }
f(['a']);
The same syntax can be used to extract data. Again, one property at a time:
Destructuring 118
const f = obj.first;
const l = obj.last;
Additionally, there is syntax to construct multiple properties at the same time, via an object
literal:
Before ES6, there was no corresponding mechanism for extracting data. That’s what destructur-
ing is – it lets you extract multiple properties from an object via an object pattern. For example,
on the left-hand side of an assignment:
10.3 Patterns
The following two parties are involved in destructuring:
• Destructuring source: the data to be destructured. For example, the right-hand side of a
destructuring assignment.
• Destructuring target: the pattern used for destructuring. For example, the left-hand side
of a destructuring assignment.
const { x: x } = { x: 7, y: 3 }; // x = 7
The coercion to object is not performed via Object(), but via the internal operation ToObject()¹.
The two operations handle undefined and null differently.
Object() converts primitive values to wrapper objects and leaves objects untouched:
¹http://www.ecma-international.org/ecma-262/6.0/#sec-toobject
Destructuring 120
> Object(undefined)
{}
> Object(null)
{}
As a consequence, you can use the empty object pattern {} to check whether a value is coercible
to an object. As we have seen, only undefined and null aren’t:
The parentheses around the expressions are necessary because statements must not begin with
curly braces in JavaScript (details are explained later).
Don’t forget that the iterator over strings returns code points (“Unicode characters”, 21 bits),
not code units (“JavaScript characters”, 16 bits). (For more information on Unicode, consult the
chapter “Chapter 24. Unicode and JavaScript²” in “Speaking JavaScript”.) For example:
You can’t access the elements of a Set via indices, but you can do so via an iterator. Therefore,
Array destructuring works for Sets:
²http://speakingjs.com/es5/ch24.html
Destructuring 121
The Set iterator always returns elements in the order in which they were inserted, which is why
the result of the previous destructuring is always the same.
A value is iterable if it has a method whose key is Symbol.iterator that returns an object.
Array-destructuring throws a TypeError if the value to be destructured isn’t iterable:
let x;
[x] = [true, false]; // OK, Arrays are iterable
[x] = 'abc'; // OK, strings are iterable
[x] = { * [Symbol.iterator]() { yield 1 } }; // OK, iterable
The TypeError is thrown even before accessing elements of the iterable, which means that you
can use the empty Array pattern [] to check whether a value is iterable:
Let’s look at an example. In the following destructuring, the element at index 0 has no match on
the right-hand side. Therefore, destructuring continues by matching x against 3, which leads to
x being set to 3.
The rationale for this behavior is explained in the next chapter, in the section on parameter
default values.
is equivalent to:
let y;
if (someValue.prop === undefined) {
y = someFunc();
} else {
y = someValue.prop;
}
In the second destructuring, the default value is not triggered and log() is not called.
However, order matters: the variables x and y are declared from left to right and produce a
ReferenceError if they are accessed before their declarations:
What does this mean? Recall the rule for default values: If a part has no match in the source,
destructuring continues with the default value.
The element at index 0 has no match, which is why destructuring continues with:
You can more easily see why things work this way if you replace the pattern { prop: x } with
the variable pattern:
Because the Array element at index 0 has no match on the right-hand side, destructuring
continues as follows and x is set to 123.
However, x is not assigned a value in this manner if the right-hand side has an element at index
0, because then the default value isn’t triggered.
Destructuring 124
Thus, if you want x to be 123 if either the object or the property is missing, you need to specify
a default value for x itself:
Here, destructuring continues as follows, independently of whether the right-hand side is [{}]
or [].
Still confused?
A later section explains destructuring from a different angle, as an algorithm. That may
give you additional insight.
// Same as:
const { x: x, y: y } = { x: 11, y: 8 };
You can also combine property value shorthands with default values:
Computed property keys allow you to destructure properties whose keys are symbols:
// Extract Array.prototype[Symbol.iterator]
const { [Symbol.iterator]: func } = [];
console.log(typeof func); // function
10.7.1 Elision
Elision lets you use the syntax of Array “holes” to skip elements during destructuring:
The spread operator has exactly the same syntax as the rest operator – three dots.
But they are different: the former contributes data to Array literals and function calls,
whereas the latter is used for destructuring and extracts data.
If the operator can’t find any elements, it matches its operand against the empty Array. That is,
it never produces undefined or null. For example:
The operand of the rest operator doesn’t have to be a variable, you can use patterns, too:
Destructuring 126
You can also assign to object properties and Array elements via the rest operator (...):
If you declare variables or define parameters via destructuring then you must use simple
identifiers, you can’t refer to object properties and Array elements.
{ a, b } = someObject; // SyntaxError
({ a, b } = someObject); // OK
({ a, b }) = someObject; // SyntaxError
With let, var and const, curly braces never cause problems:
const { a, b } = someObject; // OK
You can use destructuring to swap values. That is something that engines could optimize, so that
no Array would be created.
If you are only interested in the groups (and not in the complete match, all), you can use elision
to skip the array element at index 0:
exec() returns null if the regular expression doesn’t match. Unfortunately, you can’t handle
null via default values, which is why you must use the Or operator (||) in this case:
The function iterates over all elements of array, via the Array method entries(), which
returns an iterable over [index,element] pairs (line A). The parts of the pairs are accessed via
destructuring.
Let’s use findElement():
Several ECMAScript 6 features allowed us to write more concise code: The callback is an arrow
function; the return value is destructured via an object pattern with property value shorthands.
Due to index and element also referring to property keys, the order in which we mention them
doesn’t matter. We can swap them and nothing changes:
We have successfully handled the case of needing both index and element. What if we are only
interested in one of them? It turns out that, thanks to ECMAScript 6, our implementation can
take care of that, too. And the syntactic overhead compared to functions with single return values
is minimal.
Destructuring 130
Each time, we only extract the value of the one property that we need.
This different angle should especially help with understanding default values. If you
feel you don’t fully understand them yet, read on.
At the end, I’ll use the algorithm to explain the difference between the following two function
declarations.
«pattern» = «value»
We want to use pattern to extract data from value. I’ll now describe an algorithm for doing
so, which is known in functional programming as pattern matching (short: matching). The
algorithm specifies the operator � (“match against”) for destructuring assignment that matches
a pattern against a value and assigns to variables while doing so:
«pattern» � «value»
The algorithm is specified via recursive rules that take apart both operands of the � operator.
The declarative notation may take some getting used to, but it makes the specification of the
algorithm more concise. Each rule has two parts:
• The head (first line) describes the condition that triggers the rule.
• The body (remaining lines) describes what happens if the rule is triggered.
«pattern» � obj.key
{«properties»} � obj
// Nothing to do
In rule (2c), the head means that this rule is executed if there is an object pattern with at least one
property and zero or more remaining properties. That pattern is matched against a value obj.
The effect of this rule is that execution continues with the property value pattern being matched
against obj.key and the remaining properties being matched against obj.
In rule (2e), the head means that this rule is executed if the empty object pattern {} is matched
against a value obj. Then there is nothing to be done.
Whenever the algorithm is invoked, the rules are checked top to bottom and only the first rule
that is applicable is executed.
I only show the algorithm for destructuring assignment. Destructuring variable declarations and
destructuring parameter definitions work similarly.
I don’t cover advanced features (computed property keys; property value shorthands; object
properties and array elements as assignment targets), either. Only the basics.
10.11.1.1 Patterns
A pattern is either:
• A variable: x
• An object pattern: {«properties»}
• An Array pattern: [«elements»]
10.11.1.2 Variable
x = value
«pattern» � obj.key
{«properties»} � obj
// Nothing to do
Array pattern and iterable. The algorithm for Array destructuring starts with an Array pattern
and an iterable:
Helper function:
Destructuring 133
function isIterable(value) {
return (value !== null
&& typeof value === 'object'
&& typeof value[Symbol.iterator] === 'function');
}
Array elements and iterator. The algorithm continues with the elements of the pattern (left-
hand side of the arrow) and the iterator that was obtained from the iterable (right-hand side of
the arrow).
getNext(iterator); // skip
«elements» � iterator
// Nothing to do
Helper function:
Destructuring 134
function getNext(iterator) {
const {done,value} = iterator.next();
return (done ? undefined : value);
}
But why would you define the parameters as in the previous code snippet? Why not as follows
– which is also completely legal ES6 code?
function move2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
To see why move1() is correct, let’s use both functions for two examples. Before we do that, let’s
see how the passing of parameters can be explained via matching.
For function calls, formal parameters (inside function definitions) are matched against actual
parameters (inside function calls). As an example, take the following function definition and the
following function call.
Destructuring 135
[{x, y} = { x: 0, y: 0 }] � []
The single Array element on the left-hand side does not have a match on the right-hand side,
which is why {x,y} is matched against the default value and not against data from the right-hand
side (rules 3b, 3d):
{x, y} � { x: 0, y: 0 }
{x: x, y: y} � { x: 0, y: 0 }
This destructuring leads to the following two assignments (rules 2c, 1):
x = 0;
y = 0;
Example 2. Let’s examine the function call move2({z:3}) which leads to the following destruc-
turing:
[{x, y} = { x: 0, y: 0 }] � [{z:3}]
There is an Array element at index 0 on the right-hand side. Therefore, the default value is
ignored and the next step is (rule 3d):
{x, y} � { z: 3 }
That leads to both x and y being set to undefined, which is not what we want.
We don’t have an Array element at index 0 on the right-hand side and use the default value (rule
3d):
{x=0, y=0} � {}
The left-hand side contains property value shorthands, which means that this destructuring is
equivalent to:
Neither property x nor property y have a match on the right-hand side. Therefore, the default
values are used and the following destructurings are performed next (rule 2d):
x � 0
y � 0
x = 0
y = 0
Example 2: move1({z:3})
The first element of the Array pattern has a match on the right-hand side and that match is used
to continue destructuring (rule 3d):
Like in example 1, there are no properties x and y on the right-hand side and the default values
are used:
x = 0
y = 0
10.11.2.4 Conclusion
The examples demonstrate that default values are a feature of pattern parts (object properties or
Array elements). If a part has no match or is matched against undefined then the default value
is used. That is, the pattern is matched against the default value, instead.
11. Parameter handling
For this chapter, it is useful to be familiar with destructuring (which is explained in the
previous chapter).
11.1 Overview
Parameter handling has been significantly upgraded in ECMAScript 6. It now supports parameter
default values, rest parameters (varargs) and destructuring.
Additionally, the spread operator helps with function/method/constructor calls and Array
literals.
In Array literals, the spread operator turns iterable values into Array elements:
function func(«FORMAL_PARAMETERS») {
«CODE»
}
func(«ACTUAL_PARAMETERS»);
{
let [«FORMAL_PARAMETERS»] = [«ACTUAL_PARAMETERS»];
{
«CODE»
}
}
becomes:
{
let [x=0, y=0] = [7, 8];
{
console.log(x + y);
}
}
> f(1)
[1, 0]
> f()
[undefined, 0]
function setLevel(newLevel = 0) {
light.intensity = newLevel;
}
function setOptions(options) {
// Missing prop returns undefined => use default
setLevel(options.dimmerLevel);
setMotorSpeed(options.speed);
···
}
setOptions({speed:5});
In the second example, square() doesn’t have to define a default for x, it can delegate that task
to multiply():
¹https://github.com/rwaldron/tc39-notes/blob/master/es6/2012-07/july-24.md#413-destructuring-issues
Parameter handling 141
Default values further entrench the role of undefined as indicating that something doesn’t exist,
versus null indicating emptiness.
However, order matters. Parameters are declared from left to right. “Inside” a default value, you
get a ReferenceError if you access a parameter that hasn’t been declared, yet:
const x = 'outer';
function foo(a = x) {
const x = 'inner';
console.log(a); // outer
}
If there were no outer x in the previous example, the default value x would produce a
ReferenceError (if triggered).
const QUX = 2;
function bar(callback = () => QUX) { // returns 2
const QUX = 3;
callback();
}
bar(); // ReferenceError
If there are no remaining parameters, the rest parameter will be set to the empty Array:
f(); // x = undefined; y = []
The spread operator (...) looks exactly like the rest operator, but is used inside function
calls and Array literals (not inside destructuring patterns).
// ECMAScript 5: arguments
function logAllArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
One interesting feature of arguments is that you can have normal parameters and an Array of
all parameters at the same time:
You can avoid arguments in such cases if you combine a rest parameter with Array destructuring.
The resulting code is longer, but more explicit:
function foo(...args) {
let [x=0, y=0] = args;
console.log('Arity: '+args.length);
···
}
arguments is iterable² in ECMAScript 6, which means that you can use for-of and the spread
operator:
• Positional parameters are mapped by position. The first actual parameter is mapped to the
first formal parameter, the second actual to the second formal, and so on:
²Iterables are explained in another chapter.
Parameter handling 144
selectEntries(3, 20, 2)
• Named parameters use names (labels) to perform the mapping. Formal parameters have
labels. In a function call, these labels determine which value belongs to which formal
parameter. It does not matter in which order named actual parameters appear, as long as
they are labeled correctly. Simulating named parameters in JavaScript looks as follows.
Named parameters have two main benefits: they provide descriptions for arguments in function
calls and they work well for optional parameters. I’ll first explain the benefits and then show
you how to simulate named parameters in JavaScript via object literals.
what do these three numbers mean? Python supports named parameters, and they make it easy
to figure out what is going on:
# Python syntax
selectEntries(start=3, end=20, step=2)
# Python syntax
selectEntries(step=2)
selectEntries(end=20, start=3)
selectEntries()
The function receives an object with the properties start, end, and step. You can omit any of
them:
function selectEntries(options) {
options = options || {};
var start = options.start || 0;
var end = options.end || -1;
var step = options.step || 1;
···
}
If you call selectEntries() with zero arguments, the destructuring fails, because you can’t
match an object pattern against undefined. That can be fixed via a default value. In the following
code, the object pattern is matched against {} if the first parameter is missing.
You can also combine positional parameters with named parameters. It is customary for the latter
to come last:
In principle, JavaScript engines could optimize this pattern so that no intermediate object is
created, because both the object literals at the call sites and the object patterns in the function
definitions are static.
In JavaScript, the pattern for named parameters shown here is sometimes called options
or option object (e.g., by the jQuery documentation).
Parameter handling 146
const items = [
{ word:'foo', count:3 },
{ word:'bar', count:9 },
];
items.forEach(({word, count}) => {
console.log(word+' '+count);
});
Destructuring helps with handling the Array that the result of Promise.all() is fulfilled with:
const urls = [
'http://example.com/foo.html',
'http://example.com/bar.html',
'http://example.com/baz.html',
];
Promise.all(urls.map(downloadUrl))
.then(([fooStr, barStr, bazStr]) => {
···
});
function foo(mustBeProvided) {
if (arguments.length < 1) {
throw new Error();
}
if (! (0 in arguments)) {
throw new Error();
}
if (mustBeProvided === undefined) {
throw new Error();
}
···
}
In ECMAScript 6, you can (ab)use default parameter values to achieve more concise code (credit:
idea by Allen Wirfs-Brock):
/**
* Called if a parameter is missing and
* the default value is evaluated.
*/
function mandatory() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = mandatory()) {
return mustBeProvided;
}
Interaction:
Parameter handling 149
> foo()
Error: Missing parameter
> foo(123)
123
function f(...args) {
if (args.length > 2) {
throw new Error();
}
// Extract the real parameters
let [x, y] = args;
}
The second approach relies on unwanted actual parameters appearing in the formal rest
parameter empty.
The third approach uses a sentinel value that is gone if there is a third parameter. One caveat is
that the default value OK is also triggered if there is a third parameter whose value is undefined.
const OK = Symbol();
function f(x, y, arity=OK) {
if (arity !== OK) {
throw new Error();
}
}
Sadly, each one of these approaches introduces significant visual and conceptual clutter. I’m
tempted to recommend checking arguments.length, but I also want arguments to go away.
Parameter handling 150
function f(x, y) {
if (arguments.length > 2) {
throw new Error();
}
}
• Rest operator: collects the remaining items of an iterable into an Array and is used for rest
parameters and destructuring.
• Spread operator: turns the items of an iterable into arguments of a function call or into
elements of an Array.
In contrast to the rest operator, you can use the spread operator anywhere in a sequence of parts:
Another example is JavaScript not having a way to destructively append the elements of one
Array to another one. However, Arrays do have the method push(x1, x2, ···), which appends
all of its arguments to its receiver. The following code shows how you can use push() to append
the elements of arr2 to arr1.
arr1.push(...arr2);
// arr1 is now ['a', 'b', 'c', 'd']
const arr = [...x, ...y, ...z]; // ['a', 'b', 'c', 'd', 'e']
One advantage of the spread operator is that its operand can be any iterable value (in contrast
to the Array method concat(), which does not support iteration).
The spread operator lets you convert any iterable value to an Array:
Your own iterable objects can be converted to Arrays in the same manner:
⁴http://speakingjs.com/es5/ch17.html#apply_constructors
Parameter handling 152
const obj = {
* [Symbol.iterator]() {
yield 'a';
yield 'b';
yield 'c';
}
};
const arr = [...obj]; // ['a', 'b', 'c']
Note that, just like the for-of loop, the spread operator only works for iterable values. All built-
in data structures are iterable: Arrays, Maps and Sets. All Array-like DOM data structures are
also iterable.
Should you ever encounter something that is not iterable, but Array-like (indexed elements plus
a property length), you can use Array.from()⁵ to convert it to an Array:
const arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ECMAScript 5:
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ECMAScript 6:
const arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
12.1 Overview
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:
Callable entities in ECMAScript 6 155
Even though their behaviors differ (as explained later), all of these entities are functions. For
example:
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.
• Arrow functions are made for non-method functions. They pick up this (and other
variables) from their surrounding scopes (“lexical this”).
• Method definitions are made for methods. They provide support for super, to refer to
super-properties and to make super-method calls.
Callable entities in ECMAScript 6 156
For callbacks that span multiple lines, I find traditional function expressions acceptable,
too. But you have to be careful with this.
Alas, some JavaScript APIs use 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.
beforeEach(function () { // (A)
this.addMatchers({ // (B)
toBeInRange: function (start, end) {
···
}
});
});
This pattern is less explicit and prevents you from using arrow functions.
beforeEach(api => {
api.addMatchers({
toBeInRange(start, end) {
···
}
});
});
We have turned the API from an implicit parameter this into an explicit parameter api. I like
this kind of explicitness.
In some APIs, there are alternate ways to get to the value of this. For example, the following
code uses this.
But the target of the event can also be accessed via event.target:
• Subjectively, I find they look nicer. In this case, the verbose keyword function is an
advantage – you want the construct to stand out.
• They look like generator function declarations, leading to more visual consistency of the
code.
Callable entities in ECMAScript 6 158
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.
The following is a quick way to do the same thing in ES6 (caveat: Object.assign() doesn’t move
methods with super properly).
Object.assign(MyClass.prototype, {
foo(arg1, arg2) {
···
}
});
The this of a method is the receiver of the method call (e.g. obj if the method call is obj.m(···)).
For example, you can use the WHATWG streams API¹ as follows:
¹https://streams.spec.whatwg.org/
Callable entities in ECMAScript 6 159
const surroundingObject = {
surroundingMethod() {
const obj = {
data: 'abc',
start(controller) {
···
console.log(this.data); // abc (*)
this.pull(); // (**)
···
},
pull() {
···
},
cancel() {
···
},
};
const stream = new ReadableStream(obj);
},
};
That is, obj is an object whose properties start, pull and cancel are methods. Accordingly,
these methods can use this to access object-local state (line *) and to call each other (line **).
The 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 start, pull and cancel are arrow functions then they pick up the this of
surroundingMethod() (their surrounding scope):
const surroundingObject = {
surroundingData: 'xyz',
surroundingMethod() {
const obj = {
start: controller => {
···
console.log(this.surroundingData); // xyz (*)
···
},
pull: () => {
···
Callable entities in ECMAScript 6 160
},
cancel: () => {
···
},
};
const stream = new ReadableStream(obj);
},
};
const stream = new ReadableStream();
If the output in line * surprises you then consider the following code:
const obj = {
foo: 123,
bar() {
const f = () => console.log(this.foo); // 123
const o = {
p: () => console.log(this.foo), // 123
};
},
}
Inside method bar(), f and 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.
In ES5, you had to use an IIFE if you wanted to keep a variable local:
console.log(tmp); // ReferenceError
In ECMAScript 6, you can simply use a block and a let or const declaration:
Callable entities in ECMAScript 6 161
{ // open block
let tmp = ···;
···
} // close block
console.log(tmp); // ReferenceError
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:
function myFunc(x) {
countInvocations++;
···
}
// Exported by module:
return {
myFunc: myFunc
};
}());
my_module.myFunc(33);
In ECMAScript 6, modules are built in, which is why the barrier to adopting them is low:
// my_module.js
// Module-private variable:
let countInvocations = 0;
This module does not produce a global variable and is used as follows:
Callable entities in ECMAScript 6 162
myFunc(33);
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.
Value:
Func decl/Func expr Arrow Class Method
Function-callable ✔ ✔ × ✔
Constructor-callable ✔ × ✔ ×
Prototype F.p F.p SC F.p
Property prototype ✔ × ✔ ×
Callable entities in ECMAScript 6 163
Whole construct:
Func decl Func expr Arrow Class Method
Hoisted ✔ ×
Creates window prop. (1) ✔ ×
Inner name (2) × ✔ ✔ ×
Body:
Abbreviations in cells:
• ✔ exists, allowed
• × does not exist, not allowed
• Empty cell: not applicable, not relevant
• lex: lexical, inherited from surrounding lexical scope
• F.p: Function.prototype
• SC: superclass for derived classes, Function.prototype for base classes. The details are
explained in the chapter on classes.
Notes:
• (1) The rules for what declarations create properties for the global object are explained in
the chapter on variables and scoping.
• (2) The inner names of named function expressions and classes are explained in the chapter
on classes.
• (3) This column is about the body of the class constructor.
What about generator functions and methods? Those work like their non-generator counter-
parts, with two exceptions:
Abbreviations in cells:
• Function expression:
• Function declaration:
• Function calls: this is undefined in strict mode and the global object in sloppy mode.
• Method calls: this is the receiver of the method call (or the first argument of call/apply).
• Constructor calls: this is the newly created instance.
The rules for this are as follows. Note that it never refers to the generator object.
• Function/method calls: this is handled like it is with traditional functions. The results of
such calls are generator objects.
• Constructor calls: You can’t constructor-call generator functions. A TypeError is thrown
if you do.
const obj = {
add(x, y) {
return x + y;
}, // comma is required
sub(x, y) {
return x - y;
}, // comma is optional
};
class AddSub {
add(x, y) {
return x + y;
} // no comma
sub(x, y) {
return x - y;
} // no comma
}
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).
Rules:
Callable entities in ECMAScript 6 166
• Function calls: If you extract a method and call it as a function, it behaves like a traditional
function.
• Method calls: work as with traditional functions, but additionally allow you to use super.
• Constructor calls: produce a TypeError.
Inside class definitions, methods whose name is constructor are special, as explained later.
const obj = {
* generatorMethod(···) {
···
},
};
class MyClass {
* generatorMethod(···) {
···
}
}
Rules:
The following variables are lexical inside an arrow function (picked up from the surrounding
scope):
• arguments
• super
• this
• new.target
Rules:
12.4.7 Classes
Classes are explained in their own chapter.
The Method constructor is special, because it “becomes” the class. That is, classes are very
similar to constructor functions:
Rules:
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.
Properties in “earlier” objects override properties in “later” objects. For example, Array.prototype
provides an Array-specific version of the toString() method, overriding Object.prototype.toString().
1. Dispatch: In the prototype chain of arr, retrieve the value of the first property whose name
is toString.
2. Call: Call the value and set the implicit parameter this to the receiver arr of the method
invocation.
You can make the two steps explicit by using the call() method of functions:
Callable entities in ECMAScript 6 169
Both method call and method apply are invoked on functions. They are different from normal
function calls in that you specify a value for this. 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 arr:
Methods that work with a variety of objects (not just with instances of “their” constructor) are
called generic. Speaking JavaScript has a list² of all methods that are generic. The list includes
most Array methods and all methods of Object.prototype (which have to work with all objects
and are thus implicitly generic).
Some functions accept multiple values, but only one value per parameter. What if you want to
pass the values via an Array?
For example, push() lets you destructively append several values to an Array:
²http://speakingjs.com/es5/ch17.html#list_of_generic_methods
Callable entities in ECMAScript 6 170
But you can’t destructively append a whole Array. You can work around that limitation by using
apply():
> Math.max(-1, 7, 2)
7
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.
Another example:
Note that apply() can’t be used with new – the above feat can only be achieved via a complicated
work-around³ in ECMAScript 5.
Some objects in JavaScript are Array-like, they are almost Arrays, but don’t have any of the
Array methods. Let’s look at two examples.
First, the special variable arguments of functions is Array-like. It has a length and indexed access
to elements.
But arguments isn’t an instance of Array and does not have the method forEach().
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:
³http://speakingjs.com/es5/ch17.html#apply_constructors
Callable entities in ECMAScript 6 172
function format(pattern) {
// params start at arguments[1], skipping `pattern`
var params = Array.prototype.slice.call(arguments, 1);
return params;
}
console.log(format('a', 'b', 'c')); // ['b', 'c']
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 prop.
Callable entities in ECMAScript 6 173
> obj.hasOwnProperty('prop')
true
However, calling hasOwnProperty via dispatch can cease to work properly if Object.prototype.hasOwnProperty
is overridden.
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 hasOwnProperty() less.
Applying an Array method such as join() to a string normally involves two steps:
Callable entities in ECMAScript 6 174
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.
Object.prototype.hasOwnProperty.call(obj, 'propKey')
{}.hasOwnProperty.call(obj, 'propKey')
Array.prototype.slice.call(arguments)
[].slice.call(arguments)
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.
This property is useful for debugging (its value shows up in stack traces) and some metapro-
gramming 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.
⁴http://jsperf.com/array-prototype-slice-call-vs-slice-call/17
Callable entities in ECMAScript 6 176
let func4;
func4 = function () {};
console.log(func4.name); // func4
var func5;
func5 = function () {};
console.log(func5.name); // func5
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 name property.
For example, a function declaration:
Callable entities in ECMAScript 6 177
function foo() {}
console.log(foo.name); // foo
The name of a named function expression also sets up the name property.
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).
function func() {}
let obj = {
m1() {}, // (A)
m2: function () {}, // (B)
['m' + '3']: function () {}, // (C)
func, // (D)
};
console.log(obj.m1.name); // m1
console.log(obj.m2.name); // m2
console.log(obj.m3.name); // m3
console.log(obj.func.name); // func
The names of getters are prefixed with 'get', the names of setters are prefixed with 'set':
Callable entities in ECMAScript 6 178
let obj = {
get foo() {},
set bar(value) {},
};
let getter = Object.getOwnPropertyDescriptor(obj, 'foo').get;
console.log(getter.name); // 'get foo'
class C {
m1() {}
['m' + '2']() {} // computed property key
static classMethod() {}
}
console.log(C.prototype.m1.name); // m1
console.log(new C().m1.name); // m1
console.log(C.prototype.m2.name); // m2
console.log(C.classMethod.name); // classMethod
Getters and setters again have the name prefixes 'get' and 'set', respectively:
class C {
get foo() {}
set bar(value) {}
}
let getter = Object.getOwnPropertyDescriptor(C.prototype, 'foo').get;
console.log(getter.name); // 'get foo'
In ES6, the key of a method can be a symbol. The name property of such a method is still a string:
• If the symbol has a description, the method’s name is the description in square brackets.
• Otherwise, the method’s name is the empty string ('').
Callable entities in ECMAScript 6 179
let obj = {
[key1]() {},
[key2]() {},
};
console.log(obj[key1].name); // '[description]'
console.log(obj[key2].name); // ''
Remember that class definitions create functions. Those functions also have their property name
set up correctly:
class Foo {}
console.log(Foo.name); // Foo
• Generator functions and generator methods get their names the same way that normal
functions and methods do.
• new Function() produces functions whose name is 'anonymous'. A webkit bug⁵ describes
why that is necessary on the web.
• func.bind(···) produces a function whose name is 'bound '+func.name:
⁵https://bugs.webkit.org/show_bug.cgi?id=7726
Callable entities in ECMAScript 6 180
function foo(x) {
return x
}
const bound = foo.bind(undefined, 123);
console.log(bound.name); // 'bound foo'
12.6.2 Caveats
Function names are always assigned during creation and never changed later on. That is,
JavaScript engines detect the previously mentioned patterns and create functions that start their
lives with the correct names. The following code demonstrates that the name of the function
created by functionFactory() is assigned in line A and not changed by the declaration in line
B.
function functionFactory() {
return function () {}; // (A)
}
const foo = functionFactory(); // (B)
console.log(foo.name.length); // 0 (anonymous)
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.
The property not being writable means that you can’t change its value via assignment:
Callable entities in ECMAScript 6 181
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 false or undefined).
12.7.1 Why are there “fat” arrow functions (=>) 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.
⁶http://www.ecma-international.org/ecma-262/6.0/#sec-setfunctionname
⁷http://www.ecma-international.org/ecma-262/6.0/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation
⁸http://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.bind
⁹http://www.ecma-international.org/ecma-262/6.0/#sec-function-definitions-runtime-semantics-evaluation
¹⁰http://www.ecma-international.org/ecma-262/6.0/#sec-arrow-function-definitions-runtime-semantics-evaluation
Callable entities in ECMAScript 6 182
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:
function realFunction() {
if (new.target !== undefined) {
throw new Error('Can’t be invoked via `new`');
}
···
}
function realFunction() {
"use strict";
if (this !== undefined) {
throw new Error('Can’t be invoked via `new`');
}
···
}
13. Arrow functions
13.1 Overview
There are two benefits to arrow functions.
First, they are less verbose than traditional function expressions:
Second, their this is picked up from surroundings (lexical). Therefore, you don’t need bind()
or that = this, anymore.
function UiComponent() {
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('CLICK');
this.handleClick(); // lexical `this`
});
}
• arguments
• super
• this
• new.target
1. Non-method functions
2. Methods
3. Constructors
These roles clash: Due to roles 2 and 3, functions always have their own this. But that prevents
you from accessing the this of, e.g., a surrounding method from inside a callback (role 1).
You can see that in the following ES5 code:
Arrow functions 184
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) { // (A)
'use strict';
return arr.map(function (x) { // (B)
// Doesn’t work:
return this.prefix + x; // (C)
});
};
In line C, we’d like to access this.prefix, but can’t do that because the this of the function
from line B shadows the this of the method from line A. In strict mode, this is undefined in
non-method functions, which is why we get an error if we use Prefixer:
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
var that = this; // (A)
return arr.map(function (x) {
return that.prefix + x;
});
};
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
return arr.map(function (x) {
return this.prefix + x;
}, this); // (A)
};
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
return arr.map(function (x) {
return this.prefix + x;
}.bind(this)); // (A)
};
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
return arr.map((x) => {
return this.prefix + x;
});
};
To fully ES6-ify the code, you’d use a class and a more compact variant of arrow functions:
Arrow functions 186
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
prefixArray(arr) {
return arr.map(x => this.prefix + x); // (A)
}
}
In line A we save a few characters by tweaking two parts of the arrow function:
• If there is only one parameter and that parameter is an identifier then the parentheses can
be omitted.
• An expression following the arrow leads to that expression being returned.
In the code, you can also see that the methods constructor and prefixArray are defined using
new, more compact ES6 syntax that also works in object literals.
Specifying a body:
The 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.
Note how much an arrow function with an expression body can reduce verbosity. Compare:
As soon as there is anything else, you have to type the parentheses, even if there is only a single
parameter. For example, you need parens if you destructure a single parameter:
And you need parens if a single parameter has a default value (undefined triggers the default
value!):
const x = 123;
function foo(y) {
return x; // value received statically
}
Second, dynamically: It receives its value via a function call. For example:
function bar(arg) {
return arg; // value received dynamically
}
• Traditional functions have a dynamic this; its value is determined by how they are called.
• Arrow functions have a lexical this; its value is determined by the surrounding scope.
Arrow functions 188
The complete list¹ of variables whose values are determined lexically is:
• arguments
• super
• this
• new.target
As a consequence, you often have to wrap arrow functions in parentheses if they appear
somewhere else. For example:
On the flip side, you can use typeof as an expression body without putting it in parens:
¹http://www.ecma-international.org/ecma-262/6.0/#sec-arrow-function-definitions-runtime-semantics-evaluation
Arrow functions 189
const func6 = ( // OK
x,
y
) => {
return x + y;
};
The rationale for this restriction is that it keeps the options open w.r.t. to “headless” arrow
functions in the future: if there are zero parameters, you’d be able to omit the parentheses.
3 + 4
foo(7)
'abc'.length
²http://speakingjs.com/es5/ch07.html#expr_vs_stmt
Arrow functions 190
Most expressions³ can be used as statements, simply by mentioning them in statement positions:
function bar() {
3 + 4;
foo(7);
'abc'.length;
}
³The exceptions are function expressions and object literals, which you have to put in parentheses, because they look like function
declarations and code blocks.
⁴http://speakingjs.com/es5/ch16.html#iife
Arrow functions 191
You can save a few characters if you use an Immediately Invoked Arrow Function (IIAF):
(() => {
return 123
})();
Similarly to IIFEs, you should terminate IIAFs with semicolons (or use an equivalent measure⁵),
to avoid two consecutive IIAFs being interpreted as a function call (the first one as the function,
the second one as the parameter).
Even if the IIAF has a block body, you must wrap it in parentheses, because it can’t be (directly)
function-called, due to how loosely it binds. Note that the parentheses must be around the arrow
function. With IIFEs you have a choice: you can either put the parentheses around the whole
statement or just around the function expression.
As mentioned in the previous section, arrow functions binding loosely is useful for expression
bodies, where you want this expression:
to be interpreted as:
A section in the chapter on callable entities has more information on using IIFEs and IIAFs in
ES6.
obj.on('anEvent', console.log.bind(console))
function add(x, y) {
return x + y;
}
const plus1 = add.bind(undefined, 1);
Apart from that, there are no observable differences between an arrow function and a normal
function. For example, typeof and instanceof produce the same results:
Consult the chapter on callable entities for more information on when to use arrow functions
and when to use traditional functions.
14. New OOP features besides
classes
Classes (which are explained in the next chapter) are the major new OOP feature in ECMAScript
6. However, it also includes new features for object literals and new utility methods in Object.
This chapter describes them.
14.1 Overview
const obj = {
myMethod(x, y) {
···
}
};
const obj = {
['h'+'ello']() {
return 'hi';
}
};
console.log(obj.hello()); // hi
The main use case for computed property keys is to make it easy to use symbols as property keys.
var obj = {
myMethod: function (x, y) {
···
}
};
In ECMAScript 6, methods are still function-valued properties, but there is now a more compact
way of defining them:
const obj = {
myMethod(x, y) {
···
}
};
Getters and setters continue to work as they did in ECMAScript 5 (note how syntactically similar
they are to method definitions):
New OOP features besides classes 196
const obj = {
get foo() {
console.log('GET foo');
return 123;
},
set bar(value) {
console.log('SET bar to '+value);
// return value is ignored
}
};
> obj.foo
GET foo
123
> obj.bar = true
SET bar to true
true
There is also a way to concisely define properties whose values are generator functions:
const obj = {
* myGeneratorMethod() {
···
}
};
const obj = {
myGeneratorMethod: function* () {
···
}
};
const x = 4;
const y = 1;
const obj = { x, y };
const obj = { x: x, y: y };
const obj = { x: 4, y: 1 };
const {x,y} = obj;
console.log(x); // 4
console.log(y); // 1
One use case for property value shorthands are multiple return values (which are explained in
the chapter on destructuring).
In object literals, you only have option #1 in ECMAScript 5. ECMAScript 6 additionally provides
option #2:
const obj = {
['h'+'ello']() {
return 'hi';
}
};
console.log(obj.hello()); // hi
The main use case for computed property keys are symbols: you can define a public symbol and
use it as a special property key that is always unique. One prominent example is the symbol
stored in Symbol.iterator. If an object has a method with that key, it becomes iterable: The
method must return an iterator, which is used by constructs such as the for-of loop to iterate
over the object. The following code demonstrates how that works.
const obj = {
* [Symbol.iterator]() { // (A)
yield 'hello';
yield 'world';
}
};
for (const x of obj) {
console.log(x);
}
// Output:
// hello
// world
Line A starts a generator method definition with a computed key (the symbol stored in
Symbol.iterator).
• Both kinds of property keys: Object.assign() is aware of both strings and symbols as
property keys.
• Only enumerable own properties: Object.assign() ignores inherited properties and
properties that are not enumerable.
• Reading a value from a source: normal “get” operation (const value = source[propKey]).
That means that if the source has a getter whose key is propKey then it will be invoked.
All properties created by Object.assign() are data properties, it won’t transfer getters to
the target.
• Writing a value to the target: normal “set” operation (target[propKey] = value). That
means that if the target has a setter whose key is propKey then it will be invoked with
value.
This is how you’d copy all properties (not just enumerable ones), while correctly transferring
getters and setters, without invoking setters on the target:
On one hand, you can’t move a method that uses super: Such a method has the internal slot
[[HomeObject]] that ties it to the object it was created in. If you move it via Object.assign(),
it will continue to refer to the super-properties of the original object. Details are explained in a
section in the chapter on classes.
On the other hand, enumerability is wrong if you move methods created by an object literal into
the prototype of a class. The former methods are all enumerable (otherwise Object.assign()
wouldn’t see them, anyway), but the prototype only has non-enumerable methods by default.
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
Object.assign() is also useful for filling in defaults for missing properties. In the following
example, we have an object DEFAULTS with default values for properties and an object options
with data.
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options); // (A)
···
}
In line A, we created a fresh object, copied the defaults into it and then copied options into it,
overriding the defaults. Object.assign() returns the result of these operations, which we assign
to options.
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
You could also manually assign functions, but then you don’t have the nice method definition
syntax and need to mention SomeClass.prototype each time:
New OOP features besides classes 201
One last use case for Object.assign() is a quick way of cloning objects:
function clone(orig) {
return Object.assign({}, orig);
}
This way of cloning is also somewhat dirty, because it doesn’t preserve the property attributes
of orig. If that is what you need, you have to use property descriptors¹.
If you want the clone to have the same prototype as the original, you can use Object.getPrototypeOf()
and Object.create():
function clone(orig) {
const origProto = Object.getPrototypeOf(orig);
return Object.assign(Object.create(origProto), orig);
}
14.3.2 Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols(obj) retrieves all own (non-inherited) symbol-valued property
keys of obj. It complements Object.getOwnPropertyNames(), which retrieves all string-valued
own property keys. Consult a later section for more details on traversing properties.
¹http://speakingjs.com/es5/ch17.html#property_attributes
New OOP features besides classes 202
> [0,NaN,2].indexOf(NaN)
-1
Second, JavaScript has two zeros², but strict equals treats them as if they were the same value:
> -0 === +0
true
If we combine Object.is() with the new ES6 Array method findIndex(), we can find NaN in
Arrays:
myIndexOf([0,NaN,2], NaN); // 1
> [0,NaN,2].indexOf(NaN)
-1
• Object.keys(obj) : Array<string>
retrieves all string keys of all enumerable own (non-inherited) properties.
• Object.getOwnPropertyNames(obj) : Array<string>
retrieves all string keys of all own properties.
• Object.getOwnPropertySymbols(obj) : Array<symbol>
retrieves all symbol keys of all own properties.
• Reflect.ownKeys(obj) : Array<string|symbol>
retrieves all keys of all own properties.
• for (const key in obj)
retrieves all string keys of all enumerable properties (inherited and own).
• Retrieves the keys of all own properties of an object, in the following order:
– First, the string keys that are integer indices (what these are is explained in the next
section), in ascending numeric order.
– Then all other string keys, in the order in which they were added to the object.
– Lastly, all symbol keys, in the order in which they were added to the object.
• Used by: Object.assign(), Object.defineProperties(), Object.getOwnPropertyNames(),
Object.getOwnPropertySymbols(), Reflect.ownKeys()
• Retrieves the string keys of all enumerable own properties of an object. The order is not
defined by ES6, but it must be the same order in which for-in traverses properties.
• Used by: JSON.parse(), JSON.stringify(), Object.keys()
The order in which for-in traverses properties is not defined. Quoting Allen Wirfs-Brock³:
Historically, the for-in order was not defined and there has been variation among
browser implementations in the order they produce (and other specifics). ES5 added
Object.keys and the requirement that it should order the keys identically to for-
in. During development of both ES5 and ES6, the possibility of defining a specific
for-in order was considered but not adopted because of web legacy compatibility
concerns and uncertainty about the willingness of browsers to make changes in the
ordering they currently produce.
³https://mail.mozilla.org/pipermail/es-discuss/2015-August/043998.html
New OOP features besides classes 204
Many engines treat integer indices specially, even though they are still strings (at least as far as
the ES6 spec is concerned). Therefore, it makes sense to treat them as a separate category of keys.
Roughly, an integer index is a string that, if converted to a 53-bit non-negative integer and back
is the same value. Therefore:
In ES6, instances of String and Typed Arrays have integer indices. The indices of normal Arrays
are a subset of integer indices: they have a smaller range of 32 bits. For more information on Array
indices, consult “Array Indices in Detail⁴” in “Speaking JavaScript”.
Integer indices have a 53-bit range, because thats the largest range of integers that
JavaScript can handle. For details, see Sect. “Safe integers”.
14.4.2.2 Example
The following code demonstrates the traversal order “Own Property Keys”:
const obj = {
[Symbol('first')]: true,
'02': true,
'10': true,
'01': true,
'2': true,
[Symbol('second')]: true,
};
Reflect.ownKeys(obj);
// [ '2', '10', '02', '01',
// Symbol('first'), Symbol('second') ]
Explanation:
• '2' and '10' are integer indices, come first and are sorted numerically (not in the order
in which they were added).
• '02' and '01' are normal string keys, come next and appear in the order in which they
were added to obj.
• Symbol('first') and Symbol('second') are symbols and come last, in the order in which
they were added to obj.
⁴http://speakingjs.com/es5/ch18.html#_array_indices_in_detail
New OOP features besides classes 205
14.4.2.3 Why does the spec standardize in which order property keys are
returned?
Because, for objects at least, all implementations used approximately the same
order (matching the current spec), and lots of code was inadvertently written that
depended on that ordering, and would break if you enumerated it in a different
order. Since browsers have to implement this particular ordering to be web-
compatible, it was specced as a requirement.
There was some discussion about breaking from this in Maps/Sets, but doing so
would require us to specify an order that is impossible for code to depend on; in other
words, we’d have to mandate that the ordering be random, not just unspecified.
This was deemed too much effort, and creation-order is reasonable valuable (see
OrderedDict in Python, for example), so it was decided to have Maps and Sets match
Objects.
The following parts of the spec are relevant for this section:
• The section on Array exotic objects⁶ has a note on what Array indices are.
• The internal method [[OwnPropertyKeys]]⁷ is used by Reflect.ownKeys() and others.
• The operation EnumerableOwnNames⁸ is used by Object.keys() and others.
• The internal method [[Enumerate]]⁹ is used by for-in.
There are two similar ways of adding a property prop to an object obj:
There are three cases in which assigning does not create an own property prop, even if it doesn’t
exist, yet:
⁵https://esdiscuss.org/topic/nailing-object-property-order
⁶http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects
⁷http://www.ecma-international.org/ecma-262/6.0/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
⁸http://www.ecma-international.org/ecma-262/6.0/#sec-enumerableownnames
⁹http://www.ecma-international.org/ecma-262/6.0/#sec-ordinary-object-internal-methods-and-internal-slots-enumerate
New OOP features besides classes 206
1. A read-only property prop exists in the prototype chain. Then the assignment causes a
TypeError in strict mode.
2. A setter for prop exists in the prototype chain. Then that setter is called.
3. A getter for prop without a setter exists in the prototype chain. Then a TypeError is thrown
in strict mode. This case is similar to the first one.
None of these cases prevent Object.defineProperty() from creating an own property. The next
section looks at case #3 in more detail.
This is similar to how an inherited property works that has a getter, but no setter. It is in line with
viewing assignment as changing the value of an inherited property. It does so non-destructively:
the original is not modified, but overridden by a newly created own property. Therefore, an
inherited read-only property and an inherited setter-less property both prevent changes via
assignment. You can, however, force the creation of an own property by defining a property:
14.6.1.1 Prototypes
Each object in JavaScript starts a chain of one or more objects, a so-called prototype chain.
Each object points to its successor, its prototype via the internal slot [[Prototype]] (which is
null if there is no successor). That slot is called internal, because it only exists in the language
specification and cannot be directly accessed from JavaScript. In ECMAScript 5, the standard
way of getting the prototype p of an object obj is:
var p = Object.getPrototypeOf(obj);
There is no standard way to change the prototype of an existing object, but you can create a new
object obj that has the given prototype p:
14.6.1.2 __proto__
A long time ago, Firefox got the non-standard property __proto__. Other browsers eventually
copied that feature, due to its popularity.
Prior to ECMAScript 6, __proto__ worked in obscure ways:
The main reason why __proto__ became popular was because it enabled the only way to create
a subclass MyArray of Array in ES5: Array instances were exotic objects that couldn’t be created
by ordinary constructors. Therefore, the following trick was used:
New OOP features besides classes 208
function MyArray() {
var instance = new Array(); // exotic object
instance.__proto__ = MyArray.prototype;
return instance;
}
MyArray.prototype = Object.create(Array.prototype);
MyArray.prototype.customMethod = function (···) { ··· };
Subclassing in ES6 works differently than in ES5 and supports subclassing builtins out of the
box.
The main problem is that __proto__ mixes two levels: the object level (normal properties, holding
data) and the meta level.
If you accidentally use __proto__ as a normal property (object level!), to store data, you get into
trouble, because the two levels clash. The situation is compounded by the fact that you have to
abuse objects as maps in ES5, because it has no built-in data structure for that purpose. Maps
should be able to hold arbitrary keys, but you can’t use the key '__proto__' with objects-as-
maps.
In theory, one could fix the problem by using a symbol instead of the special name __proto__,
but keeping meta-operations completely separate (as done via Object.getPrototypeOf()) is the
best approach.
The ECMAScript language syntax and semantics defined in this annex are required
when the ECMAScript host is a web browser. The content of this annex is normative
but optional if the ECMAScript host is not a web browser.
JavaScript has several undesirable features that are required by a significant amount of code on
the web. Therefore, web browsers must implement them, but other JavaScript engines don’t have
to.
In order to explain the magic behind __proto__, two mechanisms were introduced in ES6:
14.6.2.1 Object.prototype.__proto__
ECMAScript 6 enables getting and setting the property __proto__ via a getter and a setter stored
in Object.prototype. If you were to implement them manually, this is roughly what it would
look like:
Object.defineProperty(Object.prototype, '__proto__', {
get() {
const _thisObj = Object(this);
return Object.getPrototypeOf(_thisObj);
},
set(proto) {
if (this === undefined || this === null) {
throw new TypeError();
}
if (!isObject(this)) {
return undefined;
}
if (!isObject(proto)) {
return undefined;
}
const status = Reflect.setPrototypeOf(this, proto);
if (! status) {
throw new TypeError();
}
},
});
function isObject(value) {
return Object(value) === value;
}
The getter and the setter for __proto__ in the ES6 spec:
• get Object.prototype.__proto__¹²
• set Object.prototype.__proto__¹³
If __proto__ appears as an unquoted or quoted property key in an object literal, the prototype
of the object created by that literal is set to the property value:
¹²http://www.ecma-international.org/ecma-262/6.0/#sec-get-object.prototype.__proto__
¹³http://www.ecma-international.org/ecma-262/6.0/#sec-set-object.prototype.__proto__
New OOP features besides classes 210
Using the string value '__proto__' as a computed property key does not change the prototype,
it creates an own property:
In ECMAScript 6, if you define the own property __proto__, no special functionality is triggered
and the getter/setter Object.prototype.__proto__ is overridden:
Object.keys(obj); // [ '__proto__' ]
console.log(obj.__proto__); // 123
¹⁴http://www.ecma-international.org/ecma-262/6.0/#sec-__proto__-property-names-in-object-initializers
New OOP features besides classes 211
If you want to use an object as a dictionary then it is best if it doesn’t have a prototype. That’s
why prototype-less objects are also called dict objects¹⁵. In ES6, you don’t even have to escape the
property key '__proto__' for dict objects, because it doesn’t trigger any special functionality.
__proto__ as an operator in an object literal lets you create dict objects more concisely:
const dictObj = {
__proto__: null,
yes: true,
no: false,
};
Note that in ES6, you should normally prefer the built-in data structure Map to dict objects,
especially if keys are not fixed.
With __proto__ being a getter/setter in ES6, JSON.parse() works fine, because it defines
properties, it doesn’t assign them (if implemented properly, an older version of V8 did assign¹⁶).
JSON.stringify() isn’t affected by __proto__, either, because it only considers own properties.
Objects that have an own property whose name is __proto__ work fine:
• Object.prototype.__proto__¹⁷
• __proto__ in object literals¹⁸
The following two sections describe how you can programmatically detect whether an engine
supports either of the two kinds of __proto__.
An awkward thing about programming in Python: there are lots of double un-
derscores. For example, the standard method names beneath the syntactic sugar
have names like __getattr__, constructors are __init__, built-in operators can be
overloaded with __add__, and so on. […]
My problem with the double underscore is that it’s hard to say. How do you
pronounce __init__? “underscore underscore init underscore underscore”? “under
under init under under”? Just plain “init” seems to leave out something important.
I have a solution: double underscore should be pronounced “dunder”. So __init__ is
“dunder init dunder”, or just “dunder init”.
Thus, __proto__ is pronounced “dunder proto”. The chances for this pronunciation catching on
are good, JavaScript creator Brendan Eich uses it.
This section explains how the attribute enumerable works in ES6. All other attributes and how
to change attributes is explained in Sect. “Property Attributes and Property Descriptors²⁰” in
“Speaking JavaScript”.
• for-in loop: traverses the string keys of own and inherited enumerable properties.
• Object.keys(): returns the string keys of enumerable own properties.
• JSON.stringify(): only stringifies enumerable own properties with string keys.
ECMAScript 6:
• Object.assign(): only copies enumerable own properties (both string keys and symbol
keys are considered).
for-in is the only built-in operations where enumerability matters for inherited properties. All
other operations only work with own properties.
The for-in loop traverses all enumerable properties of an object, own and inherited ones.
Therefore, the attribute enumerable is used to hide properties that should not be traversed. That
was the reason for introducing enumerability in ECMAScript 1.
• In Arrays, length is not enumerable, which means that for-in only traverses indices.
(However, that can easily change if you add a property via assignment, which is makes it
enumerable.)
The main reason for making all of these properties non-enumerable is to hide them (especially the
inherited ones) from legacy code that uses the for-in loop or $.extend() (and similar operations
that copy both inherited and own properties; see next section). Both operations should be avoided
in ES6. Hiding them ensures that the legacy code doesn’t break.
When it comes to copying properties, there are two important historical precedents that take
enumerability into consideration:
²¹http://api.prototypejs.org/language/Object/extend/
New OOP features besides classes 216
• jQuery’s $.extend(target, source1, source2, ···)²² copies all enumerable own and
inherited properties of source1 etc. into own properties of target.
• Turning inherited source properties into own target properties is rarely what you want.
That’s why enumerability is used to hide inherited properties.
• Which properties to copy and which not often depends on the task at hand, it rarely
makes sense to have a single flag for everything. A better choice is to provide the copying
operation with a predicate (a callback that returns a boolean) that tells it when to consider
a property.
The only instance property that is non-enumerable in the standard library is property length
of Arrays. However, that property only needs to be hidden due to it magically updating itself
via other properties. You can’t create that kind of magic property for your own objects (short of
using a Proxy).
In ES6, Object.assign(target, source_1, source_2, ···) can be used to merge the sources
into the target. All own enumerable properties of the sources are considered (that is, keys can be
either strings or symbols). Object.assign() uses a “get” operation to read a value from a source
and a “set” operation to write a value to the target.
With regard to enumerability, Object.assign() continues the tradition of Object.extend() and
$.extend(). Quoting Yehuda Katz²³:
²²https://api.jquery.com/jquery.extend/
²³https://mail.mozilla.org/pipermail/es-discuss/2012-October/025934.html
New OOP features besides classes 217
Object.assign would pave the cowpath of all of the extend() APIs already in
circulation. We thought the precedent of not copying enumerable methods in those
cases was enough reason for Object.assign to have this behavior.
In other words: Object.assign() was created with an upgrade path from $.extend() (and
similar) in mind. Its approach is cleaner than $.extend’s, because it ignores inherited properties.
If you make a property non-enumerable, it can’t by seen by Object.keys() and the for-in loop,
anymore. With regard to those mechanisms, the property is private.
However, there are several problems with this approach:
• When copying an object, you normally want to copy private properties. That clashes
making properties non-enumerable that shouldn’t be copied (see previous section).
• The property isn’t really private. Getting, setting and several other mechanisms make no
distinction between enumerable and non-enumerable properties.
• When working with code either as source or interactively, you can’t immediately see
whether a property is enumerable or not. A naming convention (such as prefixing property
names with an underscore) is easier to discover.
• You can’t use enumerability to distinguish between public and private methods, because
methods in prototypes are non-enumerable by default.
JSON.stringify() does not include properties in its output that are non-enumerable. You can
therefore use enumerability to determine which own properties should be exported to JSON. This
use case is similar to marking properties as private, the previous use case. But it is also different,
because this is more about exporting and slightly different considerations apply. For example:
Can an object be completely reconstructed from JSON?
An alternative for specifying how an object should be converted to JSON is to use toJSON():
const obj = {
foo: 123,
toJSON() {
return { bar: 456 };
},
};
JSON.stringify(obj); // '{"bar":456}'
I find toJSON() cleaner than enumerability for the current use case. It also gives you more control,
because you can export properties that don’t exist on the object.
New OOP features besides classes 218
However, Reflect.ownKeys() deviates from that rule, it ignores enumerability and returns the
keys of all properties. Additionally, starting with ES6, the following distinction is made:
• I don’t think there is a need for a general flag specifying whether or not to copy a property.
• Non-enumerability does not work well as a way to keep properties private.
• The toJSON() method is more powerful and explicit than enumerability when it comes to
controlling how to convert an object to JSON.
I’m not sure what the best strategy is for enumerability going forward. If, with ES6, we had
started to pretend that it didn’t exist (except for making prototype properties non-enumerable
so that old code doesn’t break), we might eventually have been able to deprecate enumerability.
However, Object.assign() considering enumerability runs counter that strategy (but it does so
for a valid reason, backward compatibility).
In my own ES6 code, I’m not using enumerability, except (implicitly) for classes whose prototype
methods are non-enumerable.
Lastly, when using an interactive command line, I occasionally miss an operation that returns all
property keys of an object, not just the own ones (Reflect.ownKeys). Such an operation would
provide a nice overview of the contents of an object.
New OOP features besides classes 219
• Symbol.hasInstance (method)
Lets an object C customize the behavior of x instanceof C.
• Symbol.toPrimitive (method)
Lets an object customize how it is converted to a primitive value. This is the first step
whenever something is coerced to a primitive type (via operators etc.).
• Symbol.toStringTag (string)
Called by Object.prototype.toString() to compute the default string description of an
object obj: ‘[object ‘+obj[Symbol.toStringTag]+’]’.
• Symbol.unscopables (Object)
Lets an object hide some properties from the with statement.
[Symbol.hasInstance](potentialInstance : any)
The only method in the standard library that has this key is:
• Function.prototype[Symbol.hasInstance]()
This is the implementation of instanceof that all functions (including classes) use by default.
Quoting the spec²⁴:
²⁴http://www.ecma-international.org/ecma-262/6.0/sec-function.prototype-@@hasinstance
New OOP features besides classes 220
As an example, let’s implement an object ReferenceType whose “instances” are all objects, not
just objects that are instances of Object (and therefore have Object.prototype in their prototype
chains).
const ReferenceType = {
[Symbol.hasInstance](value) {
return (value !== null
&& (typeof value === 'object'
|| typeof value === 'function'));
}
};
const obj1 = {};
console.log(obj1 instanceof Object); // true
console.log(obj1 instanceof ReferenceType); // true
The following are the most common types that values are coerced to:
• Boolean: Coercion returns true for truthy values, false for falsy values. Objects are
always truthy (even new Boolean(false)).
²⁵http://www.ecma-international.org/ecma-262/6.0/#sec-ordinaryhasinstance
New OOP features besides classes 221
• Number: Coercion converts objects to primitives first. Primitives are then converted to
numbers (null → 0, true → 1, '123' → 123, etc.).
• String: Coercion converts objects to primitives first. Primitives are then converted to
strings (null → 'null', true → 'true', 123 → '123', etc.).
• Object: The coercion wraps primitive values (booleans b via new Boolean(b), numbers n
via new Number(n), etc.).
Thus, for numbers and strings, the first step is to ensure that a value is any kind of primitive.
That is handled by the spec-internal operation ToPrimitive(), which has three modes:
If the value is a primitive then ToPrimitive() is already done. Otherwise, the value is an object
obj, which is converted to a primitive as follows:
• Number mode: Return the result of obj.valueOf() if it is primitive. Otherwise, return the
result of obj.toString() if it is primitive. Otherwise, throw a TypeError.
• String mode: works like Number mode, but toString() is called first, valueOf() second.
• Default mode: works exactly like Number mode.
This normal algorithm can be overridden by giving an object a method with the following
signature:
14.8.2.1 Example
The following code demonstrates how coercion affects the object obj.
²⁶http://www.ecma-international.org/ecma-262/6.0/#sec-symbol.prototype-@@toprimitive
²⁷http://www.ecma-international.org/ecma-262/6.0/#sec-date.prototype-@@toprimitive
New OOP features besides classes 222
const obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
};
console.log(2 * obj); // 246
console.log(3 + obj); // '3default'
console.log(obj == 'default'); // true
console.log(String(obj)); // 'str'
The default values for various kinds of objects are shown in the following table.
²⁸http://www.ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring
New OOP features besides classes 223
Most of the checks in the left column are performed by looking at internal slots. For example, if
an object has the internal slot [[Call]], it is callable.
The following interaction demonstrates the default toString tags.
> Object.prototype.toString.call(null)
'[object Null]'
> Object.prototype.toString.call([])
'[object Array]'
> Object.prototype.toString.call({})
'[object Object]'
> Object.prototype.toString.call(Object.create(null))
'[object Object]'
If an object has an (own or inherited) property whose key is Symbol.toStringTag then its value
overrides the default toString tag. For example:
> ({}.toString())
'[object Object]'
> ({[Symbol.toStringTag]: 'Foo'}.toString())
'[object Foo]'
Instances of user-defined classes get the default toString tag (of objects):
class Foo { }
console.log(new Foo().toString()); // [object Object]
class Bar {
get [Symbol.toStringTag]() {
return 'Bar';
}
}
console.log(new Bar().toString()); // [object Bar]
In the JavaScript standard library, there are the following custom toString tags. Objects that have
no global names are quoted with percent symbols (for example: %TypedArray%).
• Module-like objects:
– JSON[Symbol.toStringTag] → 'JSON'
– Math[Symbol.toStringTag] → 'Math'
• Actual module objects M: M[Symbol.toStringTag] → 'Module'
• Built-in classes
– ArrayBuffer.prototype[Symbol.toStringTag] → 'ArrayBuffer'
– DataView.prototype[Symbol.toStringTag] → 'DataView'
– Map.prototype[Symbol.toStringTag] → 'Map'
– Promise.prototype[Symbol.toStringTag] → 'Promise'
– Set.prototype[Symbol.toStringTag] → 'Set'
– get %TypedArray%.prototype[Symbol.toStringTag] → 'Uint8Array' etc.
– WeakMap.prototype[Symbol.toStringTag] → 'WeakMap'
– WeakSet.prototype[Symbol.toStringTag] → 'WeakSet'
• Iterators
– %MapIteratorPrototype%[Symbol.toStringTag] → 'Map Iterator'
– %SetIteratorPrototype%[Symbol.toStringTag] → 'Set Iterator'
– %StringIteratorPrototype%[Symbol.toStringTag] → 'String Iterator'
• Miscellaneous
– Symbol.prototype[Symbol.toStringTag] → 'Symbol'
– Generator.prototype[Symbol.toStringTag] → 'Generator'
– GeneratorFunction.prototype[Symbol.toStringTag] → 'GeneratorFunction'
All of the built-in properties whose keys are Symbol.toStringTag have the following property
descriptor:
{
writable: false,
enumerable: false,
configurable: true,
}
As mentioned earlier, you can’t use assignment to override those properties, because they are
read-only.
New OOP features besides classes 225
The reason for doing so is that it allows TC39 to add new methods to Array.prototype without
breaking old code. Note that current code rarely uses with, which is forbidden in strict mode and
therefore ES6 modules (which are implicitly in strict mode).
Why would adding methods to Array.prototype break code that uses with (such as the
widely deployed Ext JS 4.2.1²⁹)? Take a look at the following code. The existence of a property
Array.prototype.values breaks foo(), if you call it with an Array:
function foo(values) {
with (values) {
console.log(values.length); // abc (*)
}
}
Array.prototype.values = { length: 'abc' };
foo([]);
Inside the with statement, all properties of values become local variables, shadowing even
values itself. Therefore, if values has a property values then the statement in line * logs
values.values.length and not values.length.
• Array.prototype[Symbol.unscopables]
– Holds an object with the following properties (which are therefore hidden from the
with statement): copyWithin, entries, fill, find, findIndex, keys, values
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
> cp.toString();
'(25, 8) in green'
Under the hood, ES6 classes are not something that is radically new: They mainly provide more
convenient syntax to create old-school constructor functions. You can see that if you use typeof:
Classes 227
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
However, you can only invoke a class via new, not via a function call (the rationale behind this
is explained later):
> Point()
TypeError: Classes can’t be function-called
There is no separating punctuation between the members of a class definition. For example, the
members of an object literal are separated by commas, which are illegal at the top levels of class
definitions. Semicolons are allowed, but ignored:
¹http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
Classes 228
class MyClass {
foo() {}
; // OK, ignored
, // SyntaxError
bar() {}
}
Semicolons are allowed in preparation for future syntax which may include semicolon-termi-
nated members. Commas are forbidden to emphasize that class definitions are different from
object literals.
Function declarations are hoisted: When entering a scope, the functions that are declared in it
are immediately available – independently of where the declarations happen. That means that
you can call a function that is declared later:
function foo() {}
In contrast, class declarations are not hoisted. Therefore, a class only exists after execution
reached its definition and it was evaluated. Accessing it beforehand leads to a ReferenceError:
class Foo {}
The reason for this limitation is that classes can have an extends clause whose value is an
arbitrary expression. That expression must be evaluated in the proper “location”, its evaluation
can’t be hoisted.
Not having hoisting is less limiting than you may think. For example, a function that comes
before a class declaration can still refer to that class, but you have to wait until the class
declaration has been evaluated before you can call the function.
function functionThatUsesBar() {
new Bar();
}
functionThatUsesBar(); // ReferenceError
class Bar {}
functionThatUsesBar(); // OK
Similarly to functions, there are two kinds of class definitions, two ways to define a class: class
declarations and class expressions.
Similarly to function expressions, class expressions can be anonymous:
Classes 229
Also similarly to function expressions, class expressions can have names that are only visible
inside them:
console.log(inst.getClassName()); // Me
console.log(Me.name); // ReferenceError: Me is not defined
The last two lines demonstrate that Me does not become a variable outside of the class, but can
be used inside it.
Let’s examine three kinds of methods that you often find in class definitions.
class Foo {
constructor(prop) {
this.prop = prop;
}
static staticMethod() {
return 'classy';
}
prototypeMethod() {
return 'prototypical';
}
}
const foo = new Foo(123);
Classes 230
The object diagram for this class declaration looks as follows. Tip for understanding it:
[[Prototype]] is an inheritance relationship between objects, while prototype is a normal
property whose value is an object. The property prototype is only special w.r.t. the new operator
using its value as the prototype for instances it creates.
First, the pseudo-method constructor. This method is special, as it defines the function that
represents the class:
It is sometimes called a class constructor. It has features that normal constructor functions
don’t have (mainly the ability to constructor-call its superconstructor via super(), which is
explained later).
Second, static methods. Static properties (or class properties) are properties of Foo itself. If you
prefix a method definition with static, you create a class method:
Third, prototype methods. The prototype properties of Foo are the properties of Foo.prototype.
They are usually methods and inherited by instances of Foo.
Classes 231
For the sake of finishing ES6 classes in time, they were deliberately designed to be “maximally
minimal”. That’s why you can currently only create static methods, getters, and setters, but not
static data properties. There is a proposal for adding them to the language. Until that proposal is
accepted, there are two work-arounds that you can use.
First, you can manually add a static property:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
Point.ZERO = new Point(0, 0);
You could use Object.defineProperty() to create a read-only property, but I like the simplicity
of an assignment.
Second, you can create a static getter:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static get ZERO() {
return new Point(0, 0);
}
}
In both cases, you get a property Point.ZERO that you can read. In the first case, the same instance
is returned every time. In the second case, a new instance is returned every time.
The syntax for getters and setters is just like in ECMAScript 5 object literals²:
²http://speakingjs.com/es5/ch17.html#getters_setters
Classes 232
class MyClass {
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
You can define the name of a method via an expression, if you put it in square brackets. For
example, the following ways of defining Foo are all equivalent.
class Foo {
myMethod() {}
}
class Foo {
['my'+'Method']() {}
}
const m = 'myMethod';
class Foo {
[m]() {}
}
Several special methods in ECMAScript 6 have keys that are symbols. Computed method names
allow you to define such methods. For example, if an object has a method whose key is
Symbol.iterator, it is iterable. That means that its contents can be iterated over by the for-
of loop and other language mechanisms.
Classes 233
class IterableClass {
[Symbol.iterator]() {
···
}
}
If you prefix a method definition with an asterisk (*), it becomes a generator method. Among
other things, a generator is useful for defining the method whose key is Symbol.iterator. The
following code demonstrates how that works.
class IterableArguments {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (const arg of this.args) {
yield arg;
}
}
}
// Output:
// hello
// world
15.2.3 Subclassing
The extends clause lets you create a subclass of an existing constructor (which may or may not
have been defined via a class):
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
Classes 234
class Foo {
static classMethod() {
return 'hello';
}
}
class Foo {
static classMethod() {
return 'hello';
}
}
In a derived class, you must call super() before you can use this:
class Foo {}
Implicitly leaving a derived constructor without calling super() also causes an error:
Classes 236
class Foo {}
Just like in ES5, you can override the result of a constructor by explicitly returning an object:
class Foo {
constructor() {
return Object.create(null);
}
}
console.log(new Foo() instanceof Foo); // false
If you do so, it doesn’t matter whether this has been initialized or not. In other words: you don’t
have to call super() in a derived constructor if you override the result in this manner.
If you don’t specify a constructor for a base class, the following definition is used:
constructor() {}
constructor(...args) {
super(...args);
}
In ECMAScript 6, you can finally subclass all built-in constructors (there are work-arounds for
ES5³, but these have significant limitations).
For example, you can now create your own exception classes (that will inherit the feature of
having a stack trace in most engines):
³http://speakingjs.com/es5/ch28.html
Classes 237
You can also create subclasses of Array whose instances properly handle length:
Note that subclassing Array is usually not the best solution. It’s often better to create your own
class (whose interface you control) and to delegate to an Array in a private property.
Approaches #1 and #2 were already common in ES5, for constructors. Approaches #3 and #4 are
new in ES6. Let’s implement the same example four times, via each of the approaches.
class Countdown {
constructor(counter, action) {
Object.assign(this, {
dec() {
if (counter < 1) return;
counter--;
if (counter === 0) {
action();
}
}
});
}
}
Pros:
Cons:
• The code becomes less elegant, because you need to add all methods to the instance, inside
the constructor (at least those methods that need access to the private data).
• Due to the instance methods, the code wastes memory. If the methods were prototype
methods, they would be shared.
More information on this technique: Sect. “Private Data in the Environment of a Constructor
(Crockford Privacy Pattern)⁴” in “Speaking JavaScript”.
⁴http://speakingjs.com/es5/ch17.html#private_data_constructor_environment
Classes 239
class Countdown {
constructor(counter, action) {
this._counter = counter;
this._action = action;
}
dec() {
if (this._counter < 1) return;
this._counter--;
if (this._counter === 0) {
this._action();
}
}
}
Pros:
Cons:
_action.get(this)();
}
}
}
Each of the two WeakMaps _counter and _action maps objects to their private data. Due to
how WeakMaps work that won’t prevent objects from being garbage-collected. As long as you
keep the WeakMaps hidden from the outside world, the private data is safe.
If you want to be even safer, you can store WeakMap.prototype.get and WeakMap.prototype.set
in variables and invoke those (instead of the methods, dynamically):
Then your code won’t be affected if malicious code replaces those methods with ones that snoop
on our private data. However, you are only protected against code that runs after your code.
There is nothing you can do if it runs before yours.
Pros:
Con:
class Countdown {
constructor(counter, action) {
this[_counter] = counter;
this[_action] = action;
}
dec() {
if (this[_counter] < 1) return;
this[_counter]--;
if (this[_counter] === 0) {
this[_action]();
}
}
}
Each symbol is unique, which is why a symbol-valued property key will never clash with any
other property key. Additionally, symbols are somewhat hidden from the outside world, but not
completely:
console.log(Object.keys(c));
// []
console.log(Reflect.ownKeys(c));
// [ Symbol(counter), Symbol(action) ]
Pros:
Cons:
• Interface inheritance: Every object that is an instance of a subclass (as tested by in-
stanceof) is also an instance of the superclass. The expectation is that subclass instances
behave like superclass instances, but may do more.
• Implementation inheritance: Superclasses pass on functionality to their subclasses.
The usefulness of classes for implementation inheritance is limited, because they only support
single inheritance (a class can have at most one superclass). Therefore, it is impossible to inherit
tool methods from multiple sources – they must all come from the superclass.
So how can we solve this problem? Let’s explore a solution via an example. Consider a
management system for an enterprise where Employee is a subclass of Person.
Additionally, there are tool classes for storage and for data validation:
class Storage {
save(database) { ··· }
}
class Validation {
validate(schema) { ··· }
}
That is, we want Employee to be a subclass of Storage which should be a subclass of Validation
which should be a subclass of Person. Employee and Person will only be used in one such chain of
classes. But Storage and Validation will be used multiple times. We want them to be templates
for classes whose superclasses we fill in. Such templates are called abstract subclasses or mixins.
One way of implementing a mixin in ES6 is to view it as a function whose input is a superclass
and whose output is a subclass extending that superclass:
Classes 243
Here, we profit from the operand of the extends clause not being a fixed identifier, but an
arbitrary expression. With these mixins, Employee is created like this:
Acknowledgement. The first occurrence of this technique that I’m aware of is a Gist by
Sebastian Markbåge⁶.
ClassDeclaration:
"class" BindingIdentifier ClassTail
ClassExpression:
"class" BindingIdentifier? ClassTail
ClassTail:
ClassHeritage? "{" ClassBody? "}"
ClassHeritage:
"extends" AssignmentExpression
ClassBody:
ClassElement+
ClassElement:
MethodDefinition
"static" MethodDefinition
";"
MethodDefinition:
PropName "(" FormalParams ")" "{" FuncBody "}"
"*" PropName "(" FormalParams ")" "{" GeneratorBody "}"
"get" PropName "(" ")" "{" FuncBody "}"
"set" PropName "(" PropSetParams ")" "{" FuncBody "}"
⁶https://gist.github.com/sebmarkbage/fac0830dbb13ccbff596
⁷http://www.ecma-international.org/ecma-262/6.0/#sec-functions-and-classes
Classes 244
PropertyName:
LiteralPropertyName
ComputedPropertyName
LiteralPropertyName:
IdentifierName /* foo */
StringLiteral /* "foo" */
NumericLiteral /* 123.45, 0xFF */
ComputedPropertyName:
"[" Expression "]"
Two observations:
• The value to be extended can be produced by an arbitrary expression. Which means that
you’ll be able to write code such as the following:
class C {
m() {}
}
new C.prototype.m(); // TypeError
Notes:
Classes 245
The properties shown in the table are created in Sect. “Runtime Semantics: ClassDefi-
nitionEvaluation⁸” in the spec.
You may know that named function expressions have lexical inner names:
The name me of the named function expression becomes a lexically bound variable that is
unaffected by which variable currently holds the function.
Interestingly, ES6 classes also have lexical inner names that you can use in methods (constructor
methods and regular methods):
⁸http://www.ecma-international.org/ecma-262/6.0/#sec-runtime-semantics-classdefinitionevaluation
Classes 246
class C {
constructor() {
// Use inner name C to refer to class
console.log(`constructor: ${C.prop}`);
}
logProp() {
// Use inner name C to refer to class
console.log(`logProp: ${C.prop}`);
}
}
C.prop = 'Hi!';
const D = C;
C = null;
(In the ES6 spec the inner name is set up by the dynamic semantics of ClassDefinitionEvalua-
tion⁹.)
Acknowledgement: Thanks to Michael Ficarra for pointing out that classes have inner names.
class Person {
constructor(name) {
this.name = name;
}
toString() {
return `Person named ${this.name}`;
}
static logNames(persons) {
for (const person of persons) {
console.log(person.name);
⁹http://www.ecma-international.org/ecma-262/6.0/#sec-runtime-semantics-classdefinitionevaluation
Classes 247
}
}
}
The next section examines the structure of the objects that were created by the previous example.
The section after that examines how jane is allocated and initialized.
Prototype chains are objects linked via the [[Prototype]] relationship (which is an inheritance
relationship). In the diagram, you can see two prototype chains:
The prototype of a derived class is the class it extends. The reason for this setup is that you want
a subclass to inherit all properties of its superclass:
Classes 248
The prototype of a base class is Function.prototype, which is also the prototype of functions:
That means that base classes and all their derived classes (their prototypees) are functions.
Traditional ES5 functions are essentially base classes.
The main purpose of a class is to set up this prototype chain. The prototype chain ends with
Object.prototype (whose prototype is null). That makes Object an implicit superclass of every
base class (as far as instances and the instanceof operator are concerned).
The reason for this setup is that you want the instance prototype of a subclass to inherit all
properties of the superclass instance prototype.
As an aside, objects created via object literals also have the prototype Object.prototype:
this.name = name;
}
···
this.title = title;
}
Object.setPrototypeOf(Employee, Person);
···
• In ES6, it is created in the base constructor, the last in a chain of constructor calls. The
superconstructor is invoked via super(), which triggers a constructor call.
• In ES5, it is created in the operand of new, the first in a chain of constructor calls. The
superconstructor is invoked via a function call.
• new.target is an implicit parameter that all functions have. In a chain of constructor calls,
its role is similar to this in a chain of supermethod calls.
– If a constructor is directly invoked via new (as in line B), the value of new.target is
that constructor.
– If a constructor is called via super() (as in line A), the value of new.target is the
new.target of the constructor that makes the call.
– During a normal function call, it is undefined. That means that you can use
new.target to determine whether a function was function-called or constructor-
called (via new).
– Inside an arrow function, new.target refers to the new.target of the surrounding
non-arrow function.
• Reflect.construct() lets you make constructor calls while specifying new.target via the
last parameter.
The advantage of this way of subclassing is that it enables normal code to subclass built-in
constructors (such as Error and Array). A later section explains why a different approach was
necessary.
As a reminder, here is how you do subclassing in ES5:
Classes 250
function Person(name) {
this.name = name;
}
···
• this originally being uninitialized in derived constructors means that an error is thrown
if they access this in any way before they have called super().
• Once this is initialized, calling super() produces a ReferenceError. This protects you
against calling super() twice.
• If a constructor returns implicitly (without a return statement), the result is this. If this
is uninitialized, a ReferenceError is thrown. This protects you against forgetting to call
super().
• If a constructor explicitly returns a non-object (including undefined and null), the result
is this (this behavior is required to remain compatible with ES5 and earlier). If this is
uninitialized, a TypeError is thrown.
• If a constructor explicitly returns an object, it is used as its result. Then it doesn’t matter
whether this is initialized or not.
Let’s examine how the extends clause influences how a class is set up (Sect. 14.5.14 of the spec¹⁰).
The value of an extends clause must be “constructible” (invocable via new). null is allowed,
though.
class C {
}
¹⁰http://www.ecma-international.org/ecma-262/6.0/#sec-runtime-semantics-classdefinitionevaluation
Classes 251
class C extends B {
}
Note the following subtle difference with the first case: If there is no extends clause, the class
is a base class and allocates instances. If a class extends Object, it is a derived class and Object
allocates the instances. The resulting instances (including their prototype chains) are the same,
but you get there differently.
function MyArray(len) {
Array.call(this, len); // (A)
}
MyArray.prototype = Object.create(Array.prototype);
Unfortunately, if we instantiate MyArray, we find out that it doesn’t work properly: The instance
property length does not change in reaction to us adding Array elements:
¹¹http://speakingjs.com/es5/ch28.html
Classes 252
There are two obstracles that prevent myArr from being a proper Array.
First obstacle: initialization. The this you hand to the constructor Array (in line A) is
completely ignored. That means you can’t use Array to set up the instance that was created
for MyArray.
Second obstacle: allocation. The instance objects created by Array are exotic (a term used by the
ECMAScript specification for objects that have features that normal objects don’t have): Their
property length tracks and influences the management of Array elements. In general, exotic
objects can be created from scratch, but you can’t convert an existing normal object into an
exotic one. Unfortunately, that is what Array would have to do, when called in line A: It would
have to turn the normal object created for MyArray into an exotic Array object.
This works:
Classes 253
Let’s examine how the ES6 approach to subclassing removes the previously mentioned obstacles:
• The first obstacle, Array not being able to set up an instance, is removed by Array returning
a fully configured instance. In contrast to ES5, this instance has the prototype of the
subclass.
• The second obstacle, subconstructors not creating exotic instances, is removed by derived
classes relying on base classes for allocating instances.
class Person {
constructor(name) {
this.name = name;
}
toString() { // (A)
return `Person named ${this.name}`;
}
}
To understand how super-calls work, let’s look at the object diagram of jane:
Classes 254
1. Start your search in the prototype of the home object of the current method.
2. Look for a method whose name is toString. That method may be found in the object
where the search started or later in the prototype chain.
3. Call that method with the current this. The reason for doing so is: the super-called method
must be able to access the same instance properties (in our example, the own properties of
jane).
Note that even if you are only getting (super.prop) or setting (super.prop = 123) a superprop-
erty (versus making a method call), this may still (internally) play a role in step #3, because a
getter or a setter may be invoked.
Let’s express these steps in three different – but equivalent – ways:
Classes 255
// Variation 3: ES6
var homeObject = Employee.prototype;
var superObject = Object.getPrototypeOf(homeObject); // step 1
var superMethod = superObject.toString; // step 2
var result = superMethod.call(this) // step 3
Variation 3 is how ECMAScript 6 handles super-calls. This approach is supported by two internal
bindings¹² that the environments of functions have (environments provide storage space, so-
called bindings, for the variables in a scope):
• [[thisValue]]: This internal binding also exists in ECMAScript 5 and stores the value of
this.
• [[HomeObject]]: Refers to the home object of the environment’s function. Filled in via the
internal slot [[HomeObject]] that all methods have that use super. Both the binding and
the slot are new in ECMAScript 6.
Referring to superproperties is handy whenever prototype chains are involved, which is why you
can use it in method definitions (incl. generator method definitions, getters and setters) inside
object literals and class definitions. The class can be derived or not, the method can be static or
not.
Using super to refer to a property is not allowed in function declarations, function expressions
and generator functions.
¹²http://www.ecma-international.org/ecma-262/6.0/#sec-function-environment-records
Classes 256
You can’t move a method that uses super: Such a method has the internal slot [[HomeObject]]
that ties it to the object it was created in. If you move it via an assignment, it will continue to
refer to the superproperties of the original object. In future ECMAScript versions, there may be
a way to transfer such a method, too.
function isObject(value) {
return (value !== null
&& (typeof value === 'object'
|| typeof value === 'function'));
}
/**
* Spec-internal operation that determines whether `x`
* can be used as a constructor.
*/
function isConstructor(x) {
···
}
The standard species pattern is implemented in the spec via the operation
SpeciesConstructor()¹³.
¹³http://www.ecma-international.org/ecma-262/6.0/#sec-speciesconstructor
Classes 258
The species pattern for Arrays is implemented in the spec via the operation
ArraySpeciesCreate()¹⁴.
This default getter is implemented by the built-in classes Array, ArrayBuffer, Map, Promise,
RegExp, Set and %TypedArray%. It is automatically inherited by subclasses of these built-in classes.
There are two ways in which you can override the default species: with a constructor of your
choosing or with null.
You can override the default species via a static getter (line A):
¹⁴http://www.ecma-international.org/ecma-262/6.0/#sec-arrayspeciescreate
¹⁵http://www.ecma-international.org/ecma-262/6.0/#sec-promise.all
Classes 259
If you don’t override the default species, map() returns an instance of the subclass:
If you don’t want to use a static getter, you need to use Object.defineProperty(). You can’t
use assignment, as there is already a property with that key that only has a getter. That means
that it is read-only and can’t be assigned to.
For example, here we set the species of MyArray1 to Array:
Object.defineProperty(
MyArray1, Symbol.species, {
value: Array
});
If you set the species to null then the default constructor is used (which one that is depends on
which variant of the species pattern is used, consult the previous sections for more information).
Let’s look at a few common complaints about ES6 classes. You will see me agree with most of
them, but I also think that they benefits of classes much outweigh their disadvantages. I’m glad
that they are in ES6 and I recommend to use them.
Then a class becomes an instantiable entity and a location where you assemble traits. Until that
happens, you will need to resort to libraries if you want multiple inheritance.
• You can override the default result returned by the new operator, by returning an object
from the constructor method of a class.
• Due to its built-in modules and classes, ES6 makes it easier for IDEs to refactor code.
Therefore, going from new to a function call will be simple. Obviously that doesn’t help
you if you don’t control the code that calls your code, as is the case for libraries.
Therefore, classes do somewhat limit you syntactically, but, once JavaScript has traits, they won’t
limit you conceptually (w.r.t. object-oriented design).
¹⁸https://github.com/rwaldron/tc39-notes/blob/master/es6/2015-01/jan2015-allen-slides.pdf
16. Modules
16.1 Overview
JavaScript has had modules for a long time. However, they were implemented via libraries, not
built into the language. ES6 is the first time that JavaScript has built-in modules.
ES6 modules are stored in files. There is exactly one module per file and one file per module.
You have two ways of exporting things from a module. These two ways can be mixed, but it is
usually better to use them separately.
Or a class:
Note that there is no semicolon at the end if you default-export a function or a class (which are
anonymous declarations).
Scripts Modules
HTML element <script> <script type="module">
Default mode non-strict strict
Top-level variables are global local to module
Value of this at top level window undefined
Executed synchronously asynchronously
Declarative imports (import statement) no yes
Programmatic imports (Promise-based API) yes yes
File extension .js .js
This approach to modules avoids global variables, the only things that are global are module
specifiers.
The above is but a simplified explanation of ES5 modules. If you want more in-depth material,
take a look at “Writing Modular JavaScript With AMD, CommonJS & ES Harmony³” by Addy
Osmani.
• Similarly to CommonJS, they have a compact syntax, a preference for single exports and
support for cyclic dependencies.
• Similarly to AMD, they have direct support for asynchronous loading and configurable
module loading.
Being built into the language allows ES6 modules to go beyond CommonJS and AMD (details
are explained later):
¹http://nodejs.org/api/modules.html
²http://requirejs.org/
³http://addyosmani.com/writing-modular-js/
Modules 266
There are other ways to specify named exports (which are explained later), but I find this one
quite convenient: simply write your code as if there were no outside world, then label everything
that you want to export with a keyword.
If you want to, you can also import the whole module and refer to its named exports via property
notation:
Modules 267
The same code in CommonJS syntax: For a while, I tried several clever strategies to be less
redundant with my module exports in Node.js. Now I prefer the following simple but slightly
verbose style that is reminiscent of the revealing module pattern⁴:
⁴http://christianheilmann.com/2007/08/22/again-with-the-module-pattern-reveal-something-to-the-world/
Modules 268
1. Labeling declarations
2. Default-exporting values directly
You can prefix any function declaration (or generator function declaration) or class declaration
with the keywords export default to make it the default export:
You can also omit the name in this case. That makes default exports the only place where
JavaScript has anonymous function declarations and anonymous class declarations:
When you look at the previous two lines of code, you’d expect the operands of export default
to be expressions. They are only declarations for reasons of consistency: operands can be named
declarations, interpreting their anonymous versions as expressions would be confusing (even
more so than introducing new kinds of declarations).
If you want the operands to be interpreted as expressions, you need to use parentheses:
Modules 269
The second default export style was introduced because variable declarations can’t be meaning-
fully turned into default exports if they declare multiple variables:
Which one of the three variables foo, bar and baz would be the default export?
if (Math.random()) {
import 'foo'; // SyntaxError
}
foo();
• They enable cyclic dependencies, even for unqualified imports (as explained in the next
section).
Modules 271
• Qualified and unqualified imports work the same way (they are both indirections).
• You can split code into multiple modules and it will continue to work (as long as you don’t
try to change the values of imports).
The following CommonJS code correctly handles two modules a and b cyclically depending on
each other.
If module a is imported first then, in line i, module b gets a’s exports object before the exports
are added to it. Therefore, b cannot access a.foo in its top level, but that property exists once the
execution of a is finished. If bar() is called afterwards then the method call in line ii works.
As a general rule, keep in mind that with cyclic dependencies, you can’t access imports in the
body of the module. That is inherent to the phenomenon and doesn’t change with ECMAScript
6 modules.
The limitations of the CommonJS approach are:
• Node.js-style single-value exports don’t work. There, you export single values instead of
objects:
⁵http://en.wikipedia.org/wiki/Circular_dependency
Modules 272
If module a did that then module b’s variable a would not be updated once the assignment
is made. It would continue to refer to the original exports object.
• You can’t use named exports directly. That is, module b can’t import foo like this:
foo would simply be undefined. In other words, you have no choice but to refer to foo
via a.foo.
These limitations mean that both exporter and importers must be aware of cyclic dependencies
and support them explicitly.
ES6 modules support cyclic dependencies automatically. That is, they do not have the two
limitations of CommonJS modules that were mentioned in the previous section: default exports
work, as do unqualified named imports (lines i and iii in the following example). Therefore, you
can implement modules that cyclically depend on each other as follows.
This code works, because, as explained in the previous section, imports are views on exports. That
means that even unqualified imports (such as bar in line ii and foo in line iv) are indirections
that refer to the original data. Thus, in the face of cyclic dependencies, it doesn’t matter whether
you access a named export via an unqualified import or via its module: There is an indirection
involved in either case and it always works.
Modules 273
• Default import:
• Namespace import: imports the module as an object (with one property per named export).
• Named imports:
• Empty import: only loads the module, doesn’t import anything. The first such import in a
program executes the body of the module.
import 'src/my_lib';
There are only two ways to combine these styles and the order in which they appear is fixed;
the default export always comes first.
On the other hand, you can list everything you want to export at the end of the module (which
is similar in style to the revealing module pattern).
16.4.3 Re-exporting
Re-exporting means adding another module’s exports to those of the current module. You can
either add all of the other module’s exports:
⁸[Spec] The specification method GetExportedNames() collects the exports of a module. In step (7.d.i), a check prevents other modules’
default exports from being re-exported.
Modules 275
The following statement makes the default export of another module foo the default export of
the current module:
The following statement makes the named export myFunc of module foo the default export of
the current module:
• Re-exporting:
– Re-export everything (except for the default export):
export * from 'src/other_module';
– Re-export via a clause:
export { foo as myFoo, bar } from 'src/other_module';
⁹[Spec] Sect. “Exports” starts with grammar rules and continues with semantics.
Modules 276
With ES6 glasses, the function _ is the default export, while each and forEach are named exports.
As it turns out, you can actually have named exports and a default export at the same time. As
an example, the previous CommonJS module, rewritten as an ES6 module, looks like this:
Modules 277
Note that the CommonJS version and the ECMAScript 6 version are only roughly similar. The
latter has a flat structure, whereas the former is nested.
I generally recommend to keep the two kinds of exporting separate: per module, either only have
a default export or only have named exports.
However, that is not a very strong recommendation; it occasionally may make sense to mix the
two kinds. One example is a module that default-exports an entity. For unit tests, one could
additionally make some of the internals available via named exports.
The default export is actually just a named export with the special name default. That is, the
following two statements are equivalent:
Similarly, the following two modules have the same default export:
You can’t use reserved words (such as default and new) as variable names, but you can use them
as names for exports (you can also use them as property names in ECMAScript 5). If you want
to directly import such named exports, you have to rename them to proper variables names.
That means that default can only appear on the left-hand side of a renaming import:
Modules 278
¹⁰https://github.com/whatwg/loader/
¹¹https://github.com/whatwg/loader/
¹²https://github.com/ModuleLoader/es6-module-loader
Modules 279
16.5.1 Loaders
Loaders handle resolving module specifiers (the string IDs at the end of import-from), loading
modules, etc. Their constructor is Reflect.Loader. Each platform keeps a default instance in
the global variable System (the system loader), which implements its specific style of module
loading.
System.import('some_module')
.then(some_module => {
// Use some_module
})
.catch(error => {
···
});
• Use modules inside <script> elements (where module syntax is not supported, consult the
section on modules versus scripts for details).
• Load modules conditionally.
System.import() retrieves a single module, you can use Promise.all() to import several
modules:
Promise.all(
['module1', 'module2', 'module3']
.map(x => System.import(x)))
.then(([module1, module2, module3]) => {
// Use module1, module2, module3
});
• System.module(source, options?)
evaluates the JavaScript code in source to a module (which is delivered asynchronously
via a Promise).
• System.set(name, module)
is for registering a module (e.g. one you have created via System.module()).
• System.define(name, source, options?)
both evaluates the module code in source and registers the result.
Modules 280
Configurable module loading is an area where Node.js and CommonJS are limited.
Scripts Modules
HTML element <script> <script type="module">
Default mode non-strict strict
Top-level variables are global local to module
Value of this at top level window undefined
Executed synchronously asynchronously
Declarative imports (import statement) no yes
Programmatic imports (Promise-based API) yes yes
File extension .js .js
Modules 281
16.6.1.1 Scripts
Scripts are the traditional browser way to embed JavaScript and to refer to external JavaScript
files. Scripts have an internet media type¹³ that is used as:
• text/javascript: is a legacy value and used as the default if you omit the type attribute
in a script tag. It is the safest choice¹⁴ for Internet Explorer 8 and earlier.
• application/javascript: is recommended¹⁵ for current browsers.
Scripts are normally loaded or executed synchronously. The JavaScript thread stops until the
code has been loaded or executed.
16.6.1.2 Modules
To be in line with JavaScript’s usual run-to-completion semantics, the body of a module must be
executed without interruption. That leaves two options for importing modules:
1. Load modules synchronously, while the body is executed. That is what Node.js does.
2. Load all modules asynchronously, before the body is executed. That is how AMD modules
are handled. It is the best option for browsers, because modules are loaded over the internet
and execution doesn’t have to pause while they are. As an added benefit, this approach
allows one to load multiple modules in parallel.
ECMAScript 6 gives you the best of both worlds: The synchronous syntax of Node.js plus the
asynchronous loading of AMD. To make both possible, ES6 modules are syntactically less flexible
than Node.js modules: Imports and exports must happen at the top level. That means that they
can’t be conditional, either. This restriction allows an ES6 module loader to analyze statically
what modules are imported by a module and load them before executing its body.
The synchronous nature of scripts prevents them from becoming modules. Scripts cannot even
import modules declaratively (you have to use the programmatic module loader API if you want
to do so).
Modules can be used from browsers via a new variant of the <script> element that is completely
asynchronous:
¹³http://en.wikipedia.org/wiki/Internet_media_type
¹⁴http://stackoverflow.com/questions/359895/what-are-the-most-likely-causes-of-javascript-errors-in-ie8/703590#703590
¹⁵http://tools.ietf.org/html/rfc4329#section-7
Modules 282
<script type="module">
import $ from 'lib/jquery';
var x = 123;
As you can see, the element has its own scope and variables “inside” it are local to that scope.
Note that module code is implicitly in strict mode. This is great news – no more 'use strict'.
Similar to normal <script> elements, <script type="module"> can also be used to load external
modules. For example, the following tag starts a web application via a main module (the attribute
name import is my invention, it isn’t yet clear what name will be used).
The advantage of supporting modules in HTML via a custom <script> type is that it is easy to
bring that support to older engines via a polyfill (a library). There may or may not eventually be
a dedicated element for modules (e.g. <module>).
Whether a file is a module or a script is only determined by how it is imported or loaded. Most
modules have either imports or exports and can thus be detected. But if a module has neither
then it is indistinguishable from a script. For example:
var x = 123;
The semantics of this piece of code differs depending on whether it is interpreted as a module or
as a script:
More realistic example is a module that installs something, e.g. a polyfill in global variables or
a global event listener. Such a module neither imports nor exports anything and is activated via
an empty import:
Modules 283
import './my_module';
¹⁶https://github.com/rwaldron/tc39-notes/blob/master/es6/2013-09/modules.pdf
¹⁷https://mail.mozilla.org/pipermail/es-discuss/2013-November/034869.html
¹⁸https://github.com/rauschma/imports-are-views-demo
Modules 284
If you access the value via the exports object, it is still copied once, on export:
¹⁹http://speakingjs.com/es5/ch17.html#freezing_objects
Modules 285
If you import the module object via the asterisk (*), you get the same results:
Note that while you can’t change the values of imports, you can change the objects that they are
referring to. For example:
obj.prop = 123; // OK
obj = {}; // TypeError
Why introduce such a relatively complicated mechanism for importing that deviates from
established practices?
Modules 286
• Cyclic dependencies: The main advantage is that it supports cyclic dependencies even for
unqualified imports.
• Qualified and unqualified imports work the same. In CommonJS, they don’t: a qualified
import provides direct access to a property of a module’s export object, an unqualified
import is a copy of it.
• You can split code into multiple modules and it will continue to work (as long as you don’t
try to change the values of imports).
• On the flip side, module folding, combining multiple modules into a single module becomes
simpler, too.
In my experience, ES6 imports just work, you rarely have to think about what’s going on under
the hood.
• Local name: is the name under which the export is stored inside the module.
• Export name: is the name that importing modules need to use to access the export.
After you have imported an entity, that entity is always accessed via a pointer that has the two
components module and local name. In other words, that pointer refers to a binding (the storage
space of a variable) inside a module.
Let’s examine the export names and local names created by various kinds of exporting. The
following table (adapted from the ES6 spec²⁰) gives an overview, subsequent sections have more
details.
Statement Local name Export name
export {v}; 'v' 'v'
export {v as x}; 'v' 'x'
export const v = 123; 'v' 'v'
export function f() {} 'f' 'f'
export default function f() {} 'f' 'default'
export default function () {} '*default*' 'default'
export default 123; '*default*' 'default'
²⁰http://www.ecma-international.org/ecma-262/6.0/#table-42
Modules 287
function foo() {}
export { foo };
function foo() {}
export { foo as bar };
function foo() {}
export { foo };
It is equivalent to:
The local name was chosen so that it wouldn’t clash with any other local name.
Note that a default export still leads to a binding being created. But, due to *default* not being
a legal identifier, you can’t access that binding from inside the module.
It is equivalent to:
function foo() {}
export { foo as default };
That means that you can change the value of the default export from within the module, by
assigning a different value to foo.
(Only) for default exports, you can also omit the name of a function declaration:
The export names and local names created by the various kinds of exports are shown in table 42²⁶
in the section “Source Text Module Records²⁷”. The section “Static Semantics: ExportEntries²⁸”
has more details. You can see that export entries are set up statically (before evaluating
the module), evaluating export statements is described in the section “Runtime Semantics:
Evaluation²⁹”.
ECMAScript 6 favors the single/default export style, and gives the sweetest syntax
to importing the default. Importing named exports can and even should be slightly
less concise.
var my_lib;
if (Math.random()) {
my_lib = require('foo');
} else {
my_lib = require('bar');
}
In the second example, you have to run the code to find out what it exports:
if (Math.random()) {
exports.baz = ···;
}
ECMAScript 6 modules are less flexible and force you to be static. As a result, you get several
benefits, which are described next.
³⁰http://esdiscuss.org/topic/moduleimport#content-0
Modules 291
Reason #1 is important for HTTP/1, where the cost for requesting a file is relatively high. That
will change with HTTP/2, which is why this reason doesn’t matter there.
Reason #3 will remain compelling. It can only be achieved with a module format that has a static
structure.
The module bundler Rollup³¹ proved that ES6 modules can be combined efficiently, because they
all fit into a single scope (after renaming variables to eliminate name clashes). This is possible
due to two characteristics of ES6 modules:
• Their static structure means that the bundle format does not have to account for con-
ditionally loaded modules (a common technique for doing so is putting module code in
functions).
• Imports being read-only views on exports means that you don’t have to copy exports, you
can refer to them directly.
// lib.js
export function foo() {}
export function bar() {}
// main.js
import {foo} from './lib.js';
console.log(foo());
Rollup can bundle these two ES6 modules into the following single ES6 module (note the
eliminated unused export bar):
³¹https://github.com/rollup/rollup
Modules 292
function foo() {}
console.log(foo());
Another benefit of Rollup’s approach is that the bundle does not have a custom format, it is just
an ES6 module.
Thus, accessing a named export via lib.someFunc means you have to do a property lookup,
which is slow, because it is dynamic.
In contrast, if you import a library in ES6, you statically know its contents and can optimize
accesses:
With a static module structure, you always statically know which variables are visible at any
location inside the module:
• Global variables: increasingly, the only completely global variables will come from the
language proper. Everything else will come from modules (including functionality from
the standard library and the browser). That is, you statically know all global variables.
• Module imports: You statically know those, too.
• Module-local variables: can be determined by statically examining the module.
This helps tremendously with checking whether a given identifier has been spelled properly. This
kind of check is a popular feature of linters such as JSLint and JSHint; in ECMAScript 6, most of
it can be performed by JavaScript engines.
Additionally, any access of named imports (such as lib.foo) can also be checked statically.
Macros are still on the roadmap for JavaScript’s future. If a JavaScript engine supports macros,
you can add new syntax to it via a library. Sweet.js³² is an experimental macro system for
JavaScript. The following is an example from the Sweet.js website: a macro for classes.
³²http://sweetjs.org
Modules 293
For macros, a JavaScript engine performs a preprocessing step before compilation: If a sequence
of tokens in the token stream produced by the parser matches the pattern part of the macro, it is
replaced by tokens generated via the body of macro. The preprocessing step only works if you
are able to statically find macro definitions. Therefore, if you want to import macros via modules
then they must have a static structure.
Static type checking imposes constraints similar to macros: it can only be done if type definitions
can be found statically. Again, types can only be imported from modules if they have a static
structure.
Types are appealing because they enable statically typed fast dialects of JavaScript in which
performance-critical code can be written. One such dialect is Low-Level JavaScript³³ (LLJS).
If you want to support compiling languages with macros and static types to JavaScript then
JavaScript’s modules should have a static structure, for the reasons mentioned in the previous
two sections.
³³http://lljs.org
Modules 294
Data point: I once implemented a system like [ECMAScript 6 modules] for Firefox.
I got asked³⁷ for cyclic dependency support 3 weeks after shipping.
That system that Alex Fritze invented and I worked on is not perfect, and the syntax
isn’t very pretty. But it’s still getting used³⁸ 7 years later, so it must have gotten
something right.
³⁴http://calculist.org/blog/2012/06/29/static-module-resolution/
³⁵http://nodejs.org/api/modules.html#modules_cycles
³⁶https://mail.mozilla.org/pipermail/es-discuss/2014-July/038250.html
³⁷https://bugzilla.mozilla.org/show_bug.cgi?id=384168#c7
³⁸https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Using
Modules 295
if (Math.random()) {
System.import('some_module')
.then(some_module => {
// Use some_module
})
}
// Illegal syntax:
import foo from 'some_module'+SUFFIX;
// Illegal syntax:
import { foo: { bar } } from 'some_module';
ES6 modules will also – hopefully – end the fragmentation between the currently dominant
standards CommonJS and AMD. Having a single, native standard for modules means:
• No more UMD (Universal Module Definition³⁹): UMD is a name for patterns that enable
the same file to be used by several module systems (e.g. both CommonJS and AMD). Once
ES6 is the only module standard, UMD becomes obsolete.
• New browser APIs become modules instead of global variables or properties of navigator.
• No more objects-as-namespaces: Objects such as Math and JSON serve as namespaces for
functions in ECMAScript 5. In the future, such functionality can be provided via modules.
³⁹https://github.com/umdjs/umd
⁴⁰http://jsmodules.io/
⁴¹https://github.com/wycats/jsmodules
⁴²http://jsmodules.io/cjs.html
IV Collections
17. The for-of loop
17.1 Overview
for-of is a new loop in ES6 that replaces both for-in and forEach() and supports the new
iteration protocol.
Use it to loop over iterable objects (Arrays, strings, Maps, Sets, etc.; see Chap. “Iterables and
iterators”):
// Output:
// a
// b
// Output:
// a
Access both elements and their indices while looping over an Array (the square brackets before
of mean that we are using destructuring):
// Output:
// 0. a
// 1. b
Looping over the [key, value] entries in a Map (the square brackets before of mean that we are
using destructuring):
The for-of loop 299
// Output:
// false => no
// true => yes
// Output:
// a
// b
for-of goes through the items of iterable and assigns them, one at a time, to the loop variable
x, before it executes the body. The scope of x is the loop, it only exists inside it.
// Output:
// a
A let declaration works the same way as a const declaration (but the bindings are mutable).
It is instructive to see how things are different if you var-declare the iteration variable. Now all
arrow functions refer to the same binding of elem.
The for-of loop 301
Having one binding per iteration is very helpful whenever you create functions via a loop (e.g.
to add event listeners).
You also get per-iteration bindings in for loops (via let) and for-in loops (via const or let).
Details are explained in the chapter on variables.
let x;
for (x of ['a', 'b']) {
console.log(x);
}
Therefore, entries() gives you a way to treat iterated items differently, depending on their
position:
• Iterating:
– Array.prototype.entries()
– Array.prototype.keys()
– Array.prototype.values()
• Searching for elements:
– Array.prototype.find(predicate, thisArg?)
– Array.prototype.findIndex(predicate, thisArg?)
• Array.prototype.copyWithin(target, start, end=this.length)
• Array.prototype.fill(value, start=0, end=this.length)
• Array-like values¹, which have a property length and indexed elements. Examples include
the results of DOM operations such as document.getElementsByClassName().
• Iterable values, whose contents can be retrieved one element at a time. Strings and Arrays
are iterable, as are ECMAScript’s new data structures Map and Set.
¹http://speakingjs.com/es5/ch18.html#_pitfall_array_like_objects
New Array features 304
// map(), generically:
const names1 = Array.prototype.map.call(spans, s => s.textContent);
// Array.from():
const names2 = Array.from(spans, s => s.textContent);
Another use case for Array.from() is to convert an Array-like or iterable value to an instance of
a subclass of Array. For example, if you create a subclass MyArray of Array and want to convert
such an object to an instance of MyArray, you simply use MyArray.from(). The reason that that
works is because constructors inherit from each other in ECMAScript 6 (a super-constructor is
the prototype of its sub-constructors).
²http://speakingjs.com/es5/ch17.html#generic_method
New Array features 305
You can also combine this functionality with mapping, to get a map operation where you control
the result’s constructor:
The species pattern lets you configure what instances non-static built-in methods (such as
slice(), filter() and map()) return. It is explained in Sect. “The species pattern” in Chap.
“Classes”.
18.2.2 Array.of(...items)
Array.of(item_0, item_1, ···) creates an Array whose elements are item_0, item_1, etc.
If you want to turn several values into an Array, you should always use an Array literal, especially
since the Array constructor doesn’t work properly if there is a single value that is a number (more
information³ on this quirk):
But how are you supposed to turn values into an instance of a sub-constructor of Array then?
This is where Array.of() helps (remember that sub-constructors of Array inherit all of Array’s
methods, including of()).
³http://speakingjs.com/es5/ch18.html#array_constructor
New Array features 306
• Array.prototype.entries()
• Array.prototype.keys()
• Array.prototype.values()
The result of each of the aforementioned methods is a sequence of values, but they are not
returned as an Array; they are revealed one by one, via an iterator. Let’s look at an example. I’m
using Array.from() to put the iterators’ contents into Arrays:
I could also have used the spread operator (...) to convert iterators to Arrays:
You can combine entries() with ECMAScript 6’s for-of loop and destructuring to conveniently
iterate over [index, element] pairs:
New Array features 307
Array.prototype.findIndex(predicate, thisArg?)
Returns the index of the first element for which the callback predicate returns true. If there is
no such element, it returns -1. Example:
> [NaN].indexOf(NaN)
-1
With findIndex(), you can use Object.is() (explained in the chapter on OOP) and will have
no such problem:
You can also adopt a more general approach, by creating a helper function elemIs():
⁴http://speakingjs.com/es5/ch18.html#_searching_for_values_nondestructive
New Array features 308
18.3.3 Array.prototype.copyWithin()
The signature of this method is:
Array.prototype.copyWithin(target : number,
start : number, end = this.length) : This
It copies the elements whose indices are in the range [start,end) to index target and subsequent
indices. If the two index ranges overlap, care is taken that all source elements are copied before
they are overwritten.
Example:
18.3.4 Array.prototype.fill()
The signature of this method is:
Optionally, you can restrict where the filling starts and ends:
• 0 ≤ i < arr.length
• !(i in arr)
You’ll see lots of examples involving holes in this section. Should anything ever be unclear, you
can consult Sect. “Holes in Arrays⁵” in “Speaking JavaScript” for more information.
ES6 pretends that holes don’t exist (as much as it can while being backward-com-
patible). And so should you – especially if you consider that holes can also affect
performance negatively. Then you don’t have to burden your brain with the numerous
and inconsistent rules around holes.
> Array.from(['a',,'b'])
[ 'a', undefined, 'b' ]
> [,'a'].findIndex(x => x === undefined)
0
> [...[,'a'].entries()]
[ [ 0, undefined ], [ 1, 'a' ] ]
The idea is to steer people away from holes and to simplify long-term. Unfortunately that means
that things are even more inconsistent now.
⁵http://speakingjs.com/es5/ch18.html#array_holes
New Array features 310
18.4.2.1 Iteration
If we invoke next() twice, we get the hole at index 0 and the element 'a' at index 1. As you can
see, the former produces undefined:
> iter.next()
{ value: undefined, done: false }
> iter.next()
{ value: 'a', done: false }
Among others, two operations are based on the iteration protocol. Therefore, these operations
also treat holes as undefined elements.
First, the spread operator (...):
Note that the Array prototype methods (filter() etc.) do not use the iteration protocol.
18.4.2.2 Array.from()
If its argument is iterable, Array.from() uses iteration to convert it to an Array. Then it works
exactly like the spread operator:
New Array features 311
But Array.from() can also convert Array-like objects⁶ to Arrays. Then holes become undefined,
too:
• copyWithin() creates holes when copying holes (i.e., it deletes elements if necessary).
• entries(), keys(), values() treat each hole as if it was the element undefined.
• find() and findIndex() do the same.
• fill() doesn’t care whether there are elements at indices or not.
Notes:
Array.prototype.fill() replaces all Array elements (incl. holes) with a fixed value:
New Array features 313
new Array(3) creates an Array with three holes and fill() replaces each hole with the value 7.
Array.prototype.keys() reports keys even if an Array only has holes. It returns an iterable,
which you can convert to an Array via the spread operator:
The mapping function in the second parameter of Array.from() is notified of holes. Therefore,
you can use Array.from() for more sophisticated filling:
If you need an Array that is filled with undefined, you can use the fact that iteration (as triggered
by the spread operator) converts holes to undefineds:
ES6 iteration (triggered via the spread operator) lets you convert holes to undefined elements:
> [...['a',,'c']]
[ 'a', undefined, 'c' ]
New Array features 314
With Symbol.isConcatSpreadable, you can override the default and avoid spreading for Arrays:
console.log(Array.prototype.concat.call(
arrayLike, ['e','f'], 'g'));
// [arrayLike, 'e', 'f', 'g']
arrayLike[Symbol.isConcatSpreadable] = true;
console.log(Array.prototype.concat.call(
arrayLike, ['e','f'], 'g'));
// ['c', 'd', 'e', 'f', 'g']
• Subclasses of Array are spread by default (because their instances are Array objects).
• A subclass of Array can prevent its instances from being spread by setting a property
to false whose key is Symbol.isConcatSpreadable. That property can be a prototype
property or an instance property.
• Other Array-like objects are spread by concat() if property [Symbol.isConcatSpreadable]
is true. That would enable one, for example, to turn on spreading for some Array-like
DOM collections.
• Typed Arrays are not spread. They don’t have a method concat(), either.
New Array features 316
However, Typed Arrays have a larger range of indices: 0 ≤ i < 2³²−1 (2⁵³−1 is the largest integer
that JavaScript’s floating point numbers can safely represent). That’s why generic Array methods
such as push()¹⁰ and unshift()¹¹ allow a larger range of indices. Range checks appropriate for
Arrays are performed elsewhere¹², whenever length is set.
⁷http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.concat
⁸http://www.ecma-international.org/ecma-262/6.0/#sec-isconcatspreadable
⁹http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects
¹⁰http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.push
¹¹http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.unshift
¹²http://www.ecma-international.org/ecma-262/6.0/#sec-arraysetlength
19. Maps and Sets
19.1 Overview
Among others, the following four data structures are new in ECMAScript 6: Map, WeakMap, Set
and WeakSet.
19.1.1 Maps
The keys of a Map can be arbitrary values:
You can use an Array (or any iterable) with [key, value] pairs to set up the initial data in the
Map:
19.1.2 Sets
A Set is a collection of unique elements:
As you can see, you can initialize a Set with elements if you hand the constructor an iterable
(arr in the example) over those elements.
Maps and Sets 318
19.1.3 WeakMaps
A WeakMap is a Map that doesn’t prevent its keys from being garbage-collected. That means that
you can associate data with objects without having to worry about memory leaks. For example:
function triggerListeners(obj) {
const listeners = _objToListeners.get(obj);
if (listeners) {
for (const listener of listeners) {
listener();
}
}
}
triggerListeners(obj);
// Output:
// hello
// world
19.2 Map
JavaScript has always had a very spartan standard library. Sorely missing was a data structure
for mapping values to values. The best you can get in ECMAScript 5 is a Map from strings to
arbitrary values, by abusing objects. Even then there are several pitfalls¹ that can trip you up.
¹http://speakingjs.com/es5/ch17.html#_pitfalls_using_an_object_as_a_map
Maps and Sets 319
The Map data structure in ECMAScript 6 lets you use arbitrary values as keys and is highly
welcome.
> map.has('foo')
true
> map.delete('foo')
true
> map.has('foo')
false
> map.size
2
> map.clear();
> map.size
0
19.2.3 Keys
Any value can be a key, even an object:
Most Map operations need to check whether a value is equal to one of the keys. They do so via
the internal operation SameValueZero², which works like ===, but considers NaN to be equal to
itself.
Let’s first see how === handles NaN:
Conversely, you can use NaN as a key in Maps, just like any other value:
Like ===, -0 and +0 are considered the same value. That is normally the best way to handle the
two zeros (details are explained in “Speaking JavaScript”³).
²http://www.ecma-international.org/ecma-262/6.0/#sec-samevaluezero
³http://speakingjs.com/es5/ch11.html#two_zeros
Maps and Sets 321
Different objects are always considered different. That is something that can’t be configured
(yet), as explained later, in the FAQ.
Maps record the order in which elements are inserted and honor that order when iterating over
keys, values or entries.
entries() returns the entries of the Map as an iterable over [key,value] pairs (Arrays).
Thus, you can make the previous code snippet even shorter:
The spread operator (...) can turn an iterable into an Array. That lets us convert the result of
Map.prototype.keys() (an iterable) into an Array:
Maps and Sets 323
Maps are also iterable, which means that the spread operator can turn Maps into Arrays:
The signature of the first parameter mirrors the signature of the callback of Array.prototype.forEach,
which is why the value comes first.
Mapping originalMap:
Filtering originalMap:
Step 1 is performed by the spread operator (...) which I have explained previously.
To combine map1 and map2, I turn them into Arrays via the spread operator (...) and concatenate
those Arrays. Afterwards, I convert the result back to a Map. All of that is done in the first line.
Maps and Sets 325
Let’s use this knowledge to convert any Map with JSON-compatible data to JSON and back:
function mapToJson(map) {
return JSON.stringify([...map]);
}
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
> mapToJson(myMap)
'[[true,7],[{"foo":3},["abc"]]]'
> jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
Map {true => 7, Object {foo: 3} => ['abc']}
The following two function convert string Maps to and from objects:
function strMapToObj(strMap) {
const obj = Object.create(null);
for (const [k,v] of strMap) {
// We don’t escape the key '__proto__'
// which can cause problems on older engines
obj[k] = v;
}
return obj;
}
function objToStrMap(obj) {
const strMap = new Map();
for (const k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
> strMapToObj(myMap)
{ yes: true, no: false }
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
> strMapToJson(myMap)
'{"yes":true,"no":false}'
> jsonToStrMap('{"yes":true,"no":false}');
Map {'yes' => true, 'no' => false}
• Map.prototype.get(key) : any
Returns the value that key is mapped to in this Map. If there is no key key in this Map,
undefined is returned.
• Map.prototype.set(key, value) : this
Maps the given key to the given value. If there is already an entry whose key is key, it is
updated. Otherwise, a new entry is created. This method returns this, which means that
you can chain it.
• Map.prototype.has(key) : boolean
Returns whether the given key exists in this Map.
Maps and Sets 328
• Map.prototype.delete(key) : boolean
If there is an entry whose key is key, it is removed and true is returned. Otherwise, nothing
happens and false is returned.
Iterating and looping: happens in the order in which entries were added to a Map.
• Map.prototype.entries() : Iterable<[any,any]>
Returns an iterable with one [key,value] pair for each entry in this Map. The pairs are
Arrays of length 2.
• Map.prototype.forEach((value, key, collection) => void, thisArg?) : void
The first parameter is a callback that is invoked once for each entry in this Map. If thisArg
is provided, this is set to it for each invocation. Otherwise, this is set to undefined.
• Map.prototype.keys() : Iterable<any>
Returns an iterable over all keys in this Map.
• Map.prototype.values() : Iterable<any>
Returns an iterable over all values in this Map.
• Map.prototype[Symbol.iterator]() : Iterable<[any,any]>
The default way of iterating over Maps. Refers to Map.prototype.entries.
19.3 WeakMap
WeakMaps work mostly like Maps, with the following differences:
With WeakMaps, you can associate previously computed results with objects, without having
to worry about memory management. The following function countOwnKeys is an example: it
caches previous results in the WeakMap cache.
⁴https://github.com/rwaldron/tc39-notes/blob/master/es6/2014-11/nov-19.md#412-should-weakmapweakset-have-a-clear-method-
markm
Maps and Sets 330
If we use this function with an object obj, you can see that the result is only computed for the
first invocation, while a cached value is used for the second invocation:
Let’s say we want to attach listeners to objects without changing the objects. You’d be able to
add listeners to an object obj:
triggerListeners(obj);
// Output:
// hello
// world
function triggerListeners(obj) {
const listeners = _objToListeners.get(obj);
if (listeners) {
for (const listener of listeners) {
listener();
}
}
}
The advantage of using a WeakMap here is that, once an object is garbage-collected, its listeners
will be garbage-collected, too. In other words: there won’t be any memory leaks.
In the following code, the WeakMaps _counter and _action are used to store the data of virtual
properties of instances of Countdown:
WeakMap.prototype.get(key) : any
WeakMap.prototype.set(key, value) : this
WeakMap.prototype.has(key) : boolean
WeakMap.prototype.delete(key) : boolean
19.4 Set
ECMAScript 5 doesn’t have a Set data structure, either. There are two possible work-arounds:
ECMAScript 6 has the data structure Set which works for arbitrary values, is fast and handles
NaN correctly.
> set.has('red')
true
> set.delete('red')
true
> set.has('red')
false
> set.size
2
> set.clear();
> set.size
0
For the second Set, only 'foo' is used (which is iterable) to define the Set.
> set.add('foo');
> set.size
1
> set.add('foo');
> set.size
1
Similarly to ===, two different objects are never considered equal (which can’t currently be
customized, as explained later, in the FAQ, later):
> set.add({});
> set.size
1
> set.add({});
> set.size
2
19.4.4 Iterating
Sets are iterable and the for-of loop works as you’d expect:
As you can see, Sets preserve iteration order. That is, elements are always iterated over in the
order in which they were inserted.
The previously explained spread operator (...) works with iterables and thus lets you convert a
Set to an Array:
Maps and Sets 335
We now have a concise way to convert an Array to a Set and back, which has the effect of
eliminating duplicates from the Array:
Filtering:
19.4.6.1 Union
Union (a ∪ b): create a Set that contains the elements of both Set a and Set b.
The spread operator (...) inserts the elements of something iterable (such as a Set) into an
Array. Therefore, [...a, ...b] means that a and b are converted to Arrays and concatenated.
It is equivalent to [...a].concat([...b]).
Maps and Sets 336
19.4.6.2 Intersection
Intersection (a ∩ b): create a Set that contains those elements of Set a that are also in Set b.
Steps: Convert a to an Array, filter the elements, convert the result to a Set.
19.4.6.3 Difference
Difference (a \ b): create a Set that contains those elements of Set a that are not in Set b. This
operation is also sometimes called minus (-).
• Set.prototype.add(value) : this
Adds value to this Set. This method returns this, which means that it can be chained.
• Set.prototype.has(value) : boolean
Checks whether value is in this Set.
• Set.prototype.delete(value) : boolean
Removes value from this Set.
• Set.prototype.values() : Iterable<any>
Returns an iterable over all elements of this Set.
• Set.prototype[Symbol.iterator]() : Iterable<any>
The default way of iterating over Sets. Points to Set.prototype.values.
• Set.prototype.forEach((value, key, collection) => void, thisArg?)
Loops over the elements of this Set and invokes the callback (first parameter) for each
one. value and key are both set to the element, so that this method works similarly to
Map.prototype.forEach. If thisArg is provided, this is set to it for each call. Otherwise,
this is set to undefined.
Symmetry with Map: The following two methods only exist so that the interface of Sets is similar
to the interface of Maps. Each Set element is handled as if it were a Map entry whose key and
value are the element.
• Set.prototype.entries() : Iterable<[any,any]>
• Set.prototype.keys() : Iterable<any>
19.5 WeakSet
A WeakSet is a Set that doesn’t prevent its elements from being garbage-collected. Consult the
section on WeakMap for an explanation of why WeakSets don’t allow iteration, looping and
clearing.
For example, if you have a factory function for proxies, you can use a WeakSet to record which
objects were created by that factory:
Maps and Sets 338
function createProxy(obj) {
const proxy = ···;
_proxies.add(proxy);
return proxy;
}
function isProxy(obj) {
return _proxies.has(obj);
}
Domenic Denicola shows⁵ how a class Foo can ensure that its methods are only applied to
instances that were created by it:
class Foo {
constructor() {
foos.add(this);
}
method() {
if (!foos.has(this)) {
throw new TypeError('Incompatible object!');
}
}
}
⁵https://mail.mozilla.org/pipermail/es-discuss/2015-June/043027.html
Maps and Sets 339
WeakSet.prototype.add(value)
WeakSet.prototype.has(value)
WeakSet.prototype.delete(value)
19.6.1 Why do Maps and Sets have the property size and not
length?
Arrays have the property length to count the number of entries. Maps and Sets have a different
property, size.
The reason for this difference is that length is for sequences, data structures that are indexable
– like Arrays. size is for collections that are primarily unordered – like Maps and Sets.
19.6.2 Why can’t I configure how Maps and Sets compare keys
and values?
It would be nice if there were a way to configure what Map keys and what Set elements are
considered equal. But that feature has been postponed, as it is difficult to implement properly
and efficiently.
function countChars(chars) {
const charCounts = new Map();
for (const ch of chars) {
ch = ch.toLowerCase();
const prevCount = charCounts.get(ch) || 0; // (A)
charCounts.set(ch, prevCount+1);
}
return charCounts;
}
In line A, the default 0 is used if ch is not in the charCounts and get() returns undefined.
Maps and Sets 340
Instances of ArrayBuffer store the binary data to be processed. Two kinds of views are used to
access the data:
The following browser APIs support Typed Arrays (details are mentioned in a dedicated section):
• File API
• XMLHttpRequest
• Fetch API
• Canvas
• WebSockets
• And more
20.2 Introduction
Much data one encounters on the web is text: JSON files, HTML files, CSS files, JavaScript code,
etc. For handling such data, JavaScript’s built-in string data type works well. However, until
a few years ago, JavaScript was ill-equipped to handle binary data. On 8 February 2011, the
Typed Array Specification 1.0¹ standardized facilities for handling binary data. By now, Typed
Arrays are well supported² by various engines. With ECMAScript 6, they became part of the
¹https://www.khronos.org/registry/typedarray/specs/1.0/
²http://caniuse.com/#feat=typedarrays
Typed Arrays 342
core language and gained many methods in the process that were previously only available for
Arrays (map(), filter(), etc.).
The main uses cases for Typed Arrays are:
• Processing binary data: manipulating image data in HTML Canvas elements, parsing
binary files, handling binary network protocols, etc.
• Interacting with native APIs: Native APIs often receive and return data in a binary format,
which you could neither store nor manipulate well in traditional JavaScript. That meant
that whenever you were communicating with such an API, data had to be converted from
JavaScript to binary and back, for every call. Typed Arrays eliminate this bottleneck. One
example of communicating with native APIs is WebGL, for which Typed Arrays were
initially created. Section “History of Typed Arrays³” of the article “Typed Arrays: Binary
Data in the Browser⁴” (by Ilmari Heikkinen for HTML5 Rocks) has more information.
This is a diagram of the structure of the Typed Array API (notable: all Typed Arrays have a
common superclass):
³http://www.html5rocks.com/en/tutorials/webgl/typed_arrays/#toc-history
⁴http://www.html5rocks.com/en/tutorials/webgl/typed_arrays/#toc-history
Typed Arrays 343
The element type Uint8C is special: it is not supported by DataView and only exists to enable
Uint8ClampedArray. This Typed Array is used by the canvas element (where it replaces Can-
vasPixelArray). The only difference between Uint8C and Uint8 is how overflow and underflow
are handled (as explained in the next section). It is recommended to avoid the former – quoting
Brendan Eich⁵:
⁵https://mail.mozilla.org/pipermail/es-discuss/2015-August/043902.html
Typed Arrays 344
• The highest value plus one is converted to the lowest value (0 for unsigned integers).
• The lowest value minus one is converted to the highest value.
20.2.3 Endianness
Whenever a type (such as Uint16) is stored as multiple bytes, endianness matters:
• Big endian: the most significant byte comes first. For example, the Uint16 value 0xABCD
is stored as two bytes – first 0xAB, then 0xCD.
• Little endian: the least significant byte comes first. For example, the Uint16 value 0xABCD
is stored as two bytes – first 0xCD, then 0xAB.
Endianness tends to be fixed per CPU architecture and consistent across native APIs. Typed
Arrays are used to communicate with those APIs, which is why their endianness follows the
endianness of the platform and can’t be changed.
On the other hand, the endianness of protocols and binary files varies and is fixed across
platforms. Therefore, we must be able to access data with either endianness. DataViews serve
this use case and let you specify endianness when you get or set a value.
Quoting Wikipedia on Endianness⁶:
• Big-endian representation is the most common convention in data networking; fields in the
protocols of the Internet protocol suite, such as IPv4, IPv6, TCP, and UDP, are transmitted
in big-endian order. For this reason, big-endian byte order is also referred to as network
byte order.
• Little-endian storage is popular for microprocessors in part due to significant historical
influence on microprocessor designs by Intel Corporation.
You can use the following function to determine the endianness of a platform.
⁶https://en.wikipedia.org/wiki/Endianness
Typed Arrays 346
There are also platforms that arrange words (pairs of bytes) with a different endianness than bytes
inside words. That is called mixed endianness. Should you want to support such a platform then
it is easy to extend the previous code.
Offsets, on the other hand, must be non-negative. If, for example, you pass -1 to:
DataView.prototype.getInt8(byteOffset)
20.3 ArrayBuffers
ArrayBuffers store the data, views (Typed Arrays and DataViews) let you read and change it.
In order to create a DataView, you need to provide its constructor with an ArrayBuffer. Typed
Array constructors can optionally create an ArrayBuffer for you.
ArrayBuffer(length : number)
Invoking this constructor via new creates an instance whose capacity is length bytes. Each of
those bytes is initially 0.
• All of their elements have the same type, setting elements converts values to that type.
• They are contiguous. Normal Arrays can have holes (indices in the range [0, arr.length)
that have no associated element), Typed Arrays can’t.
• Initialized with zeros. This is a consequence of the previous item:
– new Array(10) creates a normal Array without any elements (it only has holes).
– new Uint8Array(10) creates a Typed Array whose 10 elements are all 0.
• An associated buffer. The elements of a Typed Array ta are not stored in ta, they are stored
in an associated ArrayBuffer that can be accessed via ta.buffer.
Typed Arrays 348
The classic way to convert a Typed Array to an Array is to invoke Array.prototype.slice on it.
This trick works for all Array-like objects (such as arguments) and Typed Arrays are Array-like.
> Array.prototype.slice.call(tarr)
[ 0, 1, 2 ]
In ES6, you can use the spread operator (...), because Typed Arrays are iterable:
> [...tarr]
[ 0, 1, 2 ]
Another ES6 alternative is Array.from(), which works with either iterables or Array-like objects:
> Array.from(tarr)
[ 0, 1, 2 ]
Typed Arrays 349
• ArrayBuffer.prototype.slice()
• Whenever an ArrayBuffer is cloned inside a Typed Array or DataView.
• TypedArray<T>.prototype.filter()
• TypedArray<T>.prototype.map()
• TypedArray<T>.prototype.slice()
• TypedArray<T>.prototype.subarray()
20.4.6.1 TypedArray.of()
TypedArray.of(...items)
It creates a new Typed Array that is an instance of this (the class on which of() was invoked).
The elements of that instance are the parameters of of().
You can think of of() as a custom literal for Typed Arrays:
Typed Arrays 350
20.4.6.2 TypedArray.from()
The optional mapfn lets you transform the elements of source before they become elements of the
result. Why perform the two steps mapping and conversion in one go? Compared to performing
the first step separately, via source.map(), there are two advantages:
To illustrate the second advantage, let’s use map() to double the elements of a Typed Array:
As you can see, the values overflow and are coerced into the Int8 range of values. If map via
from(), you can choose the type of the result so that values don’t overflow:
According to Allen Wirfs-Brock⁷, mapping between Typed Arrays was what motivated the mapfn
parameter of from().
⁷https://twitter.com/awbjs/status/585199958661472257
Typed Arrays 351
The following properties are specific to Typed Arrays, normal Arrays don’t have them:
The following methods are basically the same as the methods of normal Arrays:
Due to all of these methods being available for Arrays, you can consult the following two sources
to find out more about how they work:
• The following methods are new in ES6 and explained in chapter “New Array features”:
copyWithin, entries, fill, find, findIndex, keys, values.
• All other methods are explained in chapter “Arrays⁸” of “Speaking JavaScript”.
Note that while normal Array methods are generic (any Array-like this is OK), the methods
listed in this section are not (this must be a Typed Array).
The following code shows three different ways of creating the same Typed Array:
⁸http://speakingjs.com/es5/ch18.html
Typed Arrays 354
> Uint8Array.BYTES_PER_ELEMENT
1
> Int16Array.BYTES_PER_ELEMENT
2
> Float64Array.BYTES_PER_ELEMENT
8
typedArray.set(arrayOrTypedArray, offset=0)
That method copies an existing Typed Array (or normal Array) into typedArray at index offset.
Then you only have to make sure that typedArray is big enough to hold all (Typed) Arrays you
want to concatenate:
Typed Arrays 355
20.5 DataViews
20.6.2 XMLHttpRequest
In newer versions of the XMLHttpRequest API¹⁰, you can have the results delivered in an
ArrayBuffer:
xhr.onload = function () {
const arrayBuffer = xhr.response;
···
};
xhr.send();
fetch(url)
.then(request => request.arrayBuffer())
.then(arrayBuffer => ···);
⁹http://www.w3.org/TR/FileAPI/
¹⁰http://www.w3.org/TR/XMLHttpRequest/
¹¹https://fetch.spec.whatwg.org/
Typed Arrays 357
20.6.4 Canvas
Quoting the HTML5 specification¹²:
The 2D Context of canvas¹³ lets you retrieve the bitmap data as an instance of Uint8ClampedArray:
20.6.5 WebSockets
WebSockets¹⁴ let you send and receive binary data via ArrayBuffers:
• Media Source Extensions¹⁸: The HTML media elements are currently <audio> and <video>.
The Media Source Extensions API enables you to create streams to be played via those
elements. You can add binary data to such streams via ArrayBuffers, Typed Arrays or
DataViews.
• Communication with Web Workers¹⁹: If you send data to a Worker via postMessage()²⁰,
either the message (which will be cloned) or the transferable objects can contain Array-
Buffers.
• Cross-document communication²¹: works similarly to communication with Web Workers
and also uses the method postMessage().
The code of the following example is on GitHub²². And you can run it online²³.
The example is a web pages that lets you upload a JPEG file and parses its structure to determine
the height and the width of the image and more.
• Marker (two bytes): declares what kind of data is stored in the segment. The first of the
two bytes is always 0xFF. Each of the standard markers has a human readable name. For
example, the marker 0xFFC0 has the name “Start Of Frame (Baseline DCT)”, short: “SOF0”.
• Length of segment (two bytes): how long is this segment (in bytes, including the length
itself)?
JPEG files are big-endian on all platforms. Therefore, this example demonstrates how important
it is that we can specify endianness when using DataViews.
function processArrayBuffer(arrayBuffer) {
try {
var dv = new DataView(arrayBuffer);
···
var ptr = 2;
while (true) {
···
var lastPtr = ptr;
enforceValue(0xFF, dv.getUint8(ptr),
'Not a marker');
ptr++;
var marker = dv.getUint8(ptr);
ptr++;
var len = dv.getUint16(ptr, IS_LITTLE_ENDIAN);
ptr += len;
logInfo('Marker: '+hex(marker)+' ('+len+' byte(s))');
···
This code uses the following helper functions (that are not shown here):
• enforceValue() throws an error if the expected value (first parameter) doesn’t match the
actual value (second parameter).
• logInfo() and logError() display messages on the page.
• hex() turns a number into a string with two hexadecimal digits.
20.8 Availability
Much of the Typed Array API is implemented by all modern JavaScript engines, but several
features are new to ECMAScript 6:
It may take a while until these are available everywhere. As usual, kangax’ “ES6 compatibility
table²⁶” describes the status quo.
²⁴https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure
²⁵https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure
²⁶https://kangax.github.io/compat-table/es6/#typed_arrays
21. Iterables and iterators
21.1 Overview
ES6 introduces a new mechanism for traversing data: iteration. Two concepts are central to
iteration:
• An iterable is a data structure that wants to make its elements accessible to the public.
It does so by implementing a method whose key is Symbol.iterator. That method is a
factory for iterators.
• An iterator is a pointer for traversing the elements of a data structure (think cursors in
databases).
interface Iterable {
[Symbol.iterator]() : Iterator;
}
interface Iterator {
next() : IteratorResult;
}
interface IteratorResult {
value: any;
done: boolean;
}
• Arrays
• Strings
• Maps
• Sets
• DOM data structures (work in progress)
• for-of loop:
• Array.from():
• Promise.all(), Promise.race():
Promise.all(iterableOverPromises).then(···);
Promise.race(iterableOverPromises).then(···);
• yield*:
yield* anIterable;
21.2 Iterability
The idea of iterability is as follows.
• Data consumers: JavaScript has language constructs that consume data. For example, for-
of loops over values and the spread operator (...) inserts values into Arrays or function
calls.
• Data sources: The data consumers could get their values from a variety of sources. For
example, you may want to iterate over the elements of an Array, the key-value entries in
a Map or the characters of a string.
It’s not practical for every consumer to support all sources, especially because it should be
possible to create new sources (e.g. via libraries). Therefore, ES6 introduces the interface
Iterable. Data consumers use it, data sources implement it:
Iterables and iterators 363
Given that JavaScript does not have interfaces, Iterable is more of a convention:
• Source: A value is considered iterable if it has a method whose key is the symbol
Symbol.iterator that returns a so-called iterator. The iterator is an object that returns
values via its method next(). We say: it iterates over the items (the content) of the iterable,
one per method call.
• Consumption: Data consumers use the iterator to retrieve the values they are consuming.
Let’s see what consumption looks like for an Array arr. First, you create an iterator via the
method whose key is Symbol.iterator:
Then you call the iterator’s method next() repeatedly to retrieve the items “inside” the Array:
> iter.next()
{ value: 'a', done: false }
> iter.next()
{ value: 'b', done: false }
> iter.next()
{ value: 'c', done: false }
> iter.next()
{ value: undefined, done: true }
As you can see, next() returns each item wrapped in an object, as the value of the property
value. The boolean property done indicates when the end of the sequence of items has been
reached.
Iterable and iterators are part of a so-called protocol (interfaces plus rules for using them) for
iteration. A key characteristic of this protocol is that it is sequential: the iterator returns values
one at a time. That means that if an iterable data structure is non-linear (such as a tree), iteration
will linearize it.
Iterables and iterators 364
21.3.1 Arrays
Arrays (and Typed Arrays) are iterables over their elements:
21.3.2 Strings
Strings are iterable, but they iterate over Unicode code points, each of which may comprise one
or two JavaScript characters:
You have just seen that primitive values can be iterable. A value doesn’t have to be an
object in order to be iterable. That’s because all values are coerced to objects before the
iterator method (property key Symbol.iterator) is accessed.
21.3.3 Maps
Maps are iterables over their entries. Each entry is encoded as a [key, value] pair, an Array with
two elements. The entries are always iterated over deterministically, in the same order in which
they were added to the map.
Iterables and iterators 365
21.3.4 Sets
Sets are iterables over their elements (which are iterated over in the same order in which they
were added to the Set).
21.3.5 arguments
Even though the special variable arguments is more or less obsolete in ECMAScript 6 (due to
rest parameters), it is iterable:
function printArgs() {
for (const x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// Output:
// 'a'
// 'b'
Note that implementing this functionality is work in progress. But it is relatively easy to do so,
because the symbol Symbol.iterator can’t clash with existing property keys.
• entries() returns an iterable over entries encoded as [key, value] Arrays. For Arrays, the
values are the Array elements and the keys are their indices. For Sets, each key and value
are the same – the Set element.
• keys() returns an iterable over the keys of the entries.
• values() returns an iterable over the values of the entries.
Let’s see what that looks like. entries() gives you a nice way to get both Array elements and
their indices:
Why aren’t objects iterable over properties, by default? The reasoning is as follows. There are
two levels at which you can iterate in JavaScript:
1. The program level: iterating over properties means examining the structure of the program.
Iterables and iterators 367
2. The data level: iterating over a data structure means examining the data managed by the
program.
Making iteration over properties the default would mean mixing those levels, which would have
two disadvantages:
The proper (and safe) way to iterate over properties is via a tool function. For example, via
objectEntries(), whose implementation is shown later (future ECMAScript versions may have
something similar built in):
// Output:
// first: Jane
// last: Doe
21.4.3 Array.from()
Array.from() converts iterable and Array-like values to Arrays. It is also available for typed
Arrays.
That means that it provides you with a compact way to convert any iterable to an Array:
The spread operator also turns an iterable into the arguments of a function, method or constructor
call:
The constructors of WeakMap and WeakSet work similarly. Furthermore, Maps and Sets are iterable
themselves (WeakMaps and WeakSets aren’t), which means that you can use their constructors
to clone them.
21.4.6 Promises
Promise.all() and Promise.race() accept iterables over Promises:
Iterables and iterators 370
Promise.all(iterableOverPromises).then(···);
Promise.race(iterableOverPromises).then(···);
21.4.7 yield*
yield* is an operator that is only available inside generators. It yields all items iterated over by
an iterable.
function* yieldAllValuesOf(iterable) {
yield* iterable;
}
The most important use case for yield* is to recursively call a generator (which produces
something iterable).
An object becomes iterable (“implements” the interface Iterable) if it has a method (own or
inherited) whose key is Symbol.iterator. That method must return an iterator, an object that
iterates over the items “inside” the iterable via its method next().
In TypeScript notation, the interfaces for iterables and iterators look as follows².
interface Iterable {
[Symbol.iterator]() : Iterator;
}
interface Iterator {
next() : IteratorResult;
return?(value? : any) : IteratorResult;
}
interface IteratorResult {
value: any;
done: boolean;
}
return() is an optional method that we’ll get to later³. Let’s first implement a dummy iterable
to get a feeling for how iteration works.
const iterable = {
[Symbol.iterator]() {
let step = 0;
const iterator = {
next() {
if (step <= 2) {
step++;
}
switch (step) {
case 1:
return { value: 'hello', done: false };
case 2:
return { value: 'world', done: false };
default:
return { value: undefined, done: true };
}
}
};
return iterator;
}
};
³throw() is also an optional method, but is practically never used for iterators and therefore explained in the chapter on generators)
Iterables and iterators 372
The code executes three steps, with the counter step ensuring that everything happens in the
right order. First, we return the value 'hello', then the value 'world' and then we indicate that
the end of the iteration has been reached. Each item is wrapped in an object with the properties:
You can omit done if it is false and value if it is undefined. That is, the switch statement could
be written as follows.
switch (step) {
case 1:
return { value: 'hello' };
case 2:
return { value: 'world' };
default:
return { done: true };
}
As is explained in the the chapter on generators, there are cases where you want even the last
item with done: true to have a value. Otherwise, next() could be simpler and return items
directly (without wrapping them in objects). The end of iteration would then be indicated via a
special value (e.g., a symbol).
Let’s look at one more implementation of an iterable. The function iterateOver() returns an
iterable over the arguments that are passed to it:
function iterateOver(...args) {
let index = 0;
const iterable = {
[Symbol.iterator]() {
const iterator = {
next() {
if (index < args.length) {
return { value: args[index++] };
} else {
return { done: true };
}
Iterables and iterators 373
}
};
return iterator;
}
}
return iterable;
}
// Using `iterateOver()`:
for (const x of iterateOver('fee', 'fi', 'fo', 'fum')) {
console.log(x);
}
// Output:
// fee
// fi
// fo
// fum
function iterateOver(...args) {
let index = 0;
const iterable = {
[Symbol.iterator]() {
return this;
},
next() {
if (index < args.length) {
return { value: args[index++] };
} else {
return { done: true };
}
},
};
return iterable;
}
Even if the original iterable and the iterator are not the same object, it is still occasionally useful
if an iterator has the following method (which also makes it an iterable):
Iterables and iterators 374
[Symbol.iterator]() {
return this;
}
All built-in ES6 iterators follow this pattern (via a common prototype, see the chapter on
generators). For example, the default iterator for Arrays:
Why is it useful if an iterator is also an iterable? for-of only works for iterables, not for iterators.
Because Array iterators are iterable, you can continue an iteration in another loop:
One use case for continuing an iteration is that you can remove initial items (e.g. a header) before
processing the actual content via for-of.
As mentioned before, the optional iterator method return() is about letting an iterator clean up
if it wasn’t iterated over until the end. It closes an iterator. In for-of loops, premature (or abrupt,
in spec language) termination can be caused by:
Iterables and iterators 375
• break
• continue (if you continue an outer loop, continue acts like a break)
• throw
• return
In each of these cases, for-of lets the iterator know that the loop won’t finish. Let’s look at an
example, a function readLinesSync that returns an iterable of text lines in a file and would like
to close that file no matter what happens:
function readLinesSync(fileName) {
const file = ···;
return {
···
next() {
if (file.isAtEndOfFile()) {
file.close();
return { done: true };
}
···
},
return() {
file.close();
return { done: true };
},
};
}
Due to return(), the file will be properly closed in the following loop:
The return() method must return an object. That is due to how generators handle the return
statement and will be explained in the chapter on generators.
The following constructs close iterators that aren’t completely “drained”:
• for-of
• yield*
• Destructuring
• Array.from()
• Map(), Set(), WeakMap(), WeakSet()
• Promise.all(), Promise.race()
function objectEntries(obj) {
let index = 0;
return {
[Symbol.iterator]() {
return this;
},
next() {
if (index < propKeys.length) {
const key = propKeys[index];
index++;
return { value: [key, obj[key]] };
} else {
return { done: true };
}
}
};
}
// Output:
// first: Jane
// last: Doe
Another option is to use an iterator instead of an index to traverse the Array with the property
keys:
Iterables and iterators 377
function objectEntries(obj) {
let iter = Reflect.ownKeys(obj)[Symbol.iterator]();
return {
[Symbol.iterator]() {
return this;
},
next() {
let { done, value: key } = iter.next();
if (done) {
return { done: true };
}
return { value: [key, obj[key]] };
}
};
}
Let’s start with the combinator function take(n, iterable), which returns an iterable over the
first n items of iterable.
console.log(x);
}
// Output:
// a
// b
This version of take() doesn’t close the iterator iter. How to do that is shown later,
after I explain what closing an iterator actually means.
21.6.2.2 zip(...iterables)
zip turns n iterables into an iterable of n-tuples (encoded as Arrays of length n).
function zip(...iterables) {
const iterators = iterables.map(i => i[Symbol.iterator]());
let done = false;
return {
[Symbol.iterator]() {
return this;
},
next() {
if (!done) {
const items = iterators.map(i => i.next());
done = items.some(item => item.done);
if (!done) {
return { value: items.map(i => i.value) };
}
// Done for the first time: close all iterators
for (const iterator of iterators) {
if (typeof iterator.return === 'function') {
iterator.return();
}
}
}
// We are done
return { done: true };
}
}
}
As you can see, the shortest iterable determines the length of the result:
Iterables and iterators 379
function naturalNumbers() {
let n = 0;
return {
[Symbol.iterator]() {
return this;
},
next() {
return { value: n++ };
}
}
}
With an infinite iterable, you must not iterate over “all” of it. For example, by breaking from a
for-of loop:
The “length” of the iterable returned by zip() is determined by its shortest input iterable. That
means that zip() and naturalNumbers() provide you with the means to number iterables of
arbitrary (finite) length:
⁵https://esdiscuss.org/topic/performance-of-iterator-next-as-specified
Iterables and iterators 381
If an iterator reuses its iteration result object, iterationResults will, in general, contain the
same object multiple times.
Eventually, one such library or pieces from several libraries will be added to the JavaScript
standard library.
If you want to get an impression of what such a library could look like, take a look at the standard
Python module itertools⁶.
⁶https://docs.python.org/3/library/itertools.html
Iterables and iterators 382
interface Iterable {
[Symbol.iterator]() : Iterator;
}
interface Iterator {
next() : IteratorResult;
return?(value? : any) : IteratorResult;
}
interface IteratorResult {
value : any;
done : boolean;
}
21.8.1 Iteration
Rules for next():
• As long as the iterator still has values x to produce, next() returns objects { value: x,
done: false }.
• After the last value was iterated over, next() should always return an object whose
property done is true.
The property done of an iterator result doesn’t have to be true or false, truthy or falsy is enough.
All built-in language mechanisms let you omit done: false.
21.8.1.2 Iterables that return fresh iterators versus those that always
return the same iterator
Some iterables produce a new iterator each time they are asked for one. For example, Arrays:
function getIterator(iterable) {
return iterable[Symbol.iterator]();
}
Other iterables return the same iterator each time. For example, generator objects:
⁷http://www.ecma-international.org/ecma-262/6.0/#sec-iteration
Iterables and iterators 383
function* elements() {
yield 'a';
yield 'b';
}
const iterable = elements();
console.log(getIterator(iterable) === getIterator(iterable)); // true
Whether an iterable produces a fresh iterators or not matter when you iterate over the same
iterable multiple times. For example, via the following function:
function iterateTwice(iterable) {
for (const x of iterable) {
console.log(x);
}
for (const x of iterable) {
console.log(x);
}
}
With fresh iterators, you can iterate over the same iterable multiple times:
iterateTwice(['a', 'b']);
// Output:
// a
// b
// a
// b
iterateTwice(elements());
// Output:
// a
// b
Note that each iterator in the standard library is also an iterable. Its method [Symbol.iterator]()
return this, meaning that it always returns the same iterator (itself).
• Exhaustion: the regular way of finishing an iterator is by retrieving all of its values. That
is, one calls next() until it returns an object whose property done is true.
Iterables and iterators 384
• Closing: by calling return(), you tell the iterator that you don’t intend to call next(),
anymore.
• return() is an optional method, not all iterators have it. Iterators that do have it are called
closable.
• return() should only be called if an iterator hasn’t be exhausted. For example, for-of calls
return() whenever it is left “abruptly” (before it is finished). The following operations
cause abrupt exits: break, continue (with a label of an outer block), return, throw.
• The method call return(x) should normally produce the object { done: true, value:
x }, but language mechanisms only throw an error (source in spec⁸) if the result isn’t an
object.
• After return() was called, the objects returned by next() should be done, too.
The following code illustrates that the for-of loop calls return() if it is aborted before it receives
a done iterator result. That is, return() is even called if you abort after receiving the last value.
This is subtle and you have to be careful to get it right when you iterate manually or implement
iterators.
function createIterable() {
let done = false;
const iterable = {
[Symbol.iterator]() {
return this;
},
next() {
if (!done) {
done = true;
return { done: false, value: 'a' };
} else {
return { done: true, value: undefined };
}
},
return() {
console.log('return() was called!');
},
};
return iterable;
}
⁸http://www.ecma-international.org/ecma-262/6.0/#sec-iteratorclose
Iterables and iterators 385
An iterator is closable if it has a method return(). Not all iterators are closable. For example,
Array iterators are not:
Generator objects are closable by default. For example, the ones returned by the following
generator function:
function* elements() {
yield 'a';
yield 'b';
yield 'c';
}
If an iterator is not closable, you can continue iterating over it after an abrupt exit (such as the
one in line A) from a for-of loop:
Iterables and iterators 386
function twoLoops(iterator) {
for (const x of iterator) {
console.log(x);
break; // (A)
}
for (const x of iterator) {
console.log(x);
}
}
function getIterator(iterable) {
return iterable[Symbol.iterator]();
}
Conversely, elements() returns a closable iterator and the second loop inside twoLoops() doesn’t
have anything to iterate over:
twoLoops(elements());
// Output:
// a
The following class is a generic solution for preventing iterators from being closed. It does so by
wrapping the iterator and forwarding all method calls except return().
class PreventReturn {
constructor(iterator) {
this.iterator = iterator;
}
/** Must also be iterable, so that for-of works */
[Symbol.iterator]() {
return this;
}
next() {
return this.iterator.next();
}
return(value = undefined) {
return { done: false, value };
}
// Not relevant for iterators: `throw()`
}
Iterables and iterators 387
If we use PreventReturn, the result of the generator elements() won’t be closed after the abrupt
exit in the first loop of twoLoops().
function* elements() {
yield 'a';
yield 'b';
yield 'c';
}
function twoLoops(iterator) {
for (const x of iterator) {
console.log(x);
break; // abrupt exit
}
for (const x of iterator) {
console.log(x);
}
}
twoLoops(elements());
// Output:
// a
twoLoops(new PreventReturn(elements()));
// Output:
// a
// b
// c
There is another way of making generators unclosable: All generator objects produced by
the generator function elements() have the prototype object elements.prototype. Via ele-
ments.prototype, you can hide the default implementation of return() (which resides in a
prototype of elements.prototype) as follows:
twoLoops(elements());
// Output:
// a
// b
// c
Some generators need to clean up (release allocated resources, close open files, etc.) after iteration
over them is finished. Naively, this is how we’d implement it:
Iterables and iterators 388
function* genFunc() {
yield 'a';
yield 'b';
console.log('Performing cleanup');
}
However, if you exit the loop after the first yield, execution seemingly pauses there forever and
never reaches the cleanup step:
What actually happens is that, whenever one leaves a for-of loop early, for-of sends a return()
to the current iterator. That means that the cleanup step isn’t reached because the generator
function returns beforehand.
Thankfully, this is easily fixed, by performing the cleanup in a finally clause:
function* genFunc() {
try {
yield 'a';
yield 'b';
} finally {
console.log('Performing cleanup');
}
}
The general pattern for using resources that need to be closed or cleaned up in some manner is
therefore:
function* funcThatUsesResource() {
const resource = allocateResource();
try {
···
} finally {
resource.deallocate();
}
}
const iterable = {
[Symbol.iterator]() {
function hasNextValue() { ··· }
function getNextValue() { ··· }
function cleanUp() { ··· }
let returnedDoneResult = false;
return {
next() {
if (hasNextValue()) {
const value = getNextValue();
return { done: false, value: value };
} else {
if (!returnedDoneResult) {
// Client receives first `done` iterator result
// => won’t call `return()`
cleanUp();
returnedDoneResult = true;
}
return { done: true, value: undefined };
}
},
return() {
cleanUp();
}
Iterables and iterators 390
};
}
}
Note that you must call cleanUp() when you are going to return a done iterator result for the
first time. You must not do it earlier, because then return() may still be called. This can be tricky
to get right.
If you use iterators, you should close them properly. In generators, you can let for-of do all the
work for you:
/**
* Converts a (potentially infinite) sequence of
* iterated values into a sequence of length `n`
*/
function* take(n, iterable) {
for (const x of iterable) {
if (n <= 0) {
break; // closes iterable
}
n--;
yield x;
}
}
iterator.return();
}
}
21.8.3 Checklist
• Documenting an iterable: provide the following information.
– Does it return fresh iterators or the same iterator each time?
– Are its iterators closable?
• Implementing an iterator:
– Clean-up activity must happen if either an iterator is exhausted or if return() is
called.
* In generators, try-finally lets you handle both in a single location.
– After an iterator was closed via return(), it should not produce any more iterator
results via next().
• Using an iterator manually (versus via for-of etc.):
– Don’t forget to close the iterator via return, if – and only if – you don’t exhaust it.
Getting this right can be tricky.
• Continuing to iterate over an iterator after an abrupt exit: The iterator must either be
unclosable or made unclosable (e.g. via a tool class).
22. Generators
The following GitHub repository contains the example code: generator-examples¹
22.1 Overview
function* genFunc() {
// (A)
console.log('First');
yield;
console.log('Second');
}
Note the new syntax: function* is a new “keyword” for generator functions (there are also
generator methods). yield is an operator with which a generator can pause itself. Additionally,
generators can also receive input and send output via yield.
When you call a generator function genFunc(), you get a generator object genObj that you can
use to control the process:
The process is initially paused in line A. genObj.next() resumes execution, a yield inside
genFunc() pauses execution:
genObj.next();
// Output: First
genObj.next();
// output: Second
const obj = {
* generatorMethod() {
···
}
};
const genObj = obj.generatorMethod();
class MyClass {
* generatorMethod() {
···
}
}
const myInst = new MyClass();
const genObj = myInst.generatorMethod();
function* objectEntries(obj) {
const propKeys = Reflect.ownKeys(obj);
How exactly objectEntries() works is explained in a dedicated section. Implementing the same
functionality without generators is much more work.
function fetchJson(url) {
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR: ${error.stack}`);
});
}
With the library co² and a generator, this asynchronous code looks synchronous:
ECMAScript 2017 will have async functions which are internally based on generators. With
them, the code looks like this:
²https://github.com/tj/co
Generators 395
fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));
function* genFunc() {
// (A)
console.log('First');
yield; // (B)
console.log('Second'); // (C)
}
Calling genFunc does not execute its body. Instead, you get a so-called generator object, with
which you can control the execution of the body:
Generators 396
genFunc() is initially suspended before the body (line A). The method call genObj.next()
continues execution until the next yield:
> genObj.next()
First
{ value: undefined, done: false }
As you can see in the last line, genObj.next() also returns an object. Let’s ignore that for now.
It will matter later.
genFunc is now paused in line B. If we call next() again, execution resumes and line C is
executed:
> genObj.next()
Second
{ value: undefined, done: true }
Afterwards, the function is finished, execution has left the body and further calls of genObj.next()
have no effect.
1. Iterators (data producers): Each yield can return a value via next(), which means that
generators can produce sequences of values via loops and recursion. Due to generator
objects implementing the interface Iterable (which is explained in the chapter on
iteration), these sequences can be processed by any ECMAScript 6 construct that supports
iterables. Two examples are: for-of loops and the spread operator (...).
2. Observers (data consumers): yield can also receive a value from next() (via a parameter).
That means that generators become data consumers that pause until a new value is pushed
into them via next().
3. Coroutines (data producers and consumers): Given that generators are pausable and can
be both data producers and data consumers, not much work is needed to turn them into
coroutines (cooperatively multitasked tasks).
For this section, you should be familiar with ES6 iteration. The previous chapter has
more information.
As explained before, generator objects can be data producers, data consumers or both. This
section looks at them as data producers, where they implement both the interfaces Iterable and
Iterator (shown below). That means that the result of a generator function is both an iterable
and an iterator. The full interface of generator objects will be shown later.
interface Iterable {
[Symbol.iterator]() : Iterator;
}
interface Iterator {
next() : IteratorResult;
}
interface IteratorResult {
value : any;
done : boolean;
}
I have omitted method return() of interface Iterable, because it is not relevant in this section.
A generator function produces a sequence of values via yield, a data consumer consumes thoses
values via the iterator method next(). For example, the following generator function produces
the values 'a' and 'b':
function* genFunc() {
yield 'a';
yield 'b';
}
This interaction shows how to retrieve the yielded values via the generator object genObj:
Second, the spread operator (...), which turns iterated sequences into elements of an array
(consult the chapter on parameter handling for more information on this operator):
Third, destructuring:
function* genFuncWithReturn() {
yield 'a';
yield 'b';
return 'result';
}
The returned value shows up in the last object returned by next(), whose property done is true:
However, most constructs that work with iterables ignore the value inside the done object:
Generators 399
yield*, an operator for making recursive generator calls, does consider values inside done
objects. It is explained later.
function* genFunc() {
throw new Error('Problem!');
}
const genObj = genFunc();
genObj.next(); // Error: Problem!
function* objectEntries(obj) {
// In ES6, you can use strings or symbols as property keys,
// Reflect.ownKeys() retrieves both
const propKeys = Reflect.ownKeys(obj);
This function enables you to iterate over the properties of an object jane via the for-of loop:
Generators 400
function objectEntries(obj) {
let index = 0;
let propKeys = Reflect.ownKeys(obj);
return {
[Symbol.iterator]() {
return this;
},
next() {
if (index < propKeys.length) {
let key = propKeys[index];
index++;
return { value: [key, obj[key]] };
} else {
return { done: true };
}
}
};
}
function* genFunc() {
['a', 'b'].forEach(x => yield x); // SyntaxError
}
yield is not allowed inside non-generator functions, which is why the previous code causes a
syntax error. In this case, it is easy to rewrite the code so that it doesn’t use callbacks (as shown
below). But unfortunately that isn’t always possible.
Generators 401
function* genFunc() {
for (const x of ['a', 'b']) {
yield x; // OK
}
}
The upside of this limitation is explained later: it makes generators easier to implement and
compatible with event loops.
function* foo() {
yield 'a';
yield 'b';
}
How would you call foo from another generator function bar? The following approach does not
work!
function* bar() {
yield 'x';
foo(); // does nothing!
yield 'y';
}
Calling foo() returns an object, but does not actually execute foo(). That’s why ECMAScript 6
has the operator yield* for making recursive generator calls:
Generators 402
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
function* bar() {
yield 'x';
for (const value of foo()) {
yield value;
}
yield 'y';
}
The operand of yield* does not have to be a generator object, it can be any iterable:
function* bla() {
yield 'sequence';
yield* ['of', 'yielded'];
yield 'values';
}
Most constructs that support iterables ignore the value included in the end-of-iteration object
(whose property done is true). Generators provide that value via return. The result of yield*
is the end-of-iteration value:
Generators 403
function* genFuncWithReturn() {
yield 'a';
yield 'b';
return 'The result';
}
function* logReturned(genObj) {
const result = yield* genObj;
console.log(result); // (A)
}
If we want to get to line A, we first must iterate over all values yielded by logReturned():
> [...logReturned(genFuncWithReturn())]
The result
[ 'a', 'b' ]
Iterating over a tree with recursion is simple, writing an iterator for a tree with traditional
means is complicated. That’s why generators shine here: they let you implement an iterator
via recursion. As an example, consider the following data structure for binary trees. It is iterable,
because it has a method whose key is Symbol.iterator. That method is a generator method and
returns an iterator when called.
class BinaryTree {
constructor(value, left=null, right=null) {
this.value = value;
this.left = left;
this.right = right;
}
The following code creates a binary tree and iterates over it via for-of:
Generators 404
interface Observer {
next(value? : any) : void;
return(value? : any) : void;
throw(error) : void;
}
As an observer, a generator pauses until it receives input. There are three kinds of input,
transmitted via the methods specified by the interface:
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`); // (A)
console.log(`2. ${yield}`);
return 'result';
}
We now call genObj.next(), which starts the generator. Execution continues until the first
yield, which is where the generator pauses. The result of next() is the value yielded in line
A (undefined, because yield doesn’t have an operand). In this section, we are not interested in
what next() returns, because we only use it to send values, not to retrieve values.
> genObj.next()
Started
{ value: undefined, done: false }
We call next() two more times, in order to send the value 'a' to the first yield and the value
'b' to the second yield:
> genObj.next('a')
1. a
{ value: undefined, done: false }
> genObj.next('b')
2. b
{ value: 'result', done: true }
The result of the last next() is the value returned from dataConsumer(). done being true
indicates that the generator is finished.
Unfortunately, next() is asymmetric, but that can’t be helped: It always sends a value to the
currently suspended yield, but returns the operand of the following yield.
When using a generator as an observer, it is important to note that the only purpose of the first
invocation of next() is to start the observer. It is only ready for input afterwards, because this
first invocation advances execution to the first yield. Therefore, any input you send via the first
next() is ignored:
Generators 406
function* gen() {
// (A)
while (true) {
const input = yield; // (B)
console.log(input);
}
}
const obj = gen();
obj.next('a');
obj.next('b');
// Output:
// b
• Feeds the argument 'a' of next() to the generator, which has no way to receive it (as
there is no yield). That’s why it is ignored.
• Advances to the yield in line B and pauses execution.
• Returns yield’s operand (undefined, because it doesn’t have an operand).
• Feeds the argument 'b' of next() to the generator, which receives it via the yield in line
B and assigns it to the variable input.
• Then execution continues until the next loop iteration, where it is paused again, in line B.
• Then next() returns with that yield’s operand (undefined).
/**
* Returns a function that, when called,
* returns a generator object that is immediately
* ready for input via `next()`
*/
function coroutine(generatorFunction) {
return function (...args) {
const generatorObject = generatorFunction(...args);
generatorObject.next();
return generatorObject;
};
}
To see how coroutine() works, let’s compare a wrapped generator with a normal one:
Generators 407
> wrapped().next('hello!')
First input: hello!
The normal generator needs an extra next() until it is ready for input:
yield a + b + c;
yield (a + b + c);
Not as:
(yield a) + b + c;
As a consequence, many operators bind more tightly than yield and you have to put yield in
parentheses if you want to use it as an operand. For example, you get a SyntaxError if you make
an unparenthesized yield an operand of plus:
Generators 408
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
You do not need parens if yield is a direct argument in a function or method call:
You also don’t need parens if you use yield on the right-hand side of an assignment:
The need for parens around yield can be seen in the following grammar rules in the ECMAScript
6 specification³. These rules describe how expressions are parsed. I list them here from general
(loose binding, lower precedence) to specific (tight binding, higher precedence). Wherever a
certain kind of expression is demanded, you can also use more specific ones. The opposite is not
true. The hierarchy ends with ParenthesizedExpression, which means that you can mention
any expression anywhere, if you put it in parentheses.
Expression :
AssignmentExpression
Expression , AssignmentExpression
AssignmentExpression :
ConditionalExpression
YieldExpression
ArrowFunction
LeftHandSideExpression = AssignmentExpression
LeftHandSideExpression AssignmentOperator AssignmentExpression
···
AdditiveExpression :
MultiplicativeExpression
AdditiveExpression + MultiplicativeExpression
AdditiveExpression - MultiplicativeExpression
MultiplicativeExpression :
UnaryExpression
MultiplicativeExpression MultiplicativeOperator UnaryExpression
³http://www.ecma-international.org/ecma-262/6.0/#sec-expressions
Generators 409
···
PrimaryExpression :
this
IdentifierReference
Literal
ArrayLiteral
ObjectLiteral
FunctionExpression
ClassExpression
GeneratorExpression
RegularExpressionLiteral
TemplateLiteral
ParenthesizedExpression
ParenthesizedExpression :
( Expression )
return() and throw() work similarly to next(), but they do something different in step 2:
function* genFunc1() {
try {
console.log('Started');
yield; // (A)
} finally {
console.log('Exiting');
}
}
In the following interaction, we first use next() to start the generator and to proceed until the
yield in line A. Then we return from that location via return().
You can prevent return() from terminating the generator if you yield inside the finally clause
(using a return statement in that clause is also possible):
function* genFunc2() {
try {
console.log('Started');
yield;
} finally {
yield 'Not done, yet!';
}
}
This time, return() does not exit the generator function. Accordingly, the property done of the
object it returns is false.
Generators 411
> genObj2.next()
Started
{ value: undefined, done: false }
> genObj2.return('Result')
{ value: 'Not done, yet!', done: false }
You can invoke next() one more time. Similarly to non-generator functions, the return value of
the generator function is the value that was queued prior to entering the finally clause.
> genObj2.next()
{ value: 'Result', done: true }
Returning a value from a newborn generator (that hasn’t started yet) is allowed:
Further reading
return() is also used to close iterators. The chapter on iteration has a detailed section
on that.
function* genFunc1() {
try {
console.log('Started');
yield; // (A)
} catch (error) {
console.log('Caught: ' + error);
}
}
In the following interaction, we first use next() to start the generator and proceed until the
yield in line A. Then we throw an exception from that location.
Generators 412
> genObj1.next()
Started
{ value: undefined, done: false }
The result of throw() (shown in the last line) stems from us leaving the function with an implicit
return.
• Each member of the chain of generators (except the last one) has a parameter target. It
receives data via yield and sends data via target.next().
• The last member of the chain of generators has no parameter target and only receives
data.
The whole chain is prefixed by a non-generator function that makes an asynchronous request
and pushes the results into the chain of generators via next().
As an example, let’s chain generators to process a file that is read asynchronously.
The following code sets up the chain: it contains the generators splitLines, numberLines and
printLines. Data is pushed into the chain via the non-generator function readFile.
⁴https://github.com/rauschma/generator-examples/blob/gh-pages/node/readlines.js
Generators 413
readFile(fileName, splitLines(numberLines(printLines())));
/**
* Creates an asynchronous ReadStream for the file whose name
* is `fileName` and feeds it to the generator object `target`.
*
* @see ReadStream https://nodejs.org/api/fs.html#fs_class_fs_readstream
*/
function readFile(fileName, target) {
const readStream = createReadStream(fileName,
{ encoding: 'utf8', bufferSize: 1024 });
readStream.on('data', buffer => {
const str = buffer.toString('utf8');
target.next(str);
});
readStream.on('end', () => {
// Signal end of output sequence
target.return();
});
}
/**
* Turns a sequence of text chunks into a sequence of lines
* (where lines are separated by newlines)
*/
const splitLines = coroutine(function* (target) {
let previous = '';
try {
while (true) {
previous += yield;
let eolIndex;
while ((eolIndex = previous.indexOf('\n')) >= 0) {
const line = previous.slice(0, eolIndex);
target.next(line);
Generators 414
previous = previous.slice(eolIndex+1);
}
}
} finally {
// Handle the end of the input sequence
// (signaled via `return()`)
if (previous.length > 0) {
target.next(previous);
}
// Signal end of output sequence
target.return();
}
});
• readFile uses the generator object method return() to signal the end of the sequence of
chunks that it sends.
• readFile sends that signal while splitLines is waiting for input via yield, inside an
infinite loop. return() breaks from that loop.
• splitLines uses a finally clause to handle the end-of-sequence.
//**
* Prefixes numbers to a sequence of lines
*/
const numberLines = coroutine(function* (target) {
try {
for (const lineNo = 0; ; lineNo++) {
const line = yield;
target.next(`${lineNo}: ${line}`);
}
} finally {
// Signal end of output sequence
target.return();
}
});
/**
* Receives a sequence of lines (without newlines)
* and logs them (adding newlines).
*/
const printLines = coroutine(function* () {
while (true) {
const line = yield;
console.log(line);
}
});
The neat thing about this code is that everything happens lazily (on demand): lines are split,
numbered and printed as they arrive; we don’t have to wait for all of the text before we can start
printing.
The following generator function caller() invokes the generator function callee() via yield*.
function* callee() {
console.log('callee: ' + (yield));
}
function* caller() {
while (true) {
yield* callee();
}
}
callee logs values received via next(), which allows us to check whether it receives the value
'a' and 'b' that we send to caller.
Generators 416
> callerObj.next('a')
callee: a
{ value: undefined, done: false }
> callerObj.next('b')
callee: b
{ value: undefined, done: false }
I’ll explain the complete semantics of yield* by showing how you’d implemented it in
JavaScript.
The following statement:
let yieldStarResult;
interface Generator {
next(value? : any) : IteratorResult;
throw(value? : any) : IteratorResult;
return(value? : any) : IteratorResult;
}
interface IteratorResult {
value : any;
done : boolean;
}
This interface is described in the spec in the section “Properties of Generator Proto-
type⁵”.
The interface Generator combines two interfaces that we have seen previously: Iterator for
output and Observer for input.
⁵http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-generator-prototype
Generators 418
• Multiprocessing: Web Workers let you run JavaScript in multiple processes. Shared access
to data is one of the biggest pitfalls of multiprocessing. Web Workers avoid it by not sharing
any data. That is, if you want a Web Worker to have a piece of data, you must send it a
copy or transfer your data to it (after which you can’t access it anymore).
• Cooperative multitasking: There are various patterns and libraries that experiment with
cooperative multitasking. Multiple tasks are run, but only one at a time. Each task must
explicitly suspend itself, giving it full control over when a task switch happens. In these
experiments, data is often shared between tasks. But due to explicit suspension, there are
few risks.
Two use cases benefit from cooperative multitasking, because they involve control flows that are
mostly sequential, anyway, with occasional pauses:
• Streams: A task sequentially processes a stream of data and pauses if there is no data
available.
– For binary streams, WHATWG is currently working on a standard proposal⁶ that is
based on callbacks and Promises.
– For streams of data, Communicating Sequential Processes (CSP) are an interesting
solution. A generator-based CSP library is covered later in this chapter.
• Asynchronous computations: A task blocks (pauses) until it receives the result of a long-
running computation.
– In JavaScript, Promises have become a popular way of handling asynchronous
computations. Support for them is included in ES6. The next section explains how
generators can make using Promises simpler.
⁶https://streams.spec.whatwg.org/
Generators 419
Several Promise-based libraries simplify asynchronous code via generators. Generators are ideal
as clients of Promises, because they can be suspended until a result arrives.
The following example demonstrates what that looks like if one uses the library co⁷ by T.J.
Holowaychuk. We need two libraries (if we run Node.js code via babel-node):
co is the actual library for cooperative multitasking, isomorphic-fetch is a polyfill for the new
Promise-based fetch API (a replacement of XMLHttpRequest; read “That’s so fetch!⁸” by Jake
Archibald for more information). fetch makes it easy to write a function getFile that returns
the text of a file at a url via a Promise:
function getFile(url) {
return fetch(url)
.then(request => request.text());
}
We now have all the ingredients to use co. The following task reads the texts of two files, parses
the JSON inside them and logs the result.
co(function* () {
try {
const [croftStr, bondStr] = yield Promise.all([ // (A)
getFile('http://localhost:8000/croft.json'),
getFile('http://localhost:8000/bond.json'),
]);
const croftJson = JSON.parse(croftStr);
const bondJson = JSON.parse(bondStr);
console.log(croftJson);
console.log(bondJson);
} catch (e) {
console.log('Failure to read: ' + e);
}
});
Note how nicely synchronous this code looks, even though it makes an asynchronous call in line
A. A generator-as-task makes an async call by yielding a Promise to the scheduler function co.
The yielding pauses the generator. Once the Promise returns a result, the scheduler resumes the
generator by passing it the result via next(). A simple version of co looks as follows.
⁷https://github.com/tj/co
⁸http://jakearchibald.com/2015/thats-so-fetch/
Generators 420
function co(genFunc) {
const genObj = genFunc();
step(genObj.next());
function step({value,done}) {
if (!done) {
// A Promise was yielded
value
.then(result => {
step(genObj.next(result)); // (A)
})
.catch(error => {
step(genObj.throw(error)); // (B)
});
}
}
}
I have ignored that next() (line A) and throw() (line B) may throw exceptions (whenever an
exception escapes the body of the generator function).
• Generators are compatible with event loops, which provide simple cooperative multitask-
ing in browsers. I’ll explain the details momentarily.
• Generators are relatively easy to implement, because only a single function activation
needs to be suspended and because browsers can continue to use event loops.
JavaScript already has a very simple style of cooperative multitasking: the event loop, which
schedules the execution of tasks in a queue. Each task is started by calling a function and finished
once that function is finished. Events, setTimeout() and other mechanisms add tasks to the
queue.
Generators 421
This explanation of the event loop is a simplification that is good enough for now. If
you are interested in details, consult the chapter on asynchronous programming.
This style of multitasking makes one important guarantee: run to completion; every function can
rely on not being interrupted by another task until it is finished. Functions become transactions
and can perform complete algorithms without anyone seeing the data they operate on in an
itermediate state. Concurrent access to shared data makes multitasking complicated and is not
allowed by JavaScript’s concurrency model. That’s why run to completion is a good thing.
Alas, coroutines prevent run to completion, because any function could suspend its caller. For
example, the following algorithm consists of multiple steps:
step1(sharedData);
step2(sharedData);
lastStep(sharedData);
If step2 was to suspend the algorithm, other tasks could run before the last step of the
algorithm is performed. Those tasks could contain other parts of the application which would
see sharedData in an unfinished state. Generators preserve run to completion, they only suspend
themselves and return to their caller.
co and similar libraries give you most of the power of coroutines, without their disadvantages:
take() converts a (potentially infinite) sequence of iterated values into a sequence of length n:
⁹https://github.com/rauschma/generator-examples
Generators 422
Note that the iterable combinator zip() does not profit much from being implemented via a
generator, because multiple iterables are involved and for-of can’t be used.
function* naturalNumbers() {
for (const n=0;; n++) {
yield n;
}
}
function naturalNumbers() {
let n = 0;
return {
[Symbol.iterator]() {
return this;
},
next() {
return { value: n++ };
}
}
}
Arrays can be transformed via the methods map and filter. Those methods can be generalized
to have iterables as input and iterables as output.
The neat thing is that everything is computed lazily (incrementally and on demand): computation
starts as soon as the first character arrives. For example, we don’t have to wait until we have all
characters to get the first word.
Generators 425
Lazy pull with generators works as follows. The three generators implementing steps 1–3 are
chained as follows:
addNumbers(extractNumbers(tokenize(CHARS)))
Each of the chain members pulls data from a source and yields a sequence of items. Processing
starts with tokenize whose source is the string CHARS.
The following trick makes the code a bit simpler: the end-of-sequence iterator result (whose
property done is false) is converted into the sentinel value END_OF_SEQUENCE.
/**
* Returns an iterable that transforms the input sequence
* of characters into an output sequence of words.
*/
function* tokenize(chars) {
const iterator = chars[Symbol.iterator]();
let ch;
do {
ch = getNextItem(iterator); // (A)
if (isWordChar(ch)) {
let word = '';
do {
word += ch;
ch = getNextItem(iterator); // (B)
} while (isWordChar(ch));
yield word; // (C)
}
// Ignore all other characters
} while (ch !== END_OF_SEQUENCE);
}
const END_OF_SEQUENCE = Symbol();
function getNextItem(iterator) {
const {value,done} = iterator.next();
return done ? END_OF_SEQUENCE : value;
}
function isWordChar(ch) {
return typeof ch === 'string' && /^[A-Za-z0-9]$/.test(ch);
}
How is this generator lazy? When you ask it for a token via next(), it pulls its iterator (lines
A and B) as often as needed to produce as token and then yields that token (line C). Then it
Generators 426
pauses until it is again asked for a token. That means that tokenization starts as soon as the first
characters are available, which is convenient for streams.
Let’s try out tokenization. Note that the spaces and the dot are non-words. They are ignored,
but they separate words. We use the fact that strings are iterables over characters (Unicode code
points). The result of tokenize() is an iterable over words, which we turn into an array via the
spread operator (...).
This step is relatively simple, we only yield words that contain nothing but digits, after
converting them to numbers via Number().
/**
* Returns an iterable that filters the input sequence
* of words and only yields those that are numbers.
*/
function* extractNumbers(words) {
for (const word of words) {
if (/^[0-9]+$/.test(word)) {
yield Number(word);
}
}
}
You can again see the laziness: If you ask for a number via next(), you get one (via yield) as
soon as one is encountered in words.
Let’s extract the numbers from an Array of words:
/**
* Returns an iterable that contains, for each number in
* `numbers`, the total sum of numbers encountered so far.
* For example: 7, 4, -1 --> 7, 11, 10
*/
function* addNumbers(numbers) {
let result = 0;
for (const n of numbers) {
result += n;
yield result;
}
}
On its own, the chain of generator doesn’t produce output. We need to actively pull the output
via the spread operator:
The helper function logAndYield allows us to examine whether things are indeed computed
lazily:
// Output:
// 2
//
Generators 428
// -> 2
// a
// p
// p
// l
// e
// s
//
// a
// n
// d
//
// 5
//
// -> 7
// o
// r
// a
// n
// g
// e
// s
// .
The output shows that addNumbers produces a result as soon as the characters '2' and ' ' are
received.
Not much work is needed to convert the previous pull-based algorithm into a push-based one.
The steps are the same. But instead of finishing via pulling, we start via pushing.
As previously explained, if generators receive input via yield, the first invocation of next() on
the generator object doesn’t do anything. That’s why I use the previously shown helper function
coroutine() to create coroutines here. It executes the first next() for us.
The following function send() does the pushing.
/**
* Pushes the items of `iterable` into `sink`, a generator.
* It uses the generator method `next()` to do so.
*/
function send(iterable, sink) {
for (const x of iterable) {
sink.next(x);
}
sink.return(); // signal end of stream
}
Generators 429
When a generator processes a stream, it needs to be aware of the end of the stream, so that it
can clean up properly. For pull, we did this via a special end-of-stream sentinel. For push, the
end-of-stream is signaled via return().
Let’s test send() via a generator that simply outputs everything it receives:
/**
* This generator logs everything that it receives via `next()`.
*/
const logItems = coroutine(function* () {
try {
while (true) {
const item = yield; // receive item via `next()`
console.log(item);
}
} finally {
console.log('DONE');
}
});
Let’s send logItems() three characters via a string (which is an iterable over Unicode code
points).
Note how this generator reacts to the end of the stream (as signaled via return()) in two
finally clauses. We depend on return() being sent to either one of the two yields. Otherwise,
the generator would never terminate, because the infinite loop starting in line A would never
terminate.
/**
* Receives a sequence of characters (via the generator object
* method `next()`), groups them into words and pushes them
* into the generator `sink`.
*/
const tokenize = coroutine(function* (sink) {
try {
while (true) { // (A)
let ch = yield; // (B)
if (isWordChar(ch)) {
Generators 430
function isWordChar(ch) {
return /^[A-Za-z0-9]$/.test(ch);
}
This time, the laziness is driven by push: as soon as the generator has received enough characters
for a word (in line C), it pushes the word into sink (line D). That is, the generator does not wait
until it has received all characters.
tokenize() demonstrates that generators work well as implementations of linear state machines.
In this case, the machine has two states: “inside a word” and “not inside a word”.
Let’s tokenize a string:
/**
* Receives a sequence of strings (via the generator object
* method `next()`) and pushes only those strings to the generator
* `sink` that are “numbers” (consist only of decimal digits).
*/
const extractNumbers = coroutine(function* (sink) {
try {
while (true) {
const word = yield;
if (/^[0-9]+$/.test(word)) {
sink.next(Number(word));
}
}
} finally {
// Only reached via `return()`, forward.
sink.return();
}
});
Note that the input is a sequence of strings, while the output is a sequence of numbers.
This time, we react to the end of the stream by pushing a single value and then closing the sink.
Generators 432
/**
* Receives a sequence of numbers (via the generator object
* method `next()`). For each number, it pushes the total sum
* so far to the generator `sink`.
*/
const addNumbers = coroutine(function* (sink) {
let sum = 0;
try {
while (true) {
sum += yield;
sink.next(sum);
}
} finally {
// We received an end-of-stream
sink.return(); // signal end of stream
}
});
The chain of generators starts with tokenize and ends with logItems, which logs everything it
receives. We push a sequence of characters into the chain via send:
// Output
// 2
// 7
// DONE
// Output
// 2
//
// -> 2
// a
// p
// p
// l
// e
// s
//
// a
// n
// d
//
// 5
//
// -> 7
// o
// r
// a
// n
// g
// e
// s
// .
// DONE
The output shows that addNumbers produces a result as soon as the characters '2' and ' ' are
pushed.
In this example, we create a counter that is displayed on a web page. We improve an initial
version until we have a cooperatively multitasked version that doesn’t block the main thread
and the user interface.
This is the part of the web page in which the counter should be displayed:
Generators 434
<body>
Counter: <span id="counter"></span>
</body>
function countUp(start = 0) {
const counterSpan = document.querySelector('#counter');
while (true) {
counterSpan.textContent = String(start);
start++;
}
}
If you ran this function, it would completely block the user interface thread in which it runs and
its tab would become unresponsive.
Let’s implement the same functionality via a generator that periodically pauses via yield (a
scheduling function for running this generator is shown later):
function* countUp(start = 0) {
const counterSpan = document.querySelector('#counter');
while (true) {
counterSpan.textContent = String(start);
start++;
yield; // pause
}
}
Let’s add one small improvement. We move the update of the user interface to another generator,
displayCounter, which we call via yield*. As it is a generator, it can also take care of pausing.
function* countUp(start = 0) {
while (true) {
start++;
yield* displayCounter(start);
}
}
function* displayCounter(counter) {
const counterSpan = document.querySelector('#counter');
counterSpan.textContent = String(counter);
yield; // pause
}
Lastly, this is a scheduling function that we can use to run countUp(). Each execution step of the
generator is handled by a separate task, which is created via setTimeout(). That means that the
user interface can schedule other tasks in between and will remain responsive.
¹⁰Or rather, the function counts up until the number start overflows and becomes Infinity, at which point it doesn’t change, anymore.
Generators 435
function run(generatorObject) {
if (!generatorObject.next().done) {
// Add a new task to the event queue
setTimeout(function () {
run(generatorObject);
}, 1000);
}
}
With the help of run, we get a (nearly) infinite count-up that doesn’t block the user interface:
run(countUp());
If you call a generator function (or method), it does not have access to its generator object; its
this is the this it would have if it were a non-generator function. A work-around is to pass the
generator object into the generator function via yield.
The following Node.js script uses this technique, but wraps the generator object in a callback
(next, line A). It must be run via babel-node.
run(function* () {
const next = yield;
for (const f of fileNames) {
const contents = yield readFile(f, { encoding: 'utf8' }, next);
console.log('##### ' + f);
console.log(contents);
}
});
In line A, we get a callback that we can use with functions that follow Node.js callback
conventions. The callback uses the generator object to wake up the generator, as you can see
in the implementation of run():
¹¹https://rauschma.github.io/generator-examples/nonblocking-counter/
Generators 436
function run(generatorFunction) {
const generatorObject = generatorFunction();
The library js-csp¹² brings Communicating Sequential Processes (CSP) to JavaScript, a style of
cooperative multitasking that is similar to ClojureScript’s core.async and Go’s goroutines. js-csp
has two abstractions:
As an example, let’s use CSP to handle DOM events, in a manner reminiscent of Functional
Reactive Programming. The following code uses the function listen() (which is shown later)
to create a channel that outputs mousemove events. It then continuously retrieves the output via
take, inside an infinite loop. Thanks to yield, the process blocks until the channel has output.
¹²https://github.com/ubolonton/js-csp
Generators 437
csp.go(function* () {
const element = document.querySelector('#uiElement1');
const channel = listen(element, 'mousemove');
while (true) {
const event = yield csp.take(channel);
const x = event.layerX || event.clientX;
const y = event.layerY || event.clientY;
element.textContent = `${x}, ${y}`;
}
});
This example is taken from the blog post “Taming the Asynchronous Beast with CSP
Channels in JavaScript¹³” by James Long. Consult this blog post for more information
on CSP.
Legend:
• The white (hollow) arrows express the has-prototype relationship (inheritance) between
objects. In other words: a white arrow from x to y means that Object.getPrototypeOf(x)
=== y.
• Parentheses indicate that an object exists, but is not accessible via a global variable.
• An instanceof arrow from x to y means that x instanceof y.
– Remember that o instanceof C is equivalent to C.prototype.isPrototypeOf(o).
• A prototype arrow from x to y means that x.prototype === y.
• The right column shows an instance with its prototypes, the middle column shows a
function and its prototypes, the left column shows classes for functions (metafunctions,
if you will), connected via a subclass-of relationship.
Second, if you want to make methods available for all generator objects, it’s best to add them to
(Generator).prototype. One way of accessing that object is as follows:
22.7.1 IteratorPrototype
There is no (Iterator) in the diagram, because no such object exists. But, given how instanceof
works and because (IteratorPrototype) is a prototype of g1(), you could still say that g1() is
an instance of Iterator.
All iterators in ES6 have (IteratorPrototype) in their prototype chain. That object is iterable,
because it has the following method. Therefore, all ES6 iterators are iterable (as a consequence,
you can apply for-of etc. to them).
[Symbol.iterator]() {
return this;
}
ECMAScript code may also define objects that inherit from IteratorPrototype.
The IteratorPrototype object provides a place where additional methods that are
applicable to all iterator objects may be added.
That’s why it’s not immediately obvious what the value of this should be inside a generator.
In function calls and method calls, this is what it would be if gen() wasn’t a generator function,
but a normal function:
function* gen() {
'use strict'; // just in case
yield this;
}
If you access this in a generator that was invoked via new, you get a ReferenceError (source:
ES6 spec¹⁶):
function* gen() {
console.log(this); // ReferenceError
}
new gen();
A work-around is to wrap the generator in a normal function that hands the generator its
generator object via next(). That means that the generator must use its first yield to retrieve its
generator object:
Let’s figure out which of these variations make sense for which constructs and why.
generator foo(x, y) {
···
}
Instead of generator, ECMAScript 6 marks the function keyword with an asterisk. Thus,
function* can be seen as a synonym for generator, which suggests writing generator function
declarations as follows.
function* foo(x, y) {
···
}
const obj = {
* generatorMethod(x, y) {
···
}
};
There are three arguments in favor of writing a space after the asterisk.
First, the asterisk shouldn’t be part of the method name. On one hand, it isn’t part of the name of a
generator function. On the other hand, the asterisk is only mentioned when defining a generator,
not when using it.
Second, a generator method definition is an abbreviation for the following syntax. (To make my
point, I’m redundantly giving the function expression a name, too.)
const obj = {
generatorMethod: function* generatorMethod(x, y) {
···
}
};
If method definitions are about omitting the function keyword then the asterisk should be
followed by a space.
Third, generator method definitions are syntactically similar to getters and setters (which are
already available in ECMAScript 5):
const obj = {
get foo() {
···
}
set foo(value) {
···
}
};
The keywords get and set can be seen as modifiers of a normal method definition. Arguably,
an asterisk is also such a modifier.
function* foo(x) {
···
yield* foo(x - 1);
···
}
The asterisk marks a different kind of yield operator, which is why the above way of writing it
makes sense.
22.9.1 Why use the keyword function* for generators and not
generator?
Due to backward compatibility, using the keyword generator wasn’t an option. For example, the
following code (a hypothetical ES6 anonymous generator expression) could be an ES5 function
call followed by a code block.
generator (a, b, c) {
···
}
22.10 Conclusion
I hope that this chapter convinced you that generators are a useful and versatile tool.
I like that generators let you implement cooperatively multitasked tasks that block while making
asynchronous function calls. In my opinion that’s the right mental model for async calls.
Hopefully, JavaScript goes further in this direction in the future.
• “Regular Expressions¹”
• “Unicode and JavaScript²”
23.1 Overview
The following regular expression features are new in ECMAScript 6:
• The new flag /y (sticky) anchors each match of a regular expression to the end of the
previous match.
• The new flag /u (unicode) handles surrogate pairs (such as \uD83D\uDE80) as code points
and lets you use Unicode code point escapes (such as \u{1F680}) in regular expressions.
• The new data property flags gives you access to the flags of a regular expression, just like
source already gives you access to the pattern in ES5:
• You can use the constructor RegExp() to make a copy of a regular expression:
• Anchored to re.lastIndex: The match must start at re.lastIndex (the index after the
previous match). This behavior is similar to the ˆ anchor, but with that anchor, matches
must always start at index 0.
¹http://speakingjs.com/es5/ch19.html
²http://speakingjs.com/es5/ch24.html
New regular expression features 447
• Match repeatedly: If a match was found, re.lastIndex is set to the index after the match.
This behavior is similar to the /g flag. Like /g, /y is normally used to match multiple times.
The main use case for this matching behavior is tokenizing, where you want each match to
immediately follow its predecessor. An example of tokenizing via a sticky regular expression
and exec() is given later.
Let’s look at how various regular expression operations react to the /y flag. The following tables
give an overview. I’ll provide more details afterwards.
Methods of regular expressions (re is the regular expression that a method is invoked on):
Methods of strings (str is the string that a method is invoked on, r is the regular expression
parameter):
23.2.1 RegExp.prototype.exec(str)
If /g is not set, matching always starts at the beginning, but skips ahead until a match is found.
REGEX.lastIndex is not changed.
New regular expression features 448
REGEX.lastIndex = 7; // ignored
const match = REGEX.exec('xaxa');
console.log(match.index); // 1
console.log(REGEX.lastIndex); // 7 (unchanged)
If /g is set, matching starts at REGEX.lastIndex and skips ahead until a match is found.
REGEX.lastIndex is set to the position after the match. That means that you receive all matches
if you loop until exec() returns null.
REGEX.lastIndex = 2;
const match = REGEX.exec('xaxa');
console.log(match.index); // 3
console.log(REGEX.lastIndex); // 4 (updated)
If only /y is set, matching starts at REGEX.lastIndex and is anchored to that position (no skipping
ahead until a match is found). REGEX.lastIndex is updated similarly to when /g is set.
// No match at index 2
REGEX.lastIndex = 2;
console.log(REGEX.exec('xaxa')); // null
// Match at index 3
REGEX.lastIndex = 3;
const match = REGEX.exec('xaxa');
console.log(match.index); // 3
console.log(REGEX.lastIndex); // 4
23.2.2 RegExp.prototype.test(str)
test() works the same as exec(), but it returns true or false (instead of a match object or null)
when matching succeeds or fails:
New regular expression features 449
REGEX.lastIndex = 2;
console.log(REGEX.test('xaxa')); // false
REGEX.lastIndex = 3;
console.log(REGEX.test('xaxa')); // true
console.log(REGEX.lastIndex); // 4
23.2.3 String.prototype.search(regex)
search() ignores the flag /g and lastIndex (which is not changed, either). Starting at the
beginning of the string, it looks for the first match and returns its index (or -1 if there was
no match):
REGEX.lastIndex = 2; // ignored
console.log('xaxa'.search(REGEX)); // 1
If you set the flag /y, lastIndex is still ignored, but the regular expression is now anchored to
index 0.
REGEX.lastIndex = 1; // ignored
console.log('xaxa'.search(REGEX)); // -1 (no match)
23.2.4 String.prototype.match(regex)
match() has two modes:
{
const REGEX = /a/;
REGEX.lastIndex = 7; // ignored
console.log('xaxa'.match(REGEX).index); // 1
console.log(REGEX.lastIndex); // 7 (unchanged)
}
{
const REGEX = /a/y;
REGEX.lastIndex = 2;
console.log('xaxa'.match(REGEX)); // null
REGEX.lastIndex = 3;
console.log('xaxa'.match(REGEX).index); // 3
console.log(REGEX.lastIndex); // 4
}
If only the flag /g is set then match() returns all matching substrings in an Array (or null).
Matching always starts at position 0.
If you additionally set the flag /y, then matching is still performed repeatedly, while anchoring
the regular expression to the index after the previous match (or 0).
REGEX.lastIndex = 0; // ignored
console.log('xab'.match(REGEX)); // null
REGEX.lastIndex = 1; // ignored
console.log('xab'.match(REGEX)); // null
Subsequent separators are only recognized if they immediately follow the first separator:
That means that the string before the first separator and the strings between separators are
always empty.
As usual, you can use groups to put parts of the separators into the result array:
> '##'.split(/(#)/y)
[ '', '#', '', '#', '' ]
// One match
console.log('xaxa'.replace(REGEX, '-')); // 'x-xa'
If only /y is set, you also get at most one match, but that match is always anchored to the
beginning of the string. lastIndex is ignored and unchanged.
// One match
console.log('axa'.replace(REGEX, '-')); // '-xa'
// Multiple matches
console.log('xaxa'.replace(REGEX, '-')); // 'x-x-'
With /gy set, replace() replaces all matches, but each match is anchored to the end of the
previous match:
// Multiple matches
console.log('aaxa'.replace(REGEX, '-')); // '--xa'
The parameter replacement can also be a function, consult “Speaking JavaScript” for details⁴.
In a legal sequence of tokens, sticky matching and non-sticky matching produce the same output:
If, however, there is non-token text in the string then sticky matching stops tokenizing, while
non-sticky matching skips the non-token text:
⁴http://speakingjs.com/es5/ch19.html#String.prototype.replace
New regular expression features 453
The behavior of sticky matching during tokenizing helps with error handling.
1. You can use Unicode code point escape sequences such as \u{1F42A} for specifying
characters via code points. Normal Unicode escapes such as \u03B1 only have a range
of four hexadecimal digits (which equals the basic multilingual plane).
New regular expression features 454
2. “characters” in the regular expression pattern and the string are code points (not UTF-16
code units). Code units are converted into code points.
A section in the chapter on Unicode has more information on escape sequences. I’ll explain the
consequences of feature 2 next. Instead of Unicode code point escapes (e.g., \u{1F680}), I’m using
two UTF-16 code units (e.g., \uD83D\uDE80). That makes it clear that surrogate pairs are grouped
in Unicode mode and works in both Unicode mode and non-Unicode mode.
> /\uD83D/.test('\uD83D\uDC2A')
true
In Unicode mode, surrogate pairs become atomic units and lone surrogates are not found “inside”
them:
> /\uD83D/u.test('\uD83D\uDC2A')
false
> /^[\uD83D\uDC2A]$/u.test('\uD83D\uDC2A')
true
> /^[\uD83D\uDC2A]$/.test('\uD83D\uDC2A')
false
> /^[\uD83D\uDC2A]$/u.test('\uD83D')
false
> /^[\uD83D\uDC2A]$/.test('\uD83D')
true
> '\uD83D\uDE80'.match(/./gu).length
1
> '\uD83D\uDE80'.match(/./g).length
2
> /\uD83D\uDE80{2}/u.test('\uD83D\uDE80\uD83D\uDE80')
true
> /\uD83D\uDE80{2}/.test('\uD83D\uDE80\uD83D\uDE80')
false
> /\uD83D\uDE80{2}/.test('\uD83D\uDE80\uDE80')
true
As an aside, lastIndex is the only instance property now, all other data properties are imple-
mented via internal instance properties and getters such as get RegExp.prototype.global⁵.
The property source (which already existed in ES5) contains the regular expression pattern as a
string:
> /abc/ig.source
'abc'
The property flags is new, it contains the flags as a string, with one character per flag:
> /abc/ig.flags
'gi'
You can’t change the flags of an existing regular expression (ignoreCase etc. have always been
immutable), but flags allows you to make a copy where the flags are changed:
function copyWithIgnoreCase(regex) {
return new RegExp(regex.source, regex.flags+'i');
}
The next section explains another way to make modified copies of regular expressions.
function copyWithIgnoreCase(regex) {
return new RegExp(regex, regex.flags+'i');
}
• Looping over the matches is unnecessarily complicated (you call exec() until it returns
null).
• exec() mutates the regular expression, which means that side effects can become a
problem.
• The flag /g must be set. Otherwise, only the first match is returned.
Using execAll():
For more information, consult Sect. “String methods that delegate regular expression work to
their parameters” in the chapter on strings.
Further reading
If you want to know in more detail how the regular expression flag /u works, I
recommend the article “Unicode-aware regular expressions in ECMAScript 6⁶” by
Mathias Bynens.
⁶https://mathiasbynens.be/notes/es6-unicode-regex
24. Asynchronous programming
(background)
This chapter explains foundations of asynchronous programming in JavaScript. It provides
background knowledge for the next chapter on ES6 Promises.
function h(z) {
// Print stack trace
console.log(new Error().stack); // (A)
}
function g(y) {
h(y + 1); // (B)
}
function f(x) {
g(x + 1); // (C)
}
f(3); // (D)
return; // (E)
Initially, when the program above is started, the call stack is empty. After the function call f(3)
in line D, the stack has one entry:
After the function call g(x + 1) in line C, the stack has two entries:
• Location in f
• Location in global scope
After the function call h(y + 1) in line B, the stack has three entries:
• Location in g
• Location in f
• Location in global scope
The stack trace printed in line A shows you what the call stack looks like:
Asynchronous programming (background) 460
Error
at h (stack_trace.js:2:17)
at g (stack_trace.js:6:5)
at f (stack_trace.js:9:5)
at <global> (stack_trace.js:11:1)
Next, each of the functions terminates and each time the top entry is removed from the stack.
After function f is done, we are back in global scope and the call stack is empty. In line E we
return and the stack is empty, which means that the program terminates.
1. Parsing HTML
2. Executing JavaScript code in script elements
3. Reacting to user input (mouse clicks, key presses, etc.)
4. Processing the result of an asynchronous network request
Items 2–4 are tasks that run JavaScript code, via the engine built into the browser. They terminate
when the code terminates. Then the next task from the queue can be executed. The following
diagram (inspired by a slide by Philip Roberts [1]) gives an overview of how all these mechanisms
are connected.
¹https://html.spec.whatwg.org/multipage/webappapis.html#event-loop
Asynchronous programming (background) 461
The event loop is surrounded by other processes running in parallel to it (timers, input handling,
etc.). These processes communicate with it by adding tasks to its queue.
24.2.1 Timers
Browsers have timers². setTimeout() creates a timer, waits until it fires and then adds a task to
the queue. It has the signature:
setTimeout(callback, ms)
After ms milliseconds, callback is added to the task queue. It is important to note that ms only
specifies when the callback is added, not when it actually executed. That may happen much later,
especially if the event loop is blocked (as demonstrated later in this chapter).
²https://html.spec.whatwg.org/multipage/webappapis.html#timers
Asynchronous programming (background) 462
setTimeout() with ms set to zero is a commonly used work-around to add something to the task
queue right away. However, some browsers do not allow ms to be below a minimum (4 ms in
Firefox); they set it to that minimum if it is.
setTimeout(function () { // (A)
console.log('Second');
}, 0);
console.log('First'); // (B)
The function starting in line A is added to the task queue immediately, but only executed after
the current piece of code is done (in particular line B!). That means that this code’s output will
always be:
First
Second
³https://twitter.com/bz_moz/status/513777809287028736
⁴https://developer.mozilla.org/en/docs/Web/API/window.requestAnimationFrame
Asynchronous programming (background) 463
function onClick(event) {
event.preventDefault();
setStatusMessage('Blocking...');
Whenever the link at the beginning is clicked, the function onClick() is triggered. It uses the –
synchronous – sleep() function to block the event loop for five seconds. During those seconds,
the user interface doesn’t work. For example, you can’t click the “Simple button”.
Second, you don’t (synchronously) wait for the results of a long-running computation (your
own algorithm in a Worker process, a network request, etc.), you carry on with the event loop
and let the computation notify you when it is finished. In fact, you usually don’t even have a
choice in browsers and have to do things this way. For example, there is no built-in way to sleep
synchronously (like the previously implemented sleep()). Instead, setTimeout() lets you sleep
asynchronously.
The next section explains techniques for waiting asynchronously for results.
req.onload = function () {
if (req.status == 200) {
processData(req.response);
} else {
console.log('ERROR', req.statusText);
}
};
req.onerror = function () {
console.log('Network Error');
};
Note that the last line doesn’t actually perform the request, it adds it to the task queue. Therefore,
you could also call that method right after open(), before setting up onload and onerror. Things
would work the same, due to JavaScript’s run-to-completion semantics.
The browser API IndexedDB has a slightly peculiar style of event handling:
Asynchronous programming (background) 465
You first create a request object, to which you add event listeners that are notified of results.
However, you don’t need to explicitly queue the request, that is done by open(). It is executed
after the current task is finished. That is why you can (and in fact must) register event handlers
after calling open().
If you are used to multi-threaded programming languages, this style of handling requests
probably looks strange, as if it may be prone to race conditions. But, due to run to completion,
things are always safe.
This style of handling asynchronously computed results is OK if you receive results multiple
times. If, however, there is only a single result then the verbosity becomes a problem. For that
use case, callbacks have become popular.
// Node.js
fs.readFile('myfile.txt', { encoding: 'utf8' },
function (error, text) { // (A)
if (error) {
// ...
}
console.log(text);
});
If readFile() is successful, the callback in line A receives a result via the parameter text. If
it isn’t, the callback gets an error (often an instance of Error or a sub-constructor) via its first
parameter.
The same code in classic functional programming style would look like this:
Asynchronous programming (background) 466
// Functional
readFileFunctional('myfile.txt', { encoding: 'utf8' },
function (text) { // success
console.log(text);
},
function (error) { // failure
// ...
});
console.log('A');
identity('B', function step2(result2) {
console.log(result2);
identity('C', function step3(result3) {
console.log(result3);
});
console.log('D');
});
console.log('E');
// Output: A E B D C
For each step, the control flow of the program continues inside the callback. This leads to
nested functions, which are sometimes referred to as callback hell. However, you can often avoid
nesting, because JavaScript’s function declarations are hoisted (their definitions are evaluated at
the beginning of their scope). That means that you can call ahead and invoke functions defined
later in the program. The following code uses hoisting to flatten the previous example.
Asynchronous programming (background) 467
console.log('A');
identity('B', step2);
function step2(result2) {
// The program continues here
console.log(result2);
identity('C', step3);
console.log('D');
}
function step3(result3) {
console.log(result3);
}
console.log('E');
1. Putting them one after another. This is blindingly obvious, but it’s good to remind ourselves
that concatenating code in normal style is sequential composition.
2. Array methods such as map(), filter() and forEach()
3. Loops such as for and while
The library Async.js⁷ provides combinators to let you do similar things in CPS, with Node.js-style
callbacks. It is used in the following example to load the contents of three files, whose names are
stored in an Array.
• Error handling becomes more complicated: There are now two ways in which errors
are reported – via callbacks and via exceptions. You have to be careful to combine both
properly.
• Less elegant signatures: In synchronous functions, there is a clear separation of concerns
between input (parameters) and output (function result). In asynchronous functions that
use callbacks, these concerns are mixed: the function result doesn’t matter and some
parameters are used for input, others for output.
• Composition is more complicated: Because the concern “output” shows up in the param-
eters, it is more complicated to compose code via combinators.
Callbacks in Node.js style have three disadvantages (compared to those in a functional style):
25.1 Overview
Promises are an alternative to callbacks for delivering the results of an asynchronous computa-
tion. They require more effort from implementors of asynchronous functions, but provide several
benefits for users of those functions.
The following function returns a result asynchronously, via a Promise:
function asyncFunc() {
return new Promise(
function (resolve, reject) {
···
resolve(result);
···
reject(error);
});
}
asyncFunc()
.then(result => { ··· })
.catch(error => { ··· });
asyncFunc1()
.then(result1 => {
// Use result1
return asyncFunction2(); // (A)
})
.then(result2 => { // (B)
// Use result2
})
.catch(error => {
// Handle errors of asyncFunc1() and asyncFunc2()
});
How the Promise P returned by then() is settled depends on what its callback does:
• If it returns a Promise (as in line A), the settlement of that Promise is forwarded to P. That’s
why the callback from line B can pick up the settlement of asyncFunction2’s Promise.
• If it returns a different value, that value is used to settle P.
• If throws an exception then P is rejected with that exception.
Furthermore, note how catch() handles the errors of two asynchronous function calls (asyncFunction1()
and asyncFunction2()). That is, uncaught errors are passed on until there is an error handler.
asyncFunc1()
.then(() => asyncFunc2());
If you don’t do that and call all of them immediately, they are basically executed in parallel (a
fork in Unix process terminology):
asyncFunc1();
asyncFunc2();
Promise.all() enables you to be notified once all results are in (a join in Unix process
terminology). Its input is an Array of Promises, its output a single Promise that is fulfilled with
an Array of the results.
Promises for asynchronous programming 471
Promise.all([
asyncFunc1(),
asyncFunc2(),
])
.then(([result1, result2]) => {
···
})
.catch(err => {
// Receives first rejection among the Promises
···
});
• Promise reactions are callbacks that you register with the Promise method then(), to be
notified of a fulfillment or a rejection.
• A thenable is an object that has a Promise-style then() method. Whenever the API
is only interested in being notified of settlements, it only demands thenables (e.g. the
values returned from then() and catch(); or the values handed to Promise.all() and
Promise.race()).
Changing states: There are two operations for changing the state of a Promise. After you have
invoked either one of them once, further invocations have no effect.
asyncFunction(arg1, arg2,
result => {
console.log(result);
});
Promises provide a better way of working with callbacks: Now an asynchronous function returns
a Promise, an object that serves as a placeholder and container for the final result. Callbacks
registered via the Promise method then() are notified of the result:
asyncFunction(arg1, arg2)
.then(result => {
console.log(result);
});
asyncFunction1(a, b)
.then(result1 => {
console.log(result1);
return asyncFunction2(x, y);
})
.then(result2 => {
console.log(result2);
});
• Composing asynchronous calls (loops, mapping, etc.): is a little easier, because you have
data (Promise objects) you can work with.
• Error handling: As we shall see later, error handling is simpler with Promises, because, once
again, there isn’t an inversion of control. Furthermore, both exceptions and asynchronous
errors are managed the same way.
Promises for asynchronous programming 473
• Cleaner signatures: With callbacks, the parameters of a function are mixed; some are input
for the function, others are responsible for delivering its output. With Promises, function
signatures become cleaner; all parameters are input.
• Standardized: Prior to Promises, there were several incompatible ways of handling asyn-
chronous results (Node.js callbacks, XMLHttpRequest, IndexedDB, etc.). With Promises,
there is a clearly defined standard: ECMAScript 6. ES6 follows the standard Promises/A+
[1]. Since ES6, an increasing number of APIs is based on Promises.
fs.readFile('config.json',
function (error, text) {
if (error) {
console.error('Error while reading config file');
} else {
try {
const obj = JSON.parse(text);
console.log(JSON.stringify(obj, null, 4));
} catch (e) {
console.error('Invalid JSON in file');
}
}
});
readFilePromisified('config.json')
.then(function (text) { // (A)
const obj = JSON.parse(text);
console.log(JSON.stringify(obj, null, 4));
})
.catch(function (error) { // (B)
// File read error or JSON SyntaxError
console.error('An error occurred', error);
});
There are still callbacks, but they are provided via methods that are invoked on the result (then()
and catch()). The error callback in line B is convenient in two ways: First, it’s a single style
of handling errors (versus if (error) and try-catch in the previous example). Second, you
can handle the errors of both readFilePromisified() and the callback in line A from a single
location.
The code of readFilePromisified() is shown later.
Promises for asynchronous programming 474
function asyncFunc() {
return new Promise((resolve, reject) => { // (A)
setTimeout(() => resolve('DONE'), 100); // (B)
});
}
asyncFunc()
.then(x => console.log('Result: '+x));
// Output:
// Result: DONE
asyncFunc() returns a Promise. Once the actual result 'DONE' of the asynchronous computation
is ready, it is delivered via resolve() (line B), which is a parameter of the callback that starts in
line A.
So what is a Promise?
// Same as:
// asyncFunc()
// .then(x => console.log('Result: '+x));
}
main();
The body of main() expresses well what’s going on conceptually, how we usually think about
asynchronous computations. Namely, asyncFunc() is a blocking function call:
¹http://exploringjs.com/es2016-es2017/ch_async-functions.html
Promises for asynchronous programming 475
Prior to ECMAScript 6 and generators, you couldn’t suspend and resume code. That’s why, for
Promises, you put everything that happens after the code is resumed into a callback. Invoking
that callback is the same as resuming the code.
function asyncFunc() {
const blank = [];
setTimeout(() => blank.push('DONE'), 100);
return blank;
}
const blank = asyncFunc();
// Wait until the value has been filled in
setTimeout(() => {
const x = blank[0]; // (A)
console.log('Result: '+x);
}, 200);
With Promises, you don’t access the eventual value via [0] (as in line A), you use method then()
and a callback.
function asyncFunc() {
const eventEmitter = { success: [] };
setTimeout(() => { // (A)
for (const handler of eventEmitter.success) {
handler('DONE');
}
}, 100);
return eventEmitter;
}
asyncFunc()
.success.push(x => console.log('Result: '+x)); // (B)
Promises for asynchronous programming 476
Registering the event listener (line B) can be done after calling asyncFunc(), because the callback
handed to setTimeout() (line A) is executed asynchronously (after this piece of code is finished).
Normal event emitters specialize in delivering multiple events, starting as soon as you register.
In contrast, Promises specialize in delivering exactly one value and come with built-in protection
against registering too late: the result of a Promise is cached and passed to event listeners that
are registered after the Promise was settled.
• Pending: the result hasn’t been computed, yet (the initial state of each Promise)
• Fulfilled: the result was computed successfully
• Rejected: a failure occurred during computation
A Promise is settled (the computation it represents has finished) if it is either fulfilled or rejected.
A Promise can only be settled once and then stays settled. Subsequent attempts to settle have no
effect.
Promises for asynchronous programming 477
• Resolving: If the computation went well, the executor sends the result via resolve(). That
usually fulfills the Promise p. But it may not – resolving with a Promise q leads to p tracking
q: If q is still pending then so is p. However q is settled, p will be settled the same way.
• Rejecting: If an error happened, the executor notifies the Promise consumer via reject().
That always rejects the Promise.
promise
.then(value => { /* fulfillment */ })
.catch(error => { /* rejection */ });
What makes Promises so useful for asynchronous functions (with one-off results) is that once a
Promise is settled, it doesn’t change anymore. Furthermore, there are never any race conditions,
because it doesn’t matter whether you invoke then() or catch() before or after a Promise is
settled:
• Reactions that are registered with a Promise before it is settled, are notified of the
settlement once it happens.
• Reactions that are registered with a Promise after it is settled, receive the cached settled
value “immediately” (their invocations are queued as tasks).
Note that catch() is simply a more convenient (and recommended) alternative to calling then().
That is, the following two invocations are equivalent:
Promises for asynchronous programming 478
promise.then(
null,
error => { /* rejection */ });
promise.catch(
error => { /* rejection */ });
onFulfilled or onRejected must not be called until the execution context stack
contains only platform code.
That means that your code can rely on run-to-completion semantics (as explained in the previous
chapter) and that chaining Promises won’t starve other tasks of processing time.
Additionally, this constraint prevents you from writing functions that sometimes return results
immediately, sometimes asynchronously. This is an anti-pattern, because it makes code unpre-
dictable. For more information, consult “Designing APIs for Asynchrony³” by Isaac Z. Schlueter.
25.6 Examples
Before we dig deeper into Promises, let’s use what we have learned so far in a few examples.
Some of the examples in this section are available in the GitHub repository
promise-examples⁴.
²http://promisesaplus.com/#point-34
³http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
⁴https://github.com/rauschma/promise-examples
⁵https://nodejs.org/api/fs.html#fs_fs_readfile_filename_options_callback
Promises for asynchronous programming 479
function readFilePromisified(filename) {
return new Promise(
function (resolve, reject) {
readFile(filename, { encoding: 'utf8' },
(error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
readFilePromisified(process.argv[2])
.then(text => {
console.log(text);
})
.catch(error => {
console.log(error);
});
function httpGet(url) {
return new Promise(
function (resolve, reject) {
const request = new XMLHttpRequest();
request.onload = function () {
if (this.status === 200) {
// Success
resolve(this.response);
} else {
// Something went wrong (404 etc.)
reject(new Error(this.statusText));
}
⁶https://xhr.spec.whatwg.org/
Promises for asynchronous programming 480
};
request.onerror = function () {
reject(new Error(
'XMLHttpRequest Error: '+this.statusText));
};
request.open('GET', url);
request.send();
});
}
httpGet('http://example.com/file.txt')
.then(
function (value) {
console.log('Contents: ' + value);
},
function (reason) {
console.error('Something went wrong', reason);
});
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms); // (A)
});
}
// Using delay():
delay(5000).then(function () { // (B)
console.log('5 seconds have passed!')
});
Note that in line A, we are calling resolve with zero parameters, which is the same as calling
resolve(undefined). We don’t need the fulfillment value in line B, either and simply ignore it.
Just being notified is enough here.
⁷https://github.com/kriskowal/q/wiki/API-Reference#qdelayms
Promises for asynchronous programming 481
Note that the rejection after the timeout (in line A) does not cancel the request, but it does prevent
the Promise being fulfilled with its result.
Using timeout() looks like this:
timeout(5000, httpGet('http://example.com/file.txt'))
.then(function (value) {
console.log('Contents: ' + value);
})
.catch(function (reason) {
console.error('Error or timeout', reason);
});
25.7.1 Promise.resolve()
Promise.resolve(x) works as follows:
Promise.resolve('abc')
.then(x => console.log(x)); // abc
const fulfilledThenable = {
then(reaction) {
reaction('hello');
}
};
const promise = Promise.resolve(fulfilledThenable);
console.log(promise instanceof Promise); // true
promise.then(x => console.log(x)); // hello
That means that you can use Promise.resolve() to convert any value (Promise, thenable or
other) to a Promise. In fact, it is used by Promise.all() and Promise.race() to convert Arrays
of arbitrary values to Arrays of Promises.
25.7.2 Promise.reject()
Promise.reject(err) returns a Promise that is rejected with err:
P.then(onFulfilled, onRejected)
is a new Promise Q. That means that you can keep the Promise-based control flow going by
invoking then() on Q:
asyncFunc()
.then(function (value1) {
return 123;
})
.then(function (value2) {
console.log(value2); // 123
});
The main use for this mechanism is to flatten nested then() calls, like in the following example:
asyncFunc1()
.then(function (value1) {
asyncFunc2()
.then(function (value2) {
···
});
})
asyncFunc1()
.then(function (value1) {
return asyncFunc2();
})
.then(function (value2) {
···
})
retrieveFileName()
.catch(function () {
// Something went wrong, use a default value
return 'Untitled.txt';
})
.then(function (fileName) {
···
});
asyncFunc()
.then(function (value) {
throw new Error();
})
.catch(function (reason) {
// Handle error here
});
asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.catch(function (reason) {
// Something went wrong above
});
// Don’t do this
function foo() {
const promise = asyncFunc();
promise.then(result => {
···
});
return promise;
}
function foo() {
const promise = asyncFunc();
return promise.then(result => {
···
});
}
If you don’t need the variable promise, you can simplify this code further:
function foo() {
return asyncFunc()
.then(result => {
···
});
}
// Don’t do this
asyncFunc1()
.then(result1 => {
asyncFunc2()
.then(result2 => {
···
});
});
The fix is to un-nest this code by returning the second Promise from the first then() and handling
it via a second, chained, then():
Promises for asynchronous programming 486
asyncFunc1()
.then(result1 => {
return asyncFunc2();
})
.then(result2 => {
···
});
// Don’t do this
class Model {
insertInto(db) {
return new Promise((resolve, reject) => { // (A)
db.insert(this.fields) // (B)
.then(resultCode => {
this.notifyObservers({event: 'created', model: this});
resolve(resultCode); // (C)
}).catch(err => {
reject(err); // (D)
})
});
}
···
}
If you look closely, you can see that the result Promise is mainly used to forward the fulfillment
(line C) and the rejection (line D) of the asynchronous method call db.insert() (line B).
The fix is to not create a Promise, by relying on then() and chaining:
class Model {
insertInto(db) {
return db.insert(this.fields) // (A)
.then(resultCode => {
this.notifyObservers({event: 'created', model: this});
return resultCode; // (B)
});
}
···
}
Explanations:
Promises for asynchronous programming 487
• We return resultCode (line B) and let then() create the Promise for us.
• We return the Promise chain (line A) and then() will pass on any rejection produced by
db.insert().
// Don’t do this
asyncFunc1()
.then(
value => { // (A)
doSomething(); // (B)
return asyncFunc2(); // (C)
},
error => { // (D)
···
});
The rejection callback (line D) receives all rejections of asyncFunc1(), but it does not receive
rejections created by the fulfillment callback (line A). For example, the synchronous function
call in line B may throw an exception or the asynchronous function call in line C may produce
a rejection.
Therefore, it is better to move the rejection callback to a chained catch():
asyncFunc1()
.then(value => {
doSomething();
return asyncFunc2();
})
.catch(error => {
···
});
For operational errors, each function should support exactly one way of signaling errors. For
Promise-based functions that means not mixing rejections and exceptions, which is the same as
saying that they shouldn’t throw exceptions.
For programmer errors, it can make sense to fail as quickly as possible, by throwing an exception:
function downloadFile(url) {
if (typeof url !== 'string') {
throw new Error('Illegal argument: ' + url);
}
return new Promise(···).
}
If you do this, you must make sure that your asynchronous code can handle exceptions. I find
throwing exceptions acceptable for assertions and similar things that could, in theory, be checked
statically (e.g. via a linter that analyzes the source code).
function asyncFunc() {
doSomethingSync(); // (A)
return doSomethingAsync()
.then(result => {
···
});
}
If an exception is thrown in line A then the whole function throws an exception. There are two
solutions to this problem.
function asyncFunc() {
try {
doSomethingSync();
return doSomethingAsync()
.then(result => {
···
});
} catch (err) {
return Promise.reject(err);
}
}
You can also start a chain of then() method calls via Promise.resolve() and execute the
synchronous code inside a callback:
function asyncFunc() {
return Promise.resolve()
.then(() => {
doSomethingSync();
return doSomethingAsync();
})
.then(result => {
···
});
}
function asyncFunc() {
return new Promise((resolve, reject) => {
doSomethingSync();
resolve(doSomethingAsync());
})
.then(result => {
···
});
}
This approach saves you a tick (the synchronous code is executed right away), but it makes your
code less regular.
Promises for asynchronous programming 490
• Chaining:
– “Promise Anti-patterns⁸” on Tao of Code.
• Error handling:
– “Error Handling in Node.js⁹” by Joyent
– A post by user Mörre Noseshine¹⁰ in the “Exploring ES6” Google Group
– Feedback to a tweet¹¹ asking whether it is OK to throw exceptions from Promise-
based functions.
P.then(() => Q)
Note that this is similar to the semicolon for synchronous code: Sequential composition of the
synchronous operations f() and g() looks as follows.
f(); g()
⁸http://taoofcode.net/promise-anti-patterns/
⁹https://www.joyent.com/developers/node/design/errors
¹⁰https://groups.google.com/d/topic/exploring-es6/vZDdN8dCx0w/discussion
¹¹https://twitter.com/rauschma/status/713371400686473216
Promises for asynchronous programming 491
// Don’t do this
asyncFunc1()
.then(result1 => {
handleSuccess({result1});
});
.catch(handleError);
asyncFunc2()
.then(result2 => {
handleSuccess({result2});
})
.catch(handleError);
The two function calls asyncFunc1() and asyncFunc2() are made without then() chaining.
As a consequence, they are both executed immediately and more or less in parallel. Execution
is now forked; each function call spawned a separate “thread”. Once both threads are finished
(with a result or an error), execution is joined into a single thread in either handleSuccess() or
handleError().
The problem with this approach is that it involves too much manual and error-prone work. The
fix is to not do this yourself, by relying on the built-in method Promise.all().
Promise.all([
asyncFunc1(),
asyncFunc2(),
])
.then(([result1, result2]) => {
···
})
.catch(err => {
// Receives first rejection among the Promises
···
});
const fileUrls = [
'http://example.com/file1.txt',
'http://example.com/file2.txt',
];
const promisedTexts = fileUrls.map(httpGet);
Promise.all(promisedTexts)
.then(texts => {
for (const text of texts) {
console.log(text);
}
})
.catch(reason => {
// Receives first rejection among the Promises
});
Promise.race([
httpGet('http://example.com/file.txt'),
delay(5000).then(function () {
throw new Error('Timed out')
});
])
.then(function (text) { ··· })
.catch(function (reason) { ··· });
25.12.1 done()
When you chain several Promise method calls, you risk silently discarding errors. For example:
function doSomething() {
asyncFunc()
.then(f1)
.catch(r1)
.then(f2); // (A)
}
If then() in line A produces a rejection, it will never be handled anywhere. The Promise library
Q provides a method done(), to be used as the last element in a chain of method calls. It either
replaces the last then() (and has one to two arguments):
function doSomething() {
asyncFunc()
.then(f1)
.catch(r1)
.done(f2);
}
function doSomething() {
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
}
The Golden Rule of done versus then usage is: either return your promise to someone
else, or if the chain ends with you, call done to terminate it. Terminating with catch
is not sufficient because the catch handler may itself throw an error.
While done’s functionality is clearly useful, it has not been added to ECMAScript 6. The idea
was to first explore how much engines can detect automatically. Depending on how well that
works, it may to be necessary to introduce done().
25.12.2 finally()
Sometimes you want to perform an action independently of whether an error happened or not.
For example, to clean up after you are done with a resource. That’s what the Promise method
finally() is for, which works much like the finally clause in exception handling. Its callback
receives no arguments, but is notified of either a resolution or a rejection.
¹²https://github.com/kriskowal/q/wiki/API-Reference#promisedoneonfulfilled-onrejected-onprogress
Promises for asynchronous programming 495
createResource(···)
.then(function (value1) {
// Use resource
})
.then(function (value2) {
// Use resource
})
.finally(function () {
// Clean up
});
The callback determines how the settlement of the receiver (this) is handled:
• If the callback throws an exception or returns a rejected Promise then that becomes/con-
tributes the rejection value.
• Otherwise, the settlement (fulfillment or rejection) of the receiver becomes the settlement
of the Promise returned by finally(). In a way, we take finally() out of the chain of
methods.
Example 1 (by Jake Archibald¹⁴): using finally() to hide a spinner. Simplified version:
¹³https://github.com/domenic/promises-unwrapping/issues/18
¹⁴https://gist.github.com/jakearchibald/785f79b0dea5bfe0c448
Promises for asynchronous programming 496
showSpinner();
fetchGalleryData()
.then(data => updateGallery(data))
.catch(showNoDataError)
.finally(hideSpinner);
readFile('foo.txt', 'utf-8')
.then(function (text) {
···
});
denodify¹⁷ is a micro-library that only provides the functionality of Q.denodeify() and complies
with the ECMAScript 6 Promise API.
• “ES6-Promises¹⁸” by Jake Archibald extracts just the ES6 API out of RSVP.js.
• “Native Promise Only (NPO)¹⁹” by Kyle Simpson is “a polyfill for native ES6 promises, as
close as possible (no extensions) to the strict spec definitions”.
• “Lie²⁰” by Calvin Metcalf is “a small, performant, promise library implementing the
Promises/A+ spec”.
The solution is to bring blocking calls to JavaScript. Generators let us do that, via libraries: In
the following code, I use the control flow library co²⁶ to asynchronously retrieve two JSON files.
¹⁸https://github.com/jakearchibald/es6-promise
¹⁹https://github.com/getify/native-promise-only
²⁰https://github.com/calvinmetcalf/lie
²¹https://github.com/tildeio/rsvp.js/
²²https://github.com/petkaantonov/bluebird
²³https://github.com/kriskowal/q#using-qpromise
²⁴https://github.com/paulmillr/es6-shim
²⁵https://github.com/zloirock/core-js
²⁶https://github.com/tj/co
Promises for asynchronous programming 498
co(function* () {
try {
const [croftStr, bondStr] = yield Promise.all([ // (A)
getFile('http://localhost:8000/croft.json'),
getFile('http://localhost:8000/bond.json'),
]);
const croftJson = JSON.parse(croftStr);
const bondJson = JSON.parse(bondStr);
console.log(croftJson);
console.log(bondJson);
} catch (e) {
console.log('Failure to read: ' + e);
}
});
In line A, execution blocks (waits) via yield until the result of Promise.all() is ready. That
means that the code looks synchronous while performing asynchronous operations.
Details are explained in the chapter on generators.
• DemoPromise.prototype.resolve(value)
• DemoPromise.prototype.reject(reason)
• DemoPromise.prototype.then(onFulfilled, onRejected)
That is, resolve and reject are methods (versus functions handed to a callback parameter of
the constructor).
²⁷https://github.com/rauschma/demo_promise
Promises for asynchronous programming 499
25.17.1.1 DemoPromise.prototype.then()
then(onFulfilled, onRejected) {
const self = this;
const fulfilledTask = function () {
onFulfilled(self.promiseResult);
};
const rejectedTask = function () {
onRejected(self.promiseResult);
};
switch (this.promiseState) {
case 'pending':
this.fulfillReactions.push(fulfilledTask);
this.rejectReactions.push(rejectedTask);
break;
case 'fulfilled':
addToTaskQueue(fulfilledTask);
break;
case 'rejected':
addToTaskQueue(rejectedTask);
break;
}
}
function addToTaskQueue(task) {
setTimeout(task, 0);
}
25.17.1.2 DemoPromise.prototype.resolve()
resolve() works as follows: If the Promise is already settled, it does nothing (ensuring that a
Promise can only be settled once). Otherwise, the state of the Promise changes to 'fulfilled'
and the result is cached in this.promiseResult. Next, all fulfillment reactions, that have been
enqueued so far, are be triggered.
resolve(value) {
if (this.promiseState !== 'pending') return;
this.promiseState = 'fulfilled';
this.promiseResult = value;
this._clearAndEnqueueReactions(this.fulfillReactions);
return this; // enable chaining
}
_clearAndEnqueueReactions(reactions) {
this.fulfillReactions = undefined;
this.rejectReactions = undefined;
reactions.map(addToTaskQueue);
}
Promises for asynchronous programming 501
25.17.2 Chaining
The next feature we implement is chaining:
• then() returns a Promise that is resolved with what either onFulfilled or onRejected
return.
• If onFulfilled or onRejected are missing, whatever they would have received is passed
on to the Promise returned by then().
then(onFulfilled, onRejected) {
const returnValue = new Promise(); // (A)
const self = this;
let fulfilledTask;
if (typeof onFulfilled === 'function') {
fulfilledTask = function () {
const r = onFulfilled(self.promiseResult);
returnValue.resolve(r); // (B)
};
} else {
fulfilledTask = function () {
returnValue.resolve(self.promiseResult); // (C)
};
}
let rejectedTask;
if (typeof onRejected === 'function') {
Promises for asynchronous programming 502
rejectedTask = function () {
const r = onRejected(self.promiseResult);
returnValue.resolve(r); // (D)
};
} else {
rejectedTask = function () {
// `onRejected` has not been provided
// => we must pass on the rejection
returnValue.reject(self.promiseResult); // (E)
};
}
···
return returnValue; // (F)
}
then() creates and returns a new Promise (lines A and F). Additionally, fulfilledTask and
rejectedTask are set up differently: After a settlement…
25.17.3 Flattening
Flattening is mostly about making chaining more convenient: Normally, returning a value from
a reaction passes it on to the next then(). If we return a Promise, it would be nice if it could be
“unwrapped” for us, like in the following example:
asyncFunc1()
.then(function (value1) {
return asyncFunc2(); // (A)
})
.then(function (value2) {
// value2 is fulfillment value of asyncFunc2() Promise
console.log(value2);
});
We returned a Promise in line A and didn’t have to nest a call to then() inside the current
method, we could invoke then() on the method’s result. Thus: no nested then(), everything
remains flat.
We implement this by letting the resolve() method do the flattening:
Promises for asynchronous programming 503
• Resolving a Promise P with a Promise Q means that Q’s settlement is forwarded to P’s
reactions.
• P becomes “locked in” on Q: it can’t be resolved (incl. rejected), anymore. And its state
and result are always the same as Q’s.
We can make flattening more generic if we allow Q to be a thenable (instead of only a Promise).
resolve(value) {
if (this.alreadyResolved) return;
this.alreadyResolved = true;
this._doResolve(value);
return this; // enable chaining
}
_doResolve(value) {
const self = this;
// Is `value` a thenable?
if (typeof value === 'object' && value !== null && 'then' in value) {
// Forward fulfillments and rejections from `value` to `this`.
// Added as a task (versus done immediately) to preserve async semant\
ics.
addToTaskQueue(function () { // (A)
value.then(
Promises for asynchronous programming 504
function onFulfilled(result) {
self._doResolve(result);
},
function onRejected(error) {
self._doReject(error);
});
});
} else {
this.promiseState = 'fulfilled';
this.promiseResult = value;
this._clearAndEnqueueReactions(this.fulfillReactions);
}
}
The flattening is performed in line A: If value is fulfilled, we want self to be fulfilled and if
value is rejected, we want self to be rejected. The forwarding happens via the private methods
_doResolve and _doReject, to get around the protection via alreadyResolved.
If you are only using Promises, you can normally adopt a simplified worldview and ignore
locking-in. The most important state-related concept remains “settledness”: a Promise is settled
if it is either fulfilled or rejected. After a Promise is settled, it doesn’t change, anymore (state and
fulfillment or rejection value).
If you want to implement Promises then “resolving” matters, too and is now harder to
understand:
25.17.5 Exceptions
As our final feature, we’d like our Promises to handle exceptions in user code as rejections. For
now, “user code” means the two callback parameters of then().
Promises for asynchronous programming 506
The following excerpt shows how we turn exceptions inside onFulfilled into rejections – by
wrapping a try-catch around its invocation in line A.
then(onFulfilled, onRejected) {
···
let fulfilledTask;
if (typeof onFulfilled === 'function') {
fulfilledTask = function () {
try {
const r = onFulfilled(self.promiseResult); // (A)
returnValue.resolve(r);
} catch (e) {
returnValue.reject(e);
}
};
} else {
fulfilledTask = function () {
returnValue.resolve(self.promiseResult);
};
}
···
}
One important advantage of Promises is that they will increasingly be used by asynchronous
browser APIs and unify currently diverse and incompatible patterns and conventions. Let’s look
at two upcoming Promise-based APIs.
The fetch API²⁹ is a Promise-based alternative to XMLHttpRequest:
fetch(url)
.then(request => request.text())
.then(str => ···)
fetch() returns a Promise for the actual request, text() returns a Promise for the content as a
string.
The ECMAScript 6 API for programmatically importing modules is based on Promises, too:
²⁹http://jakearchibald.com/2015/thats-so-fetch/
Promises for asynchronous programming 508
System.import('some_module.js')
.then(some_module => {
···
})
Compared to events, Promises are better for handling one-off results. It doesn’t matter whether
you register for a result before or after it has been computed, you will get it. This advantage of
Promises is fundamental in nature. On the flip side, you can’t use them for handling recurring
events. Chaining is another advantage of Promises, but one that could be added to event handling.
Compared to callbacks, Promises have cleaner function (or method) signatures. With callbacks,
parameters are used for input and output:
• Recurring events: If you are interested in those, take a look at reactive programming³⁰,
which add a clever way of chaining to normal event handling.
• Streams of data: A standard³¹ for supporting those is currently in development.
The Q Promise library has support³² for the latter and there are plans³³ to add both capabilities
to Promises/A+.
The callback of this constructor is called an executor. The executor can use its parameters to
resolve or reject the new Promise p:
The following two static methods create new instances of their receivers:
Intuitively, the static methods Promise.all() and Promise.race() compose iterables of Promises
to a single Promise. That is:
• They take an iterable. The elements of the iterable are converted to Promises via
this.resolve().
• They return a new Promise. That Promise is a new instance of the receiver.
function defaultOnFulfilled(x) {
return x;
}
function defaultOnRejected(e) {
throw e;
}
Promises for asynchronous programming 511
25.19.3.2 Promise.prototype.catch(onRejected)
³⁶http://promisesaplus.com/
³⁷http://domenic.me/2014/02/13/the-revealing-constructor-pattern/
VI Miscellaneous
26. Unicode in ES6
This chapter explains the improved support for Unicode that ECMAScript 6 brings. For a general
introduction to Unicode, read Chap. “Unicode and JavaScript¹” in “Speaking JavaScript”.
Additionally, ES6 is based on Unicode version 5.1.0, whereas ES5 is based on Unicode version
3.0.
¹http://speakingjs.com/es5/ch24.html
Unicode in ES6 514
Unicode code point escapes are new in ES6. They let you specify code points beyond 16 bits. If
you wanted to do that in ECMAScript 5, you had to encode each code point as two UTF-16 code
units (a surrogate pair). These code units could be expressed via Unicode escapes. For example,
the following statement logs a rocket (code point 0x1F680) to most consoles:
console.log('\uD83D\uDE80');
With a Unicode code point escape you can specify code points greater than 16 bits directly:
console.log('\u{1F680}');
Identifiers ✔ ✔
String literals ✔ ✔ ✔
Template literals ✔ ✔ ✔
Regular expression literals ✔ Only with flag /u ✔
Identifiers:
String literals:
Template literals:
Unicode in ES6 515
Regular expressions:
• Unicode code point escapes are only allowed if the flag /u is set, because \u{3} is
interpreted as three times the character u, otherwise:
> /^\u{3}$/.test('uuu')
true
• The spec treats source code as a sequence of Unicode code points: “Source Text²”
• Unicode escape sequences sequences in identifiers: “Names and Keywords³”
• Strings are internally stored as sequences of UTF-16 code units: “String Literals⁴”
• Strings – how various escape sequences are translated to UTF-16 code units: “Static
Semantics: SV⁵”
• Template literals – how various escape sequences are translated to UTF-16 code units:
“Static Semantics: TV and TRV⁶”
The spec distinguishes between BMP patterns (flag /u not set) and Unicode patterns (flag /u set).
Sect. “Pattern Semantics⁷” explains that they are handled differently and how.
As a reminder, here is how grammar rules are be parameterized in the spec:
²http://www.ecma-international.org/ecma-262/6.0/#sec-source-text
³http://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords
⁴http://www.ecma-international.org/ecma-262/6.0/#sec-literals-string-literals
⁵http://www.ecma-international.org/ecma-262/6.0/#sec-static-semantics-sv
⁶http://www.ecma-international.org/ecma-262/6.0/#sec-static-semantics-tv-and-trv
⁷http://www.ecma-international.org/ecma-262/6.0/#sec-pattern-semantics
Unicode in ES6 516
• If a grammar rule R has the subscript [U] then that means there are two versions of it: R
and R_U.
• Parts of the rule can pass on the subscript via [?U].
• If a part of a rule has the prefix [+U] it only exists if the subscript [U] is present.
• If a part of a rule has the prefix [∼U] it only exists if the subscript [U] is not present.
You can see this parameterization in action in Sect. “Patterns⁸”, where the subscript [U] creates
separate grammars for BMP patterns and Unicode patterns:
• IdentityEscape: In BMP patterns, many characters can be prefixed with a backslash and are
interpreted as themselves (for example: if \u is not followed by four hexadecimal digits,
it is interpreted as u). In Unicode patterns that only works for the following characters
(which frees up \u for Unicode code point escapes): ˆ $ \ . * + ? ( ) [ ] { } |
• RegExpUnicodeEscapeSequence: "\u{" HexDigits "}" is only allowed in Unicode pat-
terns. In those patterns, lead and trail surrogates are also grouped to help with UTF-16
decoding.
Sect. “CharacterEscape⁹” explains how various escape sequences are translated to characters
(roughly: either code units or code points).
Further reading
“JavaScript has a Unicode problem¹⁰” (by Mathias Bynens) explains new Unicode
features in ES6.
⁸http://www.ecma-international.org/ecma-262/6.0/#sec-patterns
⁹http://www.ecma-international.org/ecma-262/6.0/#sec-characterescape
¹⁰https://mathiasbynens.be/notes/javascript-unicode
27. Tail call optimization
ECMAScript 6 offers tail call optimization, where you can make some function calls without
growing the call stack. This chapter explains how that works and what benefits it brings.
function id(x) {
return x; // (A)
}
function f(a) {
const b = a + 1;
return id(b); // (B)
}
console.log(f(2)); // (C)
The block of stack entries encodes the state (local variables, including parameters) of the current
scope and is called a stack frame.
Tail call optimization 518
Step 2. In line C, f() is called: First, the location to return to is saved on the stack. Then f’s
parameters are allocated and execution jumps to its body. The stack now looks as follows.
There are now two frames on the stack: One for the global scope (bottom) and one for f() (top).
f’s stack frame includes the return address, line C.
Step 3. id() is called in line B. Again, a stack frame is created that contains the return address
and id’s parameter.
Tail call optimization 519
Step 4. In line A, the result x is returned. id’s stack frame is removed and execution jumps to
the return address, line B. (There are several ways in which returning a value could be handled.
Two common solutions are to leave the result on a stack or to hand it over in a register. I ignore
this part of execution here.)
The stack now looks as follows:
Step 5. In line B, the value that was returned by id is returned to f’s caller. Again, the topmost
Tail call optimization 520
stack frame is removed and execution jumps to the return address, line C.
If you look at the previous section then there is one step that is unnecessary – step 5. All that
happens in line B is that the value returned by id() is passed on to line C. Ideally, id() could do
that itself and the intermediate step could be skipped.
We can make this happen by implementing the function call in line B differently. Before the call
happens, the stack looks as follows.
Tail call optimization 521
If we examine the call we see that it is the very last action in f(). Once id() is done, the only
remaining action performed by f() is to pass id’s result to f’s caller. Therefore, f’s variables
are not needed, anymore and its stack frame can be removed before making the call. The return
address given to id() is f’s return address, line C. During the execution of id(), the stack looks
like this:
Then id() returns the value 3. You could say that it returns that value for f(), because it
transports it to f’s caller, line C.
Let’s review: The function call in line B is a tail call. Such a call can be done with zero stack
growth. To find out whether a function call is a tail call, we must check whether it is in a tail
position (i.e., the last action in a function). How that is done is explained in the next section.
Tail call optimization 522
f() is not in a tail position, but g() is in a tail position. To see why, take a look at the following
code, which is equivalent to the previous code:
Tail call optimization 523
const a = () => {
const fResult = f(); // not a tail call
if (fResult) {
return fResult;
} else {
return g(); // tail call
}
};
The result of the logical Or operator depends on the result of f(), which is why that function
call is not in a tail position (the caller does something with it other than returning it). However,
g() is in a tail position.
f() is not in a tail position, but g() is in a tail position. To see why, take a look at the following
code, which is equivalent to the previous code:
const a = () => {
const fResult = f(); // not a tail call
if (!fResult) {
return fResult;
} else {
return g(); // tail call
}
};
The result of the logical And operator depends on the result of f(), which is why that function
call is not in a tail position (the caller does something with it other than returning it). However,
g() is in a tail position.
f() is not in a tail position, but g() is in a tail position. To see why, take a look at the following
code, which is equivalent to the previous code:
Tail call optimization 524
const a = () => {
f();
return g();
}
Of all the atomic (non-compound) statements, only return can contain a tail call. All other
statements have context that can’t be optimized away. The following statement contains a tail
call if expr contains a tail call.
return «expr»;
With tail call optimization, these properties don’t work, because the information that they rely
on may have been removed. Therefore, strict mode forbids these properties (as described in the
language specification¹) and tail call optimization only works in strict mode.
¹http://www.ecma-international.org/ecma-262/6.0/#sec-addrestrictedfunctionproperties
Tail call optimization 525
function foo() {
bar(); // this is not a tail call in JS
}
The reason is that the last action of foo() is not the function call bar(), it is (implicitly) returning
undefined. In other words, foo() behaves like this:
function foo() {
bar();
return undefined;
}
Callers can rely on foo() always returning undefined. If bar() were to return a result for foo(),
due to tail call optimization, then that would change foo’s behavior.
Therefore, if we want bar() to be a tail call, we have to change foo() as follows.
function foo() {
return bar(); // tail call
}
function factorial(x) {
if (x <= 0) {
return 1;
} else {
return x * factorial(x-1); // (A)
}
}
factorial() can be implemented via a tail-recursive helper function facRec(). The main
recursive call in line A is in a tail position.
Tail call optimization 526
function factorial(n) {
return facRec(n, 1);
}
function facRec(x, acc) {
if (x <= 1) {
return acc;
} else {
return facRec(x-1, x*acc); // (A)
}
}
That is, some non-tail-recursive functions can be transformed into tail-recursive functions.
27.3.1.1 forEach()
// Output:
// 0. a
// 1. b
27.3.1.2 findIndex()
When we get the property proxy.foo, the handler intercepts that operation:
> proxy.foo
get foo
123
Consult the reference for the complete API for a list of operations that can be intercepted.
• At the base level (also called: application level), code processes user input.
• At the meta level, code processes base level code.
Base and meta level can be different languages. In the following meta program, the metapro-
gramming language is JavaScript and the base programming language is Java.
Metaprogramming with proxies 528
Metaprogramming can take different forms. In the previous example, we have printed Java code
to the console. Let’s use JavaScript as both metaprogramming language and base programming
language. The classic example for this is the eval() function¹, which lets you evaluate/compile
JavaScript code on the fly. There are not that many actual use cases² for eval(). In the interaction
below, we use it to evaluate the expression 5 + 2.
Other JavaScript operations may not look like metaprogramming, but actually are, if you look
closer:
// Base level
const obj = {
hello() {
console.log('Hello!');
}
};
// Meta level
for (const key of Object.keys(obj)) {
console.log(key);
}
The program is examining its own structure while running. This doesn’t look like metaprogram-
ming, because the separation between programming constructs and data structures is fuzzy in
JavaScript. All of the Object.* methods³ can be considered metaprogramming functionality.
Using moveProperty():
> obj1
{}
> obj2
{ prop: 'abc' }
ECMAScript 5 doesn’t support intercession; proxies were created to fill that gap.
Proxies are special objects that allow you customize some of these operations. A proxy is created
with two parameters:
• handler: For each operation, there is a corresponding handler method that – if present –
performs that operation. Such a method intercepts the operation (on its way to the target)
and is called a trap (a term borrowed from the domain of operating systems).
• target: If the handler doesn’t intercept an operation then it is performed on the target.
That is, it acts as a fallback for the handler. In a way, the proxy wraps the target.
In the following example, the handler intercepts the operations get and has.
⁴http://speakingjs.com/es5/ch17.html#property_attributes
Metaprogramming with proxies 530
> proxy.foo
GET foo
123
The handler doesn’t implement the trap set (setting properties). Therefore, setting proxy.bar is
forwarded to target and leads to target.bar being set.
The reason for only enabling these traps for function targets is simple: You wouldn’t be able to
forward the operations apply and construct, otherwise.
Metaprogramming with proxies 531
function traceMethodCalls(obj) {
const handler = {
get(target, propKey, receiver) {
const origMethod = target[propKey];
return function (...args) {
const result = origMethod.apply(this, args);
console.log(propKey + JSON.stringify(args)
+ ' -> ' + JSON.stringify(result));
return result;
};
}
};
return new Proxy(obj, handler);
}
I’m not using a Proxy for the latter task, I’m simply wrapping the original method with a function.
Let’s use the following object to try out traceMethodCalls():
const obj = {
multiply(x, y) {
return x * y;
},
squared(x) {
return this.multiply(x, x);
},
};
tracedObj is a traced version of obj. The first line after each method call is the output of
console.log(), the second line is the result of the method call.
Metaprogramming with proxies 532
The nice thing is that even the call this.multiply() that is made inside obj.squared() is traced.
That’s because this keeps referring to the proxy.
This is not the most efficient solution. One could, for example, cache methods. Furthermore,
Proxies themselves have an impact on performance.
On the left hand side of the assignment operator (=), we are using destructuring to access the
properties proxy and revoke of the object returned by Proxy.revocable().
After you call the function revoke for the first time, any operation you apply to proxy causes a
TypeError. Subsequent calls of revoke have no further effect.
proxy.foo = 123;
console.log(proxy.foo); // 123
revoke();
// Output:
// GET bla
The property bla can’t be found in obj, which is why the search continues in proto and the trap
get is triggered there. There are more operations that affect prototypes; they are listed at the end
of this chapter.
const handler = {
deleteProperty(target, propKey) {
console.log('DELETE ' + propKey);
return delete target[propKey];
},
has(target, propKey) {
console.log('HAS ' + propKey);
return propKey in target;
},
// Other traps: similar
}
For each trap, we first log the name of the operation and then forward it by performing it
manually. ECMAScript 6 has the module-like object Reflect that helps with forwarding: for
each trap
const handler = {
deleteProperty(target, propKey) {
console.log('DELETE ' + propKey);
return Reflect.deleteProperty(target, propKey);
},
has(target, propKey) {
console.log('HAS ' + propKey);
return Reflect.has(target, propKey);
},
// Other traps: similar
}
Now what each of the traps does is so similar that we can implement the handler via a proxy:
For each trap, the proxy asks for a handler method via the get operation and we give it one. That
is, all of the handler methods can be implemented via the single meta method get. It was one of
the goals for the proxy API to make this kind of virtualization simple.
Let’s use this proxy-based handler:
The following interaction confirms that the set operation was correctly forwarded to the target:
Metaprogramming with proxies 535
> target.foo
123
Before we dig deeper, let’s quickly review how wrapping a target affects this:
const target = {
foo() {
return {
thisIsTarget: this === target,
thisIsProxy: this === proxy,
};
}
};
const handler = {};
const proxy = new Proxy(target, handler);
> target.foo()
{ thisIsTarget: true, thisIsProxy: false }
If you invoke that method via the proxy, this points to proxy:
> proxy.foo()
{ thisIsTarget: false, thisIsProxy: true }
That’s done so that the proxy continues to be in the loop if, e.g., the target invokes methods on
this.
Metaprogramming with proxies 536
Normally, proxies with an empty handler wrap targets transparently: you don’t notice that they
are there and they don’t change the behavior of the targets.
If, however, a target associates information with this via a mechanism that is not controlled by
proxies, you have a problem: things fail, because different information is associated depending
on whether the target is wrapped or not.
For example, the following class Person stores private information in the WeakMap _name (more
information on this technique is given in the chapter on classes):
jane.name is different from the wrapped proxy.name. The following implementation does not
have this problem:
class Person2 {
constructor(name) {
this._name = name;
}
get name() {
return this._name;
}
}
Instances of most built-in constructors also have a mechanism that is not intercepted by proxies.
They therefore can’t be wrapped transparently, either. I’ll demonstrate the problem for an
instance of Date:
proxy.getDate();
// TypeError: this is not a Date object.
The mechanism that is unaffected by proxies is called internal slots. These slots are property-like
storage associated with instances. The specification handles these slots as if they were properties
with names in square brackets. For example, the following method is internal and can be invoked
on all objects O:
O.[[GetPrototypeOf]]()
However, access to internal slots does not happen via normal “get” and “set” operations. If
getDate() is invoked via a proxy, it can’t find the internal slot it needs on this and complains
via a TypeError.
For Date methods, the language specification states⁵:
Unless explicitly stated otherwise, the methods of the Number prototype object
defined below are not generic and the this value passed to them must be either
a Number value or an object that has a [[NumberData]] internal slot that has been
initialized to a Number value.
The reason for Arrays being wrappable is that, even though property access is customized to
make length work, Array methods don’t rely on internal slots – they are generic.
⁵http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-number-prototype-object
Metaprogramming with proxies 538
28.3.6.5 A work-around
As a work-around, you can change how the handler forwards method calls and selectively set
this to the target and not the proxy:
const handler = {
get(target, propKey, receiver) {
if (propKey === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, propKey, receiver);
},
};
const proxy = new Proxy(new Date('2020-12-24'), handler);
proxy.getDate(); // 24
The drawback of this approach is that none of the operations that the method performs on this
go through the proxy.
Acknowlegement: Thanks to Allen Wirfs-Brock for pointing out the pitfall explained in this
section.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `Point(${this.x}, ${this.y})`;
}
}
// Trace accesses to properties `x` and `y`
const p = new Point(5, 7);
p = tracePropAccess(p, ['x', 'y']);
Getting and setting properties of the traced object p has the following effects:
Metaprogramming with proxies 539
> p.x
GET x
5
> p.x = 21
SET x=21
21
Intriguingly, tracing also works whenever Point accesses the properties, because this now refers
to the traced object, not to an instance of Point.
> p.toString()
GET x
GET y
'Point(21, 7)'
In ECMAScript 6, we can use a simpler, proxy-based solution. We intercept property getting and
setting and don’t have to change the implementation.
Metaprogramming with proxies 540
If we turn PropertyChecker into a constructor, we can use it for ECMAScript 6 classes via
extends:
function PropertyChecker() { }
PropertyChecker.prototype = new Proxy(···);
If you are worried about accidentally creating properties, you have two options: You can either
wrap a proxy around objects that traps set. Or you can make an object obj non-extensible via
Object.preventExtensions(obj)⁶, which means that JavaScript doesn’t let you add new (own)
properties to obj.
Alas, that doesn’t work when accessing elements via the bracket operator ([]). We can, however,
use proxies to add that capability. The following function createArray() creates Arrays that
support negative indices. It does so by wrapping proxies around Array instances. The proxies
intercept the get operation that is triggered by the bracket operator.
⁶http://speakingjs.com/es5/ch17.html#_preventing_extensions
Metaprogramming with proxies 542
function createArray(...elements) {
const handler = {
get(target, propKey, receiver) {
// Sloppy way of checking for negative indices
const index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
// Wrap a proxy around an Array
const target = [];
target.push(...elements);
return new Proxy(target, handler);
}
const arr = createArray('a', 'b', 'c');
console.log(arr[-1]); // c
Acknowledgement: The idea for this example comes from a blog post⁷ by hemanth.hm.
function createObservedArray(callback) {
const array = [];
return new Proxy(array, {
set(target, propertyKey, value, receiver) {
callback(propertyKey, value);
return Reflect.set(target, propertyKey, value, receiver);
}
});
}
const observedArray = createObservedArray(
(key, value) => console.log(`${key}=${value}`));
observedArray.push('a');
Output:
⁷http://h3manth.com/new/blog/2013/negative-array-index-in-javascript/
Metaprogramming with proxies 543
0=a
length=1
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
// Return the method to be called
return () => httpGet(baseUrl+'/'+propKey);
}
});
}
Both implementations use the following function to make HTTP GET requests (how it works is
explained in the chapter on Promises.
Metaprogramming with proxies 544
function httpGet(url) {
return new Promise(
(resolve, reject) => {
const request = new XMLHttpRequest();
Object.assign(request, {
onload() {
if (this.status === 200) {
// Success
resolve(this.response);
} else {
// Something went wrong (404 etc.)
reject(new Error(this.statusText));
}
},
onerror() {
reject(new Error(
'XMLHttpRequest Error: '+this.statusText));
}
});
request.open('GET', url);
request.send();
});
}
// Access granted
console.log(reference.x); // 11
revoke();
// Access denied
console.log(reference.x); // TypeError: Revoked
Metaprogramming with proxies 545
Proxies are ideally suited for implementing revocable references, because they can intercept and
forward operations. This is a simple proxy-based implementation of createRevocableRefer-
ence:
function createRevocableReference(target) {
let enabled = true;
return {
reference: new Proxy(target, {
get(target, propKey, receiver) {
if (!enabled) {
throw new TypeError('Revoked');
}
return Reflect.get(target, propKey, receiver);
},
has(target, propKey) {
if (!enabled) {
throw new TypeError('Revoked');
}
return Reflect.has(target, propKey);
},
···
}),
revoke() {
enabled = false;
},
};
}
The code can be simplified via the proxy-as-handler technique from the previous section. This
time, the handler basically is the Reflect object. Thus, the get trap normally returns the
appropriate Reflect method. If the reference has been revoked, a TypeError is thrown, instead.
function createRevocableReference(target) {
let enabled = true;
const handler = new Proxy({}, {
get(dummyTarget, trapName, receiver) {
if (!enabled) {
throw new TypeError('Revoked');
}
return Reflect[trapName];
}
});
return {
reference: new Proxy(target, handler),
revoke() {
Metaprogramming with proxies 546
enabled = false;
},
};
}
However, you don’t have to implement revocable references yourself, because ECMAScript 6 lets
you create proxies that can be revoked. This time, the revoking happens in the proxy, not in the
handler. All the handler has to do is forward every operation to the target. As we have seen that
happens automatically if the handler doesn’t implement any traps.
function createRevocableReference(target) {
const handler = {}; // forward everything
const { proxy, revoke } = Proxy.revocable(target, handler);
return { reference: proxy, revoke };
}
28.4.6.1 Membranes
Membranes build on the idea of revocable references: Environments that are designed to run
untrusted code, wrap a membrane around that code to isolate it and keep the rest of the system
safe. Objects pass the membrane in two directions:
• The code may receive objects (“dry objects”) from the outside.
• Or it may hand objects (“wet objects”) to the outside.
In both cases, revocable references are wrapped around the objects. Objects returned by wrapped
functions or methods are also wrapped. Additionally, if a wrapped wet object is passed back into
a membrane, it is unwrapped.
Once the untrusted code is done, all of the revocable references are revoked. As a result, none of
its code on the outside can be executed anymore and outside objects that it has cease to work,
as well. The Caja Compiler⁸ is “a tool for making third party HTML, CSS and JavaScript safe to
embed in your website”. It uses membranes to achieve this task.
Alas, the standard DOM can do things that are not easy to replicate in JavaScript. For example,
most DOM collections are live views on the current state of the DOM that change dynamically
whenever the DOM changes. As a result, pure JavaScript implementations of the DOM are not
very efficient. One of the reasons for adding proxies to JavaScript was to help write more efficient
DOM implementations.
• Remoting: Local placeholder objects forward method invocations to remote objects. This
use case is similar to the web service example.
• Data access objects for databases: Reading and writing to the object reads and writes to
the database. This use case is similar to the web service example.
• Profiling: Intercept method invocations to track how much time is spent in each method.
This use case is similar to the tracing example.
• Type checking: Nicholas Zakas has used proxies to type-check objects¹⁰.
const obj = {
__noSuchMethod__: function (name, args) {
console.log(name+': '+args);
}
};
// Neither of the following two methods exist,
// but we can make it look like they do
obj.foo(1); // Output: foo: 1
obj.bar(1, 2); // Output: bar: 1,2
Thus, __noSuchMethod__ works similarly to a proxy trap. In contrast to proxies, the trap is an
own or inherited method of the object whose operations we want to intercept. The problem with
that approach is that base level (normal methods) and meta level (__noSuchMethod__) are mixed.
¹⁰http://www.nczonline.net/blog/2014/04/29/creating-type-safe-properties-with-ecmascript-6-proxies/
Metaprogramming with proxies 548
Base-level code may accidentally invoke or see a meta level method and there is the possibility
of accidentally defining a meta level method.
Even in standard ECMAScript 5, base level and meta level are sometimes mixed. For example,
the following metaprogramming mechanisms can fail, because they exist at the base level:
{ hasOwnProperty: null }
Object.prototype.hasOwnProperty.call(obj, propKey)
// Abbreviated version:
{}.hasOwnProperty.call(obj, propKey)
• func.call(···), func.apply(···): For each of these two methods, problem and solution
are the same as with hasOwnProperty.
• obj.__proto__: In most JavaScript engines, __proto__ is a special property that lets you
get and set the prototype of obj. Hence, when you use objects as dictionaries, you must
be careful to avoid __proto__ as a property key¹¹.
By now, it should be obvious that making (base level) property keys special is problematic.
Therefore, proxies are stratified – base level (the proxy object) and meta level (the handler object)
are separate.
• As wrappers, they wrap their targets, they control access to them. Examples of wrappers
are: revocable resources and tracing proxies.
• As virtual objects, they are simply objects with special behavior and their targets don’t
matter. An example is a proxy that forwards method calls to a remote object.
An earlier design of the proxy API conceived proxies as purely virtual objects. However, it turned
out that even in that role, a target was useful, to enforce invariants (which is explained later) and
as a fallback for traps that the handler doesn’t implement.
¹¹http://speakingjs.com/es5/ch17.html#_pitfall_3_the_special_property___proto
Metaprogramming with proxies 549
Both principles give proxies considerable power for impersonating other objects. One reason for
enforcing invariants (as explained later) is to keep that power in check.
If you do need a way to tell proxies apart from non-proxies, you have to implement it yourself.
The following code is a module lib.js that exports two functions: one of them creates proxies,
the other one determines whether an object is one of those proxies.
// lib.js
const proxies = new WeakSet();
This module uses the ECMAScript 6 data structure WeakSet for keeping track of proxies. WeakSet
is ideally suited for this purpose, because it doesn’t prevent its elements from being garbage-
collected.
The next example shows how lib.js can be used.
// main.js
import { createProxy, isProxy } from './lib.js';
const p = createProxy({});
console.log(isProxy(p)); // true
console.log(isProxy({})); // false
Metaprogramming with proxies 550
// Method definition
[[Get]](propKey, receiver) {
const desc = this.[[GetOwnProperty]](propKey);
if (desc === undefined) {
const parent = this.[[GetPrototypeOf]]();
if (parent === null) return undefined;
return parent.[[Get]](propKey, receiver); // (A)
}
if ('value' in desc) {
return desc.value;
}
const getter = desc.get;
if (getter === undefined) return undefined;
return getter.[[Call]](receiver, []);
}
In line A you can see why proxies in a prototype chain find out about get if a property isn’t found
in an “earlier” object: If there is no own property whose key is propKey, the search continues in
the prototype parent of this.
Fundamental versus derived operations. You can see that [[Get]] calls other MOP operations.
Operations that do that are called derived. Operations that don’t depend on other operations are
called fundamental.
¹²http://www.ecma-international.org/ecma-262/6.0/#sec-ordinary-and-exotic-objects-behaviours
¹³http://www.ecma-international.org/ecma-262/6.0/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver
Metaprogramming with proxies 551
The meta object protocol of proxies¹⁴ is different from that of normal objects. For normal objects,
derived operations call other operations. For proxies, each operation (regardless of whether it is
fundamental or derived) is either intercepted by a handler method or forwarded to the target.
What operations should be interceptable via proxies? One possibility is to only provide traps for
fundamental operations. The alternative is to include some derived operations. The advantage
of doing so is that it increases performance and is more convenient. For example, if there
weren’t a trap for get, you’d have to implement its functionality via getOwnPropertyDescriptor.
One problem with derived traps is that they can lead to proxies behaving inconsistently. For
example, get may return a value that is different from the value in the descriptor returned by
getOwnPropertyDescriptor.
Intercession by proxies is selective: you can’t intercept every language operation. Why were some
operations excluded? Let’s look at two reasons.
First, stable operations are not well suited for intercession. An operation is stable if it always
produces the same results for the same arguments. If a proxy can trap a stable operation, it can
become unstable and thus unreliable. Strict equality¹⁵ (===) is one such stable operation. It can’t
be trapped and its result is computed by treating the proxy itself as just another object. Another
way of maintaining stability is by applying an operation to the target instead of the proxy. As
explained later, when we look at how invariants are enfored for proxies, this happens when
Object.getPrototypeOf() is applied to a proxy whose target is non-extensible.
A second reason for not making more operations interceptable is that intercession means exe-
cuting custom code in situations where that normally isn’t possible. The more this interleaving
of code happens, the harder it is to understand and debug a program. It also affects performance
negatively.
If you want to create virtual methods via ECMAScript 6 proxies, you have to return functions
from a get trap. That raises the question: why not introduce an extra trap for method invocations
(e.g. invoke)? That would enable us to distinguish between:
Second, extracting a method and invoking it later via call() or apply() should have the same
effect as invoking the method via dispatch. In other words, the following two variants should
work equivalently. If there was an extra trap invoke then that equivalence would be harder to
maintain.
Some things can only be done if you are able to distinguish between get and invoke. Those
things are therefore impossible with the current proxy API. Two examples are: auto-binding
and intercepting missing methods. Let’s examine how one would implement them if proxies
supported invoke.
Auto-binding. By making a proxy the prototype of an object obj, you can automatically bind
methods:
• Retrieving the value of a method m via obj.m returns a function whose this is bound to
obj.
• obj.m() performs a method call.
Auto-binding helps with using methods as callbacks. For example, variant 2 from the previous
example becomes simpler:
Intercepting missing methods. invoke lets a proxy emulate the previously mentioned __no-
SuchMethod__ mechanism that Firefox supports. The proxy would again become the prototype of
an object obj. It would react differently depending on how an unknown property foo is accessed:
• If you read that property via obj.foo, no intercession happens and undefined is returned.
• If you make the method call obj.foo() then the proxy intercepts and, e.g., notifies a
callback.
Non-extensibility. If an object is non-extensible, you can’t add properties and you can’t change
its prototype:
Non-configurability. All the data of a property is stored in attributes. A property is like a record
and attributes are like the fields of that record. Examples of attributes:
Thus, if a property is both non-writable and non-configurable, it is read-only and remains that
way:
Object.defineProperty(obj, 'foo', {
configurable: true
}); // TypeError: Cannot redefine property
For more details on these topics (including how Object.defineProperty() works) consult the
following sections in “Speaking JavaScript”:
Metaprogramming with proxies 554
These and other characteristics that remain unchanged in the face of language operations are
called invariants. With proxies, it is easy to violate invariants, as they are not intrinsically bound
by non-extensibility etc.
The proxy API prevents proxies from violating invariants by checking the parameters and results
of handler methods. The following are four examples of invariants (for an arbitrary object obj)
and how they are enforced for proxies (an exhaustive list is given at the end of this chapter).
The first two invariants involve non-extensibility and non-configurability. These are enforced by
using the target object for bookkeeping: results returned by handler methods have to be mostly
in sync with the target object.
• Proxies work like all other objects with regard to extensibility and configurability.
Therefore, universality is maintained. This is achieved without preventing proxies from
virtualizing (impersonating) protected objects.
• A protected object can’t be misrepresented by wrapping a proxy around it. Misrepresen-
tation can be caused by bugs or by malicious code.
In response to the getPrototypeOf trap, the proxy must return the target’s prototype if the target
is non-extensible.
To demonstrate this invariant, let’s create a handler that returns a prototype that is different
from the target’s prototype:
We do, however, get an error if we fake the prototype for a non-extensible object.
If the target has a non-writable non-configurable property then the handler must return that
property’s value in response to a get trap. To demonstrate this invariant, let’s create a handler
that always returns the same value for properties.
Metaprogramming with proxies 556
const handler = {
get(target, propKey) {
return 'abc';
}
};
const target = Object.defineProperties(
{}, {
foo: {
value: 123,
writable: true,
configurable: true
},
bar: {
value: 456,
writable: false,
configurable: false
},
});
const proxy = new Proxy(target, handler);
Property target.foo is not both non-writable and non-configurable, which means that the
handler is allowed to pretend that it has a different value:
> proxy.foo
'abc'
> proxy.bar
TypeError: Invariant check failed
The following operations are fundamental, they don’t use other operations to do their work:
apply, defineProperty, deleteProperty, getOwnPropertyDescriptor, getPrototypeOf, isEx-
tensible, ownKeys, preventExtensions, setPrototypeOf
All other operations are derived, they can be implemented via fundamental operations. For
example, for data properties, get can be implemented by iterating over the prototype chain via
getPrototypeOf and calling getOwnPropertyDescriptor for each chain member until either an
own property is found or the chain ends.
– If propDesc were to be used to (re)define an own property for the target then that
must not cause an exception. An exception is thrown if a change is forbidden by the
attributes writable and configurable (non-extensibility is handled by the first rule).
• deleteProperty(target, propKey)
– Non-configurable own properties of the target can’t be deleted.
• get(target, propKey, receiver)
– If the target has an own, non-writable, non-configurable data property whose key is
propKey then the handler must return that property’s value.
– If the target has an own, non-configurable, getter-less accessor property then the
handler must return undefined.
• getOwnPropertyDescriptor(target, propKey)
– The handler must return either an object or undefined.
– Non-configurable own properties of the target can’t be reported as non-existent by
the handler.
– If the target is non-extensible then exactly the target’s own properties must be
reported by the handler as existing.
– If the handler reports a property as non-configurable then that property must be a
non-configurable own property of the target.
– If the result returned by the handler were used to (re)define an own property for the
target then that must not cause an exception. An exception is thrown if the change
is not allowed by the attributes writable and configurable (non-extensibility is
handled by the third rule). Therefore, the handler can’t report a non-configurable
property as configurable and it can’t report a different value for a non-configurable
non-writable property.
• getPrototypeOf(target)
– The result must be either an object or null.
– If the target object is not extensible then the handler must return the prototype of
the target object.
• has(target, propKey)
– A handler must not hide (report as non-existent) a non-configurable own property
of the target.
– If the target is non-extensible then no own property of the target may be hidden.
• isExtensible(target)
– The result returned by the handler is coerced to boolean.
– After coercion to boolean, the value returned by the handler must be the same as
target.isExtensible().
• ownKeys(target)
– The handler must return an object, which treated as Array-like and converted into
an Array.
– Each element of the result must be either a string or a symbol.
– The result must contain the keys of all non-configurable own properties of the target.
– If the target is not extensible then the result must contain exactly the keys of the own
properties of the target (and no other values).
• preventExtensions(target)
– The result returned by the handler is coerced to boolean.
Metaprogramming with proxies 560
– If the handler returns a truthy value (indicating a successful change) then tar-
get.isExtensible() must be false afterwards.
• set(target, propKey, value, receiver)
– If the target has an own, non-writable, non-configurable data property whose key is
propKey then value must be the same as the value of that property (i.e., the property
can’t be changed).
– If the target has an own, non-configurable, setter-less accessor property then a
TypeError is thrown (i.e., such a property can’t be set).
• setPrototypeOf(target, proto)
– The result returned by the handler is coerced to boolean.
– If the target is not extensible, the prototype can’t be changed. This is enforced
as follows: If the target is not extensible and the handler returns a truthy value
(indicating a successful change) then proto must be the same as the prototype of
the target. Otherwise, a TypeError is thrown.
In the spec, the invariants are listed in the section “Proxy Object Internal Methods and
Internal Slots²⁰”.
• target.get(propertyKey, receiver)
If target has no own property with the given key, get is invoked on the prototype of
target.
• target.has(propertyKey)
Similarly to get, has is invoked on the prototype of target if target has no own property
with the given key.
• target.set(propertyKey, value, receiver)
Similarly to get, set is invoked on the prototype of target if target has no own property
with the given key.
All other operations only affect own properties, they have no effect on the prototype chain.
In the spec, these (and other) operations are described in the section “Ordinary Object
Internal Methods and Internal Slots²¹”.
²⁰http://www.ecma-international.org/ecma-262/6.0/#sec-proxy-object-internal-methods-and-internal-slots
²¹http://www.ecma-international.org/ecma-262/6.0/#sec-ordinary-object-internal-methods-and-internal-slots
Metaprogramming with proxies 561
28.7.5 Reflect
The global object Reflect implements all interceptable operations of the JavaScript meta object
protocol as methods. The names of those methods are the same as those of the handler methods,
which, as we have seen, helps with forwarding operations from the handler to the target.
Several methods have boolean results. For has and isExtensible, they are the results of the
operation. For the remaining methods, they indicate whether the operation succeeded.
Metaprogramming with proxies 562
• Different return values: Reflect duplicates the following methods of Object, but its
methods return booleans indicating whether the operation succeeded (where the Object
methods return the object that was modified).
– Object.defineProperty(obj, propKey, propDesc) : Object
– Object.preventExtensions(obj) : Object
– Object.setPrototypeOf(obj, proto) : Object
• Operators as functions: The following Reflect methods implement functionality that is
otherwise only available via operators:
– Reflect.construct(target, argumentsList, newTarget=target) : Object
– Reflect.deleteProperty(target, propertyKey) : boolean
– Reflect.get(target, propertyKey, receiver=target) : any
– Reflect.has(target, propertyKey) : boolean
– Reflect.set(target, propertyKey, value, receiver=target) : boolean
• Shorter version of apply(): If you want to be completely safe about invoking the method
apply() on a function, you can’t do so via dynamic dispatch, because the function may
have an own property with the key 'apply':
• No exceptions when deleting properties: the delete operator throws in strict mode if you
try to delete a non-configurable own property. Reflect.deleteProperty() returns false
in that case.
Going forward, Object will host operations that are of interest to normal applications, while
Reflect will host operations that are more low-level.
28.8 Conclusion
This concludes our in-depth look at the proxy API. For each application, you have to take
performance into consideration and – if necessary – measure. Proxies may not always be
fast enough. On the other hand, performance is often not crucial and it is nice to have the
metaprogramming power that proxies give us. As we have seen, there are numerous use cases
they can help with.
Metaprogramming with proxies 563
²²http://soft.vub.ac.be/Publications/2012/vub-soft-tr-12-03.pdf
²³http://mitpress.mit.edu/books/art-metaobject-protocol
²⁴http://www.pearsonhighered.com/educator/product/Putting-Metaclasses-to-Work-A-New-Dimension-in-ObjectOriented-
Programming/9780201433050.page
²⁵https://github.com/tvcutsem/harmony-reflect/wiki
29. Coding style tips for ECMAScript
6
This chapter lists a few ideas related to ES6 coding style:
• var versus let versus const (details are explained in the chapter on variables):
– Prefer const. You can use it for all variables whose values never change.
– Otherwise, use let – for variables whose values do change.
– Avoid var.
• An arrow function is the superior solution whenever a function fits into a single line:
readFilePromisified(filename)
.then(text => console.log(text))
For multi-line functions, traditional functions work well, too (with the caveat of this not
being lexical):
readFilePromisified(filename)
.then(function (text) {
const obj = JSON.parse(text);
console.log(JSON.stringify(obj, null, 4));
});
const obj = {
foo() {
},
bar() {
},
};
• Modules: don’t mix default exports and named exports. Your module should either
specialize on a single thing or export multiple, named, things. Details are explained in
the chapter on modules.
• Format generators as follows:
Coding style tips for ECMAScript 6 565
• In the chapter on callable entities (traditional functions, arrow functions, classes, etc.) there
is a section that gives recommendations (when to use which one etc.).
Additionally, the ES5 coding style tips¹ in “Speaking JavaScript” are still relevant for ES6.
¹http://speakingjs.com/es5/ch26.html
30. An overview of what’s new in ES6
This chapter collects the overview sections of all the chapters in this book.
• Better syntax for features that already exist (e.g. via libraries). For example:
– Classes
– Modules
• New functionality in the standard library. For example:
– New methods for strings and Arrays
– Promises
– Maps, Sets
• Completely new features. For example:
– Generators
– Proxies
– WeakMaps
• Number.EPSILON for comparing floating point numbers with a tolerance for rounding
errors.
• Number.isInteger(num) checks whether num is an integer (a number without a decimal
fraction):
> Number.isInteger(1.05)
false
> Number.isInteger(1)
true
> Number.isInteger(-3.1)
false
> Number.isInteger(-3)
true
• A method and constants for determining whether a JavaScript integer is safe (within the
signed 53 bit range in which there is no loss of precision):
– Number.isSafeInteger(number)
– Number.MIN_SAFE_INTEGER
– Number.MAX_SAFE_INTEGER
• Number.isNaN(num) checks whether num is the value NaN. In contrast to the global function
isNaN(), it doesn’t coerce its argument to a number and is therefore safer for non-numbers:
> isNaN('???')
true
> Number.isNaN('???')
false
• Three additional methods of Number are mostly equivalent to the global functions with the
same names: Number.isFinite, Number.parseFloat, Number.parseInt.
> Math.sign(-8)
-1
> Math.sign(0)
0
> Math.sign(3)
1
> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3
> Math.log10(100)
2
Math.hypot() Computes the square root of the sum of the squares of its arguments (Pythagoras’
theorem):
> Math.hypot(3, 4)
5
> 'hello'.startsWith('hell')
true
> 'hello'.endsWith('ello')
true
> 'hello'.includes('ell')
true
> 'doo '.repeat(3)
'doo doo doo '
// Template literals also let you create strings with multiple lines
const multiLine = `
This is
a string
with multiple
lines`;
30.4 Symbols
Symbols are a new primitive type in ECMAScript 6. They are created via a factory function:
Every time you call the factory function, a new and unique symbol is created. The optional
parameter is a descriptive string that is shown when printing the symbol (it has no other purpose):
> mySymbol
Symbol(mySymbol)
const iterableObject = {
[Symbol.iterator]() { // (A)
···
}
}
for (const x of iterableObject) {
console.log(x);
}
// Output:
// hello
// world
In line A, a symbol is used as the key of the method. This unique marker makes the object iterable
and enables us to use the for-of loop.
An overview of what’s new in ES6 570
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_ORANGE:
return COLOR_BLUE;
case COLOR_YELLOW:
return COLOR_VIOLET;
case COLOR_GREEN:
return COLOR_RED;
case COLOR_BLUE:
return COLOR_ORANGE;
case COLOR_VIOLET:
return COLOR_YELLOW;
default:
throw new Exception('Unknown color: '+color);
}
}
Every time you call Symbol('Red'), a new symbol is created. Therefore, COLOR_RED can never be
mistaken for another value. That would be different if it were the string 'Red'.
Forbidding coercion prevents some errors, but also makes working with symbols more compli-
cated.
• Reflect.ownKeys()
• Property access via []
• Object.assign()
• Object.keys()
• Object.getOwnPropertyNames()
• for-in loop
Template literals are string literals that can stretch across multiple lines and include interpolated
expressions (inserted via ${···}):
An overview of what’s new in ES6 572
// Output:
// Hello Jane!
// How are you
// today?
Tagged template literals (short: tagged templates) are created by mentioning a function before a
template literal:
Tagged templates are function calls. In the previous example, the method String.raw is called
to produce the result of the tagged template.
30.6.1 let
let works similarly to var, but the variable it declares is block-scoped, it only exists within the
current block. var is function-scoped.
In the following code, you can see that the let-declared variable tmp only exists inside the block
that starts in line A:
function order(x, y) {
if (x > y) { // (A)
let tmp = x;
x = y;
y = tmp;
}
console.log(tmp===x); // ReferenceError: tmp is not defined
return [x, y];
}
30.6.2 const
const works like let, but the variable you declare must be immediately initialized, with a value
that can’t be changed afterwards.
An overview of what’s new in ES6 573
const foo;
// SyntaxError: missing = in const declaration
Since for-of creates one binding (storage space for a variable) per loop iteration, it is OK to
const-declare the loop variable:
30.7 Destructuring
Destructuring is a convenient way of extracting values from data stored in (possibly nested)
objects and Arrays. It can be used in locations that receive data (such as the left-hand side of an
assignment). How to extract the values is specified via patterns (read on for examples).
¹https://twitter.com/kangax/status/567330097603284992
An overview of what’s new in ES6 574
// Variable declarations:
const [x] = ['a'];
let [x] = ['a'];
var [x] = ['a'];
// Assignments:
[x] = ['a'];
// Parameter definitions:
function f([x]) { ··· }
f(['a']);
const arr2 = [
{name: 'Jane', age: 41},
{name: 'John', age: 40},
];
for (const {name, age} of arr2) {
console.log(name, age);
}
// Output:
// Jane 41
// John 40
Rest parameters:
An overview of what’s new in ES6 576
In Array literals, the spread operator turns iterable values into Array elements:
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:
Second, their this is picked up from surroundings (lexical). Therefore, you don’t need bind()
or that = this, anymore.
function UiComponent() {
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('CLICK');
this.handleClick(); // lexical `this`
});
}
• arguments
• super
• this
• new.target
const obj = {
myMethod(x, y) {
···
}
};
const obj = {
['h'+'ello']() {
return 'hi';
}
};
console.log(obj.hello()); // hi
The main use case for computed property keys is to make it easy to use symbols as property keys.
30.12 Classes
A class and a subclass:
An overview of what’s new in ES6 580
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
> cp.toString();
'(25, 8) in green'
Under the hood, ES6 classes are not something that is radically new: They mainly provide more
convenient syntax to create old-school constructor functions. You can see that if you use typeof:
30.13 Modules
JavaScript has had modules for a long time. However, they were implemented via libraries, not
built into the language. ES6 is the first time that JavaScript has built-in modules.
ES6 modules are stored in files. There is exactly one module per file and one file per module.
You have two ways of exporting things from a module. These two ways can be mixed, but it is
usually better to use them separately.
An overview of what’s new in ES6 581
Or a class:
An overview of what’s new in ES6 582
Note that there is no semicolon at the end if you default-export a function or a class (which are
anonymous declarations).
Scripts Modules
HTML element <script> <script type="module">
Default mode non-strict strict
Top-level variables are global local to module
Value of this at top level window undefined
Executed synchronously asynchronously
Declarative imports (import statement) no yes
Programmatic imports (Promise-based API) yes yes
File extension .js .js
// Output:
// a
// b
// Output:
// a
Access both elements and their indices while looping over an Array (the square brackets before
of mean that we are using destructuring):
// Output:
// 0. a
// 1. b
Looping over the [key, value] entries in a Map (the square brackets before of mean that we are
using destructuring):
// Output:
// false => no
// true => yes
• Iterating:
– Array.prototype.entries()
– Array.prototype.keys()
– Array.prototype.values()
• Searching for elements:
– Array.prototype.find(predicate, thisArg?)
– Array.prototype.findIndex(predicate, thisArg?)
• Array.prototype.copyWithin(target, start, end=this.length)
• Array.prototype.fill(value, start=0, end=this.length)
30.16.1 Maps
The keys of a Map can be arbitrary values:
You can use an Array (or any iterable) with [key, value] pairs to set up the initial data in the
Map:
30.16.2 Sets
A Set is a collection of unique elements:
An overview of what’s new in ES6 585
As you can see, you can initialize a Set with elements if you hand the constructor an iterable
(arr in the example) over those elements.
30.16.3 WeakMaps
A WeakMap is a Map that doesn’t prevent its keys from being garbage-collected. That means that
you can associate data with objects without having to worry about memory leaks. For example:
function triggerListeners(obj) {
const listeners = _objToListeners.get(obj);
if (listeners) {
for (const listener of listeners) {
listener();
}
}
}
triggerListeners(obj);
// Output:
// hello
// world
An overview of what’s new in ES6 586
Instances of ArrayBuffer store the binary data to be processed. Two kinds of views are used to
access the data:
The following browser APIs support Typed Arrays (details are mentioned in a dedicated section):
• File API
• XMLHttpRequest
• Fetch API
• Canvas
• WebSockets
• And more
• An iterable is a data structure that wants to make its elements accessible to the public.
It does so by implementing a method whose key is Symbol.iterator. That method is a
factory for iterators.
• An iterator is a pointer for traversing the elements of a data structure (think cursors in
databases).
interface Iterable {
[Symbol.iterator]() : Iterator;
}
interface Iterator {
next() : IteratorResult;
}
interface IteratorResult {
value: any;
done: boolean;
}
• Arrays
• Strings
• Maps
• Sets
• DOM data structures (work in progress)
• for-of loop:
• Array.from():
• Promise.all(), Promise.race():
Promise.all(iterableOverPromises).then(···);
Promise.race(iterableOverPromises).then(···);
• yield*:
yield* anIterable;
30.19 Generators
function* genFunc() {
// (A)
console.log('First');
yield;
console.log('Second');
}
Note the new syntax: function* is a new “keyword” for generator functions (there are also
generator methods). yield is an operator with which a generator can pause itself. Additionally,
generators can also receive input and send output via yield.
When you call a generator function genFunc(), you get a generator object genObj that you can
use to control the process:
The process is initially paused in line A. genObj.next() resumes execution, a yield inside
genFunc() pauses execution:
genObj.next();
// Output: First
genObj.next();
// output: Second
const obj = {
* generatorMethod() {
···
}
};
const genObj = obj.generatorMethod();
class MyClass {
* generatorMethod() {
···
}
}
const myInst = new MyClass();
const genObj = myInst.generatorMethod();
function* objectEntries(obj) {
const propKeys = Reflect.ownKeys(obj);
How exactly objectEntries() works is explained in a dedicated section. Implementing the same
functionality without generators is much more work.
function fetchJson(url) {
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR: ${error.stack}`);
});
}
With the library co² and a generator, this asynchronous code looks synchronous:
ECMAScript 2017 will have async functions which are internally based on generators. With
them, the code looks like this:
²https://github.com/tj/co
An overview of what’s new in ES6 591
fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));
• The new flag /y (sticky) anchors each match of a regular expression to the end of the
previous match.
• The new flag /u (unicode) handles surrogate pairs (such as \uD83D\uDE80) as code points
and lets you use Unicode code point escapes (such as \u{1F680}) in regular expressions.
• The new data property flags gives you access to the flags of a regular expression, just like
source already gives you access to the pattern in ES5:
• You can use the constructor RegExp() to make a copy of a regular expression:
An overview of what’s new in ES6 592
function asyncFunc() {
return new Promise(
function (resolve, reject) {
···
resolve(result);
···
reject(error);
});
}
asyncFunc()
.then(result => { ··· })
.catch(error => { ··· });
asyncFunc1()
.then(result1 => {
// Use result1
return asyncFunction2(); // (A)
})
.then(result2 => { // (B)
// Use result2
})
.catch(error => {
// Handle errors of asyncFunc1() and asyncFunc2()
});
An overview of what’s new in ES6 593
How the Promise P returned by then() is settled depends on what its callback does:
• If it returns a Promise (as in line A), the settlement of that Promise is forwarded to P. That’s
why the callback from line B can pick up the settlement of asyncFunction2’s Promise.
• If it returns a different value, that value is used to settle P.
• If throws an exception then P is rejected with that exception.
Furthermore, note how catch() handles the errors of two asynchronous function calls (asyncFunction1()
and asyncFunction2()). That is, uncaught errors are passed on until there is an error handler.
asyncFunc1()
.then(() => asyncFunc2());
If you don’t do that and call all of them immediately, they are basically executed in parallel (a
fork in Unix process terminology):
asyncFunc1();
asyncFunc2();
Promise.all() enables you to be notified once all results are in (a join in Unix process
terminology). Its input is an Array of Promises, its output a single Promise that is fulfilled with
an Array of the results.
Promise.all([
asyncFunc1(),
asyncFunc2(),
])
.then(([result1, result2]) => {
···
})
.catch(err => {
// Receives first rejection among the Promises
···
});
An overview of what’s new in ES6 594
• Promise reactions are callbacks that you register with the Promise method then(), to be
notified of a fulfillment or a rejection.
• A thenable is an object that has a Promise-style then() method. Whenever the API
is only interested in being notified of settlements, it only demands thenables (e.g. the
values returned from then() and catch(); or the values handed to Promise.all() and
Promise.race()).
Changing states: There are two operations for changing the state of a Promise. After you have
invoked either one of them once, further invocations have no effect.
When we get the property proxy.foo, the handler intercepts that operation:
> proxy.foo
get foo
123
Consult the reference for the complete API for a list of operations that can be intercepted.