diff --git a/chapters/ch01.asciidoc b/chapters/ch01.asciidoc
index b675847..1123358 100644
--- a/chapters/ch01.asciidoc
+++ b/chapters/ch01.asciidoc
@@ -45,13 +45,13 @@ The sixth edition is a significant milestone in the history of JavaScript. Besid
Having spent ten years without observing significant change to the language specification after ES3, and four years for ES6 to materialize, it was clear the TC39 process needed to improve. The revision process used to be deadline-driven. Any delay in arriving at consensus would cause long wait periods between revisions, which lead to feature creep, causing more delays. Minor revisions were delayed by large additions to the specification, and large additions faced pressure to finalize so that the revision would be pushed through avoiding further delays.
-Since ES6 came out, TC39 has streamlinedfootnote:[You can find the September 2013 presentation which lead to the streamlined proposal revisioning process here: https://mjavascript.com/out/tc39-improvement.] its proposal revisioning process and adjusted it to meet modern expectations: the need to iterate more often and consistently, and to democratize specification development. At this point, TC39 moved from an ancient Word-based flow to using ecmarkup (an HTML superset used to format ECMAScript specifications) and GitHub Pull Requests, greatly increasing the number of proposalsfootnoteref:[proposals,You can find all proposals being considered by TC39 at https://mjavascript.com/out/tc39-proposals.] being created as well as external participation by non-members.
+Since ES6 came out, TC39 has streamlinedfootnote:[You can find the September 2013 presentation which lead to the streamlined proposal revisioning process here: https://mjavascript.com/out/tc39-improvement.] its proposal revisioning process and adjusted it to meet modern expectations: the need to iterate more often and consistently, and to democratize specification development. At this point, TC39 moved from an ancient Word-based flow to using ecmarkup (an HTML superset used to format ECMAScript specifications) and GitHub Pull Requests, greatly increasing the number of proposalsfootnoteref:[proposals,You can find all proposals being considered by TC39 at https://mjavascript.com/out/tc39-proposals.] being created as well as external participation by non-members. Thew new flow is continuous and, thus, more transparent: while previously you'd have to download a Word doc or its PDF version from a web page, the latest draft of the specification is now always available at https://mjavascript.com/out/spec-draft.
Firefox, Chrome, Edge, Safari and Node.js all offer over 95% compliance of the ES6 specification,footnote:[For a detailed ES6 compatibility report across browsers, check out the following table: https://mjavascript.com/out/es6-compat.] but we’ve been able to use the features as they came out in each of these browsers rather than having to wait until the flip of a switch when their implementation of ES6 was 100% finalized.
The new process involves four different maturity stagesfootnote:[The TC39 proposal process documentation can be found at https://mjavascript.com/out/tc39-process.]. The more mature a proposal is, the more likely it is to eventually make it into the specification.
-Any discussion, idea or proposal for a change or addition which has not yet been submitted as a formal proposal is considered to be an aspirational "strawman" proposal (stage 0) and has no acceptance requirements, but only TC39 members can create strawman proposals. At the time of this writing, there's over a dozen active strawman proposalsfootnote:[You can track strawman proposals here: https://mjavascript.com/out/tc39-stage0.].
+Any discussion, idea or proposal for a change or addition which has not yet been submitted as a formal proposal is considered to be an aspirational "strawman" proposal (stage 0), but only TC39 members can create strawman proposals. At the time of this writing, there's over a dozen active strawman proposalsfootnote:[You can track strawman proposals here: https://mjavascript.com/out/tc39-stage0.].
At stage 1 a proposal is formalized and expected to address cross-cutting concerns, interactions with other proposals, and implementation concerns. Proposals at this stage should identify a discrete problem and offer a concrete solution to the problem. A stage 1 proposal often includes a high level API description, illustrative usage examples and a discussion of internal semantics and algorithms. Stage 1 proposals are likely to change significantly as they make their way through the process.
@@ -75,9 +75,9 @@ This transformation can be done at build-time, so that consumers receive code th
A transpiler can take the ES6 source code we write and produce ES5 code that browsers can interpret more consistently. This is the most reliable way of running ES6 code in production today: using a build step to produce ES5 code that most old browsers, as well as modern browsers, can execute.
-The same applies to ES7 and beyond. As new versions of the language specification are released every year, we can expect compilers to support ES2017 input, ES2018 input and beyond. Similarly, as browser support becomes better, we can also expect compilers to reduce complexity in favor of ES6 output, then ES7 output, and so on. In this sense, we can think of JavaScript-to-JavaScript transpilers as a moving window that takes code written using the latest available language semantics and produces the most modern code they can output without compromising browser support.
+The same applies to ES7 and beyond. As new versions of the language specification are released every year, we can expect compilers to support ES2017 input, ES2018 input and so on. Similarly, as browser support becomes better, we can also expect compilers to reduce complexity in favor of ES6 output, then ES7 output, and so on. In this sense, we can think of JavaScript-to-JavaScript transpilers as a moving window that takes code written using the latest available language semantics and produces the most modern code they can output without compromising browser support.
-Let's talk about how you can use Babel in your programs.
+Let's talk about how you can use Babel in as part of your workflow.
==== 1.3.1 Introduction to the Babel transpiler
@@ -112,7 +112,7 @@ Besides the REPL, Babel offers a command-line tool written as a Node.js package.
[NOTE]
====
-You can download Node.js from their website: https://mjavascript.com/out/node. After installing Node, you'll be able to use the +npm+ command-line tool in your terminal.
+You can download Node.js from its website: https://mjavascript.com/out/node. After installing +node+, you'll also be able to use the +npm+ command-line tool in your terminal.
====
Before getting started we'll make a project directory and a +package.json+ file, which is a manifest used to describe Node.js applications. We can create the +package.json+ file through the +npm+ CLI.
@@ -203,12 +203,12 @@ Once we run our build script again, we'll observe that the output is now valid E
----
» npm run build
» cat dist/example.js
-"use strict";
+"use strict"
var double = function double(value) {
- return value * 2;
-};
-console.log(double(3));
+ return value * 2
+}
+console.log(double(3))
// <- 6
----
@@ -273,7 +273,7 @@ Let's consider the following +example.js+ file, which is purposely riddled with
[source,javascript]
----
-var goodbye='Goodbye!';
+var goodbye='Goodbye!'
function hello(){
return goodbye}
@@ -309,7 +309,12 @@ function hello () {
if (false) {}
----
-Now that you know how to compile modern JavaScript into something every browser understands, and how to properly lint your code, let's jump into ES6 feature themes and the future of JavaScript.
+[NOTE]
+====
+A similar kind of tool can be found in +prettier+, which can be used to automatically format your code. Prettier can be configured to automatically overwrite our code ensuring it follows preferences such as a given amount of spaces for indentation, single or double quotes, trailing commas, or a maximum line length. You can find prettier at https://mjavascript.com/out/prettier.
+====
+
+Now that you know how to compile modern JavaScript into something every browser understands, and how to properly lint and format your code, let's jump into ES6 feature themes and the future of JavaScript.
=== 1.4 Feature Themes in ES6
diff --git a/chapters/ch02.asciidoc b/chapters/ch02.asciidoc
index 336075c..1a6b4af 100644
--- a/chapters/ch02.asciidoc
+++ b/chapters/ch02.asciidoc
@@ -18,7 +18,7 @@ var book = {
}
----
-There's a few improvements coming to object literal syntax in ES6: property value shorthands, computed property names, and method definitions. Let's go through them and describe their use cases as well.
+ES6 brings a few improvements to object literal syntax: property value shorthands, computed property names, and method definitions. Let's go through them and describe their use cases as well.
==== 2.1.1 Property Value Shorthands
@@ -178,7 +178,7 @@ var emitter = {
}
----
-Starting in ES6, you can declare methods on an object literal using the new method definition syntax. In this case, we can omit the colon and the +function+ keyword. This is meant as a terse alternative to traditional method declarations where you need to use the +function+ keyword. The following example shows how our +emitter+ object would look like when using method definitions.
+Starting in ES6, you can declare methods on an object literal using the new method definition syntax. In this case, we can omit the colon and the +function+ keyword. This is meant as a terse alternative to traditional method declarations where you need to use the +function+ keyword. The following example shows how our +emitter+ object looks like when using method definitions.
[source,javascript]
----
@@ -238,7 +238,7 @@ Let's dig into their semantic differences with traditional functions, the many w
==== 2.2.1 Lexical Scoping
-In the body of an arrow function, +this+, +arguments+ and +super+ point to the containing scope. Consider the following example. We have a +timer+ object with a +seconds+ counter and a +start+ method defined using the syntax we've learned about earlier. We then start the timer, wait for a few seconds, and log the current amount of elapsed +seconds+.
+In the body of an arrow function, +this+, +arguments+ and +super+ point to the containing scope, since arrow functions don't create a new scope. Consider the following example. We have a +timer+ object with a +seconds+ counter and a +start+ method defined using the syntax we've learned about earlier. We then start the timer, wait for a few seconds, and log the current amount of elapsed +seconds+.
[source,javascript]
----
@@ -560,7 +560,7 @@ When destructuring arrays, you can skip uninteresting properties or those that y
[source,javascript]
----
var names = ['James', 'L.', 'Howlett']
-var [ firstName,, lastName ] = names
+var [ firstName, , lastName ] = names
console.log(lastName)
// <- 'Howlett'
----
@@ -570,7 +570,7 @@ Array destructuring allows for default values just like object destructuring.
[source,javascript]
----
var names = ['James', 'L.']
-var [ firstName = 'John',, lastName = 'Doe' ] = names
+var [ firstName = 'John', , lastName = 'Doe' ] = names
console.log(lastName)
// <- 'Doe'
----
@@ -615,7 +615,7 @@ Defaults can be applied to arrow function parameters as well. When we have defau
var double = (input = 0) => input * 2
----
-Default values aren't limited to the rightmost parameters of a function, as is commonplace in other programming languages. You could provide default values for any parameter, in any position.
+Default values aren't limited to the rightmost parameters of a function, as in a few other programming languages. You could provide default values for any parameter, in any position.
[source,javascript]
----
@@ -682,7 +682,7 @@ Besides default values, you can use destructuring in function parameters to desc
[source,javascript]
----
-const car = {
+var car = {
owner: {
id: 'e2c3503a4181968c',
name: 'Donald Draper'
@@ -704,7 +704,7 @@ When we destructure everything up front, it's easy to spot when input doesn't ad
[source,javascript]
----
-const getCarProductModel = ({ brand, make, model }) => ({
+var getCarProductModel = ({ brand, make, model }) => ({
sku: brand + ':' make + ':' + model,
brand,
make,
@@ -727,7 +727,7 @@ function getCoordinates () {
var { x, y } = getCoordinates()
----
-The case for default option values bears repeating. Imagine you have a +random+ function which yields random integers between a +min+ and a +max+ value, and that it should default to values between 1 and 10. This is particularly interesting as an alternative to named parameters in other languages like Python and C#. This pattern, where you're able to define default values for options and then let consumers override them individually, offers great flexibility.
+The case for default option values bears repeating. Imagine you have a +random+ function which produces random integers between a +min+ and a +max+ value, and that it should default to values between 1 and 10. This is particularly interesting as an alternative to named parameters in other languages like Python and C#. This pattern, where you're able to define default values for options and then let consumers override them individually, offers great flexibility.
[source,javascript]
----
@@ -1152,7 +1152,7 @@ The following snippet of code shows a possible implementation of the default +ta
----
function tag (parts, ...values) {
return parts.reduce(
- (all, part, i) => all + values[i - 1] + part
+ (all, part, index) => all + values[index - 1] + part
)
}
----
@@ -1174,7 +1174,7 @@ Multiple use cases apply to tagged templates. One possible use case might be to
----
function upper (parts, ...values) {
return parts.reduce(
- (all, part, i) => all + values[i - 1].toUpperCase() + part
+ (all, part, index) => all + values[index - 1].toUpperCase() + part
)
}
var name = 'Maurice'
@@ -1190,7 +1190,7 @@ A decidedly more useful use case would be to sanitize expressions interpolated i
----
function sanitized (parts, ...values) {
return parts.reduce(
- (all, part, i) => all + sanitize(values[i - 1]) + part
+ (all, part, index) => all + sanitize(values[index - 1]) + part
)
}
var comment = 'A malicious comment'
@@ -1221,7 +1221,7 @@ isItTwo('two')
// <- undefined
----
-JavaScript code like this works, even though +two+ was declared in a code branch and then accessed outside of said branch. The reason why, as we know, is that +var+ bindings are bound to the enclosing scope, be it a function or the global scope. That, coupled with hoisting, means that what the code we've written earlier will be interpreted as if it were written in a similar way to the next piece of code.
+JavaScript code like this works, even though +two+ was declared in a code branch and then accessed outside of said branch. That behavior is due to the fact that +var+ bindings are bound to the enclosing scope, be it a function or the global scope. That, coupled with hoisting, means that the code we've written earlier will be interpreted as if it were written in a similar way to the next piece of code.
[source,javascript]
----
@@ -1405,7 +1405,7 @@ We've mentioned major differences between +let+ and +const+. The first one is th
[source,javascript]
----
const pi = 3.1415
-const e; // SyntaxError, missing initializer
+const e // SyntaxError, missing initializer
----
Besides the assignment when initializing a +const+, variables declared using a +const+ statement can't be assigned to. Once a +const+ is initialized, you can't change its value. Under strict mode, attempts to change a +const+ variable will throw. Outside of strict mode, they'll fail silently as demonstrated by the following piece of code.
diff --git a/chapters/ch03.asciidoc b/chapters/ch03.asciidoc
index 706a5ff..9fdf9f5 100644
--- a/chapters/ch03.asciidoc
+++ b/chapters/ch03.asciidoc
@@ -89,7 +89,7 @@ It's worth noting that class declarations aren't hoisted to the top of their sco
[source,javascript]
----
-new Person(); // <- ReferenceError: Person is not defined
+new Person() // <- ReferenceError: Person is not defined
class Person {
}
----
@@ -410,7 +410,7 @@ Keep in mind that symbol keys are hidden from many of the traditional ways of pu
[source,javascript]
----
-for (key in character) {
+for (let key in character) {
console.log(key)
// <- 'name'
}
@@ -449,7 +449,7 @@ Symbols, on the other hand, don't have this problem. They can be used as propert
const cache = Symbol('calendar')
function createCalendar (el) {
if (cache in el) { // check if the symbol exists in the element
- return el[cache]; // use the cache to avoid re-instantiation
+ return el[cache] // use the cache to avoid re-instantiation
}
const api = el[cache] = {
// the calendar API goes here
@@ -458,7 +458,7 @@ function createCalendar (el) {
}
----
-There is an ES6 built-in -- the +WeakMap+ -- that can be used to uniquely map objects to other objects without using regular properties, symbol properties, or arrays. In contrast with array lookup tables, +WeakMap+ is O(1), just like using symbol properties. The +WeakMap+ couldn't be accessed from outside the library unless explicitly exposed, unlike with symbols which can be accessed through +Object.getOwnPropertySymbols+. We'll explore +WeakMap+ in chapter 5, alongside other ES6 collection built-ins.
+There is an ES6 built-in -- the +WeakMap+ -- that can be used to uniquely map objects to other objects without using arrays nor placing foreign properties on the objects we want to be able to look up. In contrast with array lookup tables, +WeakMap+ lookups are constant in time or O(1). We'll explore +WeakMap+ in chapter 5, alongside other ES6 collection built-ins.
===== Defining Protocols through Symbols
@@ -722,14 +722,14 @@ const defaults = {
first: 'first',
second: 'second'
}
-function print (options) {
- console.log(Object.assign({}, defaults, options))
+function applyDefaults (options) {
+ return Object.assign({}, defaults, options)
}
-print()
+applyDefaults()
// <- { first: 'first', second: 'second' }
-print({ third: 3 })
+applyDefaults({ third: 3 })
// <- { first: 'first', second: 'second', third: 3 }
-print({ second: false })
+applyDefaults({ second: false })
// <- { first: 'first', second: false }
----
diff --git a/chapters/ch04.asciidoc b/chapters/ch04.asciidoc
index c89f99c..ec1a2b0 100644
--- a/chapters/ch04.asciidoc
+++ b/chapters/ch04.asciidoc
@@ -35,7 +35,7 @@ const p = fetch('https://github.com/items')
p.then(res => {
// handle response
})
-p.catch(error => {
+p.catch(err => {
// handle error
})
----
@@ -63,7 +63,7 @@ const p = fetch('https://github.com/items')
p.then(res => {
// handle response
})
-p.then(null, error => {
+p.then(null, err => {
// handle error
})
----
@@ -175,8 +175,8 @@ A reaction may also +throw+ an error, which would cause the promise returned by
[source,javascript]
----
const p = fetch('https://github.com/items')
- .then(res => { throw new Error('unexpectedly'); })
- .catch(error => console.error(error))
+ .then(res => { throw new Error('unexpectedly') })
+ .catch(err => console.error(err))
----
Let's take a step back and pace ourselves, walking over more examples in each particular use case.
@@ -197,7 +197,7 @@ A promise will settle as a rejection when the resolver calls +reject+, but also
[source,javascript]
----
-new Promise((resolve, reject) => { throw new Error('oops'); })
+new Promise((resolve, reject) => { throw new Error('oops') })
.catch(err => console.error(err))
----
@@ -207,7 +207,7 @@ Errors that occur while executing a fulfillment or rejection reaction behave in
----
Promise
.resolve(2)
- .then(x => { throw new Error('failed'); })
+ .then(x => { throw new Error('failed') })
.catch(err => console.error(err))
----
@@ -216,7 +216,7 @@ It might be easier to decompose that series of chained method calls into variabl
[source,javascript]
----
const p1 = Promise.resolve(2)
-const p2 = p1.then(x => { throw new Error('failed'); })
+const p2 = p1.then(x => { throw new Error('failed') })
const p3 = p2.catch(err => console.error(err))
----
@@ -229,7 +229,7 @@ We've established that the promise you attach your reactions onto is important,
[source,javascript]
----
const p1 = Promise.resolve(2)
-const p2 = p1.then(x => { throw new Error('failed'); })
+const p2 = p1.then(x => { throw new Error('failed') })
const p3 = p2.then(x => x * 2)
const p4 = p3.catch(err => console.error(err))
----
@@ -239,7 +239,7 @@ Typically, promises like +p4+ fulfill because the rejection handler in +.catch+
[source,javascript]
----
const p1 = Promise.resolve(2)
-const p2 = p1.then(x => { throw new Error('failed'); })
+const p2 = p1.then(x => { throw new Error('failed') })
const p3 = p2.catch(err => console.error(err))
const p4 = p3.then(() => console.log('crisis averted'))
----
@@ -249,8 +249,8 @@ Similarly, if an error occurred in the +p3+ rejection handler, we could capture
[source,javascript]
----
const p1 = Promise.resolve(2)
-const p2 = p1.then(x => { throw new Error('failed'); })
-const p3 = p2.catch(err => { throw new Error('oops'); })
+const p2 = p1.then(x => { throw new Error('failed') })
+const p3 = p2.catch(err => { throw new Error('oops') })
const p4 = p3.catch(err => console.error(err))
----
@@ -339,8 +339,8 @@ function resolveUnderThreeSeconds (delay) {
setTimeout(reject, 3000)
})
}
-resolveUnderThreeSeconds(2000); // becomes fulfilled after 2s
-resolveUnderThreeSeconds(7000); // becomes rejected after 3s
+resolveUnderThreeSeconds(2000) // becomes fulfilled after 2s
+resolveUnderThreeSeconds(7000) // becomes rejected after 3s
----
When creating a new promise +p1+, you could call +resolve+ with another promise +p2+ -- besides calling +resolve+ with non-promise values. In those cases, +p1+ will be resolved but blocked on the outcome of +p2+. Once +p2+ settles, +p1+ will be settled with its value and outcome. The following bit of code is, thus, effectively the same as simply doing +fetch('https://github.com/items')+.
@@ -481,7 +481,7 @@ const p2 = fetch('https://github.com/products/chair')
const p3 = fetch('https://github.com/products/table')
const p = Promise
.all([p1, p2, p3])
- .catch(reason => console.log(reason))
+ .catch(err => console.log(err))
// <- 'failed'
----
@@ -689,7 +689,7 @@ function take (sequence, amount) {
}
----
-Our implementation works great on infinite sequences because it provides them with a constant exit condition: whenever the +amount+ is depleted, the sequence returned by +take+ ends. Instead of looping to pull values out of +random+, you can now write a piece of code like the following.
+Our implementation works great on infinite sequences because it provides them with a constant exit condition: whenever the +amount+ counter is depleted, the sequence returned by +take+ ends. Instead of looping to pull values out of +random+, you can now write a piece of code like the following.
[source,javascript]
----
@@ -701,7 +701,7 @@ This pattern allows you to reduce any infinite sequence into a finite one. If yo
[source,javascript]
----
-function range (sequence, low=0, high=1) {
+function range (sequence, low = 0, high = 1) {
return {
[Symbol.iterator]() {
const iterator = sequence[Symbol.iterator]()
@@ -1126,7 +1126,7 @@ Assuming the +numbers+ iterator we created earlier, the following example shows
----
const g = numbers()
while (true) {
- let item = g.next()
+ const item = g.next()
if (item.done) {
break
}
@@ -1213,9 +1213,8 @@ Interestingly, +yield+ expressions are not just used to produce output from the
[source,javascript]
----
function* ball () {
- let question
while (true) {
- question = yield `[a] ${ answer() }`
+ const question = yield `[a] ${ answer() }`
console.log(`[q] ${ question }`)
}
}
@@ -1346,9 +1345,9 @@ The problem when taking this approach is that we're back at the case where the c
----
function ball (questions) {
const g = questions()
- let question = g.next()
ask()
function ask () {
+ const question = g.next()
if (question.done) {
return
}
@@ -1384,7 +1383,7 @@ In the case of our magic 8-ball, the iterator may experience network issues -- o
----
fetch(`/ask?q=${ encodeURIComponent(question.value) }`)
.then(response => response.text())
- .then(answer => question = g.next(answer), reason => g.throw(reason))
+ .then(answer => question = g.next(answer), err => g.throw(err))
.then(ask)
----
@@ -1676,7 +1675,7 @@ getRandomArticle()
.then(model => renderView(model))
.then(html => setPageContents(html))
.then(() => console.log('Successfully changed page!'))
- .catch(reason => console.error(reason));
+ .catch(err => console.error(err))
----
Chained promises can become hard to debug: the root cause of a flow control error can be challenging to track down, and writing promise-based code flows is typically much easier than reading them, which leads to code that becomes difficult to maintain over time.
@@ -1687,15 +1686,15 @@ If we were to use plain JavaScript callbacks, our code would become repetitive,
----
getRandomArticle((err, model) => {
if (err) {
- return console.error(reason)
+ return console.error(err)
}
renderView(model, (err, html) => {
if (err) {
- return console.error(reason)
+ return console.error(err)
}
setPageContents(html, err => {
if (err) {
- return console.error(reason)
+ return console.error(err)
}
console.log('Successfully changed page!')
})
@@ -1713,7 +1712,7 @@ async.waterfall([
setPageContents
], (err, html) => {
if (err) {
- return console.error(reason)
+ return console.error(err)
}
console.log('Successfully changed page!')
})
@@ -1724,7 +1723,7 @@ Let's look at a similar example, but this time we'll be using generators. The fo
[source,javascript]
----
function getRandomArticle (gen) {
- const g = gen();
+ const g = gen()
fetch('https://github.com/articles/random', {
headers: new Headers({
Accept: 'application/json'
@@ -1732,7 +1731,7 @@ function getRandomArticle (gen) {
})
.then(res => res.json())
.then(json => g.next(json))
- .catch(error => g.throw(error))
+ .catch(err => g.throw(err))
}
----
@@ -1741,9 +1740,9 @@ The following piece of code shows how you can pull the +json+ from +getRandomArt
[source,javascript]
----
getRandomArticle(function* printRandomArticle () {
- const json = yield;
+ const json = yield
// render view
-});
+})
----
Generators may not be the most straightforward way of accomplishing the results that we want in this case: you're only moving the complexity somewhere else. We might as well stick with promises.
diff --git a/chapters/ch05.asciidoc b/chapters/ch05.asciidoc
index eb7d920..693aa98 100644
--- a/chapters/ch05.asciidoc
+++ b/chapters/ch05.asciidoc
@@ -184,7 +184,7 @@ As an example, if we chose to use a symbol as the key for a map entry, we'd have
const map = new Map()
const key = Symbol('items')
map.set(key, [1, 2])
-map.get(Symbol('items')); // not the same reference as "key"
+map.get(Symbol('items')) // not the same reference as "key"
// <- undefined
map.get(key)
// <- [1, 2]
@@ -358,15 +358,15 @@ function storeInMap (el, api) {
return entry
}
function findByElement (el) {
- for (const i = 0; i < map.length; i++) {
- if (map[i].el === el) {
- return map[i].api
+ for (const index = 0; index < map.length; index++) {
+ if (map[index].el === el) {
+ return map[index].api
}
}
}
function destroy (entry) {
- const i = map.indexOf(entry)
- map.splice(i, 1)
+ const index = map.indexOf(entry)
+ map.splice(index, 1)
}
----
@@ -523,7 +523,7 @@ The following piece of code creates a `Set` with all of the `
` elements on
[source,javascript]
----
function divs () {
- return [...document.querySelectorAll('div')]
+ return document.querySelectorAll('div')
}
const set = new Set(divs())
console.log(set.size)
diff --git a/chapters/ch06.asciidoc b/chapters/ch06.asciidoc
index 2336a8f..0a660e5 100644
--- a/chapters/ch06.asciidoc
+++ b/chapters/ch06.asciidoc
@@ -37,7 +37,7 @@ const handler = {
}
const target = {}
const proxy = new Proxy(target, handler)
-proxy.numbers = 123
+proxy.numbers = [1, 1, 2, 3, 5, 8, 13]
proxy.numbers
// 'Get on property "numbers"'
// <- 123
diff --git a/chapters/ch07.asciidoc b/chapters/ch07.asciidoc
index 85ad8d0..5347f47 100644
--- a/chapters/ch07.asciidoc
+++ b/chapters/ch07.asciidoc
@@ -21,14 +21,14 @@ You can now use the new +0b+ prefix to represent binary integer literals. You co
[source,javascript]
----
-console.log(0b000); // <- 0
-console.log(0b001); // <- 1
-console.log(0b010); // <- 2
-console.log(0b011); // <- 3
-console.log(0b100); // <- 4
-console.log(0b101); // <- 5
-console.log(0b110); // <- 6
-console.log(0b111); // <- 7
+console.log(0b000) // <- 0
+console.log(0b001) // <- 1
+console.log(0b010) // <- 2
+console.log(0b011) // <- 3
+console.log(0b100) // <- 4
+console.log(0b101) // <- 5
+console.log(0b110) // <- 6
+console.log(0b111) // <- 7
----
In ES3, +parseInt+ interpreted strings of digits starting with a +0+ as an octal value. That meant things got weird quickly when you forgot to specify a radix of +10+. As a result, specifying the radix of +10+ became a best practice, so that user input like +012+ wouldn't unexpectedly be parsed as the integer +10+.
@@ -55,17 +55,17 @@ You can now use the +0o+ prefix for octal literals, which are new in ES6. You co
[source,javascript]
----
-console.log(0o001); // <- 1
-console.log(0o010); // <- 8
-console.log(0o100); // <- 64
+console.log(0o001) // <- 1
+console.log(0o010) // <- 8
+console.log(0o100) // <- 64
----
You might be used to hexadecimal literals present in other languages, commonly prefixed with +0x+. Those were already introduced to the JavaScript language in ES5. The prefix for literal hexadecimal notation is either +0x+, or +0X+, as shown in the following code snippet.
[source,javascript]
----
-console.log(0x0ff); // <- 255
-console.log(0xf00); // <- 3840
+console.log(0x0ff) // <- 255
+console.log(0xf00) // <- 3840
----
Besides these minor syntax changes where octal and binary literals were introduced, a few methods were added to +Number+ in ES6. The first four +Number+ methods that we'll be discussing -- +Number.isNaN+, +Number.isFinite+, +Number.parseInt+, and +Number.parseFloat+ -- already existed as functions in the global namespace. In addition, the methods in +Number+ are slightly different in that they don't coerce non-numeric values into numbers before producing a result.
@@ -266,13 +266,13 @@ This is a new method coming in ES6, and it wasn't previously available as a glob
[source,javascript]
----
-console.log(Number.isInteger(Infinity)); // <- false
-console.log(Number.isInteger(-Infinity)); // <- false
-console.log(Number.isInteger(NaN)); // <- false
-console.log(Number.isInteger(null)); // <- false
-console.log(Number.isInteger(0)); // <- true
-console.log(Number.isInteger(-10)); // <- true
-console.log(Number.isInteger(10.3)); // <- false
+console.log(Number.isInteger(Infinity)) // <- false
+console.log(Number.isInteger(-Infinity)) // <- false
+console.log(Number.isInteger(NaN)) // <- false
+console.log(Number.isInteger(null)) // <- false
+console.log(Number.isInteger(0)) // <- true
+console.log(Number.isInteger(-10)) // <- true
+console.log(Number.isInteger(10.3)) // <- false
----
You might want to consider the following code snippet as a polyfill for +Number.isInteger+. The modulus operator returns the remainder of dividing the same operands. If we divide by one, we're effectively getting the decimal part. If that's +0+, then it means the number is an integer.
@@ -387,18 +387,18 @@ This method returns +true+ for any integer in the +[MIN_SAFE_INTEGER, MAX_SAFE_I
[source,javascript]
----
-Number.isSafeInteger(`one`); // <- false
-Number.isSafeInteger(`0`); // <- false
-Number.isSafeInteger(null); // <- false
-Number.isSafeInteger(NaN); // <- false
-Number.isSafeInteger(Infinity); // <- false
-Number.isSafeInteger(-Infinity); // <- false
-Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1); // <- false
-Number.isSafeInteger(Number.MIN_SAFE_INTEGER); // <- true
-Number.isSafeInteger(1); // <- true
-Number.isSafeInteger(1.2); // <- false
-Number.isSafeInteger(Number.MAX_SAFE_INTEGER); // <- true
-Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1); // <- false
+Number.isSafeInteger(`one`) // <- false
+Number.isSafeInteger(`0`) // <- false
+Number.isSafeInteger(null) // <- false
+Number.isSafeInteger(NaN) // <- false
+Number.isSafeInteger(Infinity) // <- false
+Number.isSafeInteger(-Infinity) // <- false
+Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // <- false
+Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // <- true
+Number.isSafeInteger(1) // <- true
+Number.isSafeInteger(1.2) // <- false
+Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // <- true
+Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // <- false
----
When we want to verify if the result of an operation is within bounds, we must verify not only the result but also both operandsfootnote:[Dr. Axel Rauschmayer points this out in an article titled "New number and Math features in ES6": https://mjavascript.com/out/math-axel.]. One -- or both -- of the operands may be out of bounds, while the result is within bounds but incorrect. Similarly, the result may be out of bounds even if both operands are within bounds. Checking all of +left+, +right+, and the result of +left op right+ is, thus, necessary to verify that we can indeed trust the result.
@@ -510,14 +510,14 @@ Many languages have a mathematical +sign+ method that returns a vector (+-1+, +0
[source,javascript]
----
-Math.sign(1); // <- 1
-Math.sign(0); // <- 0
-Math.sign(-0); // <- -0
-Math.sign(-30); // <- -1
-Math.sign(NaN); // <- NaN
-Math.sign(`one`); // <- NaN, because Number(`one`) is NaN
-Math.sign(`0`); // <- 0, because Number(`0`) is 0
-Math.sign(`7`); // <- 1, because Number(`7`) is 7
+Math.sign(1) // <- 1
+Math.sign(0) // <- 0
+Math.sign(-0) // <- -0
+Math.sign(-30) // <- -1
+Math.sign(NaN) // <- NaN
+Math.sign(`one`) // <- NaN, because Number(`one`) is NaN
+Math.sign(`0`) // <- 0, because Number(`0`) is 0
+Math.sign(`7`) // <- 1, because Number(`7`) is 7
----
Note how +Math.sign+ casts its input into numeric values? While methods introduced to the +Number+ built-in don't cast their input via +Number(value)+, most of the methods added to +Math+ share this trait, as we shall see.
@@ -528,12 +528,12 @@ We already had +Math.floor+ and +Math.ceil+ in JavaScript, with which we can rou
[source,javascript]
----
-Math.trunc(12.34567); // <- 12
-Math.trunc(-13.58); // <- -13
-Math.trunc(-0.1234); // <- -0
-Math.trunc(NaN); // <- NaN
-Math.trunc(`one`); // <- NaN, because Number(`one`) is NaN
-Math.trunc(`123.456`); // <- 123, because Number(`123.456`) is 123.456
+Math.trunc(12.34567) // <- 12
+Math.trunc(-13.58) // <- -13
+Math.trunc(-0.1234) // <- -0
+Math.trunc(NaN) // <- NaN
+Math.trunc(`one`) // <- NaN, because Number(`one`) is NaN
+Math.trunc(`123.456`) // <- 123, because Number(`123.456`) is 123.456
----
Creating a simple polyfill for +Math.trunc+ would involve checking whether the value is greater than zero and applying one of +Math.floor+ or +Math.ceil+, as shown in the following code snippet.
@@ -549,18 +549,18 @@ The +Math.cbrt+ method is short for "cubic root", similarly to how +Math.sqrt+ i
[source,javascript]
----
-Math.cbrt(-1); // <- -1
-Math.cbrt(3); // <- 1.4422495703074083
-Math.cbrt(8); // <- 2
-Math.cbrt(27); // <- 3
+Math.cbrt(-1) // <- -1
+Math.cbrt(3) // <- 1.4422495703074083
+Math.cbrt(8) // <- 2
+Math.cbrt(27) // <- 3
----
Note that this method also coerces non-numerical values into numbers.
[source,javascript]
----
-Math.cbrt(`8`); // <- 2, because Number(`8`) is 8
-Math.cbrt(`one`); // <- NaN, because Number(`one`) is NaN
+Math.cbrt(`8`) // <- 2, because Number(`8`) is 8
+Math.cbrt(`one`) // <- NaN, because Number(`one`) is NaN
----
Let's move on.
@@ -636,9 +636,7 @@ You could polyfill +Math.log10+ using the +Math.LN10+ constant.
[source,javascript]
----
-function log10 (value) {
- return Math.log(x) / Math.LN10
-}
+Math.log10 = value => Math.log(x) / Math.LN10
----
And then there's +Math.log2+.
@@ -657,19 +655,17 @@ You could polyfill +Math.log2+ using the +Math.LN2+ constant.
[source,javascript]
----
-function log2 (value) {
- return Math.log(x) / Math.LN2
-}
+Math.log2 = value => Math.log(x) / Math.LN2
----
Note that the polyfilled version won't be as precise as +Math.log2+, as demonstrated in the following example.
[source,javascript]
----
-log2(1 << 29)
-// <- 29.000000000000004
-Math.log2(1 << 29)
+Math.log2(1 << 29) // native implementation
// <- 29
+Math.log2(1 << 29) // polyfill implementation
+// <- 29.000000000000004
----
The +<<+ operator performs a "bitwise left shift"footnoteref:[bitwise-shift,Definition on MDN: https://mjavascript.com/out/bitwise-shift.]. In this operation, the bits on the binary representation of the left hand side number are shifted as many places to the left as indicated in the right hand side of the operation. The following couple of examples show how shifting works, using the binary literal notation introduced in section 7.1.1.
@@ -713,18 +709,17 @@ We could polyfill +Math.hypot+ by performing these operations manually. We can u
[source,javascript]
----
-function hypot (...values) {
- return Math.sqrt(values.reduce((sum, value) => sum + value * value, 0))
-}
+Math.hypot = (...values) =>
+ Math.sqrt(values.reduce((sum, value) => sum + value * value, 0))
----
Our handmade function is, surprisingly, more precise than the native one for this particular use case. In the next code sample, we see the hand-rolled +hypot+ function offers precision with one more decimal place.
[source,javascript]
----
-Math.hypot(1, 2, 3)
+Math.hypot(1, 2, 3) // native implementation
// <- 3.741657386773941
-hypot(1, 2, 3)
+Math.hypot(1, 2, 3) // polyfill implementation
// <- 3.7416573867739413
----
@@ -738,12 +733,12 @@ The name for this method is an acronym for "count leading zero bits in 32-bit bi
[source,javascript]
----
-Math.clz32(0); // <- 32
-Math.clz32(1); // <- 31
-Math.clz32(1 << 1); // <- 30
-Math.clz32(1 << 2); // <- 29
-Math.clz32(1 << 29); // <- 2
-Math.clz32(1 << 31); // <- 0
+Math.clz32(0) // <- 32
+Math.clz32(1) // <- 31
+Math.clz32(1 << 1) // <- 30
+Math.clz32(1 << 2) // <- 29
+Math.clz32(1 << 29) // <- 2
+Math.clz32(1 << 31) // <- 0
----
===== +Math.imul+
@@ -1064,7 +1059,7 @@ Let's switch protocols and learn about Unicode.
==== 7.3.6 Unicode
-JavaScript strings are represented using UTF-16 code unitsfootnote:[Learn more about UCS-2, UCS-4, UTF-16 and UTF-32 here: https://mjavascript.com/out/unicode-encodings.]. Each code unit can be used to represent a code point in the +[U+0000, U+FFFF]+ range -- also known as the BMP, short for basic multilingual plane. You can represent individual code points in the BMP plane using the +`\u3456`+ syntax. You could also represent code units in the +[U+0000, U+0255]+ range using the +\x00..\xff+ notation. For instance, +`\xbb`+ represents +`»`+, the +187+ character, as you can verify by doing +parseInt(`bb`, 16)+ -- or +String.fromCharCode(187)+.
+JavaScript strings are represented using UTF-16 code unitsfootnote:[Learn more about UCS-2, UCS-4, UTF-16 and UTF-32 here: https://mjavascript.com/out/unicode-encodings.]. Each code unit can be used to represent a code point in the +[U+0000, U+FFFF]+ range -- also known as the BMP, short for Basic Multilingual Plane. You can represent individual code points in the BMP plane using the +`\u3456`+ syntax. You could also represent code units in the +[U+0000, U+0255]+ range using the +\x00..\xff+ notation. For instance, +`\xbb`+ represents +`»`+, the +U+00BB+ code point, as you can verify by doing +parseInt(`bb`, 16)+ -- or +String.fromCharCode(0xbb)+.
For code points beyond +U+FFFF+, you'd represent them as a surrogate pair. That is to say, two contiguous code units. For instance, the horse emoji +`🐎`+ code point is represented with the +`\ud83d\udc0e`+ contiguous code units. In ES6 notation you can also represent code points using the +`\u{1f40e}`+ notation (that example is also the horse emoji).
@@ -1096,7 +1091,7 @@ Object.keys(`🐎👱❤`)
// <- [`0`, `1`, `2`, `3`, `4`]
----
-If we now consider a +for+ loop, we can observe more clearly how this is a problem. In the following example, we wanted to exfill each individual emoji from the +text+ string, but we get each code point instead.
+If we now consider a +for+ loop, we can observe more clearly how this is a problem. In the following example, we wanted to exfill each individual emoji from the +text+ string, but we got each code unit instead of the code points they form.
[source,javascript]
----
@@ -1173,23 +1168,44 @@ Attempts to näively figure out the actual length by counting code points prove
// <- 16, should be 11
----
-As Unicode expert Mathias Bynens points out, splitting by code points isn't enough. Unlike surrogate pairs like the emojis we've used in our earlier examples, other grapheme clusters aren't taken into account by the string iteratorfootnoteref:[unicode-problem,See also "JavaScript has a Unicode problem", https://mjavascript.com/out/unicode-mathias.]. In those cases we're out of luck, and have to fall back to regular expressions or utility libraries to correctly calculate string length. Forunately, these kinds of glyphs are used infrequently.
+As Unicode expert Mathias Bynens points out, splitting by code points isn't enough. Unlike surrogate pairs like the emojis we've used in our earlier examples, other grapheme clusters aren't taken into account by the string iteratorfootnoteref:[unicode-problem,It is recommended you read "JavaScript has a Unicode problem": https://mjavascript.com/out/unicode-mathias from Mathias Bynens. In the article, Mathias analyzes JavaScript's relationship with Unicode.]. In those cases we're out of luck, and have to fall back to regular expressions or utility libraries to correctly calculate string length.
+
+==== 7.3.8 A Proposal to Split Grapheme Segments
+
+Multiple code points that combine into a single visual glyph are getting more common.footnote:[Emoji popularize this with glyphs sometimes made up of four code points. See https://mjavascript.com/out/emoji for a list of emoji made up of several code points.] There is a new proposal in the works (currently in stage 2) that may settle the matter of iterating over grapheme clusters once and for all. It introduces an `Intl.Segmenter` built-in, which can be used to split a string into an iterable sequence.
+
+To use the `Segmenter` API, we start by creating an instance of `Intl.Segmenter` specifying a locale and the granularity level we want: per grapheme, word, sentence or line. The segmenter instance can be used to produce an iterator for any given string, splitting it by the specified `granularity`. Note that the segmenting algorithm may vary depending on the locale, which is why it is a part of the API.
+
+The following example defines a `getGraphemes` function which produces an array of grapheme clusters for any given locale and piece of text.
+
+[source,javascript]
+----
+function getGraphemes (locale, text) {
+ const segmenter = new Intl.Segmenter(locale, { granularity: 'grapheme' })
+ const sequence = segmenter.segment(text)
+ const graphemes = [...sequence].map(item => item.segment)
+ return graphemes
+}
+getGraphemes('es', 'Esto está bien bueno!')
+----
+
+Using the segmenter proposal, we wouldn't have any trouble splitting strings containing emoji or other combining code units. You can learn more about the `Segmenter` proposal at: https://mjavascript.com/out/segmenter.
Let's look at more Unicode-related methods introduced in ES6.
-==== 7.3.8 +String#codePointAt+
+==== 7.3.9 +String#codePointAt+
-We can use +String#codePointAt+ to get the base-10 numeric representation of a code point at a given position in a string. Note that the expected start position is indexed by code unit, not by code point. In the example below we print the code points for each of the three emoji in our demo +`🐎👱❤`+ string.
+We can use +String#codePointAt+ to get the numeric representation of a code point at a given position in a string. Note that the expected start position is indexed by code unit, not by code point. In the example below we print the code points for each of the three emoji in our demo +`🐎👱❤`+ string.
[source,javascript]
----
const text = `\ud83d\udc0e\ud83d\udc71\u2764`
text.codePointAt(0)
-// <- 128014
+// <- 0x1f40e
text.codePointAt(2)
-// <- 128113
+// <- 0x1f471
text.codePointAt(4)
-// <- 10084
+// <- 0x2764
----
Identifying the indices that need to be provided to +String#codePointAt+ may prove cumbersome, which is why you should instead loop through a string iterator that can identify them on your behalf. You can then call +.codePointAt(0)+ for each code point in the sequence, and +0+ will always be the correct start index.
@@ -1199,9 +1215,9 @@ Identifying the indices that need to be provided to +String#codePointAt+ may pro
const text = `\ud83d\udc0e\ud83d\udc71\u2764`
for (const codePoint of text) {
console.log(codePoint.codePointAt(0))
- // <- 128014
- // <- 128113
- // <- 10084
+ // <- 0x1f40e
+ // <- 0x1f471
+ // <- 0x2764
}
----
@@ -1211,10 +1227,10 @@ We could also reduce our example to a single line of code by using a combination
----
const text = `\ud83d\udc0e\ud83d\udc71\u2764`
[...text].map(cp => cp.codePointAt(0))
-// <- [128014, 128113, 10084]
+// <- [0x1f40e, 0x1f471, 0x2764]
----
-You can take the base-16 representation of those base-10 code points, and use them to create a string with the new unicode code point escape syntax of +\u{codePoint}+. This syntax allows you to represent unicode code points that are beyond the BMP. That is, code points outside the +[U+0000, U+FFFF]+ range that are typically represented using the +\u1234+ syntax.
+You can take the base-16 representation of those base-10 code points, and use them to create a string with the new Unicode code point escape syntax of +\u{codePoint}+. This syntax allows you to represent Unicode code points that are beyond the BMP. That is, code points outside the +[U+0000, U+FFFF]+ range that are typically represented using the +\u1234+ syntax.
Let's start by updating our example to print the hexadecimal version of our code points.
@@ -1237,7 +1253,7 @@ We could wrap those base-16 values in +`\u{codePoint}`+ and voilá: you'd get th
// <- `❤`
----
-==== 7.3.9 +String.fromCodePoint+
+==== 7.3.10 +String.fromCodePoint+
This method takes in a number and returns a code point. Note how I can use the +0x+ prefix with the terse base-16 code points we got from +String#codePointAt+ moments ago.
@@ -1267,7 +1283,7 @@ You can pass in as many code points as you'd like to +String.fromCodePoint+.
[source,javascript]
----
-String.fromCodePoint(128014, 128113, 10084)
+String.fromCodePoint(0x1f40e, 0x1f471, 0x2764)
// <- '🐎👱❤'
----
@@ -1285,7 +1301,7 @@ const text = `\ud83d\udc0e\ud83d\udc71\u2764`
Reversing an string has potential to cause issues as well.
-==== 7.3.10 Unicode-Aware String Reversal
+==== 7.3.11 Unicode-Aware String Reversal
Consider the following piece of code.
@@ -1317,9 +1333,9 @@ This way we avoid breaking up code points. Once again, keep in mind that this wo
The last Unicode-related method we'll be addressing is +.normalize+.
-==== 7.3.11 +String#normalize+
+==== 7.3.12 +String#normalize+
-There are different ways of representing strings that look identical to humans even though their code points differ. Consider the following example, where two seemingly identical strings aren't deemed equal by any JavaScript runtime.
+There are different ways of representing strings that look identical to humans even though their code points differ. Consider the following example, where two seemingly identical strings aren't deemed equal by any JavaScript runtime.footnoteref:[unicode-problem]
[source,javascript]
----
@@ -1415,7 +1431,7 @@ cast(`a`, `b`)
// <- [`a`, `b`]
----
-You may also want to cast +NodeList+ DOM element collections, like those returned from +document.querySelectorAll+, through the spread operator. Once again, this is made possible thanks to ES6 adding conformance to the iterator protocol for +NodeList+.
+You may also want to cast +NodeList+ DOM element collections, like those returned from +document.querySelectorAll+, through the spread operator. This can be helpful when we need access to native array methods like +Array#map+ or +Array#filter+. Once again, this is made possible thanks to ES6 adding conformance to the iterator protocol for +NodeList+.
[source,javascript]
----
@@ -1519,28 +1535,28 @@ The +Array+ constructor has two overloads: `...items`, where you provide the ite
[source,javascript]
----
-new Array(); // <- []
-new Array(undefined); // <- [undefined]
-new Array(1); // <- [undefined x 1]
-new Array(3); // <- [undefined x 3]
-new Array(`3`); // <- [`3`]
-new Array(1, 2); // <- [1, 2]
-new Array(-1, -2); // <- [-1, -2]
-new Array(-1); // <- RangeError: Invalid array length
+new Array() // <- []
+new Array(undefined) // <- [undefined]
+new Array(1) // <- [undefined x 1]
+new Array(3) // <- [undefined x 3]
+new Array(`3`) // <- [`3`]
+new Array(1, 2) // <- [1, 2]
+new Array(-1, -2) // <- [-1, -2]
+new Array(-1) // <- RangeError: Invalid array length
----
In contrast, +Array.of+ has more consistent behavior because it doesn't have the special +length+ case. This makes it a more desirable way of consistently creating new arrays programatically.
[source,javascript]
----
-console.log(Array.of()); // <- []
-console.log(Array.of(undefined)); // <- [undefined]
-console.log(Array.of(1)); // <- [1]
-console.log(Array.of(3)); // <- [3]
-console.log(Array.of(`3`)); // <- [`3`]
-console.log(Array.of(1, 2)); // <- [1, 2]
-console.log(Array.of(-1, -2)); // <- [-1, -2]
-console.log(Array.of(-1)); // <- [-1]
+console.log(Array.of()) // <- []
+console.log(Array.of(undefined)) // <- [undefined]
+console.log(Array.of(1)) // <- [1]
+console.log(Array.of(3)) // <- [3]
+console.log(Array.of(`3`)) // <- [`3`]
+console.log(Array.of(1, 2)) // <- [1, 2]
+console.log(Array.of(-1, -2)) // <- [-1, -2]
+console.log(Array.of(-1)) // <- [-1]
----
==== 7.4.3 +Array#copyWithin+
@@ -1558,7 +1574,7 @@ Let's lead with a simple example. Consider the +items+ array in the following co
[source,javascript]
----
-const items = [1, 2, 3, ,,,,,,,]
+const items = [1, 2, 3, , , , , , , , ]
// <- [1, 2, 3, undefined x 7]
----
@@ -1566,7 +1582,7 @@ The function call shown below takes the +items+ array and determines that it'll
[source,javascript]
----
-const items = [1, 2, 3, ,,,,,,,]
+const items = [1, 2, 3, , , , , , , , ]
items.copyWithin(6, 1, 3)
// <- [1, 2, 3, undefined × 3, 2, 3, undefined × 2]
----
@@ -1577,7 +1593,7 @@ If we consider that the items to be copied were taken from the +[start, end)+ ra
[source,javascript]
----
-const items = [1, 2, 3, ,,,,,,,]
+const items = [1, 2, 3, , , , , , , , ]
const copy = items.slice(1, 3)
// <- [2, 3]
----
@@ -1586,7 +1602,7 @@ We could also consider the pasting part of the operation as an advanced usage of
[source,javascript]
----
-const items = [1, 2, 3, ,,,,,,,]
+const items = [1, 2, 3, , , , , , , , ]
const copy = items.slice(1, 3)
// <- [2, 3]
items.splice(6, 3 - 1, ...copy)
@@ -1610,7 +1626,7 @@ The example we've been trying so far would work just as well with our custom +co
[source,javascript]
----
-copyWithin([1, 2, 3, ,,,,,,,], 6, 1, 3)
+copyWithin([1, 2, 3, , , , , , , , ], 6, 1, 3)
// <- [1, 2, 3, undefined × 3, 2, 3, undefined × 2]
----
@@ -1620,15 +1636,15 @@ A convenient utility method to replace all items in an array with the provided +
[source,javascript]
----
-[`a`, `b`, `c`].fill(`x`); // <- [`x`, `x`, `x`]
-new Array(3).fill(`x`); // <- [`x`, `x`, `x`]
+[`a`, `b`, `c`].fill(`x`) // <- [`x`, `x`, `x`]
+new Array(3).fill(`x`) // <- [`x`, `x`, `x`]
----
You could also specify the starting index and end index. In this case, as shown next, only the items in those positions would be filled.
[source,javascript]
----
-[`a`, `b`, `c`,,,].fill(`x`, 2)
+[`a`, `b`, `c`, , ,].fill(`x`, 2)
// <- [`a`, `b`, `x`, `x`, `x`]
new Array(5).fill(`x`, 0, 1)
// <- [`x`, undefined x 4]
diff --git a/chapters/ch08.asciidoc b/chapters/ch08.asciidoc
index 64548ed..1898e19 100644
--- a/chapters/ch08.asciidoc
+++ b/chapters/ch08.asciidoc
@@ -33,7 +33,7 @@ Suppose we take that snippet of code and save it as +union.js+. We can now consu
[source,javascript]
----
-const union = require(`./union`)
+const union = require(`./union.js`)
console.log(union([1, 2], 3))
// <- [1, 2, 3]
console.log(union([1, 2], 2))
@@ -44,7 +44,7 @@ We could run +app.js+ in its current state through the CLI for Node.js, +node+,
[source,shell]
----
-» node app
+» node app.js
# [1, 2, 3]
# [1, 2]
----
@@ -52,8 +52,6 @@ We could run +app.js+ in its current state through the CLI for Node.js, +node+,
[NOTE]
====
You can download Node.js from their website: https://mjavascript.com/out/node. After installing Node, you'll be able to use the +node+ program in your terminal.
-
-Note the +.js+ file extension is optional when executing programs through +node+.
====
The +require+ function in CJS can be treated dynamically, just like any other JavaScript function. This aspect of +require+ is sometimes leveraged to dynamically +require+ different modules that conform to one interface. As an example, let's conjure up a +templates+ directory with a number of view template functions. Our templates will take a model and return an HTML string.
@@ -70,12 +68,12 @@ module.exports = model => `
`
----
-Our application could easily print a +
+ by leveraging the +item.js+ view template.
+Our application could print a ++ by leveraging the +item.js+ view template.
[source,javascript]
----
// app.js
-const renderItem = require(`./views/item`)
+const renderItem = require(`./views/item.js`)
const html = renderItem({
name: `Banana bread`,
amount: 3
@@ -92,7 +90,7 @@ The next template we'll make renders the grocery list itself. It receives an arr
[source,javascript]
----
// views/list.js
-const renderItem = require(`./item`)
+const renderItem = require(`./item.js`)
module.exports = model => `
${ model.map(renderItem).join(`\n`) }
@@ -104,7 +102,7 @@ We can consume the +list.js+ template in a very similar way than what we did bef
[source,javascript]
----
// app.js
-const renderList = require(`./views/list`)
+const renderList = require(`./views/list.js`)
const html = renderList([{
name: `Banana bread`,
amount: 3
@@ -127,7 +125,7 @@ Given that the views all have a similar API where they take a model and return a
----
// render.js
module.exports = function render(template, model) {
- return require(`./views/${ template }`)(model)
+ return require(`./views/${ template }`)(mode.jsl)
}
----
@@ -136,7 +134,7 @@ Once we had such an API, we wouldn't have to worry about carefully constructing
[source,javascript]
----
// app.js
-const render = require(`./render`)
+const render = require(`./render.js`)
console.log(render(`item`, {
name: `Banana bread`,
amount: 1
@@ -166,7 +164,7 @@ In the ES6 module system, strict mode is turned on by default. Strict mode is a
- Function parameters must have unique names
- Using +with+ statements is forbidden
- Assignment to read-only properties results in errors being thrown
-- Octal numbers like +00840+ are syntax errors
+- Octal numbers like +00740+ are syntax errors
- Attempts to +delete+ undeletable properties throw an error
- +delete prop+ is a syntax error, instead of assuming +delete global.prop+
- +eval+ doesn't introduce new variables into its surrounding scope
@@ -339,7 +337,7 @@ Modules consuming this API would see the +fungible+ value changing after five se
[source,javascript]
----
-import { fungible } from './fungible'
+import { fungible } from './fungible.js'
console.log(fungible) // <- { name: 'bound' }
setInterval(() => console.log(fungible), 2000)
@@ -360,30 +358,30 @@ We can expose another module's named exports using by adding a +from+ clause to
[source,javascript]
----
-export { increment } from './counter'
+export { increment } from './counter.js'
increment()
// ReferenceError: increment is not defined
----
-You can give aliases to named exports, as they pass through your module. If the module in the following example were named +aliased+, then consumers could +import { add } from './aliased'+ to get a reference to the +increment+ binding from the +counter+ module.
+You can give aliases to named exports, as they pass through your module. If the module in the following example were named +aliased+, then consumers could +import { add } from './aliased.js'+ to get a reference to the +increment+ binding from the +counter+ module.
[source,javascript]
----
-export { increment as add } from './counter'
+export { increment as add } from './counter.js'
----
An ESM module could also expose every single named export found in another module by using a wildcard, as shown in the next snippet. Note that this wouldn't include the default binding exported by the +counter+ module.
[source,javascript]
----
-export * from './counter'
+export * from './counter.js'
----
When we want to expose another module's +default+ binding, we'll have to use the named export syntax adding an alias.
[source,javascript]
----
-export { default as counter } from './counter'
+export { default as counter } from './counter.js'
----
We've now covered every way in which we can expose an API in ES6 modules. Let's jump over to +import+ statements, which can be used to consume other modules.
@@ -408,9 +406,14 @@ The statement in the following code snippet could be used to load the +counter+
[source,javascript]
----
-import './counter'
+import './counter.js'
----
+[WARNING]
+====
+While the +.js+ file extension is optional when executing programs through +node+, we should strongly consider getting into the habit of including it nevertheless. Browser implementations of ESM won't have this luxury, since that'd entail extra roundtrips to figure out the correct endpoint for a JavaScript module HTTP resource. You can learn more in the specification at: https://mjavascript.com/out/esm-integration.
+====
+
In the same fashion as +export+ statements, +import+ statements are only allowed in the top level of your module definitions. This limitation helps compilers simplify their module loading capabilities, as well as help other static analysis tools parse your codebase.
===== Importing Default Exports
@@ -419,14 +422,14 @@ CommonJS modules let you import other modules using +require+ statements. When w
[source,javascript]
----
-const counter = require(`./counter`)
+const counter = require(`./counter.js`)
----
To import the default binding exported from an ES6 module, we'll have to give it a name. The syntax and semantics are a bit different than what we use when declaring a variable, because we're importing a binding and not just assigning values to variables. This distinction also makes it easier for static analysis tools and compilers to parse our code.
[source,javascript]
----
-import counter from './counter'
+import counter from './counter.js'
console.log(counter)
// <- 0
----
@@ -439,42 +442,42 @@ The following bit of code shows how we can import the +increment+ method from ou
[source,javascript]
----
-import { increment } from './counter'
+import { increment } from './counter.js'
----
To import multiple bindings, we separate them using commas.
[source,javascript]
----
-import { increment, decrement } from './counter'
+import { increment, decrement } from './counter.js'
----
The syntax and semantics are subtly different from destructuring. While destructuring relies on colons to create aliases, +import+ statements use an +as+ keyword, mirroring the syntax in +export+ statements. The following statement imports the +increment+ method as +add+.
[source,javascript]
----
-import { increment as add } from './counter'
+import { increment as add } from './counter.js'
----
You can combine a default export with named exports by separating them with a comma.
[source,javascript]
----
-import counter, { increment } from './counter'
+import counter, { increment } from './counter.js'
----
You can also explicitly name the +default+ binding, which needs an alias.
[source,javascript]
----
-import { default as counter, increment } from './counter'
+import { default as counter, increment } from './counter.js'
----
The following example demonstrates how ESM semantics differ from those of CJS. Remember: we're exporting and importing bindings, and not direct references. For practical purposes, you can think of the +counter+ binding found in the next example as a property getter that reaches into the +counter+ module and returns its local +counter+ variable.
[source,javascript]
----
-import counter, { increment } from './counter'
+import counter, { increment } from './counter.js'
console.log(counter) // <- 0
increment()
console.log(counter) // <- 1
@@ -490,7 +493,7 @@ We can import the namespace object for a module by using a wildcard. Instead of
[source,javascript]
----
-import * as counter from './counter'
+import * as counter from './counter.js'
counter.increment()
counter.increment()
console.log(counter.default) // <- 2
@@ -506,7 +509,7 @@ Imagine you're looking to internationalize an application based on the language
[source,javascript]
----
-import localizationService from './localizationService'
+import localizationService from './localizationService.js'
import(`./localizations/${ navigator.language }.json`)
.then(module => localizationService.use(module))
----
@@ -623,7 +626,7 @@ Ease of adding or removing functionality from a module is yet another useful met
We'll plunge deeper into proper module design, effective module interaction and module testing over the next three books in this series.
-At the time of this writing, browsers are only scratching the surface of native JavaScript modules. It's expected that browsers will eventually be able to consume modules initiated by `