diff --git a/chapters/ch01.asciidoc b/chapters/ch01.asciidoc index 412a98b..01bef05 100644 --- a/chapters/ch01.asciidoc +++ b/chapters/ch01.asciidoc @@ -29,7 +29,7 @@ As AOL laid off 50 Netscape employees in 2003footnote:[You can read a news repor It took two years until Brendan, now at Mozilla, had ECMA resurrect work on TC39 by using Firefox's growing market share as leverage to get Microsoft back in the fold. By mid 2005, TC39 started meeting regularly once again. As for ES4, there were plans for introducing a module system, classes, iterators, generators, destructuring, type annotations, proper tail calls, algebraic typing, and an assortment of other features. Due to how ambitious the project was, work on ES4 was repeatedly delayed. -By 2007 the commitee was split in two: ES3.1, which hailed a more incremental approach to ES3; and ES4, which was overdesigned and underspecified. It wouldn't be until August 2008footnote:[Brendan Eich sent an email to the es-discuss mailing list in 2008 where he summarized the situation, almost ten years after ES3 had been released: https://mjavascript.com/out/harmony.] when ES3.1 was agreed upon as the way forward, but later rebranded as ES5. Although ES4 would be abandoned, many of its features eventually made its way into ES6 (which was dubbed Harmony at the time of this resolution), while some of them still remain under consideration. The ES3.1 update served as the foundation on top of which the ES4 specification could be laid upon in bits and pieces. +By 2007 the commitee was split in two: ES3.1, which hailed a more incremental approach to ES3; and ES4, which was overdesigned and underspecified. It wouldn't be until August 2008footnote:[Brendan Eich sent an email to the es-discuss mailing list in 2008 where he summarized the situation, almost ten years after ES3 had been released: https://mjavascript.com/out/harmony.] when ES3.1 was agreed upon as the way forward, but later rebranded as ES5. Although ES4 would be abandoned, many of its features eventually made its way into ES6 (which was dubbed Harmony at the time of this resolution), while some of them still remain under consideration and a few others have been abandoned, rejected, or withdrawn. The ES3.1 update served as the foundation on top of which the ES4 specification could be laid upon in bits and pieces. In December 2009, on the ten-year anniversary since the publication of ES3, the fifth edition of ECMAScript was published. This edition codified de facto extensions to the language specification that had become common among browser implementations, adding get and set accessors, functional improvements to the +Array+ prototype, reflection and introspection, as well as native support for JSON parsing, and strict mode. @@ -45,19 +45,19 @@ 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 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 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. Firefox, Chrome, Edge, Safari and Node.js all offer over 95% compliancy 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 been submitted as a formal proposal is considered to be an aspirational "strawman" proposal (stage 0) and has no acceptance requirements. At the time of this writing, there's over a dozen active stage 0 proposalsfootnote:[You can track stage 0 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) 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.]. 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. Proposals in stage 2 offer an initial draft of the specification. At this point, it's reasonable to begin experimenting with actual implementations in runtimes. The implementation could come in the form of a polyfill, user code that mangles the runtime into adhering to the proposal; an engine implementation, natively providing support for the proposal; or compiled into something existing engines can execute, using build-time tools to transform source code. -Proposals in stage 3 are candidate recommendations. At this point, implementors have expressed interest in the proposal. In practice, proposals move to this level with at least one browser implementation, a high-fidelity polyfill or when supported by a build-time compiler like Babel. At this stage, a proposal is unlikely to change beyond fixes to issues identified in the wild. +Proposals in stage 3 are candidate recommendations. In order for a proposal to advance to this stage, the specification editor and designated reviewers must have signed off on the final specification. Implementors should've expressed interest in the proposal as well. In practice, proposals move to this level with at least one browser implementation, a high-fidelity polyfill or when supported by a build-time compiler like Babel. A stage 3 proposal is unlikely to change beyond fixes to issues identified in the wild. In order for a proposal to attain stage 4 status, two independent implementations need to pass acceptance tests. Proposals that make their way through to stage four will be included in the next revision of ECMAScript. @@ -143,7 +143,7 @@ To install Babel, enter the following couple of commands into your favorite term [source,shell] ---- npm install babel-cli​@6 --save-dev -npm install babel-preset-es2015​@6 --save-dev +npm install babel-preset-env@6 --save-dev ---- [NOTE] @@ -152,7 +152,7 @@ Packages installed by +npm+ will be placed in a +node_modules+ directory at the Using the +--save-dev+ flag will add these packages to our +package.json+ manifest as development dependencies, so that when copying our project to new environments we can reinstall every dependency just by running +npm install+. -The +@+ notation indicates we want to install a specific version of a package. Using +@6+ we're telling +npm+ to install the latest version of +babel-cli+ in the +6.x+ range. This preference is handy to future-proof our applications, as it would never install version, which might contain breaking changes that could not have been foreseen at the time of this writing. +The +@+ notation indicates we want to install a specific version of a package. Using +@6+ we're telling +npm+ to install the latest version of +babel-cli+ in the +6.x+ range. This preference is handy to future-proof our applications, as it would never install `7.0.0` or later versions, which might contain breaking changes that could not have been foreseen at the time of this writing. ==== For the next step, we'll replace the value of the +scripts+ property in +package.json+ with the following. The +babel+ command-line utility provided by +babel-cli+ can take the entire contents of our +src+ directory, compile them into the desired output format, and save the results to a +dist+ directory, while preserving the original directory structure under a different root. @@ -173,8 +173,8 @@ Together with the packages we've installed in the previous step, a minimal +pack "build": "babel src --out-dir dist" }, "devDependencies": { - "babel-cli": "6.18.0", - "babel-preset-es2015": "6.18.0" + "babel-cli": "^6.24.0", + "babel-preset-env": "^1.2.1" } } ---- @@ -189,11 +189,11 @@ If you execute +npm run build+ in your terminal now, you'll note that a +dist/ex [source,json] ---- { - "presets": ["es2015"] + "presets": ["env"] } ---- -The +es2015+ preset, which we had installed earlier via +npm+, adds a series of plugins to Babel which transform different bits of ES6 code into ES5. Among other things, this preset transforms arrow functions like the one in our +example.js+ file into ES5 code. +The +env+ preset, which we had installed earlier via +npm+, adds a series of plugins to Babel which transform different bits of ES6 code into ES5. Among other things, this preset transforms arrow functions like the one in our +example.js+ file into ES5 code. The +env+ Babel preset works by convention, enabling Babel transformation plugins according to feature support in the latest browsers. This preset is configurable, meaning we can decide how far back we want to cover browser support. The more browsers we support, the larger our transpiled bundle. The less browsers we support, the less customers we can satisfy. As always, research is of the essence to identify what the correct configuration for the Babel +env+ preset is. By default, every transform is enabled, providing broad runtime support. Once we run our build script again, we'll observe that the output is now valid ES5 code. @@ -231,7 +231,7 @@ ESLint is a modern linter that packs several plugins, sporting different rules, npm install eslint@3 --save-dev ---- -Next, we need to configure ESLint. Since we installed +eslint+ as a local dependency, we'll find its command-line tool in +node_modules/.bin+. Executing the following command will guide us through configuring ESLint for our project for the first time. To get started, indicate you want to use a popular style guide and choose Standard, then pick JSON format for the configuration file. +Next, we need to configure ESLint. Since we installed +eslint+ as a local dependency, we'll find its command-line tool in +node_modules/.bin+. Executing the following command will guide us through configuring ESLint for our project for the first time. To get started, indicate you want to use a popular style guide and choose Standardfootnote:[Note that Standard is just a self-proclamation, and not actually standardized in any official capacity. It doesn't really matter which style guide you follow as long as you follow it consistently. Consistency helps reduce confusion while reading a project's code base. The AirBnB style guide is also fairly popular and it doesn't omit semicolons by default, unlike Standard.], then pick JSON format for the configuration file. [source,shell] ---- diff --git a/chapters/ch02.asciidoc b/chapters/ch02.asciidoc index dc4619c..a6efa77 100644 --- a/chapters/ch02.asciidoc +++ b/chapters/ch02.asciidoc @@ -201,8 +201,6 @@ var emitter = { } ---- -One problem with this syntax is that it tightly couples a method to an object. In terms of modular design, it would be cleaner to keep your code decoupled, and the syntax discourages it. By dropping the +function+ keyword and inferring the property name from the method, we are making it hard to extract +deplete+ from the object literal. - Arrow functions are another way of declaring functions in ES6, and they come in several flavors. Let's investigate what arrow functions are, how they can be declared, and how they behave semantically. === 2.2 Arrow Functions @@ -234,11 +232,13 @@ var example = (parameters) => { } ---- -While arrow functions look very similar to your typical anonymous function, they are fundamentally different: arrow functions can't have a name, and they are bound to their lexical scope. Let's dig into their semantic differences with traditional functions, the many ways to declare an arrow function, and practical use cases. +While arrow functions look very similar to your typical anonymous function, they are fundamentally different: arrow functions can't have a name, they can't be used as constructors, they don't have a +prototype+ property, and they are bound to their lexical scope. + +Let's dig into their semantic differences with traditional functions, the many ways to declare an arrow function, and practical use cases. ==== 2.2.1 Lexical Scoping -In the body of an arrow function, +this+ and +arguments+ both 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 ellapsed +seconds+. +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+. [source,javascript] ---- @@ -283,7 +283,7 @@ var double = value => { } ---- -Arrow functions are heavily used for simple functions, such as the +double+ function we just saw. The following flavor of arrow functions does away with the function body. Instead, you provide an expression such as +value * 2+. When the function is called, the expression is evaluated and its result is returned. The +return+ statement is implicit, and there's no need for brackets denoting the function body anymore, as you can only use a single expression. +Arrow functions are heavily used for simple functions, such as the +double+ function we just saw. The following flavor of arrow functions does away with the function body. Instead, you provide an expression such as +value * 2+. When the function is called, the expression is evaluated and its result is returned. The +return+ statement is implicit, and there's no need for curly braces denoting the function body anymore, as you can only use a single expression. [source,javascript] ---- @@ -299,14 +299,14 @@ var double = value => value * 2 .Implicitly Returning Object Literals **** -When you need to implicitly return an object literal, you'll need to wrap that object literal expression in parenthesis. Otherwise, the compiler would interpret your brackets as the start and the end of the function block. +When you need to implicitly return an object literal, you'll need to wrap that object literal expression in parenthesis. Otherwise, the compiler would interpret your curly braces as the start and the end of the function block. [source,javascript] ---- var objectFactory = () => ({ modular: 'es6' }) ---- -In the following example, JavaScript interprets the brackets as the body of our arrow function. Furthermore, +number+ is interpreted as a labelfootnote:[Labels are used as a way of identifying instructions. Labels can be used by +goto+ statements, to indicate what instruction we should jump to; +break+ statements, to indicate the sequence we want to break out of; and +continue+ statements, to indicate the sequence we want to advance. You can learn more about labels at: https://mjavascript.com/out/label.] and then figures out we have a +value+ expression that doesn't do anything. Since we're in a block and not returning anything, the mapped values will be +undefined+. +In the following example, JavaScript interprets the curly braces as the body of our arrow function. Furthermore, +number+ is interpreted as a labelfootnote:[Labels are used as a way of identifying instructions. Labels can be used by +goto+ statements, to indicate what instruction we should jump to; +break+ statements, to indicate the sequence we want to break out of; and +continue+ statements, to indicate the sequence we want to advance. You can learn more about labels at: https://mjavascript.com/out/label.] and then figures out we have a +value+ expression that doesn't do anything. Since we're in a block and not returning anything, the mapped values will be +undefined+. [source,javascript] ---- @@ -341,7 +341,7 @@ Now that you understand arrow functions, let's ponder about their merits and whe As a rule of thumb, you shouldn't blindly adopt ES6 features wherever you can. Instead, it's best to reason about each case individually and see whether adopting the new feature actually improves code readibility and maintainability. ES6 features are not strictly better than what we had all along, and it's a bad idea to treat them as such. -There's a few situations where arrow functions may not be the best tool. For example, if you have a large function with several lines of code, replacing +function+ for +=>+ is hardly going to improve your code. Arrow functions are often most effective for short routines, where the +function+ keyword and syntax boilerplate make up a significant portion of the function expression. +There's a few situations where arrow functions may not be the best tool. For example, if you have a large function comprised of several lines of code, replacing `function` with `=>` is hardly going to improve your code. Arrow functions are often most effective for short routines, where the +function+ keyword and syntax boilerplate make up a significant portion of the function expression. Properly naming a function adds context to make it easier for humans to interpret them. Arrow functions can't be explicitly named, but they can be named implicitly by assigning them to a variable. In the following example, we assign an arrow function to the +throwError+ variable. When calling this function results in an error, the stack trace properly identifies the arrow function as +throwError+. @@ -376,12 +376,12 @@ This is one of the most flexible and expressive features in ES6. It's also one o ==== 2.3.1 Destructuring Objects -Imagine you had a program with some comic book characters, Bruno Diaz being one of them, and you want to refer to properties in the object that describes him. Here's the example object we'll be using for Batman. +Imagine you had a program with some comic book characters, Bruce Wayne being one of them, and you want to refer to properties in the object that describes him. Here's the example object we'll be using for Batman. [source,javascript] ---- var character = { - name: 'Bruno', + name: 'Bruce', pseudonym: 'Batman', metadata: { age: 34, @@ -405,7 +405,7 @@ With destructuring in assignment, the syntax becomes a bit more clear. As you ca var { pseudonym } = character ---- -Just like you could declare multiple comma-separated variables with a single +var+ statement, you can also declare multiple variables within the brackets of a destructuring expression. +Just like you could declare multiple comma-separated variables with a single +var+ statement, you can also declare multiple variables within the curly braces of a destructuring expression. [source,javascript] ---- @@ -580,7 +580,7 @@ var right = 7 The last area of destructuring we'll be covering is function parameters. -==== 2.3.3 Function Parameter Defaults and Destructuring +==== 2.3.3 Function Parameter Defaults Function parameters in ES6 enjoy the ability of specifying default values as well. The following example defines a default +exponent+ with the most commonly used value. @@ -631,7 +631,11 @@ carFactory({ make: 2000 }) // <- 2000 ---- -A better approach would be to destructure +options+ entirely, providing default values for each property, individually, within the destructuring pattern. This approach also lets you reference each option without going through an +options+ object, but you lose the ability to reference +options+ directly, which might represent an issue in some situations. +We can mix function parameter default values with destructuring, and get the best of both worlds. + +==== 2.3.4 Function Parameter Destructuring + +A better approach than merely providing a default value might be to destructure +options+ entirely, providing default values for each property, individually, within the destructuring pattern. This approach also lets you reference each option without going through an +options+ object, but you lose the ability to reference +options+ directly, which might represent an issue in some situations. [source,javascript] ---- @@ -694,7 +698,7 @@ getCarProductModel(car) Besides default values and filling an +options+ object, let's explore what else destructuring is good at. -==== 2.3.4 Use Cases for Destructuring +==== 2.3.5 Use Cases for Destructuring Whenever there's a function that returns an object or an array, destructuring makes it much terser to interact with. The following example shows a function that returns an object with some coordinates, where we grab only the ones we're interested in: +x+ and +y+. We're avoiding an intermediate +point+ variable declaration that often gets in the way without adding a lot of value to the readability of your code. @@ -1172,7 +1176,7 @@ function isItTwo (value) { } ---- -Whether we like it or not, hoisting is more confusing than having block-scoped variables would be. Block scoping works on the curly-braces level, rather than the function level. +Whether we like it or not, hoisting is more confusing than having block-scoped variables would be. Block scoping works on the curly braces level, rather than the function level. ==== 2.6.1 Block Scoping and Let Statements @@ -1192,7 +1196,7 @@ With +var+, because of lexical scoping, one could still access the +deep+ variab - The block in question has many siblings that would also want to use the same variable name - One of the parent blocks already has a variable with the name we need, but the name is still appropriate to use in the inner block -The +let+ statement is an alternative to +var+. It follows block scoping rules instead of the default lexical scoping rules. With +var+, the only way of getting a deeper scope is to create a nested function, but with +let+ you can just open another pair of brackets. This means you don't need entirely new functions to get a new scope: a simple +{}+ block will do. +The +let+ statement is an alternative to +var+. It follows block scoping rules instead of the default lexical scoping rules. With +var+, the only way of getting a deeper scope is to create a nested function, but with +let+ you can just open another pair of curly braces. This means you don't need entirely new functions to get a new scope: a simple +{}+ block will do. [source,javascript] ---- diff --git a/chapters/ch03.asciidoc b/chapters/ch03.asciidoc index af13a7a..acf219f 100644 --- a/chapters/ch03.asciidoc +++ b/chapters/ch03.asciidoc @@ -253,7 +253,7 @@ Banana.prototype.slice = function () { } ---- -Given the ephemeral knowledge one has to remember, and the fact that +Object.create+ was only made available in ES5, JavaScript developers have historically turned to libraries to resolve their prototype inheritance issues. One such example is +util.inherits+ in Node.js, which is usually favored over +Object.create+ for legacy support reasons. +Given the ephemeral knowledge one has to remember, and the fact that `Object.create` was only made available in ES5, JavaScript developers have historically turned to libraries to resolve their prototype inheritance issues. One such example is +util.inherits+ in Node.js, which is usually favored over `Object.create` for legacy support reasons. [source,javascript] ---- @@ -322,7 +322,7 @@ class Plum extends createJuicyFruit('plum', 30) { } ---- -Let's move onto +Symbol+. While not iteration or flow control mechanism, learning about +Symbol+ is crucial to shaping an understanding of iteration protocols, which are discussed at length later in the chapter. +Let's move onto +Symbol+. While not an iteration or flow control mechanism, learning about +Symbol+ is crucial to shaping an understanding of iteration protocols, which are discussed at length later in the chapter. === 3.2 Symbols @@ -391,7 +391,7 @@ console.log(character[weapon]) // <- 'umbrella' ---- -Keep in mind that symbol keys are hidden from many of the traditional ways of pulling keys from an object. The next bit of code shows how +for..in+, +Object.keys+, and +Object.getOwnPropertyNames+ fail to report on symbol properties. +Keep in mind that symbol keys are hidden from many of the traditional ways of pulling keys from an object. The next bit of code shows how `for..in`, `Object.keys`, and `Object.getOwnPropertyNames` fail to report on symbol properties. [source,javascript] ---- @@ -413,7 +413,7 @@ console.log(JSON.stringify(character)) // <- '{"name":"Penguin"}' ---- -That being said, symbols are by no means a safe mechanism to conceal properties. Even though you won't stumble upon symbol properties when using reflection or serialization methods, symbols are revealed by a dedicated method as shown in the next snippet of code. In other words, symbols are not non-enumerable, but hidden in plain sight. Note that +Object.getOwnPropertySymbols+ +That being said, symbols are by no means a safe mechanism to conceal properties. Even though you won't stumble upon symbol properties when using reflection or serialization methods, symbols are revealed by a dedicated method as shown in the next snippet of code. In other words, symbols are not non-enumerable, but hidden in plain sight. Using `Object.getOwnPropertySymbols` we can retrieve all symbols used as property keys on any given object. [source,javascript] ---- @@ -550,7 +550,7 @@ console.log(Symbol.keyFor(Symbol())) // <- undefined ---- -Also keep in mind that it's not possible to match symbols in the global registry usign local symbols, even when they share the same description. The reason for that is that local symbols aren't part of the global registry, as shown in the following piece of code. +Also keep in mind that it's not possible to match symbols in the global registry using local symbols, even when they share the same description. The reason for that is that local symbols aren't part of the global registry, as shown in the following piece of code. [source,javascript] ---- @@ -703,6 +703,24 @@ print({ second: false }) Before +Object.assign+ made its way into the language, there were numerous similar implementations of this technique in user-land JavaScript, with names like assign, or extend. Adding +Object.assign+ to the language consolidates these options into a single method. +Note that +Object.assign+ takes into consideration only enumerable own properties, including both string and symbol properties. + +[source,javascript] +---- +const defaults = { + [Symbol('currency')]: 'USD' +} +const options = { + price: '0.99' +} +Object.defineProperty(options, 'name', { + value: 'Espresso Shot', + enumerable: false +}) +console.log(Object.assign({}, defaults, options)) +// <- { [Symbol('currency')]: 'USD', price: '0.99' } +---- + Note, however, that +Object.assign+ doesn't cater to every need. While most user-land implementations have the ability to perform deep assignment, +Object.assign+ doesn't offer a recursive treatment of objects. Object values are assigned as properties on +target+ directly, instead of being recursively assigned key by key. In the following bit of code you might expect the +f+ property to be added to +target.a+ while keeping +b.c+ and +b.d+ intact, but the +b.c+ and +b.d+ properties are lost when using +Object.assign+. @@ -798,7 +816,7 @@ These differences may not seem like much, but dealing with +NaN+ has always been The +Object.setPrototypeOf+ method does exactly what its name conveys: it sets the prototype of an object to a reference to another object. It's considered the proper way of setting the prototype, as opposed to using `__proto__` which is a legacy feature. -Before ES6, we were introduced to +Object.create+ in ES5. Using that method, we could create an object based on any prototype passed into +Object.create+, as shown next. +Before ES6, we were introduced to `Object.create` in ES5. Using that method, we could create an object based on any prototype passed into `Object.create`, as shown next. [source,javascript] ---- @@ -807,7 +825,7 @@ const cat = Object.create(baseCat) cat.name = 'Milanesita' ---- -The +Object.create+ method is, however, limited to newly created objects. In contrast, we could use +Object.setPrototypeOf+ to change the prototype of an object that already exists, as shown in the following code snippet. +The `Object.create` method is, however, limited to newly created objects. In contrast, we could use +Object.setPrototypeOf+ to change the prototype of an object that already exists, as shown in the following code snippet. [source,javascript] ---- @@ -815,7 +833,7 @@ const baseCat = { type: 'cat', legs: 4 } const cat = Object.setPrototypeOf({ name: 'Milanesita' }, baseCat) ---- -Note however that there are serious performance implications when using +Obect.setPrototypeOf+ as opposed to +Object.create+, and some careful consideration is in order before you decide to go ahead and sprinkle +Object.setPrototypeOf+ all over a codebase. +Note however that there are serious performance implications when using +Obect.setPrototypeOf+ as opposed to `Object.create`, and some careful consideration is in order before you decide to go ahead and sprinkle +Object.setPrototypeOf+ all over a codebase. .Performance issues [WARNING] @@ -824,7 +842,7 @@ Using +Object.setPrototypeOf+ to change the prototype of an object is an expensi [quote, Mozilla Developer Network] ____ -Changing the prototype of an object is, by the nature of how modern JavaScript engines optimize property accesses, a very slow operation, in every browser and JavaScript engine. The effects on performance of altering inheritance are subtle and far-flung, and are not limited to simply the time spent in a `obj.__proto__ = ...` statement, but may extend to any code that has access to any object whose prototype has been altered. If you care about performance you should avoid setting the prototype of an object. Instead, create a new object with the desired prototype using +Object.create()+. +Changing the prototype of an object is, by the nature of how modern JavaScript engines optimize property accesses, a very slow operation, in every browser and JavaScript engine. The effects on performance of altering inheritance are subtle and far-flung, and are not limited to simply the time spent in a `obj.__proto__ = ...` statement, but may extend to any code that has access to any object whose prototype has been altered. If you care about performance you should avoid setting the prototype of an object. Instead, create a new object with the desired prototype using `Object.create()`. ____ ==== diff --git a/chapters/ch04.asciidoc b/chapters/ch04.asciidoc index 1ec9a39..2208de2 100644 --- a/chapters/ch04.asciidoc +++ b/chapters/ch04.asciidoc @@ -99,7 +99,7 @@ fetch('/items') }) ---- -Binding several listeners for each type of event would eliminate the concern we had earlier about having to centralize response handling in a single callback. Events however make it hard to chain callbacks and have them fire when another asynchronous task is fulfilled, and that's where promises come in. +Binding several listeners for each type of event would eliminate the concern we had earlier about having to centralize response handling in a single callback. Events however make it hard to chain callbacks and have them fire when another asynchronous task is fulfilled, and that's where promises come in. Moreover, events are better suited to handle streams of values, making them somewhat inappropriate in for this particular use case. Section 9.7 discusses proper asynchronous code flow design in more detail, expanding on which kinds of constructs are better suited for which kinds of code flows. **** When it comes to promises, chaining is a major source of confusion. In an event-based API, chaining is made possible by having the +.on+ method attach the event listener and then returning the event emitter itself. Promises are different. The +.then+ and +.catch+ methods return a new promise every time. That's important because chaining can have wildly different results depending on where you append a +.then+ or a +.catch+ call onto. @@ -1382,7 +1382,7 @@ ball(function* questions () { }) ---- -Generator functions allow you to use error handling semantics -- +try+, +catch+, and +throw+ -- which were previously only useful in synchronous code paths. Having the ability to use +try+/+catch+ blocks in generator code lets us treat the code as if it were synchronous, even when there's HTTP requests sitting behind +yield+ expressions, in iterator code. +Even though generator functions allow us to suspend execution and then resume asynchronously, we can use the same error handling semantics -- +try+, +catch+, and +throw+ -- as with regular functions. Having the ability to use +try+/+catch+ blocks in generator code lets us treat the code as if it were synchronous, even when there's HTTP requests sitting behind +yield+ expressions, in iterator code. ==== 4.3.7 Returning on Behalf of a Generator @@ -1863,7 +1863,7 @@ read() As you can see, there's quite a few ways in which we can notice exceptions and then handle, log, or offload them. -==== 4.4.6 Understanding Async Function Internals +==== 4.4.5 Understanding Async Function Internals Async Functions leverage both generators and promises internally. Let's suppose we have the following Async Function. @@ -2179,6 +2179,8 @@ async function print () { print() ---- +Note that async iterators -- as well as async generators -- are in stage 3 of the ECMAScript process as of the time of this writing. + ==== 4.5.2 Async Generators Like with regular iterators, there's async generators to complement async iterators. An async generator function is like a generator function, except that it also supports +await+ and +for await..of+ declarations. The following example shows a +fetchInterval+ generator that fetches a resource periodically at an interval. diff --git a/chapters/ch05.asciidoc b/chapters/ch05.asciidoc index 2f79053..3cc5b49 100644 --- a/chapters/ch05.asciidoc +++ b/chapters/ch05.asciidoc @@ -36,7 +36,7 @@ function get (name) { } ---- -An alternative could also be using +Object.create(null)+ instead of an empty object literal. In this case, the created object won't inherit from +Object.prototype+, meaning it won't be harmed by `__proto__` and friends. +An alternative could also be using `Object.create(null)` instead of an empty object literal. In this case, the created object won't inherit from `Object.prototype`, meaning it won't be harmed by `__proto__` and friends. [source,javascript] ---- @@ -416,7 +416,7 @@ Maps aren't the only kind of built-in collection in ES6, there's also +WeakMap+, === 5.2 Understanding and Using WeakMap -For the most part, you can think of +WeakMap+ as a subset of +Map+. The +WeakMap+ collection imposes a number of limitations that we didn't find in +Map+. The biggest limitation is that +WeakMap+ is not iterable like +Map+: there is no iterable protocol in +WeakMap+, no +WeakMap#entries+, no +WeakMap#keys+, no +WeakMap#values+, no +WeakMap#forEach+ and no +WeakMap#clear+ methods. +For the most part, you can think of +WeakMap+ as a subset of +Map+. The +WeakMap+ collection has a reduced API surface with less affordances than what we could find in +Map+. Collections created using +WeakMap+ are not iterable like +Map+, meaning there is no iterable protocol in +WeakMap+, no +WeakMap#entries+, no +WeakMap#keys+, no +WeakMap#values+, no +WeakMap#forEach+ and no +WeakMap#clear+ methods. Another distinction found in +WeakMap+ is that every +key+ must be an object. This is in contrast with +Map+ where, while object references were allowed as keys, they weren't enforced. Remember that +Symbol+ is a value type, and as such, they're not allowed either. diff --git a/chapters/ch06.asciidoc b/chapters/ch06.asciidoc index 632da36..7094710 100644 --- a/chapters/ch06.asciidoc +++ b/chapters/ch06.asciidoc @@ -802,7 +802,7 @@ There are dozens of ways of binding methods to their parent object, all with the We haven't used an +apply+ trap for the +selfish+ examples, which illustrates that not everything is one-size-fits-all. Using an +apply+ trap for this use case would involve the current +selfish+ proxy returning proxies for +value+ functions, and then returning a bound function in the +apply+ trap for the +value+ proxy. While this may sound more correct, in the sense that we're not using +.bind+ but instead relying on +Reflect.apply+, we'd still need the +WeakMap+ cache and +selfish+ proxy. That is to say we'd be adding an extra layer of abstraction, a second proxy, and getting little value in terms of separation of concerns or maintainability, since both proxy layers would remain coupled to some degree, it'd be best to keep everything in a single layer. While abstractions are a great thing, too many abstractions can become more insurmountable than the problem they attempt to fix. -Up to what point is the abstraction justifiable over a few +.bind+ statements in the +constructor+ of a class object? These are hard questions that always depend on context, but they must be considered when designing a component system so that you don't add complexity for complexity's sake, while also adding abstraction layers that help you avoid repeating yourself. +Up to what point is the abstraction justifiable over a few +.bind+ statements in the +constructor+ of a class object? These are hard questions that always depend on context, but they must be considered when designing a component system so that, in the process of adding abstraction layers meant to help you avoid repeating yourself, you don't add complexity for complexity's sake. ==== 6.4.3 +construct+ Trap @@ -866,7 +866,7 @@ proxy.hello() // <- 'Hello, Nicolás' ---- -Let's move onto the next few traps. +Note that arrow functions can't be used as constructors, and thus we can't use the +construct+ trap on them. Let's move onto the last few traps. ==== 6.4.4 +getPrototypeOf+ Trap diff --git a/chapters/ch08.asciidoc b/chapters/ch08.asciidoc index f4f608a..bd63988 100644 --- a/chapters/ch08.asciidoc +++ b/chapters/ch08.asciidoc @@ -494,6 +494,13 @@ import(`./localizations/${ navigator.language }.json`) .then(module => localizationService.use(module)) ---- +Note that writing code like this is generally a bad idea for a number of reasons. + +- It can be challenging to statically analyze, given that static analysis is executed at build-time, when it can be hard or impossible to infer the value of interpolations such as `${ navigator.language }`. +- It can't be packaged up as easily by JavaScript bundlers, meaning the module would probably be loaded asynchronously while the bulk of our application has been loaded. +- It can't be tree-shaken by tools like Rollup, which can be used to remove module code that's never imported anywhere in the codebase -- and thus never used -- reducing bundle size and improving performance. +- It can't be linted by `eslint-plugin-import` or similar tools that help identify module import statements where the imported module file doesn't exist. + Just like with +import+ statements, the mechanism for retrieving the module is unspecified and left up to the host environment. The proposal does specify that once the module is resolved, the promise should fulfill with its namespace object. It also specifies that whenever an error results in the module failing to load, the promise should be rejected. @@ -599,4 +606,8 @@ 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 `