├── .gitignore
├── manuscript
├── images
│ ├── fg0601.png
│ ├── fg0601.psd
│ ├── fg0602.png
│ ├── fg0602.psd
│ ├── fg1001.jpg
│ └── title_page.png
├── Book.txt
├── B-ECMAScript-7.md
├── 00-Introduction.md
├── A-Other-Changes.md
├── 01-Block-Bindings.md
├── 05-Destructuring.md
├── 04-Objects.md
├── 13-Modules.md
├── 07-Sets-And-Maps.md
└── 06-Symbols.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | Thumbs.db
2 |
--------------------------------------------------------------------------------
/manuscript/images/fg0601.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/farhan7reza7/understandinges6/HEAD/manuscript/images/fg0601.png
--------------------------------------------------------------------------------
/manuscript/images/fg0601.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/farhan7reza7/understandinges6/HEAD/manuscript/images/fg0601.psd
--------------------------------------------------------------------------------
/manuscript/images/fg0602.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/farhan7reza7/understandinges6/HEAD/manuscript/images/fg0602.png
--------------------------------------------------------------------------------
/manuscript/images/fg0602.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/farhan7reza7/understandinges6/HEAD/manuscript/images/fg0602.psd
--------------------------------------------------------------------------------
/manuscript/images/fg1001.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/farhan7reza7/understandinges6/HEAD/manuscript/images/fg1001.jpg
--------------------------------------------------------------------------------
/manuscript/images/title_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/farhan7reza7/understandinges6/HEAD/manuscript/images/title_page.png
--------------------------------------------------------------------------------
/manuscript/Book.txt:
--------------------------------------------------------------------------------
1 | 00-Introduction.md
2 | 01-Block-Bindings.md
3 | 02-Strings-and-Regular-Expressions.md
4 | 03-Functions.md
5 | 04-Objects.md
6 | 05-Destructuring.md
7 | 06-Symbols.md
8 | 07-Sets-And-Maps.md
9 | 08-Iterators-And-Generators.md
10 | 09-Classes.md
11 | 10-Arrays.md
12 | 11-Promises.md
13 | 12-Proxies-and-Reflection.md
14 | 13-Modules.md
15 | A-Other-Changes.md
16 | B-ECMAScript-7.md
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Understanding ECMAScript 6
2 |
3 | by Nicholas C. Zakas
4 |
5 | [](https://creativecommons.org/licenses/by-nc-nd/3.0/)
6 |
7 | ECMAScript 6 represents the biggest change to the core of JavaScript in the history of the language. Not only does the sixth edition add new object types, but also new syntax and exciting new capabilities. The result of years of study and debate, ECMAScript 6 reached feature complete status in 2014. While it will take a bit of time before all JavaScript environments support ECMAScript 6, it's still useful to understand what's coming and which features are available already.
8 |
9 | This book is a guide for the transition between ECMAScript 5 and 6. It is not specific to any JavaScript environment, so it is equally useful to web developers as it is Node.js developers.
10 |
11 | What you'll learn:
12 |
13 | * All of the changes to the language since ECMAScript 5
14 | * How the new class syntax relates to more familiar JavaScript concepts
15 | * Why iterators and generators are useful
16 | * How arrow functions differ from regular functions
17 | * Additional options for storing data using sets, maps, and more
18 | * The power of inheriting from native types
19 | * Why people are so excited about promises for asynchronous programming
20 | * How modules will change the way you organize code
21 |
22 | ## Where to Read
23 |
24 | The [published version](https://leanpub.com/understandinges6/read/) is available for free and contains the latest "blessed" version. The content may be incomplete but should be correct. New releases are published several times a month.
25 |
26 | Anything that is not present in the published version is considered a work-in-progress and may be incomplete or incorrect.
27 |
28 | ## Purchasing an Ebook Copy
29 |
30 | You can purchase a copy of this ebook through [Leanpub](https://leanpub.com/understandinges6). If you purchase a copy before the ebook is finished, you will receive all updates to the ebook as they are released, up to and including the finished version.
31 |
32 | ## Purchasing a Print Copy
33 |
34 | Understanding ECMAScript 6 is being printed by No Starch Press and you can [purchase a print copy on Amazon](http://amzn.to/22YQOer). The print version is professionally copyedited and laid out.
35 |
36 | ## Supporting the Author
37 |
38 | If you enjoy this book and would like to support my efforts to create more content, there are a couple ways to do so:
39 |
40 | * [Leave a tip](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EFQLNGT3QEL6J) on PayPal
41 | * [Become a patron](https://patreon.com/nzakas) on Patreon
42 |
43 | A small donation is greatly appreciated and will allow me more time to continue my work.
44 |
45 | ## Contributions
46 |
47 | Even though this book is being developed in the open, the project works differently than open source software projects. Please read the following thoroughly before attempting to contribute.
48 |
49 | ### Leanpub Markdown
50 |
51 | Leanpub uses a specific flavor of Markdown for its source files. Please be sure to read [their documentation](https://leanpub.com/help/manual#leanpub-auto-styling-text) about what is allowable and what is not.
52 |
53 | **Note:** It may be frustrating, Leanpub does not support all of GitHub-flavored markdown.
54 |
55 | ### Pull requests
56 |
57 | Pull requests will be accepted only for *correction of content* and not for *creation of content*. Acceptable pull requests:
58 |
59 | * Typo fixes
60 | * Grammar/spelling errors
61 | * Alternate descriptions
62 | * Clarifying phrases
63 |
64 | Pull requests that will not be considered:
65 |
66 | * New sections of content
67 | * New chapters
68 |
69 | Basically, you can use pull requests to fix what is already in the repository but not to add what is not in the repository.
70 |
71 | **Note:** An accepted pull request means that your name will be added to a list of contributors. You are still bound by the conditions of the license, meaning that you are not considered an author or owner of the content once it has been merged in. It is considered a donation of your effort to this work.
72 |
73 | ### Issues
74 |
75 | Issues can be used both to point out errors as well as to make suggestions. Use issues for:
76 |
77 | * Asking questions about the content
78 | * Pointing out an error or problem with the content
79 | * Requesting more information about a section
80 | * Suggesting a new topic for inclusion
81 |
82 | Any issue that remains open will be addressed. Issues will be closed either when addressed or if the issue will not be addressed.
83 |
84 | Issues should not be used for:
85 |
86 | * Asking when a particular section or chapter will be complete
87 | * Anything already marked as "TODO" in the files
88 |
89 | These types of issues will simply be marked as invalid and closed without comment.
90 |
91 | ## Copyright and License
92 |
93 | Copyright 2014-2016 Nicholas C. Zakas.
94 |
95 | This work is licensed under a [Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License](http://creativecommons.org/licenses/by-nc-nd/3.0/).
96 |
97 | ## Frequently Asked Questions
98 |
99 | ### When will the book be finished?
100 |
101 | The book is content-complete as of June 10, 2016.
102 |
103 | ### If I buy a copy now, do I get the finished one?
104 |
105 | Yes. When you buy the Leanpub ebook today, you will automatically get all updates for the ebook. You'll be notified of updated versions via email after purchase.
106 |
107 | ### How often is the book published?
108 |
109 | Roughly every week or two the changes in GitHub are published to Leanpub.
110 |
111 | ### What is the state of content in GitHub?
112 |
113 | Everything in GitHub is a work in progress. Those parts that are known to be correct and fully-formed enough to be valuable are published to Leanpub. That's why there's more content in GitHub than is available on Leanpub, as GitHub contains everything, including content that's not quite ready for the book.
114 |
115 | ### What is the difference between the print version and the Leanpub version?
116 |
117 | The topics covered, and the order in which those topics are covered, are exactly the same between the print and Leanpub versions. The only real differences are in some words and phrases that have been altered by the copyeditors either for clarity or to follow No Starch's style guide. These changes are not backported into Leanpub because they happen on PDFs and sometimes opaquely, making it difficult to keep the two versions in sync.
118 |
119 | ### Can I acquire translation rights?
120 |
121 | Yes. Please contact [No Starch Press](https://www.nostarch.com/) for acquiring translation rights.
122 |
--------------------------------------------------------------------------------
/manuscript/B-ECMAScript-7.md:
--------------------------------------------------------------------------------
1 | # Appendix B: Understanding ECMAScript 7 (2016)
2 |
3 | The development of ECMAScript 6 took about four years, and after that, TC-39 decided that such a long development process was unsustainable. Instead, they moved to a yearly release cycle to ensure new language features would make it into development sooner.
4 |
5 | More frequent releases mean that each new edition of ECMAScript should have fewer new features than ECMAScript 6. To signify this change, new versions of the specification no longer prominently feature the edition number, and instead refer to the year in which the specification was published. As a result, ECMAScript 6 is also known as ECMAScript 2015, and ECMAScript 7 is formally known as ECMAScript 2016. TC-39 expects to use the year-based naming system for all future ECMAScript editions.
6 |
7 | ECMAScript 2016 was finalized in March 2016 and contained only three additions to the language: a new mathematical operator, a new array method, and a new syntax error. Both are covered in this appendix.
8 |
9 | ## The Exponentiation Operator
10 |
11 | The only change to JavaScript syntax introduced in ECMAScript 2016 is the *exponentiation operator*, which is a mathematical operation that applies an exponent to a base. JavaScript already had the `Math.pow()` method to perform exponentiation, but JavaScript was also one of the only languages that required a method rather than a formal operator. (And some developers argue an operator is easier to read and reason about.)
12 |
13 | The exponentiation operator is two asterisks (`**`) where the left operand is the base and the right operand is the exponent. For example:
14 |
15 | ```js
16 | let result = 5 ** 2;
17 |
18 | console.log(result); // 25
19 | console.log(result === Math.pow(5, 2)); // true
20 | ```
21 |
22 | This example calculates 5^2^, which is equal to 25. You can still use `Math.pow()` to achieve the same result.
23 |
24 | ### Order of Operations
25 |
26 | The exponentiation operator has the highest precedence of all binary operators in JavaScript (unary operators have higher precedence than `**`). That means it is applied first to any compound operation, as in this example:
27 |
28 | ```js
29 | let result = 2 * 5 ** 2;
30 | console.log(result); // 50
31 | ```
32 |
33 | The calculation of 5^2^ happens first. The resulting value is then multiplied by 2 for a final result of 50.
34 |
35 | ### Operand Restriction
36 |
37 | The exponentiation operator does have a somewhat unusual restriction that isn't present for other operators. The left side of an exponentiation operation cannot be a unary expression other than `++` or `--`. For example, this is invalid syntax:
38 |
39 | ```js
40 | // syntax error
41 | let result = -5 ** 2;
42 | ```
43 |
44 | The `-5` in this example is a syntax error because the order of operations is ambiguous. Does the `-` apply just to `5` or the result of the `5 ** 2` expression? Disallowing unary expressions on the left side of the exponentiation operator eliminates that ambiguity. In order to clearly specify intent, you need to include parentheses either around `-5` or around `5 ** 2` as follows:
45 |
46 | ```js
47 | // ok
48 | let result1 = -(5 ** 2); // equal to -25
49 |
50 | // also ok
51 | let result2 = (-5) ** 2; // equal to 25
52 | ```
53 | If you put the parentheses around the expression, the `-` is applied to the whole thing. When the parentheses surround `-5`, it's clear that you want to raise -5 to the second power.
54 |
55 | You don't need parentheses to use `++` and `--` on the left side of the exponentiation operator because both operators have clearly-defined behavior on their operands. A prefix `++` or `--` changes the operand before any other operations take place, and the postfix versions don't apply any changes until after the entire expression has been evaluated. Both use cases are safe on the left side of this operator, as this code demonstrates:
56 |
57 | ```js
58 | let num1 = 2,
59 | num2 = 2;
60 |
61 | console.log(++num1 ** 2); // 9
62 | console.log(num1); // 3
63 |
64 | console.log(num2-- ** 2); // 4
65 | console.log(num2); // 1
66 | ```
67 |
68 | In this example, `num1` is incremented before the exponentiation operator is applied, so `num1` becomes 3 and the result of the operation is 9. For `num2`, the value remains 2 for the exponentiation operation and then is decremented to 1.
69 |
70 | ## The Array.prototype.includes() Method
71 |
72 | You might recall that ECMAScript 6 added `String.prototype.includes()` in order to check whether certain substrings exist within a given string. Originally, ECMAScript 6 was also going to introduce an `Array.prototype.includes()` method to continue the trend of treating strings and arrays similarly. But the specification for `Array.prototype.includes()` was incomplete by the ECMAScript 6 deadline, and so `Array.prototype.includes()` ended up in ECMAScript 2016 instead.
73 |
74 | ### How to Use Array.prototype.includes()
75 |
76 | The `Array.prototype.includes()` method accepts two arguments: the value to search for and an optional index from which to start the search. When the second argument is provided, `includes()` starts the match from that index. (The default starting index is `0`.) The return value is `true` if the value is found inside the array and `false` if not. For example:
77 |
78 | ```js
79 | let values = [1, 2, 3];
80 |
81 | console.log(values.includes(1)); // true
82 | console.log(values.includes(0)); // false
83 |
84 | // start the search from index 2
85 | console.log(values.includes(1, 2)); // false
86 | ```
87 |
88 | Here, calling `values.includes()` returns `true` for the value of `1` and `false` for the value of `0` because `0` isn't in the array. When the second argument is used to start the search at index 2 (which contains the value `3`), the `values.includes()` method returns `false` because the number `1` is not found between index 2 and the end of the array.
89 |
90 | ### Value Comparison
91 |
92 | The value comparison performed by the `includes()` method uses the `===` operator with one exception: `NaN` is considered equal to `NaN` even though `NaN === NaN` evaluates to `false`. This is different than the behavior of the `indexOf()` method, which strictly uses `===` for comparison. To see the difference, consider this code:
93 |
94 | ```js
95 | let values = [1, NaN, 2];
96 |
97 | console.log(values.indexOf(NaN)); // -1
98 | console.log(values.includes(NaN)); // true
99 | ```
100 |
101 | The `values.indexOf()` method returns `-1` for `NaN` even though `NaN` is contained in the `values` array. On the other hand, `values.includes()` returns `true` for `NaN` because it uses a different value comparison operator.
102 |
103 | W> When you want to check just for the existence of a value in an array and don't need to know the index , I recommend using `includes()` because of the difference in how `NaN` is treated by the `includes()` and `indexOf()` methods. If you do need to know where in the array a value exists, then you have to use the `indexOf()` method.
104 |
105 | Another quirk of this implementation is that `+0` and `-0` are considered to be equal. In this case, the behavior of `indexOf()` and `includes()` is the same:
106 |
107 | ```js
108 | let values = [1, +0, 2];
109 |
110 | console.log(values.indexOf(-0)); // 1
111 | console.log(values.includes(-0)); // true
112 | ```
113 |
114 | Here, both `indexOf()` and `includes()` find `+0` when `-0` is passed because the two values are considered equal. Note that this is different than the behavior of the `Object.is()` method, which considers `+0` and `-0` to be different values.
115 |
116 | ## Change to Function-Scoped Strict Mode
117 |
118 | When strict mode was introduced in ECMAScript 5, the language was quite a bit simpler than it became in ECMAScript 6. Despite that, ECMAScript 6 still allowed you to specify strict mode using the `"use strict"` directive either in the global scope (which would make all code run in strict mode) or in a function scope (so only the function would run in strict mode). The latter ended up being a problem in ECMAScript 6 due to the more complex ways that parameters could be defined, specifically, with destructuring and default parameter values. To understand the problem, consider the following code:
119 |
120 | ```js
121 | function doSomething(first = this) {
122 | "use strict";
123 |
124 | return first;
125 | }
126 | ```
127 |
128 | Here, the named parameter `first` is assigned a default value of `this`. What would you expect the value of `first` to be? The ECMAScript 6 specification instructed JavaScript engines to treat the parameters as being run in strict mode in this case, so `this` should be equal to `undefined`. However, implementing parameters running in strict mode when `"use strict"` is present inside the function turned out to be quite difficult because parameter default values can be functions as well. This difficulty led to most JavaScript engines not implementing this feature (so `this` would be equal to the global object).
129 |
130 | As a result of the implementation difficulty, ECMAScript 2016 makes it illegal to have a `"use strict"` directive inside of a function whose parameters are either destructured or have default values. Only *simple parameter lists*, those that don't contain destructuring or default values, are allowed when `"use strict"` is present in the body of a function. Here are some examples:
131 |
132 | ```js
133 | // okay - using simple parameter list
134 | function okay(first, second) {
135 | "use strict";
136 |
137 | return first;
138 | }
139 |
140 | // syntax error
141 | function notOkay1(first, second=first) {
142 | "use strict";
143 |
144 | return first;
145 | }
146 |
147 | // syntax error
148 | function notOkay2({ first, second }) {
149 | "use strict";
150 |
151 | return first;
152 | }
153 | ```
154 |
155 | You can still use `"use strict"` with simple parameter lists, which is why `okay()` works as you would expect (the same as it would in ECMAScript 5). The `notOkay1()` function is a syntax error because you can no longer use `"use strict"` in functions with default parameter values. Similarly, the `notOkay2()` function is a syntax error because you can't use `"use strict"` in a function with destructured parameters.
156 |
157 | Overall, this change removes both a point of confusion for JavaScript developers and an implementation problem for JavaScript engines.
158 |
--------------------------------------------------------------------------------
/manuscript/00-Introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | The JavaScript core language features are defined in a standard called ECMA-262. The language defined in this standard is called ECMAScript. What you know as JavaScript in browsers and Node.js is actually a superset of ECMAScript. Browsers and Node.js add more functionality through additional objects and methods, but the core of the language remains as defined in ECMAScript. The ongoing development of ECMA-262 is vital to the success of JavaScript as a whole, and this book covers the changes brought about by the most recent major update to the language: ECMAScript 6.
4 |
5 | ## The Road to ECMAScript 6
6 |
7 | In 2007, JavaScript was at a crossroads. The popularity of Ajax was ushering in a new age of dynamic web applications, while JavaScript hadn't changed since the third edition of ECMA-262 was published in 1999. TC-39, the committee responsible for driving the ECMAScript development process, put together a large draft specification for ECMAScript 4. ECMAScript 4 was massive in scope, introducing changes both small and large to the language. Updated features included new syntax, modules, classes, classical inheritance, private object members, optional type annotations, and more.
8 |
9 | The scope of the ECMAScript 4 changes caused a rift to form in TC-39, with some members feeling that the fourth edition was trying to accomplish too much. A group of leaders from Yahoo, Google, and Microsoft created an alternate proposal for the next version of ECMAScript that they initially called ECMAScript 3.1. The "3.1" was intended to show that this was an incremental change to the existing standard.
10 |
11 | ECMAScript 3.1 introduced very few syntax changes, instead focusing on property attributes, native JSON support, and adding methods to already-existing objects. Although there was an early attempt to reconcile ECMAScript 3.1 and ECMAScript 4, this ultimately failed as the two camps had difficulty with the very different perspectives on how the language should grow.
12 |
13 | In 2008, Brendan Eich, the creator of JavaScript, announced that TC-39 would focus its efforts on standardizing ECMAScript 3.1. They would table the major syntax and feature changes of ECMAScript 4 until after the next version of ECMAScript was standardized, and all members of the committee would work to bring the best pieces of ECMAScript 3.1 and 4 together after that point into an effort initially nicknamed ECMAScript Harmony.
14 |
15 | ECMAScript 3.1 was eventually standardized as the fifth edition of ECMA-262, also described as ECMAScript 5. The committee never released an ECMAScript 4 standard to avoid confusion with the now-defunct effort of the same name. Work then began on ECMAScript Harmony, with ECMAScript 6 being the first standard released in this new "harmonious" spirit.
16 |
17 | ECMAScript 6 reached feature complete status in 2015 and was formally dubbed "ECMAScript 2015." (But this text still refers to it as ECMAScript 6, the name most familiar to developers.) The features vary widely from completely new objects and patterns to syntax changes to new methods on existing objects. The exciting thing about ECMAScript 6 is that all of its changes are geared toward solving problems that developers actually face.
18 |
19 | ## About This Book
20 |
21 | A good understanding of ECMAScript 6 features is key for all JavaScript developers going forward. The language features introduced in ECMAScript 6 represent the foundation upon which JavaScript applications will be built for the foreseeable future. That's where this book comes in. My hope is that you'll read this book to learn about ECMAScript 6 features so that you'll be ready to start using them as soon as you need to.
22 |
23 | ### Browser and Node.js Compatibility
24 |
25 | Many JavaScript environments, such as web browsers and Node.js, are actively working on implementing ECMAScript 6. This book doesn't attempt to address the inconsistencies between implementations and instead focuses on what the specification defines as the correct behavior. As such, it's possible that your JavaScript environment may not conform to the behavior described in this book.
26 |
27 | ### Who This Book is For
28 |
29 | This book is intended as a guide for those who are already familiar with JavaScript and ECMAScript 5. While a deep understanding of the language isn't necessary to use this book, it will help you understand the differences between ECMAScript 5 and 6. In particular, this book is aimed at intermediate-to-advanced JavaScript developers programming for a browser or Node.js environment who want to learn about the latest developments in the language.
30 |
31 | This book is not for beginners who have never written JavaScript. You will need to have a good basic understanding of the language to make use of this book.
32 |
33 | ### Overview
34 |
35 | Each of this book's thirteen chapters covers a different aspect of ECMAScript 6. Many chapters start by discussing problems that ECMAScript 6 changes were made to solve, to give you a broader context for those changes, and all chapters include code examples to help you learn new syntax and concepts.
36 |
37 | **Chapter 1: How Block Bindings Work** talks about `let` and `const`, the block-level replacement for `var`.
38 |
39 | **Chapter 2: Strings and Regular Expressions** covers additional functionality for string manipulation and inspection as well as the introduction of template strings.
40 |
41 | **Chapter 3: Functions in ECMAScript 6** discusses the various changes to functions. This includes the arrow function form, default parameters, rest parameters, and more.
42 |
43 | **Chapter 4: Expanded Object Functionality** explains the changes to how objects are created, modified, and used. Topics include changes to object literal syntax, and new reflection methods.
44 |
45 | **Chapter 5: Destructuring for Easier Data Access** introduces object and array destructuring, which allow you to decompose objects and arrays using a concise syntax.
46 |
47 | **Chapter 6: Symbols and Symbol Properties** introduces the concept of symbols, a new way to define properties. Symbols are a new primitive type that can be used to obscure (but not hide) object properties and methods.
48 |
49 | **Chapter 7: Sets and Maps** details the new collection types of `Set`, `WeakSet`, `Map`, and `WeakMap`. These types expand on the usefulness of arrays by adding semantics, de-duping, and memory management designed specifically for JavaScript.
50 |
51 | **Chapter 8: Iterators and Generators** discusses the addition of iterators and generators to the language. These features allow you to work with collections of data in powerful ways that were not possible in previous versions of JavaScript.
52 |
53 | **Chapter 9: Introducing JavaScript Classes** introduces the first formal concept of classes in JavaScript. Often a point of confusion for those coming from other languages, the addition of class syntax in JavaScript makes the language more approachable to others and more concise for enthusiasts.
54 |
55 | **Chapter 10: Improved Array Capabilities** details the changes to native arrays and the interesting new ways they can be used in JavaScript.
56 |
57 | **Chapter 11: Promises and Asynchronous Programming** introduces promises as a new part of the language. Promises were a grassroots effort that eventually took off and gained in popularity due to extensive library support. ECMAScript 6 formalizes promises and makes them available by default.
58 |
59 | **Chapter 12: Proxies and the Reflection API** introduces the formalized reflection API for JavaScript and the new proxy object that allows you to intercept every operation performed on an object. Proxies give developers unprecedented control over objects and, as such, unlimited possibilities for defining new interaction patterns.
60 |
61 | **Chapter 13: Encapsulating Code with Modules** details the official module format for JavaScript. The intent is that these modules can replace the numerous ad-hoc module definition formats that have appeared over the years.
62 |
63 | **Appendix A: Smaller ECMAScript 6 Changes** covers other changes implemented in ECMAScript 6 that you'll use less frequently or that didn't quite fit into the broader major topics covered in each chapter.
64 |
65 | **Appendix B: Understanding ECMAScript 7 (2016)** describes the two additions to the standard that were implemented for ECMAScript 7, which didn't impact JavaScript nearly as much as ECMAScript 6.
66 |
67 | ### Conventions Used
68 |
69 | The following typographical conventions are used in this book:
70 |
71 | * *Italics* introduces new terms
72 | * `Constant width` indicates a piece of code or filename
73 |
74 | Additionally, longer code examples are contained in constant width code blocks such as:
75 |
76 | ```js
77 | function doSomething() {
78 | // empty
79 | }
80 | ```
81 |
82 | Within a code block, comments to the right of a `console.log()` statement indicate the output you'll see in the browser or Node.js console when the code is executed, for example:
83 |
84 | ```js
85 | console.log("Hi"); // "Hi"
86 | ```
87 |
88 | If a line of code in a code block throws an error, this is also indicated to the right of the code:
89 |
90 | ```js
91 | doSomething(); // error!
92 | ```
93 |
94 | ### Help and Support
95 |
96 | You can file issues, suggest changes, and open pull requests against this book by visiting: [https://github.com/nzakas/understandinges6](https://github.com/nzakas/understandinges6)
97 |
98 |
100 |
101 | If you have questions as you read this book, please send a message to my mailing list: [http://groups.google.com/group/zakasbooks](http://groups.google.com/group/zakasbooks).
102 |
103 | ## Acknowledgments
104 |
105 | Thanks to Jennifer Griffith-Delgado, Alison Law, and everyone at No Starch Press for their support and help with this book. Their understanding and patience as my productivity slowed to a crawl during my extended illness is something I will never forget.
106 |
107 | I'm grateful for the watchful eye of Juriy Zaytsev as tech editor and to Dr. Axel Rauschmayer for his feedback and several conversations that helped to clarify some of the concepts discussed in this book.
108 |
109 | Thanks to everyone who submitted fixes to the version of this book that is hosted on GitHub: ShMcK, Ronen Elster, Rick Waldron, blacktail, Paul Salaets, Lonniebiz, Igor Skuhar, jakub-g, David Chang, Kevin Sweeney, Kyle Simpson, Peter Bakondy, Philip Borisov, Shaun Hickson, Steven Foote, kavun, Dan Kielp, Darren Huskie, Jakub Narębski, Jamund Ferguson, Josh Lubaway, Marián Rusnák, Nikolas Poniros, Robin Pokorný, Roman Lo, Yang Su, alexyans, robertd, 404, Aaron Dandy, AbdulFattah Popoola, Adam Richeimer, Ahmad Ali, Aleksandar Djindjic, Arjunkumar, Ben Regenspan, Carlo Costantini, Dmitri Suvorov, Kyle Pollock, Mallory, Erik Sundahl, Ethan Brown, Eugene Zubarev, Francesco Pongiluppi, Jake Champion, Jeremy Caney, Joe Eames, Juriy Zaytsev, Kale Worsley, Kevin Lozandier, Lewis Ellis, Mohsen Azimi, Navaneeth Kesavan, Nick Bottomley, Niels Dequeker, Pahlevi Fikri Auliya, Prayag Verma, Raj Anand, Ross Gerbasi, Roy Ling, Sarbbottam Bandyopadhyay, and Shidhin.
110 |
111 | Also, thanks to everyone who supported this book on Patreon: Casey Visco.
112 |
--------------------------------------------------------------------------------
/manuscript/A-Other-Changes.md:
--------------------------------------------------------------------------------
1 | # Appendix A: Smaller Changes
2 |
3 | Along with the major changes this book has already covered, ECMAScript 6 made several other changes that are smaller but still helpful in improving JavaScript. Those changes include making integers easier to use, adding new methods for calculations, a tweak to Unicode identifiers, and formalizing the`__proto__` property. I describe all of those in this appendix.
4 |
5 | ## Working with Integers
6 |
7 | JavaScript uses the IEEE 754 encoding system to represent both integers and floats, which has caused a lot of confusion over the years. The language takes great pains to ensure that developers don't need to worry about the details of number encoding, but problems still leak through from time to time. ECMAScript 6 seeks to address this by making integers easier to identify and work with.
8 |
9 | ### Identifying Integers
10 |
11 | First, ECMAScript 6 added the `Number.isInteger()` method, which can determine whether a value represents an integer in JavaScript. While JavaScript uses IEEE 754 to represent both types of numbers, floats and integers are stored differently. The `Number.isInteger()` method takes advantage of that, and when the method is called on a value, the JavaScript engine looks at the underlying representation of the value to determine whether that value is an integer. That means numbers that look like floats might actually be stored as integers and cause `Number.isInteger()` to return `true`. For example:
12 |
13 | ```js
14 | console.log(Number.isInteger(25)); // true
15 | console.log(Number.isInteger(25.0)); // true
16 | console.log(Number.isInteger(25.1)); // false
17 | ```
18 |
19 | In this code, `Number.isInteger()` returns `true` for both `25` and `25.0` even though the latter looks like a float. Simply adding a decimal point to a number doesn't automatically make it a float in JavaScript. Since `25.0` is really just `25`, it is stored as an integer. The number `25.1`, however, is stored as a float because there is a fraction value.
20 |
21 | ### Safe Integers
22 |
23 | IEEE 754 can only accurately represent integers between -2^53^ and 2^53^, and outside this "safe" range, binary representations end up reused for multiple numeric values. That means JavaScript can only safely represent integers within the IEEE 754 range before problems become apparent. For instance, consider this code:
24 |
25 | ```js
26 | console.log(Math.pow(2, 53)); // 9007199254740992
27 | console.log(Math.pow(2, 53) + 1); // 9007199254740992
28 | ```
29 |
30 | This example doesn't contain a typo, yet two different numbers are represented by the same JavaScript integer. The effect becomes more prevalent the further the value falls outside the safe range.
31 |
32 | ECMAScript 6 introduced the `Number.isSafeInteger()` method to better identify integers that the language can accurately represent. It also added the `Number.MAX_SAFE_INTEGER` and `Number.MIN_SAFE_INTEGER` properties to represent the upper and lower bounds of the integer range, respectively. The `Number.isSafeInteger()` method ensures that a value is an integer and falls within the safe range of integer values, as in this example:
33 |
34 | ```js
35 | var inside = Number.MAX_SAFE_INTEGER,
36 | outside = inside + 1;
37 |
38 | console.log(Number.isInteger(inside)); // true
39 | console.log(Number.isSafeInteger(inside)); // true
40 |
41 | console.log(Number.isInteger(outside)); // true
42 | console.log(Number.isSafeInteger(outside)); // false
43 | ```
44 |
45 | The number `inside` is the largest safe integer, so it returns `true` for both the `Number.isInteger()` and `Number.isSafeInteger()` methods. The number `outside` is the first questionable integer value, and it isn't considered safe even though it's still an integer.
46 |
47 | Most of the time, you only want to deal with safe integers when doing integer arithmetic or comparisons in JavaScript, so using `Number.isSafeInteger()` as part of input validation is a good idea.
48 |
49 | ## New Math Methods
50 |
51 | The new emphasis on gaming and graphics that led ECMAScript 6 to include typed arrays in JavaScript also led to the realization that a JavaScript engine could do many mathematical calculations more efficiently. But optimization strategies like asm.js, which works on a subset of JavaScript to improve performance, need more information to perform calculations in the fastest way possible. For instance, knowing whether the numbers should be treated as 32-bit integers or as 64-bit floats is important for hardware-based operations, which are much faster than software-based operations.
52 |
53 | As a result, ECMAScript 6 added several methods to the `Math` object to improve the speed of common mathematical calculations. Improving the speed of common calculations also improves the overall speed of applications that perform many calculations, such as graphics programs. The new methods are listed below:
54 |
55 | * `Math.acosh(x)` Returns the inverse hyperbolic cosine of `x`.
56 | * `Math.asinh(x)` Returns the inverse hyperbolic sine of `x`.
57 | * `Math.atanh(x)` Returns the inverse hyperbolic tangent of `x`
58 | * `Math.cbrt(x)` Returns the cubed root of `x`.
59 | * `Math.clz32(x)` Returns the number of leading zero bits in the 32-bit integer representation of `x`.
60 | * `Math.cosh(x)` Returns the hyperbolic cosine of `x`.
61 | * `Math.expm1(x)` Returns the result of subtracting 1 from the exponential function of `x`
62 | * `Math.fround(x)` Returns the nearest single-precision float of `x`.
63 | * `Math.hypot(...values)` Returns the square root of the sum of the squares of each argument.
64 | * `Math.imul(x, y)` Returns the result of performing true 32-bit multiplication of the two arguments.
65 | * `Math.log1p(x)` Returns the natural logarithm of `1 + x`.
66 | * `Math.log10(x)` Returns the base 10 logarithm of `x`.
67 | * `Math.log2(x)` Returns the base 2 logarithm of `x`.
68 | * `Math.sign(x)` Returns -1 if the `x` is negative, 0 if `x` is +0 or -0, or 1 if `x` is positive.
69 | * `Math.sinh(x)` Returns the hyperbolic sine of `x`.
70 | * `Math.tanh(x)` Returns the hyperbolic tangent of `x`.
71 | * `Math.trunc(x)` Removes fraction digits from a float and returns an integer.
72 |
73 | It's beyond the scope of this book to explain each new method and what it does in detail. But if your application needs to do a reasonably common calculation, be sure to check the new `Math` methods before implementing it yourself.
74 |
75 | ## Unicode Identifiers
76 |
77 | ECMAScript 6 offers better Unicode support than previous versions of JavaScript, and it also changes what characters may be used as identifiers. In ECMAScript 5, it was already possible to use Unicode escape sequences for identifiers. For example:
78 |
79 | ```js
80 | // Valid in ECMAScript 5 and 6
81 | var \u0061 = "abc";
82 |
83 | console.log(\u0061); // "abc"
84 |
85 | // equivalent to:
86 | console.log(a); // "abc"
87 | ```
88 |
89 | After the `var` statement in this example, you can use either `\u0061` or `a` to access the variable. In ECMAScript 6, you can also use Unicode code point escape sequences as identifiers, like this:
90 |
91 | ```js
92 | // Valid in ECMAScript 5 and 6
93 | var \u{61} = "abc";
94 |
95 | console.log(\u{61}); // "abc"
96 |
97 | // equivalent to:
98 | console.log(a); // "abc"
99 | ```
100 |
101 | This example just replaces `\u0061` with its code point equivalent. Otherwise, it does exactly the same thing as the previous example.
102 |
103 | Additionally, ECMAScript 6 formally specifies valid identifiers in terms of [Unicode Standard Annex #31: Unicode Identifier and Pattern Syntax](http://unicode.org/reports/tr31/), which gives the following rules:
104 |
105 | 1. The first character must be `$`, `_`, or any Unicode symbol with a derived core property of `ID_Start`.
106 | 1. Each subsequent character must be `$`, `_`, `\u200c` (a zero-width non-joiner), `\u200d` (a zero-width joiner), or any Unicode symbol with a derived core property of `ID_Continue`.
107 |
108 | The `ID_Start` and `ID_Continue` derived core properties are defined in Unicode Identifier and Pattern Syntax as a way to identify symbols that are appropriate for use in identifiers such as variables and domain names. The specification is not specific to JavaScript.
109 |
110 | ## Formalizing the `__proto__` Property
111 |
112 | Even before ECMAScript 5 was finished, several JavaScript engines already implemented a custom property called `__proto__` that could be used to both get and set the `[[Prototype]]` property. Effectively, `__proto__` was an early precursor to both the `Object.getPrototypeOf()` and `Object.setPrototypeOf()` methods. Expecting all JavaScript engines to remove this property is unrealistic (there were popular JavaScript libraries making use of `__proto__`), so ECMAScript 6 also formalized the `__proto__` behavior. But the formalization appears in Appendix B of ECMA-262 along with this warning:
113 |
114 | > These features are not considered part of the core ECMAScript language. Programmers should not use or assume the existence of these features and behaviours when writing new ECMAScript code. ECMAScript implementations are discouraged from implementing these features unless the
115 | implementation is part of a web browser or is required to run the same legacy ECMAScript code that web browsers encounter.
116 |
117 | The ECMAScript specification recommends using `Object.getPrototypeOf()` and `Object.setPrototypeOf()` instead because `__proto__` has the following characteristics:
118 |
119 | 1. You can only specify `__proto__` once in an object literal. If you specify two `__proto__` properties, then an error is thrown. This is the only object literal property with that restriction.
120 | 1. The computed form `["__proto__"]` acts like a regular property and doesn't set or return the current object's prototype. All rules related to object literal properties apply in this form, as opposed to the non-computed form, which has exceptions.
121 |
122 | While you should avoid using the `__proto__` property, the way the specification defined it is interesting. In ECMAScript 6 engines, `Object.prototype.__proto__` is defined as an accessor property whose `get` method calls `Object.getPrototypeOf()` and whose `set` method calls the `Object.setPrototypeOf()` method. This leaves no real difference between using `__proto__` and `Object.getPrototypeOf()`/`Object.setPrototypeOf()`, except that `__proto__` allows you to set the prototype of an object literal directly. Here's how that works:
123 |
124 | ```js
125 | let person = {
126 | getGreeting() {
127 | return "Hello";
128 | }
129 | };
130 |
131 | let dog = {
132 | getGreeting() {
133 | return "Woof";
134 | }
135 | };
136 |
137 | // prototype is person
138 | let friend = {
139 | __proto__: person
140 | };
141 | console.log(friend.getGreeting()); // "Hello"
142 | console.log(Object.getPrototypeOf(friend) === person); // true
143 | console.log(friend.__proto__ === person); // true
144 |
145 | // set prototype to dog
146 | friend.__proto__ = dog;
147 | console.log(friend.getGreeting()); // "Woof"
148 | console.log(friend.__proto__ === dog); // true
149 | console.log(Object.getPrototypeOf(friend) === dog); // true
150 | ```
151 |
152 | Instead of calling `Object.create()` to make the `friend` object, this example creates a standard object literal that assigns a value to the `__proto__` property. When creating an object with the `Object.create()` method, on the other hand, you'd have to specify full property descriptors for any additional object properties.
153 |
--------------------------------------------------------------------------------
/manuscript/01-Block-Bindings.md:
--------------------------------------------------------------------------------
1 | # Block Bindings
2 |
3 | Traditionally, the way variable declarations work has been one tricky part of programming in JavaScript. In most C-based languages, variables (or *bindings*) are created at the spot where the declaration occurs. In JavaScript, however, this is not the case. Where your variables are actually created depends on how you declare them, and ECMAScript 6 offers options to make controlling scope easier. This chapter demonstrates why classic `var` declarations can be confusing, introduces block-level bindings in ECMAScript 6, and then offers some best practices for using them.
4 |
5 | ## Var Declarations and Hoisting
6 |
7 | Variable declarations using `var` are treated as if they are at the top of the function (or global scope, if declared outside of a function) regardless of where the actual declaration occurs; this is called *hoisting*. For a demonstration of what hoisting does, consider the following function definition:
8 |
9 | ```js
10 | function getValue(condition) {
11 |
12 | if (condition) {
13 | var value = "blue";
14 |
15 | // other code
16 |
17 | return value;
18 | } else {
19 |
20 | // value exists here with a value of undefined
21 |
22 | return null;
23 | }
24 |
25 | // value exists here with a value of undefined
26 | }
27 | ```
28 |
29 | If you are unfamiliar with JavaScript, then you might expect the variable `value` to only be created if `condition` evaluates to true. In fact, the variable `value` is created regardless. Behind the scenes, the JavaScript engine changes the `getValue` function to look like this:
30 |
31 | ```js
32 | function getValue(condition) {
33 |
34 | var value;
35 |
36 | if (condition) {
37 | value = "blue";
38 |
39 | // other code
40 |
41 | return value;
42 | } else {
43 |
44 | return null;
45 | }
46 | }
47 | ```
48 |
49 | The declaration of `value` is hoisted to the top, while the initialization remains in the same spot. That means the variable `value` is actually still accessible from within the `else` clause. If accessed from there, the variable would just have a value of `undefined` because it hasn't been initialized.
50 |
51 | It often takes new JavaScript developers some time to get used to declaration hoisting, and misunderstanding this unique behavior can end up causing bugs. For this reason, ECMAScript 6 introduces block level scoping options to make the controlling a variable's lifecycle a little more powerful.
52 |
53 | ## Block-Level Declarations
54 |
55 | Block-level declarations are those that declare variables that are inaccessible outside of a given block scope. Block scopes, also called lexical scopes, are created:
56 |
57 | 1. Inside of a function
58 | 1. Inside of a block (indicated by the `{` and `}` characters)
59 |
60 | Block scoping is how many C-based languages work, and the introduction of block-level declarations in ECMAScript 6 is intended to bring that same flexibility (and uniformity) to JavaScript.
61 |
62 | ### Let Declarations
63 |
64 | The `let` declaration syntax is the same as the syntax for `var`. You can basically replace `var` with `let` to declare a variable, but limit the variable's scope to only the current code block (there are a few other subtle differences discussed a bit later, as well). Since `let` declarations are not hoisted to the top of the enclosing block, you may want to always place `let` declarations first in the block, so that they are available to the entire block. Here's an example:
65 |
66 | ```js
67 | function getValue(condition) {
68 |
69 | if (condition) {
70 | let value = "blue";
71 |
72 | // other code
73 |
74 | return value;
75 | } else {
76 |
77 | // value doesn't exist here
78 |
79 | return null;
80 | }
81 |
82 | // value doesn't exist here
83 | }
84 | ```
85 |
86 | This version of the `getValue` function behaves much closer to how you'd expect it to in other C-based languages. Since the variable `value` is declared using `let` instead of `var`, the declaration isn't hoisted to the top of the function definition, and the variable `value` is no longer accessible once execution flows out of the `if` block. If `condition` evaluates to false, then `value` is never declared or initialized.
87 |
88 | ### No Redeclaration
89 |
90 | If an identifier has already been defined in a scope, then using the identifier in a `let` declaration inside that scope causes an error to be thrown. For example:
91 |
92 | ```js
93 | var count = 30;
94 |
95 | // Syntax error
96 | let count = 40;
97 | ```
98 |
99 | In this example, `count` is declared twice: once with `var` and once with `let`. Because `let` will not redefine an identifier that already exists in the same scope, the `let` declaration will throw an error. On the other hand, no error is thrown if a `let` declaration creates a new variable with the same name as a variable in its containing scope, as demonstrated in the following code:
100 |
101 | ```js
102 | var count = 30;
103 |
104 | // Does not throw an error
105 | if (condition) {
106 |
107 | let count = 40;
108 |
109 | // more code
110 | }
111 | ```
112 |
113 | This `let` declaration doesn't throw an error because it creates a new variable called `count` within the `if` statement, instead of creating `count` in the surrounding block. Inside the `if` block, this new variable shadows the global `count`, preventing access to it until execution leaves the block.
114 |
115 | ### Constant Declarations
116 |
117 | You can also define variables in ECMAScript 6 with the `const` declaration syntax. Variables declared using `const` are considered *constants*, meaning their values cannot be changed once set. For this reason, every `const` variable must be initialized on declaration, as shown in this example:
118 |
119 | ```js
120 | // Valid constant
121 | const maxItems = 30;
122 |
123 | // Syntax error: missing initialization
124 | const name;
125 | ```
126 |
127 | The `maxItems` variable is initialized, so its `const` declaration should work without a problem. The `name` variable, however, would cause a syntax error if you tried to run the program containing this code, because `name` is not initialized.
128 |
129 |
130 | #### Constants vs Let Declarations
131 |
132 | Constants, like `let` declarations, are block-level declarations. That means constants are no longer accessible once execution flows out of the block in which they were declared, and declarations are not hoisted, as demonstrated in this example:
133 |
134 | ```js
135 | if (condition) {
136 | const maxItems = 5;
137 |
138 | // more code
139 | }
140 |
141 | // maxItems isn't accessible here
142 | ```
143 |
144 | In this code, the constant `maxItems` is declared within an `if` statement. Once the statement finishes executing, `maxItems` is not accessible outside of that block.
145 |
146 | In another similarity to `let`, a `const` declaration throws an error when made with an identifier for an already-defined variable in the same scope. It doesn't matter if that variable was declared using `var` (for global or function scope) or `let` (for block scope). For example, consider this code:
147 |
148 | ```js
149 | var message = "Hello!";
150 | let age = 25;
151 |
152 | // Each of these would throw an error.
153 | const message = "Goodbye!";
154 | const age = 30;
155 | ```
156 |
157 | The two `const` declarations would be valid alone, but given the previous `var` and `let` declarations in this case, neither will work as intended.
158 |
159 | Despite those similarities, there is one big difference between `let` and `const` to remember. Attempting to assign a `const` to a previously defined constant will throw an error, in both strict and non-strict modes:
160 |
161 | ```js
162 | const maxItems = 5;
163 |
164 | maxItems = 6; // throws error
165 | ```
166 |
167 | Much like constants in other languages, the `maxItems` variable can't be assigned a new value later on. However, unlike constants in other languages, the value a constant holds may be modified if it is an object.
168 |
169 | #### Declaring Objects with Const
170 |
171 | A `const` declaration prevents modification of the binding and not of the value itself. That means `const` declarations for objects do not prevent modification of those objects. For example:
172 |
173 | ```js
174 | const person = {
175 | name: "Nicholas"
176 | };
177 |
178 | // works
179 | person.name = "Greg";
180 |
181 | // throws an error
182 | person = {
183 | name: "Greg"
184 | };
185 | ```
186 |
187 | Here, the binding `person` is created with an initial value of an object with one property. It's possible to change `person.name` without causing an error because this changes what `person` contains and doesn't change the value that `person` is bound to. When this code attempts to assign a value to `person` (thus attempting to change the binding), an error will be thrown. This subtlety in how `const` works with objects is easy to misunderstand. Just remember: `const` prevents modification of the binding, not modification of the bound value.
188 |
189 | ### The Temporal Dead Zone
190 |
191 | A variable declared with either `let` or `const` cannot be accessed until after the declaration. Attempting to do so results in a reference error, even when using normally safe operations such as the `typeof` operation in this example:
192 |
193 | ```js
194 | if (condition) {
195 | console.log(typeof value); // ReferenceError!
196 | let value = "blue";
197 | }
198 | ```
199 |
200 | Here, the variable `value` is defined and initialized using `let`, but that statement is never executed because the previous line throws an error. The issue is that `value` exists in what the JavaScript community has dubbed the *temporal dead zone* (TDZ). The TDZ is never named explicitly in the ECMAScript specification, but the term is often used to describe why `let` and `const` declarations are not accessible before their declaration. This section covers some subtleties of declaration placement that the TDZ causes, and although the examples shown all use `let`, note that the same information applies to `const`.
201 |
202 | When a JavaScript engine looks through an upcoming block and finds a variable declaration, it either hoists the declaration to the top of the function or global scope (for `var`) or places the declaration in the TDZ (for `let` and `const`). Any attempt to access a variable in the TDZ results in a runtime error. That variable is only removed from the TDZ, and therefore safe to use, once execution flows to the variable declaration.
203 |
204 | This is true anytime you attempt to use a variable declared with `let` or `const` before it's been defined. As the previous example demonstrated, this even applies to the normally safe `typeof` operator. You can, however, use `typeof` on a variable outside of the block where that variable is declared, though it may not give the results you're after. Consider this code:
205 |
206 | ```js
207 | console.log(typeof value); // "undefined"
208 |
209 | if (condition) {
210 | let value = "blue";
211 | }
212 | ```
213 |
214 | The variable `value` isn't in the TDZ when the `typeof` operation executes because it occurs outside of the block in which `value` is declared. That means there is no `value` binding, and `typeof` simply returns `"undefined"`.
215 |
216 | The TDZ is just one unique aspect of block bindings. Another unique aspect has to do with their use inside of loops.
217 |
218 | ## Block Binding in Loops
219 |
220 | Perhaps one area where developers most want block level scoping of variables is within `for` loops, where the throwaway counter variable is meant to be used only inside the loop. For instance, it's not uncommon to see code like this in JavaScript:
221 |
222 | ```js
223 | for (var i = 0; i < 10; i++) {
224 | process(items[i]);
225 | }
226 |
227 | // i is still accessible here
228 | console.log(i); // 10
229 | ```
230 |
231 | In other languages, where block level scoping is the default, this example should work as intended, and only the `for` loop should have access to the `i` variable. In JavaScript, however, the variable `i` is still accessible after the loop is completed because the `var` declaration gets hoisted. Using `let` instead, as in the following code, should give the intended behavior:
232 |
233 | ```js
234 | for (let i = 0; i < 10; i++) {
235 | process(items[i]);
236 | }
237 |
238 | // i is not accessible here - throws an error
239 | console.log(i);
240 | ```
241 |
242 | In this example, the variable `i` only exists within the `for` loop. Once the loop is complete, the variable is no longer accessible elsewhere.
243 |
244 | ### Functions in Loops
245 |
246 | The characteristics of `var` have long made creating functions inside of loops problematic, because the loop variables are accessible from outside the scope of the loop. Consider the following code:
247 |
248 | ```js
249 | var funcs = [];
250 |
251 | for (var i = 0; i < 10; i++) {
252 | funcs.push(function() { console.log(i); });
253 | }
254 |
255 | funcs.forEach(function(func) {
256 | func(); // outputs the number "10" ten times
257 | });
258 | ```
259 |
260 | You might ordinarily expect this code to print the numbers 0 to 9, but it outputs the number 10 ten times in a row. That's because `i` is shared across each iteration of the loop, meaning the functions created inside the loop all hold a reference to the same variable. The variable `i` has a value of `10` once the loop completes, and so when `console.log(i)` is called, that value prints each time.
261 |
262 | To fix this problem, developers use immediately-invoked function expressions (IIFEs) inside of loops to force a new copy of the variable they want to iterate over to be created, as in this example:
263 |
264 | ```js
265 | var funcs = [];
266 |
267 | for (var i = 0; i < 10; i++) {
268 | funcs.push((function(value) {
269 | return function() {
270 | console.log(value);
271 | }
272 | }(i)));
273 | }
274 |
275 | funcs.forEach(function(func) {
276 | func(); // outputs 0, then 1, then 2, up to 9
277 | });
278 | ```
279 |
280 | This version uses an IIFE inside of the loop. The `i` variable is passed to the IIFE, which creates its own copy and stores it as `value`. This is the value used by the function for that iteration, so calling each function returns the expected value as the loop counts up from 0 to 9. Fortunately, block-level binding with `let` and `const` in ECMAScript 6 can simplify this loop for you.
281 |
282 | ### Let Declarations in Loops
283 |
284 | A `let` declaration simplifies loops by effectively mimicking what the IIFE does in the previous example. On each iteration, the loop creates a new variable and initializes it to the value of the variable with the same name from the previous iteration. That means you can omit the IIFE altogether and get the results you expect, like this:
285 |
286 | ```js
287 | var funcs = [];
288 |
289 | for (let i = 0; i < 10; i++) {
290 | funcs.push(function() {
291 | console.log(i);
292 | });
293 | }
294 |
295 | funcs.forEach(function(func) {
296 | func(); // outputs 0, then 1, then 2, up to 9
297 | })
298 | ```
299 |
300 | This loop works exactly like the loop that used `var` and an IIFE but is, arguably, cleaner. The `let` declaration creates a new variable `i` each time through the loop, so each function created inside the loop gets its own copy of `i`. Each copy of `i` has the value it was assigned at the beginning of the loop iteration in which it was created. The same is true for `for-in` and `for-of` loops, as shown here:
301 |
302 | ```js
303 | var funcs = [],
304 | object = {
305 | a: true,
306 | b: true,
307 | c: true
308 | };
309 |
310 | for (let key in object) {
311 | funcs.push(function() {
312 | console.log(key);
313 | });
314 | }
315 |
316 | funcs.forEach(function(func) {
317 | func(); // outputs "a", then "b", then "c"
318 | });
319 | ```
320 |
321 | In this example, the `for-in` loop shows the same behavior as the `for` loop. Each time through the loop, a new `key` binding is created, and so each function has its own copy of the `key` variable. The result is that each function outputs a different value. If `var` were used to declare `key`, all functions would output `"c"`.
322 |
323 | I> It's important to understand that the behavior of `let` declarations in loops is a specially-defined behavior in the specification and is not necessarily related to the non-hoisting characteristics of `let`. In fact, early implementations of `let` did not have this behavior, as it was added later on in the process.
324 |
325 | ### Constant Declarations in Loops
326 |
327 | The ECMAScript 6 specification doesn't explicitly disallow `const` declarations in loops; however, there are different behaviors based on the type of loop you're using. For a normal `for` loop, you can use `const` in the initializer, but the loop will throw a warning if you attempt to change the value. For example:
328 |
329 | ```js
330 | var funcs = [];
331 |
332 | // throws an error after one iteration
333 | for (const i = 0; i < 10; i++) {
334 | funcs.push(function() {
335 | console.log(i);
336 | });
337 | }
338 | ```
339 |
340 | In this code, the `i` variable is declared as a constant. The first iteration of the loop, where `i` is 0, executes successfully. An error is thrown when `i++` executes because it's attempting to modify a constant. As such, you can only use `const` to declare a variable in the loop initializer if you are not modifying that variable.
341 |
342 | When used in a `for-in` or `for-of` loop, on the other hand, a `const` variable behaves the same as a `let` variable. So the following should not cause an error:
343 |
344 | ```js
345 | var funcs = [],
346 | object = {
347 | a: true,
348 | b: true,
349 | c: true
350 | };
351 |
352 | // doesn't cause an error
353 | for (const key in object) {
354 | funcs.push(function() {
355 | console.log(key);
356 | });
357 | }
358 |
359 | funcs.forEach(function(func) {
360 | func(); // outputs "a", then "b", then "c"
361 | });
362 | ```
363 |
364 | This code functions almost exactly the same as the second example in the "Let Declarations in Loops" section. The only difference is that the value of `key` cannot be changed inside the loop. The `for-in` and `for-of` loops work with `const` because the loop initializer creates a new binding on each iteration through the loop rather than attempting to modify the value of an existing binding (as was the case with the previous example using `for` instead of `for-in`).
365 |
366 | ## Global Block Bindings
367 |
368 | Another way in which `let` and `const` are different from `var` is in their global scope behavior. When `var` is used in the global scope, it creates a new global variable, which is a property on the global object (`window` in browsers). That means you can accidentally overwrite an existing global using `var`, such as:
369 |
370 | ```js
371 | // in a browser
372 | var RegExp = "Hello!";
373 | console.log(window.RegExp); // "Hello!"
374 |
375 | var ncz = "Hi!";
376 | console.log(window.ncz); // "Hi!"
377 | ```
378 |
379 | Even though the `RegExp` global is defined on `window`, it is not safe from being overwritten by a `var` declaration. This example declares a new global variable `RegExp` that overwrites the original. Similarly, `ncz` is defined as a global variable and immediately defined as a property on `window`. This is the way JavaScript has always worked.
380 |
381 | If you instead use `let` or `const` in the global scope, a new binding is created in the global scope but no property is added to the global object. That also means you cannot overwrite a global variable using `let` or `const`, you can only shadow it. Here's an example:
382 |
383 | ```js
384 | // in a browser
385 | let RegExp = "Hello!";
386 | console.log(RegExp); // "Hello!"
387 | console.log(window.RegExp === RegExp); // false
388 |
389 | const ncz = "Hi!";
390 | console.log(ncz); // "Hi!"
391 | console.log("ncz" in window); // false
392 | ```
393 |
394 | Here, a new `let` declaration for `RegExp` creates a binding that shadows the global `RegExp`. That means `window.RegExp` and `RegExp` are not the same, so there is no disruption to the global scope. Also, the `const` declaration for `ncz` creates a binding but does not create a property on the global object. This capability makes `let` and `const` a lot safer to use in the global scope when you don't want to create properties on the global object.
395 |
396 | I> You may still want to use `var` in the global scope if you have a code that should be available from the global object. This is most common in a browser when you want to access code across frames or windows.
397 |
398 | ## Emerging Best Practices for Block Bindings
399 |
400 | While ECMAScript 6 was in development, there was widespread belief you should use `let` by default instead of `var` for variable declarations. For many JavaScript developers, `let` behaves exactly the way they thought `var` should have behaved, and so the direct replacement makes logical sense. In this case, you would use `const` for variables that needed modification protection.
401 |
402 | However, as more developers migrated to ECMAScript 6, an alternate approach gained popularity: use `const` by default and only use `let` when you know a variable's value needs to change. The rationale is that most variables should not change their value after initialization because unexpected value changes are a source of bugs. This idea has a significant amount of traction and is worth exploring in your code as you adopt ECMAScript 6.
403 |
404 | ## Summary
405 |
406 | The `let` and `const` block bindings introduce lexical scoping to JavaScript. These declarations are not hoisted and only exist within the block in which they are declared. This offers behavior that is more like other languages and less likely to cause unintentional errors, as variables can now be declared exactly where they are needed. As a side effect, you cannot access variables before they are declared, even with safe operators such as `typeof`. Attempting to access a block binding before its declaration results in an error due to the binding's presence in the temporal dead zone (TDZ).
407 |
408 | In many cases, `let` and `const` behave in a manner similar to `var`; however, this is not true for loops. For both `let` and `const`, `for-in` and `for-of` loops create a new binding with each iteration through the loop. That means functions created inside the loop body can access the loop bindings values as they are during the current iteration, rather than as they were after the loop's final iteration (the behavior with `var`). The same is true for `let` declarations in `for` loops, while attempting to use `const` declarations in a `for` loop may result in an error.
409 |
410 | The current best practice for block bindings is to use `const` by default and only use `let` when you know a variable's value needs to change. This ensures a basic level of immutability in code that can help prevent certain types of errors.
411 |
412 |
413 |
--------------------------------------------------------------------------------
/manuscript/05-Destructuring.md:
--------------------------------------------------------------------------------
1 | # Destructuring for Easier Data Access
2 |
3 | Object and array literals are two of the most frequently used notations in JavaScript, and thanks to the popular JSON data format, they've become a particularly important part of the language. It's quite common to define objects and arrays, and then systematically pull out relevant pieces of information from those structures. ECMAScript 6 simplifies this task by adding *destructuring*, which is the process of breaking a data structure down into smaller parts. This chapter shows you how to harness destructuring for both objects and arrays.
4 |
5 | ## Why is Destructuring Useful?
6 |
7 | In ECMAScript 5 and earlier, the need to fetch information from objects and arrays could lead to a lot of code that looks the same, just to get certain data into local variables. For example:
8 |
9 | ```js
10 | let options = {
11 | repeat: true,
12 | save: false
13 | };
14 |
15 | // extract data from the object
16 | let repeat = options.repeat,
17 | save = options.save;
18 | ```
19 |
20 | This code extracts the values of `repeat` and `save` from the `options` object and stores that data in local variables with the same names. While this code looks simple, imagine if you had a large number of variables to assign; you would have to assign them all one by one. And if there was a nested data structure to traverse to find the information instead, you might have to dig through the entire structure just to find one piece of data.
21 |
22 | That's why ECMAScript 6 adds destructuring for both objects and arrays. When you break a data structure into smaller parts, getting the information you need out of it becomes much easier. Many languages implement destructuring with a minimal amount of syntax to make the process simpler to use. The ECMAScript 6 implementation actually makes use of syntax you're already familiar with: the syntax for object and array literals.
23 |
24 | ## Object Destructuring
25 |
26 | Object destructuring syntax uses an object literal on the left side of an assignment operation. For example:
27 |
28 | ```js
29 | let node = {
30 | type: "Identifier",
31 | name: "foo"
32 | };
33 |
34 | let { type, name } = node;
35 |
36 | console.log(type); // "Identifier"
37 | console.log(name); // "foo"
38 | ```
39 |
40 | In this code, the value of `node.type` is stored in a variable called `type` and the value of `node.name` is stored in a variable called `name`. This syntax is the same as the object literal property initializer shorthand introduced in Chapter 4. The identifiers `type` and `name` are both declarations of local variables and the properties to read the value from on the `node` object.
41 |
42 | A> #### Don't Forget the Initializer
43 | A>
44 | A>When using destructuring to declare variables using `var`, `let`, or `const`, you must supply an initializer (the value after the equals sign). The following lines of code will all throw syntax errors due to a missing initializer:
45 | A>
46 | A>```js
47 | A>// syntax error!
48 | A>var { type, name };
49 | A>
50 | A>// syntax error!
51 | A>let { type, name };
52 | A>
53 | A>// syntax error!
54 | A>const { type, name };
55 | A>```
56 | A>
57 | A>While `const` always requires an initializer, even when using nondestructured variables, `var` and `let` only require initializers when using destructuring.
58 |
59 | #### Destructuring Assignment
60 |
61 | The object destructuring examples so far have used variable declarations. However, it's also possible to use destructuring in assignments. For instance, you may decide to change the values of variables after they are defined, as follows:
62 |
63 | ```js
64 | let node = {
65 | type: "Identifier",
66 | name: "foo"
67 | },
68 | type = "Literal",
69 | name = 5;
70 |
71 | // assign different values using destructuring
72 | ({ type, name } = node);
73 |
74 | console.log(type); // "Identifier"
75 | console.log(name); // "foo"
76 | ```
77 |
78 | In this example, `type` and `name` are initialized with values when declared, and then two variables with the same names are initialized with different values. The next line uses destructuring assignment to change those values by reading from the `node` object. Note that you must put parentheses around a destructuring assignment statement. That's because an opening curly brace is expected to a be a block statement, and a block statement cannot appear on the left side of an assignment. The parentheses signal that the next curly brace is not a block statement and should be interpreted as an expression, allowing the assignment to complete.
79 |
80 | A destructuring assignment expression evaluates to the right side of the expression (after the `=`). That means you can use a destructuring assignment expression anywhere a value is expected. For instance, passing a value to a function:
81 |
82 | ```js
83 | let node = {
84 | type: "Identifier",
85 | name: "foo"
86 | },
87 | type = "Literal",
88 | name = 5;
89 |
90 | function outputInfo(value) {
91 | console.log(value === node);
92 | }
93 |
94 | outputInfo({ type, name } = node); // true
95 |
96 | console.log(type); // "Identifier"
97 | console.log(name); // "foo"
98 | ```
99 |
100 | The `outputInfo()` function is called with a destructuring assignment expression. The expression evaluates to `node` because that is the value of the right side of the expression. The assignment to `type` and `name` both behave as normal and `node` is passed into `outputInfo()`.
101 |
102 | W> An error is thrown when the right side of the destructuring assignment expression (the expression after `=`) evaluates to `null` or `undefined`. This happens because any attempt to read a property of `null` or `undefined` results in a runtime error.
103 |
104 | #### Default Values
105 |
106 | When you use a destructuring assignment statement, if you specify a local variable with a property name that doesn't exist on the object, then that local variable is assigned a value of `undefined`. For example:
107 |
108 | ```js
109 | let node = {
110 | type: "Identifier",
111 | name: "foo"
112 | };
113 |
114 | let { type, name, value } = node;
115 |
116 | console.log(type); // "Identifier"
117 | console.log(name); // "foo"
118 | console.log(value); // undefined
119 | ```
120 |
121 | This code defines an additional local variable called `value` and attempts to assign it a value. However, there is no corresponding `value` property on the `node` object, so the variable is assigned the value of `undefined` as expected.
122 |
123 | You can optionally define a default value to use when a specified property doesn't exist. To do so, insert an equals sign (`=`) after the property name and specify the default value, like this:
124 |
125 | ```js
126 | let node = {
127 | type: "Identifier",
128 | name: "foo"
129 | };
130 |
131 | let { type, name, value = true } = node;
132 |
133 | console.log(type); // "Identifier"
134 | console.log(name); // "foo"
135 | console.log(value); // true
136 | ```
137 |
138 | In this example, the variable `value` is given `true` as a default value. The default value is only used if the property is missing on `node` or has a value of `undefined`. Since there is no `node.value` property, the variable `value` uses the default value. This works similarly to the default parameter values for functions, as discussed in Chapter 3.
139 |
140 | #### Assigning to Different Local Variable Names
141 |
142 | Up to this point, each example destructuring assignment has used the object property name as the local variable name; for example, the value of `node.type` was stored in a `type` variable. That works well when you want to use the same name, but what if you don't? ECMAScript 6 has an extended syntax that allows you to assign to a local variable with a different name, and that syntax looks like the object literal nonshorthand property initializer syntax. Here's an example:
143 |
144 | ```js
145 | let node = {
146 | type: "Identifier",
147 | name: "foo"
148 | };
149 |
150 | let { type: localType, name: localName } = node;
151 |
152 | console.log(localType); // "Identifier"
153 | console.log(localName); // "foo"
154 | ```
155 |
156 | This code uses destructuring assignment to declare the `localType` and `localName` variables, which contain the values from the `node.type` and `node.name` properties, respectively. The syntax `type: localType` says to read the property named `type` and store its value in the `localType` variable. This syntax is effectively the opposite of traditional object literal syntax, where the name is on the left of the colon and the value is on the right. In this case, the name is on the right of the colon and the location of the value to read is on the left.
157 |
158 | You can add default values when using a different variable name, as well. The equals sign and default value are still placed after the local variable name. For example:
159 |
160 | ```js
161 | let node = {
162 | type: "Identifier"
163 | };
164 |
165 | let { type: localType, name: localName = "bar" } = node;
166 |
167 | console.log(localType); // "Identifier"
168 | console.log(localName); // "bar"
169 | ```
170 |
171 | Here, the `localName` variable has a default value of `"bar"`. The variable is assigned its default value because there's no `node.name` property.
172 |
173 | So far, you've seen how to deal with destructuring of an object whose properties are primitive values. Object destructuring can also be used to retrieve values in nested object structures.
174 |
175 | #### Nested Object Destructuring
176 |
177 | By using a syntax similar to object literals, you can navigate into a nested object structure to retrieve just the information you want. Here's an example:
178 |
179 | ```js
180 | let node = {
181 | type: "Identifier",
182 | name: "foo",
183 | loc: {
184 | start: {
185 | line: 1,
186 | column: 1
187 | },
188 | end: {
189 | line: 1,
190 | column: 4
191 | }
192 | }
193 | };
194 |
195 | let { loc: { start }} = node;
196 |
197 | console.log(start.line); // 1
198 | console.log(start.column); // 1
199 | ```
200 |
201 | The destructuring pattern in this example uses curly braces to indicate that the pattern should descend into the property named `loc` on `node` and look for the `start` property. Remember from the last section that whenever there's a colon in a destructuring pattern, it means the identifier before the colon is giving a location to inspect, and the right side assigns a value. When there's a curly brace after the colon, that indicates that the destination is nested another level into the object.
202 |
203 | You can go one step further and use a different name for the local variable as well:
204 |
205 | ```js
206 | let node = {
207 | type: "Identifier",
208 | name: "foo",
209 | loc: {
210 | start: {
211 | line: 1,
212 | column: 1
213 | },
214 | end: {
215 | line: 1,
216 | column: 4
217 | }
218 | }
219 | };
220 |
221 | // extract node.loc.start
222 | let { loc: { start: localStart }} = node;
223 |
224 | console.log(localStart.line); // 1
225 | console.log(localStart.column); // 1
226 | ```
227 |
228 | In this version of the code, `node.loc.start` is stored in a new local variable called `localStart`. Destructuring patterns can be nested to an arbitrary level of depth, with all capabilities available at each level.
229 |
230 | Object destructuring is very powerful and has a lot of options, but array destructuring offers some unique capabilities that allow you to extract information from arrays.
231 |
232 | A> #### Syntax Gotcha
233 | A>
234 | A>Be careful when using nested destructuring because you can inadvertently create a statement that has no effect. Empty curly braces are legal in object destructuring, however, they don't do anything. For example:
235 | A>
236 | A>```js
237 | A>// no variables declared!
238 | A>let { loc: {} } = node;
239 | A>```
240 | A>
241 | A>There are no bindings declared in this statement. Due to the curly braces on the right, `loc` is used as a location to inspect rather than a binding to create. In such a case, it's likely that the intent was to use `=` to define a default value rather than `:` to define a location. It's possible that this syntax will be made illegal in the future, but for now, this is a gotcha to look out for.
242 |
243 | ## Array Destructuring
244 |
245 | Array destructuring syntax is very similar to object destructuring; it just uses array literal syntax instead of object literal syntax. The destructuring operates on positions within an array, rather than the named properties that are available in objects. For example:
246 |
247 | ```js
248 | let colors = [ "red", "green", "blue" ];
249 |
250 | let [ firstColor, secondColor ] = colors;
251 |
252 | console.log(firstColor); // "red"
253 | console.log(secondColor); // "green"
254 | ```
255 |
256 | Here, array destructuring pulls out the values `"red"` and `"green"` from the `colors` array and stores them in the `firstColor` and `secondColor` variables. Those values are chosen because of their position in the array; the actual variable names could be anything. Any items not explicitly mentioned in the destructuring pattern are ignored. Keep in mind that the array itself isn't changed in any way.
257 |
258 | You can also omit items in the destructuring pattern and only provide variable names for the items you're interested in. If, for example, you just want the third value of an array, you don't need to supply variable names for the first and second items. Here's how that works:
259 |
260 | ```js
261 | let colors = [ "red", "green", "blue" ];
262 |
263 | let [ , , thirdColor ] = colors;
264 |
265 | console.log(thirdColor); // "blue"
266 | ```
267 |
268 | This code uses a destructuring assignment to retrieve the third item in `colors`. The commas preceding `thirdColor` in the pattern are placeholders for the array items that come before it. By using this approach, you can easily pick out values from any number of slots in the middle of an array without needing to provide variable names for them.
269 |
270 | W> Similar to object destructuring, you must always provide an initializer when using array destructuring with `var`, `let`, or `const`.
271 |
272 | #### Destructuring Assignment
273 |
274 | You can use array destructuring in the context of an assignment, but unlike object destructuring, there is no need to wrap the expression in parentheses. For example:
275 |
276 | ```js
277 | let colors = [ "red", "green", "blue" ],
278 | firstColor = "black",
279 | secondColor = "purple";
280 |
281 | [ firstColor, secondColor ] = colors;
282 |
283 | console.log(firstColor); // "red"
284 | console.log(secondColor); // "green"
285 | ```
286 |
287 | The destructured assignment in this code works in a similar manner to the last array destructuring example. The only difference is that `firstColor` and `secondColor` have already been defined. Most of the time, that's probably all you'll need to know about array destructuring assignment, but there's a little bit more to it that you will probably find useful.
288 |
289 | Array destructuring assignment has a very unique use case that makes it easier to swap the values of two variables. Value swapping is a common operation in sorting algorithms, and the ECMAScript 5 way of swapping variables involves a third, temporary variable, as in this example:
290 |
291 | ```js
292 | // Swapping variables in ECMAScript 5
293 | let a = 1,
294 | b = 2,
295 | tmp;
296 |
297 | tmp = a;
298 | a = b;
299 | b = tmp;
300 |
301 | console.log(a); // 2
302 | console.log(b); // 1
303 | ```
304 |
305 | The intermediate variable `tmp` is necessary in order to swap the values of `a` and `b`. Using array destructuring assignment, however, there's no need for that extra variable. Here's how you can swap variables in ECMAScript 6:
306 |
307 | ```js
308 | // Swapping variables in ECMAScript 6
309 | let a = 1,
310 | b = 2;
311 |
312 | [ a, b ] = [ b, a ];
313 |
314 | console.log(a); // 2
315 | console.log(b); // 1
316 | ```
317 |
318 | The array destructuring assignment in this example looks like a mirror image. The left side of the assignment (before the equals sign) is a destructuring pattern just like those in the other array destructuring examples. The right side is an array literal that is temporarily created for the swap. The destructuring happens on the temporary array, which has the values of `b` and `a` copied into its first and second positions. The effect is that the variables have swapped values.
319 |
320 | W> Like object destructuring assignment, an error is thrown when the right side of an array destructured assignment expression evaluates to `null` or `undefined`.
321 |
322 | #### Default Values
323 |
324 | Array destructuring assignment allows you to specify a default value for any position in the array, too. The default value is used when the property at the given position either doesn't exist or has the value `undefined`. For example:
325 |
326 | ```js
327 | let colors = [ "red" ];
328 |
329 | let [ firstColor, secondColor = "green" ] = colors;
330 |
331 | console.log(firstColor); // "red"
332 | console.log(secondColor); // "green"
333 | ```
334 |
335 | In this code, the `colors` array has only one item, so there is nothing for `secondColor` to match. Since there is a default value, `secondColor` is set to `"green"` instead of `undefined`.
336 |
337 | #### Nested Destructuring
338 |
339 | You can destructure nested arrays in a manner similar to destructuring nested objects. By inserting another array pattern into the overall pattern, the destructuring will descend into a nested array, like this:
340 |
341 | ```js
342 | let colors = [ "red", [ "green", "lightgreen" ], "blue" ];
343 |
344 | // later
345 |
346 | let [ firstColor, [ secondColor ] ] = colors;
347 |
348 | console.log(firstColor); // "red"
349 | console.log(secondColor); // "green"
350 | ```
351 |
352 | Here, the `secondColor` variable refers to the `"green"` value inside the `colors` array. That item is contained within a second array, so the extra square brackets around `secondColor` in the destructuring pattern are necessary. As with objects, you can nest arrays arbitrarily deep.
353 |
354 | #### Rest Items
355 |
356 | Chapter 3 introduced rest parameters for functions, and array destructuring has a similar concept called *rest items*. Rest items use the `...` syntax to assign the remaining items in an array to a particular variable. Here's an example:
357 |
358 | ```js
359 | let colors = [ "red", "green", "blue" ];
360 |
361 | let [ firstColor, ...restColors ] = colors;
362 |
363 | console.log(firstColor); // "red"
364 | console.log(restColors.length); // 2
365 | console.log(restColors[0]); // "green"
366 | console.log(restColors[1]); // "blue"
367 | ```
368 |
369 | The first item in `colors` is assigned to `firstColor`, and the rest are assigned into a new `restColors` array. The `restColors` array, therefore, has two items: `"green"` and `"blue"`. Rest items are useful for extracting certain items from an array and keeping the rest available, but there's another helpful use.
370 |
371 | A glaring omission from JavaScript arrays is the ability to easily create a clone. In ECMAScript 5, developers frequently used the `concat()` method as an easy way to clone an array. For example:
372 |
373 | ```js
374 | // cloning an array in ECMAScript 5
375 | var colors = [ "red", "green", "blue" ];
376 | var clonedColors = colors.concat();
377 |
378 | console.log(clonedColors); //"[red,green,blue]"
379 | ```
380 |
381 | While the `concat()` method is intended to concatenate two arrays together, calling it without an argument returns a clone of the array. In ECMAScript 6, you can use rest items to achieve the same thing through syntax intended to function that way. It works like this:
382 |
383 | ```js
384 | // cloning an array in ECMAScript 6
385 | let colors = [ "red", "green", "blue" ];
386 | let [ ...clonedColors ] = colors;
387 |
388 | console.log(clonedColors); //"[red,green,blue]"
389 | ```
390 |
391 | In this example, rest items are used to copy values from the `colors` array into the `clonedColors` array. While it's a matter of perception as to whether this technique makes the developer's intent clearer than using the `concat()` method, this is a useful ability to be aware of.
392 |
393 | W> Rest items must be the last entry in the destructured array and cannot be followed by a comma. Including a comma after rest items is a syntax error.
394 |
395 | ## Mixed Destructuring
396 |
397 | Object and array destructuring can be used together to create more complex expressions. In doing so, you are able to extract just the pieces of information you want from any mixture of objects and arrays. For example:
398 |
399 | ```js
400 | let node = {
401 | type: "Identifier",
402 | name: "foo",
403 | loc: {
404 | start: {
405 | line: 1,
406 | column: 1
407 | },
408 | end: {
409 | line: 1,
410 | column: 4
411 | }
412 | },
413 | range: [0, 3]
414 | };
415 |
416 | let {
417 | loc: { start },
418 | range: [ startIndex ]
419 | } = node;
420 |
421 | console.log(start.line); // 1
422 | console.log(start.column); // 1
423 | console.log(startIndex); // 0
424 | ```
425 |
426 | This code extracts `node.loc.start` and `node.range[0]` into `start` and `startIndex`, respectively. Keep in mind that `loc:` and `range:` in the destructured pattern are just locations that correspond to properties in the `node` object. There is no part of `node` that cannot be extracted using destructuring when you use a mix of object and array destructuring. This approach is particularly useful for pulling values out of JSON configuration structures without navigating the entire structure.
427 |
428 | ## Destructured Parameters
429 |
430 | Destructuring has one more particularly helpful use case, and that is when passing function arguments. When a JavaScript function takes a large number of optional parameters, one common pattern is to create an `options` object whose properties specify the additional parameters, like this:
431 |
432 | ```js
433 | // properties on options represent additional parameters
434 | function setCookie(name, value, options) {
435 |
436 | options = options || {};
437 |
438 | let secure = options.secure,
439 | path = options.path,
440 | domain = options.domain,
441 | expires = options.expires;
442 |
443 | // code to set the cookie
444 | }
445 |
446 | // third argument maps to options
447 | setCookie("type", "js", {
448 | secure: true,
449 | expires: 60000
450 | });
451 | ```
452 | Many JavaScript libraries contain `setCookie()` functions that look similar to this one. In this function, the `name` and `value` arguments are required, but `secure`, `path`, `domain`, and `expires` are not. And since there is no priority order for the other data, it's efficient to just have an `options` object with named properties, rather than list extra named parameters. This approach works, but now you can't tell what input the function expects just by looking at the function definition; you need to read the function body.
453 |
454 | Destructured parameters offer an alternative that makes it clearer what arguments a function expects. A destructured parameter uses an object or array destructuring pattern in place of a named parameter. To see this in action, look at this rewritten version of the `setCookie()` function from the last example:
455 |
456 | ```js
457 | function setCookie(name, value, { secure, path, domain, expires }) {
458 |
459 | // code to set the cookie
460 | }
461 |
462 | setCookie("type", "js", {
463 | secure: true,
464 | expires: 60000
465 | });
466 | ```
467 |
468 | This function behaves similarly to the previous example, but now, the third argument uses destructuring to pull out the necessary data. The parameters outside the destructured parameter are clearly expected, and at the same time, it's clear to someone using `setCookie()` what options are available in terms of extra arguments. And of course, if the third argument is required, the values it should contain are crystal clear. The destructured parameters also act like regular parameters in that they are set to `undefined` if they are not passed.
469 |
470 | A>Destructured parameters have all of the capabilities of destructuring that you've learned so far in this chapter. You can use default values, mix object and array patterns, and use variable names that differ from the properties you're reading from.
471 |
472 | ### Destructured Parameters are Required
473 |
474 | One quirk of using destructured parameters is that, by default, an error is thrown when they are not provided in a function call. For instance, this call to the `setCookie()` function in the last example throws an error:
475 |
476 | ```js
477 | // Error!
478 | setCookie("type", "js");
479 | ```
480 |
481 | The third argument is missing, and so it evaluates to `undefined` as expected. This causes an error because destructured parameters are really just a shorthand for destructured declaration. When the `setCookie()` function is called, the JavaScript engine actually does this:
482 |
483 | ```js
484 | function setCookie(name, value, options) {
485 |
486 | let { secure, path, domain, expires } = options;
487 |
488 | // code to set the cookie
489 | }
490 | ```
491 |
492 | Since destructuring throws an error when the right side expression evaluates to `null` or `undefined`, the same is true when the third argument isn't passed to the `setCookie()` function.
493 |
494 | If you want the destructured parameter to be required, then this behavior isn't all that troubling. But if you want the destructured parameter to be optional, you can work around this behavior by providing a default value for the destructured parameter, like this:
495 |
496 | ```js
497 | function setCookie(name, value, { secure, path, domain, expires } = {}) {
498 |
499 | // ...
500 | }
501 | ```
502 |
503 | This example provides a new object as the default value for the third parameter. Providing a default value for the destructured parameter means that the `secure`, `path`, `domain`, and `expires` will all be `undefined` if the third argument to `setCookie()` isn't provided, and no error will be thrown.
504 |
505 | ### Default Values for Destructured Parameters
506 |
507 | You can specify destructured default values for destructured parameters just as you would in destructured assignment. Just add the equals sign after the parameter and specify the default value. For example:
508 |
509 | ```js
510 | function setCookie(name, value,
511 | {
512 | secure = false,
513 | path = "/",
514 | domain = "example.com",
515 | expires = new Date(Date.now() + 360000000)
516 | } = {}
517 | ) {
518 |
519 | // ...
520 | }
521 | ```
522 |
523 | Each property in the destructured parameter has a default value in this code, so you can avoid checking to see if a given property has been included in order to use the correct value. Also, the entire destructured parameter has a default value of an empty object, making the parameter optional. This does make the function declaration look a bit more complicated than usual, but that's a small price to pay for ensuring each argument has a usable value.
524 |
525 | ## Summary
526 |
527 | Destructuring makes working with objects and arrays in JavaScript easier. Using the familiar object literal and array literal syntax, you can pick data structures apart to get at just the information you're interested in. Object patterns allow you to extract data from objects while array patterns let you extract data from arrays.
528 |
529 | Both object and array destructuring can specify default values for any property or item that is `undefined` and both throw errors when the right side of an assignment evaluates to `null` or `undefined`. You can also navigate deeply nested data structures with object and array destructuring, descending to any arbitrary depth.
530 |
531 | Destructuring declarations use `var`, `let`, or `const` to create variables and must always have an initializer. Destructuring assignments are used in place of other assignments and allow you to destructure into object properties and already-existing variables.
532 |
533 | Destructured parameters use the destructuring syntax to make "options" objects more transparent when used as function parameters. The actual data you're interested in can be listed out along with other named parameters. Destructured parameters can be array patterns, object patterns, or a mixture, and you can use all of the features of destructuring.
534 |
--------------------------------------------------------------------------------
/manuscript/04-Objects.md:
--------------------------------------------------------------------------------
1 | # Expanded Object Functionality
2 |
3 | ECMAScript 6 focuses heavily on improving the utility of objects, which makes sense because nearly every value in JavaScript is some type of object. Additionally, the number of objects used in an average JavaScript program continues to increase as the complexity of JavaScript applications increases, meaning that programs are creating more objects all the time. With more objects comes the necessity to use them more effectively.
4 |
5 | ECMAScript 6 improves objects in a number of ways, from simple syntax extensions to options for manipulating and interacting with them.
6 |
7 | ## Object Categories
8 |
9 | JavaScript uses a mix of terminology to describe objects found in the standard as opposed to those added by execution environments such as the browser or Node.js, and the ECMAScript 6 specification has clear definitions for each category of object. It's important to understand this terminology to have a good understanding of the language as a whole. The object categories are:
10 |
11 | * *Ordinary objects* Have all the default internal behaviors for objects in JavaScript.
12 | * *Exotic objects* Have internal behavior that differs from the default in some way.
13 | * *Standard objects* Are those defined by ECMAScript 6, such as `Array`, `Date`, and so on. Standard objects may be ordinary or exotic.
14 | * *Built-in objects* Are present in a JavaScript execution environment when a script begins to execute. All standard objects are built-in objects.
15 |
16 | I will use these terms throughout the book to explain the various objects defined by ECMAScript 6.
17 |
18 | ## Object Literal Syntax Extensions
19 |
20 | The object literal is one of the most popular patterns in JavaScript. JSON is built upon its syntax, and it's in nearly every JavaScript file on the Internet. The object literal is so popular because it's a succinct syntax for creating objects that otherwise would take several lines of code. Luckily for developers, ECMAScript 6 makes object literals more powerful and even more succinct by extending the syntax in several ways.
21 |
22 | ### Property Initializer Shorthand
23 |
24 | In ECMAScript 5 and earlier, object literals were simply collections of name-value pairs. That meant there could be some duplication when property values are initialized. For example:
25 |
26 | ```js
27 | function createPerson(name, age) {
28 | return {
29 | name: name,
30 | age: age
31 | };
32 | }
33 | ```
34 |
35 | The `createPerson()` function creates an object whose property names are the same as the function parameter names. The result appears to be duplication of `name` and `age` even though one is the name of an object property while the other provides the value for that property. The key `name` in the returned object is assigned the value contained in the variable `name`, and the key `age` in the returned object is assigned the value contained in the variable `age`.
36 |
37 | In ECMAScript 6, you can eliminate the duplication that exists around property names and local variables by using the *property initializer* shorthand. When an object property name is the same as the local variable name, you can simply include the name without a colon and value. For example, `createPerson()` can be rewritten for ECMAScript 6 as follows:
38 |
39 | ```js
40 | function createPerson(name, age) {
41 | return {
42 | name,
43 | age
44 | };
45 | }
46 | ```
47 |
48 | When a property in an object literal only has a name, the JavaScript engine looks into the surrounding scope for a variable of the same name. If it finds one, that variable's value is assigned to the same name on the object literal. In this example, the object literal property `name` is assigned the value of the local variable `name`.
49 |
50 | This extension makes object literal initialization even more succinct and helps to eliminate naming errors. Assigning a property with the same name as a local variable is a very common pattern in JavaScript, making this extension a welcome addition.
51 |
52 | ### Concise Methods
53 |
54 | ECMAScript 6 also improves the syntax for assigning methods to object literals. In ECMAScript 5 and earlier, you must specify a name and then the full function definition to add a method to an object, as follows:
55 |
56 | ```js
57 | var person = {
58 | name: "Nicholas",
59 | sayName: function() {
60 | console.log(this.name);
61 | }
62 | };
63 | ```
64 |
65 | In ECMAScript 6, the syntax is made more concise by eliminating the colon and the `function` keyword. That means you can rewrite the previous example like this:
66 |
67 | ```js
68 | var person = {
69 | name: "Nicholas",
70 | sayName() {
71 | console.log(this.name);
72 | }
73 | };
74 | ```
75 |
76 | This shorthand syntax, also called *concise method* syntax, creates a method on the `person` object just as the previous example did. The `sayName()` property is assigned an anonymous function and has all the same characteristics as the ECMAScript 5 `sayName()` function. The one difference is that concise methods may use `super` (discussed later in the "Easy Prototype Access with Super References" section), while the nonconcise methods may not.
77 |
78 | I> The `name` property of a method created using concise method shorthand is the name used before the parentheses. In the last example, the `name` property for `person.sayName()` is `"sayName"`.
79 |
80 | ### Computed Property Names
81 |
82 | ECMAScript 5 and earlier could compute property names on object instances when those properties were set with square brackets instead of dot notation. The square brackets allow you to specify property names using variables and string literals that may contain characters that would cause a syntax error if used in an identifier. Here's an example:
83 |
84 | ```js
85 | var person = {},
86 | lastName = "last name";
87 |
88 | person["first name"] = "Nicholas";
89 | person[lastName] = "Zakas";
90 |
91 | console.log(person["first name"]); // "Nicholas"
92 | console.log(person[lastName]); // "Zakas"
93 | ```
94 |
95 | Since `lastName` is assigned a value of `"last name"`, both property names in this example use a space, making it impossible to reference them using dot notation. However, bracket notation allows any string value to be used as a property name, so assigning `"first name"` to `"Nicholas"` and "`last name"` to `"Zakas"` works.
96 |
97 | Additionally, you can use string literals directly as property names in object literals, like this:
98 |
99 | ```js
100 | var person = {
101 | "first name": "Nicholas"
102 | };
103 |
104 | console.log(person["first name"]); // "Nicholas"
105 | ```
106 |
107 | This pattern works for property names that are known ahead of time and can be represented with a string literal. If, however, the property name `"first name"` were contained in a variable (as in the previous example) or had to be calculated, then there would be no way to define that property using an object literal in ECMAScript 5.
108 |
109 | In ECMAScript 6, computed property names are part of the object literal syntax, and they use the same square bracket notation that has been used to reference computed property names in object instances. For example:
110 |
111 | ```js
112 | var lastName = "last name";
113 |
114 | var person = {
115 | "first name": "Nicholas",
116 | [lastName]: "Zakas"
117 | };
118 |
119 | console.log(person["first name"]); // "Nicholas"
120 | console.log(person[lastName]); // "Zakas"
121 | ```
122 |
123 | The square brackets inside the object literal indicate that the property name is computed, so its contents are evaluated as a string. That means you can also include expressions such as:
124 |
125 | ```js
126 | var suffix = " name";
127 |
128 | var person = {
129 | ["first" + suffix]: "Nicholas",
130 | ["last" + suffix]: "Zakas"
131 | };
132 |
133 | console.log(person["first name"]); // "Nicholas"
134 | console.log(person["last name"]); // "Zakas"
135 | ```
136 |
137 | These properties evaluate to `"first name"` and `"last name"`, and those strings can be used to reference the properties later. Anything you would put inside square brackets while using bracket notation on object instances will also work for computed property names inside object literals.
138 |
139 | ## New Methods
140 |
141 | One of the design goals of ECMAScript beginning with ECMAScript 5 was to avoid creating new global functions or methods on `Object.prototype`, and instead try to find objects on which new methods should be available. As a result, the `Object` global has received an increasing number of methods when no other objects are more appropriate. ECMAScript 6 introduces a couple new methods on the `Object` global that are designed to make certain tasks easier.
142 |
143 | ### The Object.is() Method
144 |
145 | When you want to compare two values in JavaScript, you're probably used to using either the equals operator (`==`) or the identically equals operator (`===`). Many developers prefer the latter, to avoid type coercion during comparison. But even the identically equals operator isn't entirely accurate. For example, the values +0 and -0 are considered equal by `===` even though they are represented differently in the JavaScript engine. Also `NaN === NaN` returns `false`, which necessitates using `isNaN()` to detect `NaN` properly.
146 |
147 | ECMAScript 6 introduces the `Object.is()` method to make up for the remaining quirks of the identically equals operator. This method accepts two arguments and returns `true` if the values are equivalent. Two values are considered equivalent when they are of the same type and have the same value. Here are some examples:
148 |
149 | ```js
150 | console.log(+0 == -0); // true
151 | console.log(+0 === -0); // true
152 | console.log(Object.is(+0, -0)); // false
153 |
154 | console.log(NaN == NaN); // false
155 | console.log(NaN === NaN); // false
156 | console.log(Object.is(NaN, NaN)); // true
157 |
158 | console.log(5 == 5); // true
159 | console.log(5 == "5"); // true
160 | console.log(5 === 5); // true
161 | console.log(5 === "5"); // false
162 | console.log(Object.is(5, 5)); // true
163 | console.log(Object.is(5, "5")); // false
164 | ```
165 |
166 | In many cases, `Object.is()` works the same as the `===` operator. The only differences are that +0 and -0 are considered not equivalent and `NaN` is considered equivalent to `NaN`. But there's no need to stop using equality operators altogether. Choose whether to use `Object.is()` instead of `==` or `===` based on how those special cases affect your code.
167 |
168 | ### The Object.assign() Method
169 |
170 | *Mixins* are among the most popular patterns for object composition in JavaScript. In a mixin, one object receives properties and methods from another object. Many JavaScript libraries have a mixin method similar to this:
171 |
172 | ```js
173 | function mixin(receiver, supplier) {
174 | Object.keys(supplier).forEach(function(key) {
175 | receiver[key] = supplier[key];
176 | });
177 |
178 | return receiver;
179 | }
180 | ```
181 |
182 | The `mixin()` function iterates over the own properties of `supplier` and copies them onto `receiver` (a shallow copy, where object references are shared when property values are objects). This allows the `receiver` to gain new properties without inheritance, as in this code:
183 |
184 | ```js
185 | function EventTarget() { /*...*/ }
186 | EventTarget.prototype = {
187 | constructor: EventTarget,
188 | emit: function() { /*...*/ },
189 | on: function() { /*...*/ }
190 | };
191 |
192 | var myObject = {};
193 | mixin(myObject, EventTarget.prototype);
194 |
195 | myObject.emit("somethingChanged");
196 | ```
197 |
198 | Here, `myObject` receives behavior from the `EventTarget.prototype` object. This gives `myObject` the ability to publish events and subscribe to them using the `emit()` and `on()` methods, respectively.
199 |
200 | This pattern became popular enough that ECMAScript 6 added the `Object.assign()` method, which behaves the same way, accepting a receiver and any number of suppliers, and then returning the receiver. The name change from `mixin()` to `assign()` reflects the actual operation that occurs. Since the `mixin()` function uses the assignment operator (`=`), it cannot copy accessor properties to the receiver as accessor properties. The name `Object.assign()` was chosen to reflect this distinction.
201 |
202 | I> Similar methods in various libraries may have other names for the same basic functionality; popular alternates include the `extend()` and `mix()` methods. There was also, briefly, an `Object.mixin()` method in ECMAScript 6 in addition to the `Object.assign()` method. The primary difference was that `Object.mixin()` also copied over accessor properties, but the method was removed due to concerns over the use of `super` (discussed in the "Easy Prototype Access with Super References" section of this chapter).
203 |
204 | You can use `Object.assign()` anywhere the `mixin()` function would have been used. Here's an example:
205 |
206 | ```js
207 | function EventTarget() { /*...*/ }
208 | EventTarget.prototype = {
209 | constructor: EventTarget,
210 | emit: function() { /*...*/ },
211 | on: function() { /*...*/ }
212 | }
213 |
214 | var myObject = {}
215 | Object.assign(myObject, EventTarget.prototype);
216 |
217 | myObject.emit("somethingChanged");
218 | ```
219 |
220 | The `Object.assign()` method accepts any number of suppliers, and the receiver receives the properties in the order in which the suppliers are specified. That means the second supplier might overwrite a value from the first supplier on the receiver, which is what happens in this snippet:
221 |
222 | ```js
223 | var receiver = {};
224 |
225 | Object.assign(receiver,
226 | {
227 | type: "js",
228 | name: "file.js"
229 | },
230 | {
231 | type: "css"
232 | }
233 | );
234 |
235 | console.log(receiver.type); // "css"
236 | console.log(receiver.name); // "file.js"
237 | ```
238 |
239 | The value of `receiver.type` is `"css"` because the second supplier overwrote the value of the first.
240 |
241 | The `Object.assign()` method isn't a big addition to ECMAScript 6, but it does formalize a common function found in many JavaScript libraries.
242 |
243 | A> ### Working with Accessor Properties
244 | A>
245 | A> Keep in mind that `Object.assign()` doesn't create accessor properties on the receiver when a supplier has accessor properties. Since `Object.assign()` uses the assignment operator, an accessor property on a supplier will become a data property on the receiver. For example:
246 | A>
247 | A> ```js
248 | A> var receiver = {},
249 | A> supplier = {
250 | A> get name() {
251 | A> return "file.js"
252 | A> }
253 | A> };
254 | A>
255 | A> Object.assign(receiver, supplier);
256 | A>
257 | A> var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");
258 | A>
259 | A> console.log(descriptor.value); // "file.js"
260 | A> console.log(descriptor.get); // undefined
261 | A> ```
262 | A>
263 | A> In this code, the `supplier` has an accessor property called `name`. After using the `Object.assign()` method, `receiver.name` exists as a data property with a value of `"file.js"` because `supplier.name` returned `"file.js"` when `Object.assign()` was called.
264 |
265 | ## Duplicate Object Literal Properties
266 |
267 | ECMAScript 5 strict mode introduced a check for duplicate object literal properties that would throw an error if a duplicate was found. For example, this code was problematic:
268 |
269 | ```js
270 | "use strict";
271 |
272 | var person = {
273 | name: "Nicholas",
274 | name: "Greg" // syntax error in ES5 strict mode
275 | };
276 | ```
277 |
278 | When running in ECMAScript 5 strict mode, the second `name` property causes a syntax error. But in ECMAScript 6, the duplicate property check was removed. Both strict and nonstrict mode code no longer check for duplicate properties. Instead, the last property of the given name becomes the property's actual value, as shown here:
279 |
280 | ```js
281 | "use strict";
282 |
283 | var person = {
284 | name: "Nicholas",
285 | name: "Greg" // no error in ES6 strict mode
286 | };
287 |
288 | console.log(person.name); // "Greg"
289 | ```
290 |
291 | In this example, the value of `person.name` is `"Greg"` because that's the last value assigned to the property.
292 |
293 | ## Own Property Enumeration Order
294 |
295 | ECMAScript 5 didn't define the enumeration order of object properties, as it left this up to the JavaScript engine vendors. However, ECMAScript 6 strictly defines the order in which own properties must be returned when they are enumerated. This affects how properties are returned using `Object.getOwnPropertyNames()` and `Reflect.ownKeys` (covered in Chapter 12). It also affects the order in which properties are processed by `Object.assign()`.
296 |
297 | The basic order for own property enumeration is:
298 |
299 | 1. All numeric keys in ascending order
300 | 2. All string keys in the order in which they were added to the object
301 | 3. All symbol keys (covered in Chapter 6) in the order in which they were added to the object
302 |
303 | Here's an example:
304 |
305 | ```js
306 | var obj = {
307 | a: 1,
308 | 0: 1,
309 | c: 1,
310 | 2: 1,
311 | b: 1,
312 | 1: 1
313 | };
314 |
315 | obj.d = 1;
316 |
317 | console.log(Object.getOwnPropertyNames(obj).join("")); // "012acbd"
318 | ```
319 |
320 | The `Object.getOwnPropertyNames()` method returns the properties in `obj` in the order `0`, `1`, `2`, `a`, `c`, `b`, `d`. Note that the numeric keys are grouped together and sorted, even though they appear out of order in the object literal. The string keys come after the numeric keys and appear in the order that they were added to `obj`. The keys in the object literal itself come first, followed by any dynamic keys that were added later (in this case, `d`).
321 |
322 | W> The `for-in` loop still has an unspecified enumeration order because not all JavaScript engines implement it the same way. The `Object.keys()` method and `JSON.stringify()` are both specified to use the same (unspecified) enumeration order as `for-in`.
323 |
324 | While enumeration order is a subtle change to how JavaScript works, it's not uncommon to find programs that rely on a specific enumeration order to work correctly. ECMAScript 6, by defining the enumeration order, ensures that JavaScript code relying on enumeration will work correctly regardless of where it is executed.
325 |
326 | ## More Powerful Prototypes
327 |
328 | Prototypes are the foundation of inheritance in JavaScript, and ECMAScript 6 continues to make prototypes more powerful. Early versions of JavaScript severely limited what could be done with prototypes. However, as the language matured and developers became more familiar with how prototypes work, it became clear that developers wanted more control over prototypes and easier ways to work with them. As a result, ECMAScript 6 introduced some improvements to prototypes.
329 |
330 | ### Changing an Object's Prototype
331 |
332 | Normally, the prototype of an object is specified when the object is created, via either a constructor or the `Object.create()` method. The idea that an object's prototype remains unchanged after instantiation was one of the biggest assumptions in JavaScript programming through ECMAScript 5. ECMAScript 5 did add the `Object.getPrototypeOf()` method for retrieving the prototype of any given object, but it still lacked a standard way to change an object's prototype after instantiation.
333 |
334 | ECMAScript 6 changes that assumption by adding the `Object.setPrototypeOf()` method, which allows you to change the prototype of any given object. The `Object.setPrototypeOf()` method accepts two arguments: the object whose prototype should change and the object that should become the first argument's prototype. For example:
335 |
336 | ```js
337 | let person = {
338 | getGreeting() {
339 | return "Hello";
340 | }
341 | };
342 |
343 | let dog = {
344 | getGreeting() {
345 | return "Woof";
346 | }
347 | };
348 |
349 | // prototype is person
350 | let friend = Object.create(person);
351 | console.log(friend.getGreeting()); // "Hello"
352 | console.log(Object.getPrototypeOf(friend) === person); // true
353 |
354 | // set prototype to dog
355 | Object.setPrototypeOf(friend, dog);
356 | console.log(friend.getGreeting()); // "Woof"
357 | console.log(Object.getPrototypeOf(friend) === dog); // true
358 | ```
359 |
360 | This code defines two base objects: `person` and `dog`. Both objects have a `getGreeting()` method that returns a string. The object `friend` first inherits from the `person` object, meaning that `getGreeting()` outputs `"Hello"`. When the prototype becomes the `dog` object, `friend.getGreeting()` outputs `"Woof"` because the original relationship to `person` is broken.
361 |
362 | The actual value of an object's prototype is stored in an internal-only property called `[[Prototype]]`. The `Object.getPrototypeOf()` method returns the value stored in `[[Prototype]]` and `Object.setPrototypeOf()` changes the value stored in `[[Prototype]]`. However, these aren't the only ways to work with the value of `[[Prototype]]`.
363 |
364 | ### Easy Prototype Access with Super References
365 |
366 | As previously mentioned, prototypes are very important for JavaScript and a lot of work went into making them easier to use in ECMAScript 6. Another improvement is the introduction of `super` references, which make accessing functionality on an object's prototype easier. For example, to override a method on an object instance such that it also calls the prototype method of the same name, you'd do the following:
367 |
368 | ```js
369 | let person = {
370 | getGreeting() {
371 | return "Hello";
372 | }
373 | };
374 |
375 | let dog = {
376 | getGreeting() {
377 | return "Woof";
378 | }
379 | };
380 |
381 |
382 | let friend = {
383 | getGreeting() {
384 | return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
385 | }
386 | };
387 |
388 | // set prototype to person
389 | Object.setPrototypeOf(friend, person);
390 | console.log(friend.getGreeting()); // "Hello, hi!"
391 | console.log(Object.getPrototypeOf(friend) === person); // true
392 |
393 | // set prototype to dog
394 | Object.setPrototypeOf(friend, dog);
395 | console.log(friend.getGreeting()); // "Woof, hi!"
396 | console.log(Object.getPrototypeOf(friend) === dog); // true
397 | ```
398 |
399 | In this example, `getGreeting()` on `friend` calls the prototype method of the same name. The `Object.getPrototypeOf()` method ensures the correct prototype is called, and then an additional string is appended to the output. The additional `.call(this)` ensures that the `this` value inside the prototype method is set correctly.
400 |
401 | Remembering to use `Object.getPrototypeOf()` and `.call(this)` to call a method on the prototype is a bit involved, so ECMAScript 6 introduced `super`. At its simplest, `super` is a pointer to the current object's prototype, effectively the `Object.getPrototypeOf(this)` value. Knowing that, you can simplify the `getGreeting()` method as follows:
402 |
403 | ```js
404 | let friend = {
405 | getGreeting() {
406 | // in the previous example, this is the same as:
407 | // Object.getPrototypeOf(this).getGreeting.call(this)
408 | return super.getGreeting() + ", hi!";
409 | }
410 | };
411 | ```
412 |
413 | The call to `super.getGreeting()` is the same as `Object.getPrototypeOf(this).getGreeting.call(this)` in this context. Similarly, you can call any method on an object's prototype by using a `super` reference, so long as it's inside a concise method. Attempting to use `super` outside of concise methods results in a syntax error, as in this example:
414 |
415 | ```js
416 | let friend = {
417 | getGreeting: function() {
418 | // syntax error
419 | return super.getGreeting() + ", hi!";
420 | }
421 | };
422 | ```
423 |
424 | This example uses a named property with a function, and the call to `super.getGreeting()` results in a syntax error because `super` is invalid in this context.
425 |
426 | The `super` reference is really powerful when you have multiple levels of inheritance, because in that case, `Object.getPrototypeOf()` no longer works in all circumstances. For example:
427 |
428 | ```js
429 | let person = {
430 | getGreeting() {
431 | return "Hello";
432 | }
433 | };
434 |
435 | // prototype is person
436 | let friend = {
437 | getGreeting() {
438 | return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
439 | }
440 | };
441 | Object.setPrototypeOf(friend, person);
442 |
443 |
444 | // prototype is friend
445 | let relative = Object.create(friend);
446 |
447 | console.log(person.getGreeting()); // "Hello"
448 | console.log(friend.getGreeting()); // "Hello, hi!"
449 | console.log(relative.getGreeting()); // error!
450 | ```
451 |
452 | The call to `Object.getPrototypeOf()` results in an error when `relative.getGreeting()` is called. That's because `this` is `relative`, and the prototype of `relative` is the `friend` object. When `friend.getGreeting().call()` is called with `relative` as `this`, the process starts over again and continues to call recursively until a stack overflow error occurs.
453 |
454 | That problem is difficult to solve in ECMAScript 5, but with ECMAScript 6 and `super`, it's easy:
455 |
456 | ```js
457 | let person = {
458 | getGreeting() {
459 | return "Hello";
460 | }
461 | };
462 |
463 | // prototype is person
464 | let friend = {
465 | getGreeting() {
466 | return super.getGreeting() + ", hi!";
467 | }
468 | };
469 | Object.setPrototypeOf(friend, person);
470 |
471 |
472 | // prototype is friend
473 | let relative = Object.create(friend);
474 |
475 | console.log(person.getGreeting()); // "Hello"
476 | console.log(friend.getGreeting()); // "Hello, hi!"
477 | console.log(relative.getGreeting()); // "Hello, hi!"
478 | ```
479 |
480 | Because `super` references are not dynamic, they always refer to the correct object. In this case, `super.getGreeting()` always refers to `person.getGreeting()`, regardless of how many other objects inherit the method.
481 |
482 | ## A Formal Method Definition
483 |
484 | Prior to ECMAScript 6, the concept of a "method" wasn't formally defined. Methods were just object properties that contained functions instead of data. ECMAScript 6 formally defines a method as a function that has an internal `[[HomeObject]]` property containing the object to which the method belongs. Consider the following:
485 |
486 | ```js
487 | let person = {
488 |
489 | // method
490 | getGreeting() {
491 | return "Hello";
492 | }
493 | };
494 |
495 | // not a method
496 | function shareGreeting() {
497 | return "Hi!";
498 | }
499 | ```
500 |
501 | This example defines `person` with a single method called `getGreeting()`. The `[[HomeObject]]` for `getGreeting()` is `person` by virtue of assigning the function directly to an object. The `shareGreeting()` function, on the other hand, has no `[[HomeObject]]` specified because it wasn't assigned to an object when it was created. In most cases, this difference isn't important, but it becomes very important when using `super` references.
502 |
503 | Any reference to `super` uses the `[[HomeObject]]` to determine what to do. The first step is to call `Object.getPrototypeOf()` on the `[[HomeObject]]` to retrieve a reference to the prototype. Then, the prototype is searched for a function with the same name. Last, the `this` binding is set and the method is called. Here's an example:
504 |
505 | ```js
506 | let person = {
507 | getGreeting() {
508 | return "Hello";
509 | }
510 | };
511 |
512 | // prototype is person
513 | let friend = {
514 | getGreeting() {
515 | return super.getGreeting() + ", hi!";
516 | }
517 | };
518 | Object.setPrototypeOf(friend, person);
519 |
520 | console.log(friend.getGreeting()); // "Hello, hi!"
521 | ```
522 |
523 | Calling `friend.getGreeting()` returns a string, which combines the value from `person.getGreeting()` with `", hi!"`. The `[[HomeObject]]` of `friend.getGreeting()` is `friend`, and the prototype of `friend` is `person`, so `super.getGreeting()` is equivalent to `person.getGreeting.call(this)`.
524 |
525 | ## Summary
526 |
527 | Objects are the center of programming in JavaScript, and ECMAScript 6 made some helpful changes to objects that both make them easier to deal with and more powerful.
528 |
529 | ECMAScript 6 makes several changes to object literals. Shorthand property definitions make assigning properties with the same names as in-scope variables easier. Computed property names allow you to specify non-literal values as property names, which you've already been able to do in other areas of the language. Shorthand methods let you type a lot fewer characters in order to define methods on object literals, by completely omitting the colon and `function` keyword. ECMAScript 6 loosens the strict mode check for duplicate object literal property names as well, meaning you can have two properties with the same name in a single object literal without throwing an error.
530 |
531 | The `Object.assign()` method makes it easier to change multiple properties on a single object at once. This can be very useful if you use the mixin pattern. The `Object.is()` method performs strict equality on any value, effectively becoming a safer version of `===` when dealing with special JavaScript values.
532 |
533 | Enumeration order for own properties is now clearly defined in ECMAScript 6. When enumerating properties, numeric keys always come first in ascending order followed by string keys in insertion order and symbol keys in insertion order.
534 |
535 | It's now possible to modify an object's prototype after it's already created, thanks to ECMAScript 6's `Object.setPrototypeOf()` method.
536 |
537 | Finally, you can use the `super` keyword to call methods on an object's prototype. The `this` binding inside a method invoked using `super` is set up to automatically work with the current value of `this`.
538 |
--------------------------------------------------------------------------------
/manuscript/13-Modules.md:
--------------------------------------------------------------------------------
1 | # Encapsulating Code With Modules
2 |
3 | JavaScript's "shared everything" approach to loading code is one of the most error-prone and confusing aspects of the language. Other languages use concepts such as packages to define code scope, but before ECMAScript 6, everything defined in every JavaScript file of an application shared one global scope. As web applications became more complex and started using even more JavaScript code, that approach caused problems like naming collisions and security concerns. One goal of ECMAScript 6 was to solve the scope problem and bring some order to JavaScript applications. That's where modules come in.
4 |
5 | ## What are Modules?
6 |
7 | *Modules* are JavaScript files that are loaded in a different mode (as opposed to *scripts*, which are loaded in the original way JavaScript worked). This different mode is necessary because modules have very different semantics than scripts:
8 |
9 | 1. Module code automatically runs in strict mode, and there's no way to opt-out of strict mode.
10 | 1. Variables created in the top level of a module aren't automatically added to the shared global scope. They exist only within the top-level scope of the module.
11 | 1. The value of `this` in the top level of a module is `undefined`.
12 | 1. Modules don't allow HTML-style comments within code (a leftover feature from JavaScript's early browser days).
13 | 1. Modules must export anything that should be available to code outside of the module.
14 | 1. Modules may import bindings from other modules.
15 |
16 | These differences may seem small at first glance, but they represent a significant change in how JavaScript code is loaded and evaluated, which I will discuss over the course of this chapter. The real power of modules is the ability to export and import only bindings you need, rather than everything in a file. A good understanding of exporting and importing is fundamental to understanding how modules differ from scripts.
17 |
18 | ## Basic Exporting
19 |
20 | You can use the `export` keyword to expose parts of published code to other modules. In the simplest case, you can place `export` in front of any variable, function, or class declaration to export it from the module, like this:
21 |
22 | ```js
23 | // export data
24 | export var color = "red";
25 | export let name = "Nicholas";
26 | export const magicNumber = 7;
27 |
28 | // export function
29 | export function sum(num1, num2) {
30 | return num1 + num1;
31 | }
32 |
33 | // export class
34 | export class Rectangle {
35 | constructor(length, width) {
36 | this.length = length;
37 | this.width = width;
38 | }
39 | }
40 |
41 | // this function is private to the module
42 | function subtract(num1, num2) {
43 | return num1 - num2;
44 | }
45 |
46 | // define a function...
47 | function multiply(num1, num2) {
48 | return num1 * num2;
49 | }
50 |
51 | // ...and then export it later
52 | export { multiply };
53 | ```
54 |
55 | There are a few things to notice in this example. First, apart from the `export` keyword, every declaration is exactly the same as it would be otherwise. Each exported function or class also has a name; that's because exported function and class declarations require a name. You can't export anonymous functions or classes using this syntax unless you use the `default` keyword (discussed in detail in the "Default Values in Modules" section).
56 |
57 | Next, consider the `multiply()` function, which isn't exported when it's defined. That works because you need not always export a declaration: you can also export references. Finally, notice that this example doesn't export the `subtract()` function. That function won't be accessible from outside this module because any variables, functions, or classes that are not explicitly exported remain private to the module.
58 |
59 | ## Basic Importing
60 |
61 | Once you have a module with exports, you can access the functionality in another module by using the `import` keyword. The two parts of an `import` statement are the identifiers you're importing and the module from which those identifiers should be imported. This is the statement's basic form:
62 |
63 | ```js
64 | import { identifier1, identifier2 } from "./example.js";
65 | ```
66 |
67 | The curly braces after `import` indicate the bindings to import from a given module. The keyword `from` indicates the module from which to import the given binding. The module is specified by a string representing the path to the module (called the *module specifier*). Browsers use the same path format you might pass to the `
375 |
376 |
377 |
384 | ```
385 |
386 | The first `
401 |
402 |
403 |
408 |
409 |
410 |
411 | ```
412 |
413 | These three `
448 |
449 | ```
450 |
451 | In this example, there are two module files loaded asynchronously. It's not possible to tell which module will execute first simply by looking at this code. If `module1.js` finishes downloading first (including all of its `import` resources), then it will execute first. If `module2.js` finishes downloading first, then that module will execute first instead.
452 |
453 | #### Loading Modules as Workers
454 |
455 | Workers, such as web workers and service workers, execute JavaScript code outside of the web page context. Creating a new worker involves creating a new instance `Worker` (or another class) and passing in the location of JavaScript file. The default loading mechanism is to load files as scripts, like this:
456 |
457 | ```js
458 | // load script.js as a script
459 | let worker = new Worker("script.js");
460 | ```
461 |
462 | To support loading modules, the developers of the HTML standard added a second argument to these constructors. The second argument is an object with a `type` property with a default value of `"script"`. You can set `type` to `"module"` in order to load module files:
463 |
464 | ```js
465 | // load module.js as a module
466 | let worker = new Worker("module.js", { type: "module" });
467 | ```
468 |
469 | This example loads `module.js` as a module instead of a script by passing a second argument with `"module"` as the `type` property's value. (The `type` property is meant to mimic how the `type` attribute of `