Repository: DrkSephy/es6-cheatsheet Branch: master Commit: a03267ceaf4e Files: 4 Total size: 76.0 KB Directory structure: gitextract_n9nqldqa/ ├── README.md ├── README_ko.md ├── README_zhCn.md └── src/ └── convert.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # es6-cheatsheet A cheatsheet containing ES2015 [ES6] tips, tricks, best practices and code snippet examples for your day to day workflow. Contributions are welcome! ## Table of Contents - [var versus let / const](#var-versus-let--const) - [Replacing IIFEs with Blocks](#replacing-iifes-with-blocks) - [Arrow Functions](#arrow-functions) - [Strings](#strings) - [Destructuring](#destructuring) - [Modules](#modules) - [Parameters](#parameters) - [Classes](#classes) - [Symbols](#symbols) - [Maps](#maps) - [WeakMaps](#weakmaps) - [Promises](#promises) - [Generators](#generators) - [Async Await](#async-await) - [Getter/Setter functions](#getter-and-setter-functions) - [License](#license) ## var versus let / const > Besides `var`, we now have access to two new identifiers for storing values —`let` and `const`. Unlike `var`, `let` and `const` statements are not hoisted to the top of their enclosing scope. An example of using `var`: ```javascript var snack = 'Meow Mix'; function getFood(food) { if (food) { var snack = 'Friskies'; return snack; } return snack; } getFood(false); // undefined ``` However, observe what happens when we replace `var` using `let`: ```javascript let snack = 'Meow Mix'; function getFood(food) { if (food) { let snack = 'Friskies'; return snack; } return snack; } getFood(false); // 'Meow Mix' ``` This change in behavior highlights that we need to be careful when refactoring legacy code which uses `var`. Blindly replacing instances of `var` with `let` may lead to unexpected behavior. > **Note**: `let` and `const` are block scoped. Therefore, referencing block-scoped identifiers before they are defined will produce a `ReferenceError`. ```javascript console.log(x); // ReferenceError: x is not defined let x = 'hi'; ``` > **Best Practice**: Leave `var` declarations inside of legacy code to denote that it needs to be carefully refactored. When working on a new codebase, use `let` for variables that will change their value over time, and `const` for variables which cannot be reassigned. [(back to table of contents)](#table-of-contents) ## Replacing IIFEs with Blocks > A common use of **Immediately Invoked Function Expressions** is to enclose values within its scope. In ES6, we now have the ability to create block-based scopes and therefore are not limited purely to function-based scope. ```javascript (function () { var food = 'Meow Mix'; }()); console.log(food); // Reference Error ``` Using ES6 Blocks: ```javascript { let food = 'Meow Mix'; }; console.log(food); // Reference Error ``` [(back to table of contents)](#table-of-contents) ## Arrow Functions Often times we have nested functions in which we would like to preserve the context of `this` from its lexical scope. An example is shown below: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; // Cannot read property 'name' of undefined }); }; ``` One common solution to this problem is to store the context of `this` using a variable: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { var that = this; // Store the context of this return arr.map(function (character) { return that.name + character; }); }; ``` We can also pass in the proper context of `this`: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }, this); }; ``` As well as bind the context: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }.bind(this)); }; ``` Using **Arrow Functions**, the lexical value of `this` isn't shadowed and we can re-write the above as shown: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(character => this.name + character); }; ``` > **Best Practice**: Use **Arrow Functions** whenever you need to preserve the lexical value of `this`. Arrow Functions are also more concise when used in function expressions which simply return a value: ```javascript var squares = arr.map(function (x) { return x * x }); // Function Expression ``` ```javascript const arr = [1, 2, 3, 4, 5]; const squares = arr.map(x => x * x); // Arrow Function for terser implementation ``` > **Best Practice**: Use **Arrow Functions** in place of function expressions when possible. [(back to table of contents)](#table-of-contents) ## Strings With ES6, the standard library has grown immensely. Along with these changes are new methods which can be used on strings, such as `.includes()` and `.repeat()`. ### .includes( ) ```javascript var string = 'food'; var substring = 'foo'; console.log(string.indexOf(substring) > -1); ``` Instead of checking for a return value `> -1` to denote string containment, we can simply use `.includes()` which will return a boolean: ```javascript const string = 'food'; const substring = 'foo'; console.log(string.includes(substring)); // true ``` ### .repeat( ) ```javascript function repeat(string, count) { var strings = []; while(strings.length < count) { strings.push(string); } return strings.join(''); } ``` In ES6, we now have access to a terser implementation: ```javascript // String.repeat(numberOfRepetitions) 'meow'.repeat(3); // 'meowmeowmeow' ``` ### Template Literals Using **Template Literals**, we can now construct strings that have special characters in them without needing to escape them explicitly. ```javascript var text = "This string contains \"double quotes\" which are escaped."; ``` ```javascript let text = `This string contains "double quotes" which don't need to be escaped anymore.`; ``` **Template Literals** also support interpolation, which makes the task of concatenating strings and values: ```javascript var name = 'Tiger'; var age = 13; console.log('My cat is named ' + name + ' and is ' + age + ' years old.'); ``` Much simpler: ```javascript const name = 'Tiger'; const age = 13; console.log(`My cat is named ${name} and is ${age} years old.`); ``` In ES5, we handled new lines as follows: ```javascript var text = ( 'cat\n' + 'dog\n' + 'nickelodeon' ); ``` Or: ```javascript var text = [ 'cat', 'dog', 'nickelodeon' ].join('\n'); ``` **Template Literals** will preserve new lines for us without having to explicitly place them in: ```javascript let text = ( `cat dog nickelodeon` ); ``` **Template Literals** can accept expressions, as well: ```javascript let today = new Date(); let text = `The time and date is ${today.toLocaleString()}`; ``` [(back to table of contents)](#table-of-contents) ## Destructuring Destructuring allows us to extract values from arrays and objects (even deeply nested) and store them in variables with a more convenient syntax. ### Destructuring Arrays ```javascript var arr = [1, 2, 3, 4]; var a = arr[0]; var b = arr[1]; var c = arr[2]; var d = arr[3]; ``` ```javascript let [a, b, c, d] = [1, 2, 3, 4]; console.log(a); // 1 console.log(b); // 2 ``` ### Destructuring Objects ```javascript var luke = { occupation: 'jedi', father: 'anakin' }; var occupation = luke.occupation; // 'jedi' var father = luke.father; // 'anakin' ``` ```javascript let luke = { occupation: 'jedi', father: 'anakin' }; let {occupation, father} = luke; console.log(occupation); // 'jedi' console.log(father); // 'anakin' ``` [(back to table of contents)](#table-of-contents) ## Modules Prior to ES6, we used libraries such as [Browserify](http://browserify.org/) to create modules on the client-side, and [require](https://nodejs.org/api/modules.html#modules_module_require_id) in **Node.js**. With ES6, we can now directly use modules of all types (AMD and CommonJS). ### Exporting in CommonJS ```javascript module.exports = 1; module.exports = { foo: 'bar' }; module.exports = ['foo', 'bar']; module.exports = function bar () {}; ``` ### Exporting in ES6 With ES6, we have various flavors of exporting. We can perform **Named Exports**: ```javascript export let name = 'David'; export let age = 25;​​ ``` As well as **exporting a list** of objects: ```javascript function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } export { sumTwo, sumThree }; ``` We can also export functions, objects and values (etc.) simply by using the `export` keyword: ```javascript export function sumTwo(a, b) { return a + b; } export function sumThree(a, b, c) { return a + b + c; } ``` And lastly, we can **export default bindings**: ```javascript function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } let api = { sumTwo, sumThree }; export default api; /* Which is the same as * export { api as default }; */ ``` > **Best Practices**: Always use the `export default` method at **the end** of the module. It makes it clear what is being exported, and saves time by having to figure out what name a value was exported as. More so, the common practice in CommonJS modules is to export a single value or object. By sticking to this paradigm, we make our code easily readable and allow ourselves to interpolate between CommonJS and ES6 modules. ### Importing in ES6 ES6 provides us with various flavors of importing. We can import an entire file: ```javascript import 'underscore'; ``` > It is important to note that simply **importing an entire file will execute all code at the top level of that file**. Similar to Python, we have named imports: ```javascript import { sumTwo, sumThree } from 'math/addition'; ``` We can also rename the named imports: ```javascript import { sumTwo as addTwoNumbers, sumThree as sumThreeNumbers } from 'math/addition'; ``` In addition, we can **import all the things** (also called namespace import): ```javascript import * as util from 'math/addition'; ``` Lastly, we can import a list of values from a module: ```javascript import * as additionUtil from 'math/addition'; const { sumTwo, sumThree } = additionUtil; ``` Importing from the default binding like this: ```javascript import api from 'math/addition'; // Same as: import { default as api } from 'math/addition'; ``` While it is better to keep the exports simple, but we can sometimes mix default import and mixed import if needed. When we are exporting like this: ```javascript // foos.js export { foo as default, foo1, foo2 }; ``` We can import them like the following: ```javascript import foo, { foo1, foo2 } from 'foos'; ``` When importing a module exported using commonjs syntax (such as React) we can do: ```javascript import React from 'react'; const { Component, PropTypes } = React; ``` This can also be simplified further, using: ```javascript import React, { Component, PropTypes } from 'react'; ``` > **Note**: Values that are exported are **bindings**, not references. Therefore, changing the binding of a variable in one module will affect the value within the exported module. Avoid changing the public interface of these exported values. [(back to table of contents)](#table-of-contents) ## Parameters In ES5, we had varying ways to handle functions which needed **default values**, **indefinite arguments**, and **named parameters**. With ES6, we can accomplish all of this and more using more concise syntax. ### Default Parameters ```javascript function addTwoNumbers(x, y) { x = x || 0; y = y || 0; return x + y; } ``` In ES6, we can simply supply default values for parameters in a function: ```javascript function addTwoNumbers(x=0, y=0) { return x + y; } ``` ```javascript addTwoNumbers(2, 4); // 6 addTwoNumbers(2); // 2 addTwoNumbers(); // 0 ``` ### Rest Parameters In ES5, we handled an indefinite number of arguments like so: ```javascript function logArguments() { for (var i=0; i < arguments.length; i++) { console.log(arguments[i]); } } ``` Using the **rest** operator, we can pass in an indefinite amount of arguments: ```javascript function logArguments(...args) { for (let arg of args) { console.log(arg); } } ``` ### Named Parameters One of the patterns in ES5 to handle named parameters was to use the **options object** pattern, adopted from jQuery. ```javascript function initializeCanvas(options) { var height = options.height || 600; var width = options.width || 400; var lineStroke = options.lineStroke || 'black'; } ``` We can achieve the same functionality using destructuring as a formal parameter to a function: ```javascript function initializeCanvas( { height=600, width=400, lineStroke='black'}) { // Use variables height, width, lineStroke here } ``` If we want to make the entire value optional, we can do so by destructuring an empty object: ```javascript function initializeCanvas( { height=600, width=400, lineStroke='black'} = {}) { // ... } ``` ### Spread Operator In ES5, we could find the max of values in an array by using the `apply` method on `Math.max` like this: ```javascript Math.max.apply(null, [-1, 100, 9001, -32]); // 9001 ``` In ES6, we can now use the spread operator to pass an array of values to be used as parameters to a function: ```javascript Math.max(...[-1, 100, 9001, -32]); // 9001 ``` We can concat array literals easily with this intuitive syntax: ```javascript let cities = ['San Francisco', 'Los Angeles']; let places = ['Miami', ...cities, 'Chicago']; // ['Miami', 'San Francisco', 'Los Angeles', 'Chicago'] ``` [(back to table of contents)](#table-of-contents) ## Classes Prior to ES6, we implemented Classes by creating a constructor function and adding properties by extending the prototype: ```javascript function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } Person.prototype.incrementAge = function () { return this.age += 1; }; ``` And created extended classes by the following: ```javascript function Personal(name, age, gender, occupation, hobby) { Person.call(this, name, age, gender); this.occupation = occupation; this.hobby = hobby; } Personal.prototype = Object.create(Person.prototype); Personal.prototype.constructor = Personal; Personal.prototype.incrementAge = function () { Person.prototype.incrementAge.call(this); this.age += 20; console.log(this.age); }; ``` ES6 provides much needed syntactic sugar for doing this under the hood. We can create Classes directly: ```javascript class Person { constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } incrementAge() { this.age += 1; } } ``` And extend them using the `extends` keyword: ```javascript class Personal extends Person { constructor(name, age, gender, occupation, hobby) { super(name, age, gender); this.occupation = occupation; this.hobby = hobby; } incrementAge() { super.incrementAge(); this.age += 20; console.log(this.age); } } ``` > **Best Practice**: While the syntax for creating classes in ES6 obscures how implementation and prototypes work under the hood, it is a good feature for beginners and allows us to write cleaner code. [(back to table of contents)](#table-of-contents) ## Symbols Symbols have existed prior to ES6, but now we have a public interface to using them directly. Symbols are immutable and unique and can be used as keys in any hash. ### Symbol( ) Calling `Symbol()` or `Symbol(description)` will create a unique symbol that cannot be looked up globally. A Use case for `Symbol()` is to patch objects or namespaces from third parties with your own logic, but be confident that you won't collide with updates to that library. For example, if you wanted to add a method `refreshComponent` to the `React.Component` class, and be certain that you didn't trample a method they add in a later update: ```javascript const refreshComponent = Symbol(); React.Component.prototype[refreshComponent] = () => { // do something } ``` ### Symbol.for(key) `Symbol.for(key)` will create a Symbol that is still immutable and unique, but can be looked up globally. Two identical calls to `Symbol.for(key)` will return the same Symbol instance. NOTE: This is not true for `Symbol(description)`: ```javascript Symbol('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol.for('foo') // true ``` A common use case for Symbols, and in particular with `Symbol.for(key)` is for interoperability. This can be achieved by having your code look for a Symbol member on object arguments from third parties that contain some known interface. For example: ```javascript function reader(obj) { const specialRead = Symbol.for('specialRead'); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader } else { throw new TypeError('object cannot be read'); } } ``` And then in another library: ```javascript const specialRead = Symbol.for('specialRead'); class SomeReadableType { [specialRead]() { const reader = createSomeReaderFrom(this); return reader; } } ``` > A notable example of Symbol use for interoperability is `Symbol.iterator` which exists on all iterable types in ES6: Arrays, strings, generators, etc. When called as a method it returns an object with an Iterator interface. [(back to table of contents)](#table-of-contents) ## Maps **Maps** is a much needed data structure in JavaScript. Prior to ES6, we created **hash** maps through objects: ```javascript var map = new Object(); map[key1] = 'value1'; map[key2] = 'value2'; ``` However, this does not protect us from accidentally overriding functions with specific property names: ```javascript > getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned'); > TypeError: Property 'hasOwnProperty' is not a function ``` Actual **Maps** allow us to `set`, `get` and `search` for values (and much more). ```javascript let map = new Map(); > map.set('name', 'david'); > map.get('name'); // david > map.has('name'); // true ``` The most amazing part of Maps is that we are no longer limited to just using strings. We can now use any type as a key, and it will not be type-cast to a string. ```javascript let map = new Map([ ['name', 'david'], [true, 'false'], [1, 'one'], [{}, 'object'], [function () {}, 'function'] ]); for (let key of map.keys()) { console.log(typeof key); // > string, boolean, number, object, function } ``` > **Note**: Using non-primitive values such as functions or objects won't work when testing equality using methods such as `map.get()`. As such, stick to primitive values such as Strings, Booleans and Numbers. We can also iterate over maps using `.entries()`: ```javascript for (let [key, value] of map.entries()) { console.log(key, value); } ``` [(back to table of contents)](#table-of-contents) ## WeakMaps In order to store private data versions < ES6, we had various ways of doing this. One such method was using naming conventions: ```javascript class Person { constructor(age) { this._age = age; } _incrementAge() { this._age += 1; } } ``` But naming conventions can cause confusion in a codebase and are not always going to be upheld. Instead, we can use WeakMaps to store our values: ```javascript let _age = new WeakMap(); class Person { constructor(age) { _age.set(this, age); } incrementAge() { let age = _age.get(this) + 1; _age.set(this, age); if (age > 50) { console.log('Midlife crisis'); } } } ``` The cool thing about using WeakMaps to store our private data is that their keys do not give away the property names, which can be seen by using `Reflect.ownKeys()`: ```javascript > const person = new Person(50); > person.incrementAge(); // 'Midlife crisis' > Reflect.ownKeys(person); // [] ``` A more practical example of using WeakMaps is to store data which is associated to a DOM element without having to pollute the DOM itself: ```javascript let map = new WeakMap(); let el = document.getElementById('someElement'); // Store a weak reference to the element with a key map.set(el, 'reference'); // Access the value of the element let value = map.get(el); // 'reference' // Remove the reference el.parentNode.removeChild(el); el = null; // map is empty, since the element is destroyed ``` As shown above, once the object is destroyed by the garbage collector, the WeakMap will automatically remove the key-value pair which was identified by that object. > **Note**: To further illustrate the usefulness of this example, consider how jQuery stores a cache of objects corresponding to DOM elements which have references. Using WeakMaps, jQuery can automatically free up any memory that was associated with a particular DOM element once it has been removed from the document. In general, WeakMaps are very useful for any library that wraps DOM elements. [(back to table of contents)](#table-of-contents) ## Promises Promises allow us to turn our horizontal code (callback hell): ```javascript func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }); }); }); }); }); ``` Into vertical code: ```javascript func1(value1) .then(func2) .then(func3) .then(func4) .then(func5, value5 => { // Do something with value 5 }); ``` Prior to ES6, we used [bluebird](https://github.com/petkaantonov/bluebird) or [Q](https://github.com/kriskowal/q). Now we have Promises natively: ```javascript new Promise((resolve, reject) => reject(new Error('Failed to fulfill Promise'))) .catch(reason => console.log(reason)); ``` Where we have two handlers, **resolve** (a function called when the Promise is **fulfilled**) and **reject** (a function called when the Promise is **rejected**). > **Benefits of Promises**: Error Handling using a bunch of nested callbacks can get chaotic. Using Promises, we have a clear path to bubbling errors up and handling them appropriately. Moreover, the value of a Promise after it has been resolved/rejected is immutable - it will never change. Here is a practical example of using Promises: ```javascript var request = require('request'); return new Promise((resolve, reject) => { request.get(url, (error, response, body) => { if (body) { resolve(JSON.parse(body)); } else { resolve({}); } }); }); ``` We can also **parallelize** Promises to handle an array of asynchronous operations by using `Promise.all()`: ```javascript let urls = [ '/api/commits', '/api/issues/opened', '/api/issues/assigned', '/api/issues/completed', '/api/issues/comments', '/api/pullrequests' ]; let promises = urls.map((url) => { return new Promise((resolve, reject) => { $.ajax({ url: url }) .done((data) => { resolve(data); }); }); }); Promise.all(promises) .then((results) => { // Do something with results of all our promises }); ``` [(back to table of contents)](#table-of-contents) ## Generators Similar to how [Promises](https://github.com/DrkSephy/es6-cheatsheet#promises) allow us to avoid [callback hell](http://callbackhell.com/), Generators allow us to flatten our code - giving our asynchronous code a synchronous feel. Generators are essentially functions which we can [pause their execution](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield) and subsequently return the value of an expression. A simple example of using generators is shown below: ```javascript function* sillyGenerator() { yield 1; yield 2; yield 3; yield 4; } var generator = sillyGenerator(); > console.log(generator.next()); // { value: 1, done: false } > console.log(generator.next()); // { value: 2, done: false } > console.log(generator.next()); // { value: 3, done: false } > console.log(generator.next()); // { value: 4, done: false } ``` Where [next](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next) will allow us to push our generator forward and evaluate a new expression. While the above example is extremely contrived, we can utilize Generators to write asynchronous code in a synchronous manner: ```javascript // Hiding asynchronousity with Generators function request(url) { getJSON(url, function(response) { generator.next(response); }); } ``` And here we write a generator function that will return our data: ```javascript function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); } ``` By the power of `yield`, we are guaranteed that `entry1` will have the data needed to be parsed and stored in `data1`. While generators allow us to write asynchronous code in a synchronous manner, there is no clear and easy path for error propagation. As such, as we can augment our generator with Promises: ```javascript function request(url) { return new Promise((resolve, reject) => { getJSON(url, resolve); }); } ``` And we write a function which will step through our generator using `next` which in turn will utilize our `request` method above to yield a Promise: ```javascript function iterateGenerator(gen) { var generator = gen(); (function iterate(val) { var ret = generator.next(); if(!ret.done) { ret.value.then(iterate); } })(); } ``` By augmenting our Generator with Promises, we have a clear way of propagating errors through the use of our Promise `.catch` and `reject`. To use our newly augmented Generator, it is as simple as before: ```javascript iterateGenerator(function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); }); ``` We were able to reuse our implementation to use our Generator as before, which shows their power. While Generators and Promises allow us to write asynchronous code in a synchronous manner while retaining the ability to propagate errors in a nice way, we can actually begin to utilize a simpler construction that provides the same benefits: [async-await](https://github.com/DrkSephy/es6-cheatsheet#async-await). [(back to table of contents)](#table-of-contents) ## Async Await While this is actually an upcoming ES2016 feature, `async await` allows us to perform the same thing we accomplished using Generators and Promises with less effort: ```javascript var request = require('request'); function getJSON(url) { return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body); }); }); } async function main() { var data = await getJSON(); console.log(data); // NOT undefined! } main(); ``` Under the hood, it performs similarly to Generators. I highly recommend using them over Generators + Promises. A great resource for getting up and running with ES7 and Babel can be found [here](http://masnun.com/2015/11/11/using-es7-asyncawait-today-with-babel.html). [(back to table of contents)](#table-of-contents) ## Getter and setter functions ES6 has started supporting getter and setter functions within classes. Using the following example: ```javascript class Employee { constructor(name) { this._name = name; } get name() { if(this._name) { return 'Mr. ' + this._name.toUpperCase(); } else { return undefined; } } set name(newName) { if (newName == this._name) { console.log('I already have this name.'); } else if (newName) { this._name = newName; } else { return false; } } } var emp = new Employee("James Bond"); // uses the get method in the background if (emp.name) { console.log(emp.name); // Mr. JAMES BOND } // uses the setter in the background emp.name = "Bond 007"; console.log(emp.name); // Mr. BOND 007 ``` Latest browsers are also supporting getter/setter functions in Objects and we can use them for computed properties, adding listeners and preprocessing before setting/getting: ```javascript var person = { firstName: 'James', lastName: 'Bond', get fullName() { console.log('Getting FullName'); return this.firstName + ' ' + this.lastName; }, set fullName (name) { console.log('Setting FullName'); var words = name.toString().split(' '); this.firstName = words[0] || ''; this.lastName = words[1] || ''; } } person.fullName; // James Bond person.fullName = 'Bond 007'; person.fullName; // Bond 007 ``` [(back to table of contents)](#table-of-contents) ## License The MIT License (MIT) Copyright (c) 2015 David Leonard Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [(back to table of contents)](#table-of-contents) ================================================ FILE: README_ko.md ================================================ # es6-cheatsheet ES2015(ES6)의 Tip & Tricks, 좋은 활용사례들과 코드 예제들이 포함된 cheatsheet입니다. 이 문서는 한국어 번역 버전입니다. 오역 제보와 더 좋은 번역을 기다리고 있습니다! ## 목차 - [var VS let / const](#var-vs-let--const) - [IIFE를 블록으로 교체하기](#iife를-블록으로-교체하기) - [애로우 펑션](#애로우-펑션) - [문자열](#문자열) - [Destructuring](#destructuring) - [모듈](#모듈) - [파라미터(Parameter)](#파라미터parameter) - [클래스](#클래스) - [심볼(Symbol)](#심볼symbol) - [맵(Map)](#맵map) - [위크맵(WeakMap)](#위크맵weakmap) - [Promise](#promise) - [제너레이터(Generator)](#제너레이터generator) - [Async Await](#async-await) - [Getter/Setter 함수](#getter와-setter-함수) - [License](#license) ## var VS let / const > 기존의 `var`에 더해 `let`과 `const`라는 값을 저장하기 위한 두 개의 새로운 식별자가 추가되었습니다. `var`와는 다르게, `let`과 `const` 상태는 스코프 내 최상단으로 호이스팅되지 않습니다. 다음은 `var`를 활용한 예제입니다. ```javascript var snack = '허니버터칩'; function getFood(food) { if (food) { var snack = '스윙칩'; return snack; } return snack; } getFood(false); // undefined ``` 그러나, `var` 대신 `let`을 사용하면 다음과 같이 동작합니다. ```javascript let snack = '허니버터칩'; function getFood(food) { if (food) { let snack = '스윙칩'; return snack; } return snack; } getFood(false); // '허니버터칩' ``` 이런 변경점으로 인해 `var`를 사용했던 레거시 코드를 리팩토링할 때 더욱 조심해야 합니다. 무턱대고 `var` 대신 `let`을 사용하면 예상치 못한 동작을 할 수도 있습니다. > **Note**: `let`과 `const`는 블록 스코프 식별자입니다. 따라서, 블록 스코프 식별자로 정의하기 전에 참조하게 되면 `ReferenceError`를 발생시킵니다. ```javascript console.log(x); let x = 'hi'; // ReferenceError: x is not defined ``` > **Best Practice**: 더 조심스럽게 리팩토링하기 위해서 레거시 코드 내에 `var`선언을 남겨두세요. 새로운 코드베이스에서 작업하게 될 때, 변수 사용을 위해서 `let`을 사용하고, 상수 사용을 위해서 `const`를 사용하세요. [(목차로 돌아가기)](#목차) ## IIFE를 블록으로 교체하기 > **Immediately Invoked Function Expressions(IIFE)**는 일반적으로 변수들을 별도의 스코프 안에서만 쓰기 위해서 사용되었습니다. ES6에서는 블록을 기반으로 스코프를 만들 수 있게 되었으므로, 더 이상 함수 기반으로 스코프를 만들지 않아도 됩니다. ```javascript (function () { var food = '허니버터칩'; }()); console.log(food); // Reference Error ``` ES6 블록을 사용하는 경우, ```javascript { let food = '허니버터칩'; }; console.log(food); // Reference Error ``` [(목차로 돌아가기)](#목차) ## 애로우 펑션 종종 다음과 같이 중첩된 함수 안에서 `this`의 문맥(context)를 보존해야 할 일이 있습니다. ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; // Cannot read property 'name' of undefined }); }; ``` 보통 이 문제를 해결하기 위해 다음과 같이 별도의 변수를 사용해서 `this`의 문맥을 저장합니다. ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { var that = this; // this의 문맥을 저장합니다. return arr.map(function (character) { return that.name + character; }); }; ``` 또는, 다음과 같은 방법으로 `this` 문맥을 통과시킬 수도 있습니다. ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }, this); }; ``` 이 뿐만 아니라 문맥을 bind 할 수도 있습니다. ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }.bind(this)); }; ``` **애로우 펑션**을 사용하면, `this`의 문맥 값이 사라지지 않기 때문에 위의 코드는 다음과 같이 다시 쓸 수 있습니다. ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(character => this.name + character); }; ``` > **Best Practice**: `this`의 문맥 값을 보존해야할 때마다 **애로우 펑션**을 사용하세요. 또한 애로우 펑션은 간단한 값을 리턴하는 함수(함수 표현식)가 필요할 때 사용하면 더욱 간결합니다. ```javascript var squares = arr.map(function (x) { return x * x }); // 함수 표현식 ``` ```javascript const arr = [1, 2, 3, 4, 5]; const squares = arr.map(x => x * x); // 간결한 구현을 위한 애로우 펑션 ``` > **Best Practice**: 가능하다면 함수 표현식 대신 **애로우 펑션**을 활용하세요. [(목차로 돌아가기)](#목차) ## 문자열 ES6에서는 표준 라이브러리가 크게 확장되었습니다. 이러한 변경에 맞춰 문자열에도 `.includes()`와 `.repeat()` 같은 새로운 메소드가 추가되었습니다. ### .includes( ) ```javascript var string = 'food'; var substring = 'foo'; console.log(string.indexOf(substring) > -1); ``` 문자열 포함 여부를 구현하기 위해서 리턴 값이 `-1`보다 큰 지 체크하는 것 대신, 간단하게 불린 값을 리턴하는 `.includes()` 메소드를 사용할 수 있습니다. ```javascript const string = 'food'; const substring = 'foo'; console.log(string.includes(substring)); // true ``` ### .repeat( ) ```javascript function repeat(string, count) { var strings = []; while(strings.length < count) { strings.push(string); } return strings.join(''); } ``` ES6에서는 이제 간결하게 구현할 수 있습니다. ```javascript // String.repeat(numberOfRepetitions) '야옹'.repeat(3); // '야옹야옹야옹' ``` ### 템플릿 리터럴 **템플릿 리터럴**을 사용하면 명시적인 문자열 이스케이프를 사용하지 않아도 특수문자를 포함한 문자열을 구축할 수 있습니다. ```javascript var text = "이 문자열은 이스케이프 된 \"큰 따옴표\"를 포함합니다."; ``` ```javascript let text = `이 문자열은 이스케이프 된 "큰 따옴표"를 포함합니다.`; ``` **템플릿 리터럴**은 문자열과 값을 연결시키는 문자열 Interpolation도 지원합니다. ```javascript var name = '나비'; var age = 13; console.log('제 고양이의 이름은 ' + name + '이고, 나이는 ' + age + '살 입니다.'); ``` 더 간단하게 구현하면, ```javascript const name = '나비'; const age = 13; console.log(`제 고양이의 이름은 ${name}이고, 나이는 ${age}살 입니다.`); ``` ES5에서는 개행을 구현하기 위해서 다음과 같이 했습니다. ```javascript var text = ( '고양이\n' + '강아지\n' + '투니버스' ); ``` 혹은 이렇게, ```javascript var text = [ '고양이', '강아지', '투니버스' ].join('\n'); ``` **템플릿 리터럴**은 명시적으로 표시하지 않아도 개행을 보존합니다. ```javascript let text = ( `고양이 강아지 투니버스` ); ``` 뿐만 아니라, **템플릿 리터럴**은 표현식에도 접근할 수 있습니다. ```javascript let today = new Date(); let text = `현재 시각은 ${today.toLocaleString()}입니다.`; ``` [(목차로 돌아가기)](#목차) ## Destructuring Destructuring은 배열 혹은 객체(깊게 중첩된 것도 포함하여)에서 편리한 문법을 이용해 값을 추출하고 저장하는데에 활용됩니다. ### 배열 Destructuring ```javascript var arr = [1, 2, 3, 4]; var a = arr[0]; var b = arr[1]; var c = arr[2]; var d = arr[3]; ``` ```javascript let [a, b, c, d] = [1, 2, 3, 4]; console.log(a); // 1 console.log(b); // 2 ``` ### 객체 Destructuring ```javascript var luke = { occupation: 'jedi', father: 'anakin' }; var occupation = luke.occupation; // 'jedi' var father = luke.father; // 'anakin' ``` ```javascript let luke = { occupation: 'jedi', father: 'anakin' }; let {occupation, father} = luke; console.log(occupation); // 'jedi' console.log(father); // 'anakin' ``` [(목차로 돌아가기)](#목차) ## 모듈 ES6 이전엔, 클라이언트 단은 [Browserify](http://browserify.org/), **Node.js**에서는 [require](https://nodejs.org/api/modules.html#modules_module_require_id)같은 라이브러리를 사용했습니다. 이제 ES6에서는 모든 종류(AMD와 CommonJS)의 모듈을 직접적으로 사용할 수 있습니다. ### CommonJS의 모듈 내보내기(Export) ```javascript module.exports = 1; module.exports = { foo: 'bar' }; module.exports = ['foo', 'bar']; module.exports = function bar () {}; ``` ### ES6의 모듈 내보내기 ES6에서는, 다양한 방식으로 모듈을 내보낼 수 있습니다. 그 중 **지정 내보내기(named export)**방식은 다음과 같습니다. ```javascript export let name = 'David'; export let age = 25;​​ ``` 또한 객체를 이용한 **리스트 내보내기(exporting a list)**방식도 있습니다. ```javascript function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } export { sumTwo, sumThree }; ``` 간단하게 `export` 키워드만 활용하면 함수, 객체, 값 등을 내보낼 수 있습니다. ```javascript export function sumTwo(a, b) { return a + b; } export function sumThree(a, b, c) { return a + b + c; } ``` 마지막으로, **디폴트 모듈로 내보내기(default binding export)**도 가능합니다. ```javascript function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } let api = { sumTwo, sumThree }; export default api; /* 위 코드는 아래와 같습니다. * export { api as default }; */ ``` > **Best Practice**: `export default`메소드는 항상 모듈 코드의 **마지막**에 위치해야 합니다. 그래야 내보내는 것이 무엇인지 분명해지며, 내보내는 값의 이름을 확인하는 시간을 절약할 수 있습니다. 그 이상으로, CommonJS의 일반적인 관행은 단일 값이나 객체를 내보내는 것입니다. 이런 컨벤션을 따름으로서, 코드의 가독성을 좋게 만들 수 있고 CommonJS와 ES6 모듈을 모두 사용할 수 있게 됩니다. ### ES6의 모듈 불러오기(import) ES6에서는 다양한 방식으로 모듈을 불러올 수 있습니다. 다음과 같이 파일 전체를 불러올 수 있습니다. ```javascript import 'underscore'; ``` > 이렇게 단순히 파일 전체를 불러오면 그 파일의 최상단에서 불러온 모든 코드가 실행된다는 점에 유의하시기 바랍니다. 파이썬하고 유사한 지정 불러오기(named import)를 사용할 수 있습니다. ```javascript import { sumTwo, sumThree } from 'math/addition'; ``` 다음과 같이 불러온 모듈의 이름을 새로 작성할 수도 있습니다. ```javascript import { sumTwo as addTwoNumbers, sumThree as sumThreeNumbers } from 'math/addition'; ``` 거기에 더해서, **모두 불러오기**(네임스페이스 불러오기)도 가능합니다. ```javascript import * as util from 'math/addition'; ``` 마지막으로, 모듈에서 값들의 리스트를 불러올 수도 있습니다. ```javascript import * as additionUtil from 'math/addition'; const { sumTwo, sumThree } = additionUtil; ``` 디폴트 모듈은 다음과 같이 불러올 수 있습니다. ```javascript import api from 'math/addition'; // 위 코드는 이렇게 표현할 수도 있습니다: import { default as api } from 'math/addition'; ``` 가급적 간단한 형태로 모듈을 내보내는 것이 좋지만, 필요하다면 때때로 디폴트 모듈을 포함해 여러 이름을 섞어서 내보낼 수도 있습니다. ```javascript // foos.js export { foo as default, foo1, foo2 }; ``` 이 모듈은 아래와 같이 불러올 수 있습니다. ```javascript import foo, { foo1, foo2 } from 'foos'; ``` React처럼 CommonJS 문법을 사용해 내보낸 모듈을 불러올 때는 다음과 같이 쓰면 됩니다. ```javascript import React from 'react'; const { Component, PropTypes } = React; ``` 다음과 같이 더욱 간결해질 수도 있습니다. ```javascript import React, { Component, PropTypes } from 'react'; ``` > **Note**: 내보내지는 값은 참조되는 것이 아니라 **바인딩**되는 것입니다. 그러므로, 어떤 모듈의 변수 바인딩을 바꾸게 되면 내보낸 모듈 내에서만 바뀌게 됩니다. 이렇게 내보낸 모듈의 값의 인터페이스를 바꾸는 것은 피하세요. [(목차로 돌아가기)](#목차) ## 파라미터(Parameter) ES5에서는 **디폴트 값(default values)**이나 **정의되지 않은 인자(indefinite arguments)** 혹은 **네임드 파라미터(named parameters)**를 다루는 함수를 구현하는 방법이 너무 많았습니다. ES6에서는 더욱 간결한 문법을 통해 이것들을 모두 다룰 수 있습니다. ### 디폴트 파라미터(Default Parameter) ```javascript function addTwoNumbers(x, y) { x = x || 0; y = y || 0; return x + y; } ``` ES6에서는 함수 내 파라미터의 디폴트 값을 간단하게 설정할 수 있습니다. ```javascript function addTwoNumbers(x=0, y=0) { return x + y; } ``` ```javascript addTwoNumbers(2, 4); // 6 addTwoNumbers(2); // 2 addTwoNumbers(); // 0 ``` ### 레스트 파라미터(Rest Parameter) ES5에서는 인수의 숫자가 가변적인 경우 다음과 같이 처리했습니다. ```javascript function logArguments() { for (var i=0; i < arguments.length; i++) { console.log(arguments[i]); } } ``` **레스트(rest)**연산자를 사용하면, 다음과 같이 가변적인 숫자의 인수를 넘길 수 있습니다. ```javascript function logArguments(...args) { for (let arg of args) { console.log(arg); } } ``` ### 네임드 파라미터(Named Parameter) ES5의 네임드 파라미터를 처리하는 방법 중 하나는 jQuery에서 차용된 **options object** 패턴을 사용하는 것입니다. ```javascript function initializeCanvas(options) { var height = options.height || 600; var width = options.width || 400; var lineStroke = options.lineStroke || 'black'; } ``` 파라미터에 destructuring을 사용하면 같은 기능을 구현할 수 있습니다. ```javascript function initializeCanvas( { height=600, width=400, lineStroke='black'}) { // 여기에서 height, width, lineStroke 변수를 사용합니다. } ``` 만약 모든 파라미터를 선택적으로 넘기고 싶다면, 다음과 같이 빈 객체로 destructuring 하면 됩니다. ```javascript function initializeCanvas( { height=600, width=400, lineStroke='black'} = {}) { // ... } ``` ### 전개 연산자(Spread Operator) ES5에서는 배열 내 숫자들의 최대 값을 찾기 위해서 `Math.max`에 `apply` 메소드를 사용했습니다. ```javascript Math.max.apply(null, [-1, 100, 9001, -32]); // 9001 ``` ES6에서는 이제 전개 연산자를 이용해서 함수에 파라미터로 배열을 넘길 수 있습니다. ```javascript Math.max(...[-1, 100, 9001, -32]); // 9001 ``` 다음과 같이 직관적인 문법을 통해 쉽게 배열 리터럴을 합칠 수도 있습니다. ```javascript let cities = ['서울', '부산']; let places = ['여수', ...cities, '제주']; // ['여수', '서울', '부산', '제주'] ``` [(목차로 돌아가기)](#목차) ## 클래스 ES6 이전에는, 생성자 함수(constructor)를 만들고 프로토타입을 확장해서 프로퍼티를 추가하여 클래스를 구현했습니다. ```javascript function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } Person.prototype.incrementAge = function () { return this.age += 1; }; ``` 그리고 다음과 같이 클래스를 상속했습니다. ```javascript function Personal(name, age, gender, occupation, hobby) { Person.call(this, name, age, gender); this.occupation = occupation; this.hobby = hobby; } Personal.prototype = Object.create(Person.prototype); Personal.prototype.constructor = Personal; Personal.prototype.incrementAge = function () { Person.prototype.incrementAge.call(this); this.age += 20; console.log(this.age); }; ``` ES6는 이런 간단한 구현을 위해 편리한 문법을 제공합니다. 이제 클래스를 직접 만들 수 있습니다. ```javascript class Person { constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } incrementAge() { this.age += 1; } } ``` 그리고 `extends` 키워드를 사용해서 상속할 수 있습니다. ```javascript class Personal extends Person { constructor(name, age, gender, occupation, hobby) { super(name, age, gender); this.occupation = occupation; this.hobby = hobby; } incrementAge() { super.incrementAge(); this.age += 20; console.log(this.age); } } ``` > **Best Practice**: 클래스를 만들기 위한 ES6의 문법은 내부적으로 어떻게 프로토타입으로 구현되는지 모호하지만, 초보자들에게 좋은 기능이고 더 깨끗한 코드를 작성하도록 도와줍니다. [(목차로 돌아가기)](#목차) ## 심볼(Symbol) 심볼은 ES6 이전에도 존재했지만, 이제 직접적으로 심볼을 사용할 수 있는 공식 인터페이스가 제공됩니다. 심볼은 고유하고 수정 불가능한 데이터 타입이고 모든 객체의 식별자로 활용할 수 있습니다. ### Symbol() `Symbol()` 혹은 `Symbol(description)` 메소드를 호출하면 전역적으로 사용할 수 없는 고유한 심볼이 생성될 것입니다. `Symbol()`은 써드 파티 라이브러리의 객체 혹은 네임스페이스에 충돌할 염려가 없는 새로운 코드를 덧입히는데 종종 쓰입니다. 예를 들어, 나중에 라이브러리가 업데이트 되더라도 겹칠 우려가 없이 `React.Component` 클래스에 `refreshComponent` 메소드를 추가하고 싶다면 다음과 같이 할 수 있습니다. ```javascript const refreshComponent = Symbol(); React.Component.prototype[refreshComponent] = () => { // do something } ``` ### Symbol.for(key) `Symbol.for(key)`는 여전히 고유하고 수정 불가능한 심볼을 생성하지만, 전역적으로 사용 가능합니다. `Symbol.for(key)`를 두 번 호출하면 두 번 다 같은 심볼 인스턴스를 반환합니다. 주의하세요. `Symbol(description)`에서는 그렇지 않습니다. ```javascript Symbol('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol.for('foo') // true ``` 일반적으로 심볼, 특히 `Symbol.for(key)`은 상호 운용성을 위해 사용합니다. 상호 운용성은 몇 가지 알려진 인터페이스를 포함하는 서드 파티 라이브러리의 객체에 인자로 심볼 멤버 형태의 코드를 사용함으로서 만족될 수 있습니다. 예를 들면, ```javascript function reader(obj) { const specialRead = Symbol.for('specialRead'); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader } else { throw new TypeError('객체를 읽을 수 없습니다.'); } } ``` 또 다른 라이브러리에선 이렇게 할 수 있습니다. ```javascript const specialRead = Symbol.for('specialRead'); class SomeReadableType { [specialRead]() { const reader = createSomeReaderFrom(this); return reader; } } ``` > 상호 운용성을 위해 심볼을 사용하는 주목할 만한 예는 모든 반복 가능한(iterable) 타입 혹은 반복자(iterator)에 존재하는 `Symbol.iterator`입니다. 배열, 문자열, 생성자, 등 반복 가능한 타입은 이 메소드를 통해 호출되면 반복자 인터페이스를 포함한 객체 형태로 리턴됩니다. [(목차로 돌아가기)](#목차) ## 맵(Map) **맵**은 자바스크립트에서 자주 필요한 데이터 구조입니다. ES6 이전엔 객체를 이용해서 **해시** 맵을 생성했습니다. ```javascript var map = new Object(); map[key1] = 'value1'; map[key2] = 'value2'; ``` 하지만, 이 방법은 특정 프로퍼티 이름으로 인한 예상치 못한 함수 오버라이드(override)로부터 안전하지 않습니다. ```javascript > getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned'); > TypeError: Property 'hasOwnProperty' is not a function ``` 실제로 **맵**은 값을 위해 `get`, `set` 그리고 `search` 등의 메소드를 제공합니다. ```javascript let map = new Map(); > map.set('name', '현섭'); > map.get('name'); // 현섭 > map.has('name'); // true ``` 맵의 가장 놀라운 점은 더 이상 키 값으로 문자열만 쓰지 않아도 된다는 것입니다. 이제 키 값으로 어떤 타입을 전달해도 문자열로 형변환되지 않습니다. ```javascript let map = new Map([ ['이름', '현섭'], [true, 'false'], [1, '하나'], [{}, '객체'], [function () {}, '함수'] ]); for (let key of map.keys()) { console.log(typeof key); // > string, boolean, number, object, function } ``` > **Note**: 함수나 객체처럼 기본형 데이터 타입이 아닌 타입을 사용하면 `map.get()`같은 메소드를 사용할 때 비교 연산자가 제대로 동작하지 않습니다. 따라서, 문자열, 불린, 숫자 같은 기본형 데이터 타입을 계속 쓰는 것이 좋습니다. 또한 `.entries()`를 사용하면 맵을 순회할 수 있습니다. ```javascript for (let [key, value] of map.entries()) { console.log(key, value); } ``` [(목차로 돌아가기)](#목차) ## 위크맵(WeakMap) ES6 이전에는 private 데이터를 저장하기 위해서 많은 방법을 사용했습니다. 그 중 한가지가 네이밍 컨벤션을 이용한 방법이죠. ```javascript class Person { constructor(age) { this._age = age; } _incrementAge() { this._age += 1; } } ``` 그러나 네이밍 컨벤션은 코드베이스에 대해 혼란을 일으킬 수 있고, 항상 유지된다는 보장을 할 수 없었습니다. 이제 위크맵으로 이런 값을 저장할 수 있습니다. ```javascript let _age = new WeakMap(); class Person { constructor(age) { _age.set(this, age); } incrementAge() { let age = _age.get(this) + 1; _age.set(this, age); if (age > 50) { console.log('중년의 위기'); } } } ``` Private 데이터를 저장하기 위해 위크맵을 사용해서 좋은 점은 `Reflect.ownKeys()`를 사용해도 프로퍼티 이름을 드러내지 않는다는 것입니다. ```javascript > const person = new Person(50); > person.incrementAge(); // '중년의 위기' > Reflect.ownKeys(person); // [] ``` 위크맵을 사용하는 더욱 실질적인 예는 DOM 요소 자체를 훼손시키지 않고도 DOM 요소에 관련된 데이터를 저장하는 것입니다. ```javascript let map = new WeakMap(); let el = document.getElementById('someElement'); // 요소에 대한 약한 참조(weak reference)를 저장 map.set(el, '참조'); // 요소의 값에 접근 let value = map.get(el); // '참조' // 참조 제거 el.parentNode.removeChild(el); el = null; value = map.get(el); // undefined ``` 위에서 보여준 대로, 객체가 가비지 콜렉터에 의해 한 번 제거된 다음에는 위크맵이 자동적으로 해당 객체에 의해 식별되는 key-value 쌍을 제거합니다. > **Note**: 더 나아가, 이 예제의 유용함을 보여주기 위해 jQuery가 참조를 가진 DOM 요소에 대응되는 객체의 캐시를 저장하는 방법을 생각해보세요. 위크맵을 사용하면, jQuery는 문서에서 지워진 특정 DOM 요소에 관련된 모든 메모리를 자동적으로 절약할 수 있습니다. 전반적으로, 위크맵은 DOM 요소를 감싸는 모든 라이브러리에 매우 유용합니다. [(목차로 돌아가기)](#목차) ## Promise Promise는 다음과 같이 수평적인 코드(콜백 지옥)의 형태를 바꿀 수 있게 해줍니다. ```javascript func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }); }); }); }); }); ``` 수직적인 코드로 바꾸면, ```javascript func1(value1) .then(func2) .then(func3) .then(func4) .then(func5, value5 => { // Do something with value 5 }); ``` ES6 이전엔, [bluebird](https://github.com/petkaantonov/bluebird) 혹은 [Q](https://github.com/kriskowal/q)같은 라이브러리를 사용했었습니다. 이제는 Promise가 네이티브로 지원됩니다. ```javascript new Promise((resolve, reject) => reject(new Error('Promise가 제대로 동작하지 않았습니다!'))) .catch(reason => console.log(reason)); ``` Promise가 제대로 동작(**fulfill**)했을 때 호출되는 **resolve** 메소드와 Promise가 제대로 동작하지 않(**rejected**)았을 때 호출되는 **reject** 메소드를 이용해 Promise를 다룰 수 있습니다. > **Promise의 장점**: 중첩된 콜백 코드에서는 에러 핸들링하기가 혼란스럽습니다. Promise를 사용하면 에러를 적절히 위로 깨끗하게 전파할 수 있습니다. 게다가, resolve/reject된 후의 Promise의 값은 불변입니다. 아래는 Promise를 사용하는 실질적인 예제입니다. ```javascript var request = require('request'); return new Promise((resolve, reject) => { request.get(url, (error, response, body) => { if (body) { resolve(JSON.parse(body)); } else { resolve({}); } }); }); ``` 또한 `Promise.all()`을 사용해서 비동기 동작들의 배열을 다루는 Promise를 **병렬화**할 수 있습니다. ```javascript let urls = [ '/api/commits', '/api/issues/opened', '/api/issues/assigned', '/api/issues/completed', '/api/issues/comments', '/api/pullrequests' ]; let promises = urls.map((url) => { return new Promise((resolve, reject) => { $.ajax({ url: url }) .done((data) => { resolve(data); }); }); }); Promise.all(promises) .then((results) => { // Do something with results of all our promises }); ``` [(목차로 돌아가기)](#목차) ## 제너레이터(Generator) [Promise](https://github.com/DrkSephy/es6-cheatsheet/blob/master/README_ko.md#promise)를 이용해 [콜백 지옥](http://callbackhell.com/)을 피하는 것과 비슷하게, 제너레이터도 비동기적인 동작을 동기적인 느낌으로 만들어서 코드를 평평(flat)하게 만들 수 있도록 해줍니다. 제너레이터는 근본적으로 코드의 [실행을 중지](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield)하고 나중에 표현식의 값을 돌려주는 함수입니다. 다음은 제너레이터를 사용하는 간단한 예입니다. ```javascript function* sillyGenerator() { yield 1; yield 2; yield 3; yield 4; } var generator = sillyGenerator(); > console.log(generator.next()); // { value: 1, done: false } > console.log(generator.next()); // { value: 2, done: false } > console.log(generator.next()); // { value: 3, done: false } > console.log(generator.next()); // { value: 4, done: false } ``` [next](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next)를 사용하면 제너레이터를 전진시키고 새로운 표현식을 계산합니다. 위의 예제는 극히 부자연스럽지만, 다음과 같이 제너레이터는 비동기적인 코드를 동기적인 방식으로 작성하는데에 활용할 수 있습니다. ```javascript // 제너레이터를 이용해 비동기 동작을 숨김 function request(url) { getJSON(url, function(response) { generator.next(response); }); } ``` 그리고 데이터를 돌려줄 제너레이터 함수를 작성합니다. ```javascript function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); } ``` `yield` 덕분에, `data1`에 데이터가 파싱될 필요가 있을 때에만 `entry1`이 데이터를 가질 것임을 보장할 수 있습니다. 제너레이터는 비동기적인 코드를 동기적인 방식으로 작성하는데 도움을 주지만, 에러 전파는 깨끗하지 않고 쉽지 않은 경로를 통해 해야 합니다. 그렇기 때문에, Promise를 통해 제너레이터를 보완할 수 있습니다. ```javascript function request(url) { return new Promise((resolve, reject) => { getJSON(url, resolve); }); } ``` 그리고 `next`를 사용하는 제너레이터를 통해 단계별로 진행하는 함수를 작성합니다. `next`는 `request` 메소드를 사용하여 위의 Promise를 돌려줍니다. ```javascript function iterateGenerator(gen) { var generator = gen(); (function iterate(val) { var ret = generator.next(); if(!ret.done) { ret.value.then(iterate); } })(); } ``` Promise를 통해 제너레이터를 보완함으로서, Promise의 `.catch`와 `reject`를 활용해 에러 전파를 깨끗하게 할 수 있게 되었습니다. 다음과 같이 새롭게 개선된 제너레이터를 사용하면 전보다 더 간단합니다. ```javascript iterateGenerator(function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); }); ``` 구현했던 코드는 전처럼 제너레이터를 사용하기 위해서 재활용할 수 있습니다. 제너레이터와 Promise가 비동기적인 코드를 동기적인 방식으로 작성하면서도 에러 전파를 좋은 방법으로 하도록 유지시킬 수 있도록 도와줬지만, 사실, [async-await](https://github.com/DrkSephy/es6-cheatsheet/blob/master/README_ko.md#async-await)는 같은 이점을 더 간단한 형태로 활용할 수 있도록 제공합니다. [(목차로 돌아가기)](#목차) ## Async Await 사실 `async await`는 곧 나올 ES2016의 기능이지만, 제너레이터와 Promise를 같이써야 할 수 있었던 것들을 더 적은 노력으로 가능하게 합니다. ```javascript var request = require('request'); function getJSON(url) { return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body); }); }); } async function main() { var data = await getJSON(); console.log(data); // undefined 값이 아님! } main(); ``` 간단한 구현이지만, 제너레이터와 비슷하게 동작하는 코드입니다. 저는 제너레이터 + Promise보다는 `async-await`를 사용하는 것을 강력하게 추천드립니다. [여기](http://masnun.com/2015/11/11/using-es7-asyncawait-today-with-babel.html)에서 ES7과 Babel을 통해 실행할 수 있는 좋은 예제를 얻을 수 있습니다. [(목차로 돌아가기)](#목차) ## Getter와 Setter 함수 ES6는 getter와 setter 함수를 지원하기 시작했습니다. 다음 예제를 보세요. ```javascript class Employee { constructor(name) { this._name = name; } get name() { if(this._name) { return this._name.toUpperCase() + ' 양'; } else { return undefined; } } set name(newName) { if (newName == this._name) { console.log('이미 같은 이름을 쓰고 있습니다.'); } else if (newName) { this._name = newName; } else { return false; } } } var emp = new Employee("솔지"); // 내부적으로 get 메소드를 활용 if (emp.name) { console.log(emp.name); // 솔지 양 } // 내부적으로 setter를 활용 emp.name = "EXID 솔지"; console.log(emp.name); // EXID 솔지 양 ``` 가장 최근의 브라우저들은 객체의 getter와 setter 함수를 지원합니다. set/get 전에 리스너와 전처리작업을 추가하여 계산된 프로퍼티를 위해 getter와 settter를 활용할 수 있습니다. ```javascript var person = { firstName: '솔지', lastName: '허', get fullName() { console.log('이름 Get'); return this.lastName + ' ' + this.firstName; }, set fullName (name) { console.log('이름 Set'); var words = name.toString().split(' '); this.lastName = words[0] || ''; this.firstName = words[1] || ''; } } person.fullName; // 허 솔지 person.fullName = 'EXID 솔지'; person.fullName; // EXID 솔지 ``` [(목차로 돌아가기)](#목차) ## License The MIT License (MIT) Copyright (c) 2015 David Leonard Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [(목차로 돌아가기)](#목차) ================================================ FILE: README_zhCn.md ================================================ # es6-cheatsheet 这是一个 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳实践和一些代码片段,帮助你 完成日复一日的开发工作。 ## Table of Contents - [var 与 let / const 声明](#var-versus-let--const) - [代码执行块替换立即执行函数](#replacing-iifes-with-blocks) - [箭头函数](#arrow-functions) - [字符串](#strings) - [解构](#destructuring) - [模块](#modules) - [参数](#parameters) - [类](#classes) - [Symbols](#symbols) - [Maps](#maps) - [WeakMaps](#weakmaps) - [Promises](#promises) - [Generators](#generators) - [Async Await](#async-await) - [License](#license) ## var versus let / const > 除了 `var` 以外,我们现在多了两个新的标识符来声明变量的存储,它们就是 `let` 和 `const`。 不同于 `var` ,`let` 和 `const` 语句不会造成声明提升。 一个 `var` 的例子: ```javascript var snack = 'Meow Mix'; function getFood(food) { if (food) { var snack = 'Friskies'; return snack; } return snack; } getFood(false); // undefined ``` 让我们再观察下面语句中,使用 `let` 替换了 `var` 后的表现: ```javascript let snack = 'Meow Mix'; function getFood(food) { if (food) { let snack = 'Friskies'; return snack; } return snack; } getFood(false); // 'Meow Mix' ``` 当我们重构使用 `var` 的老代码时,一定要注意这种变化。盲目使用 `let` 替换 `var` 后可能会导致预期意外的结果。 > **注意**:`let` 和 `const` 是块级作用域语句。所以在语句块以外引用这些变量时,会造成引用错误 `ReferenceError`。 ```javascript console.log(x); let x = 'hi'; // ReferenceError: x is not defined ``` > **最佳实践**: 在重构老代码时,`var` 声明需要格外的注意。在创建一个新项目时,使用 `let` 声明一个变量,使用 `const` 来声明一个不可改变的常量。 [(回到目录)](#table-of-contents) ## Replacing IIFEs with Blocks 我们以往创建一个 **立即执行函数** 时,一般是在函数最外层包裹一层括号。 ES6支持块级作用域(更贴近其他语言),我们现在可以通过创建一个代码块(Block)来实现,不必通过创建一个函数来实现, ```javascript (function () { var food = 'Meow Mix'; }()); console.log(food); // Reference Error ``` 使用支持块级作用域的ES6的版本: ```javascript { let food = 'Meow Mix'; }; console.log(food); // Reference Error ``` [(回到目录)](#table-of-contents) ## Arrow Functions 一些时候,我们在函数嵌套中需要访问上下文中的 `this`。比如下面的例子: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; // Cannot read property 'name' of undefined }); }; ``` 一种通用的方式是把上下文中的 `this` 保存在一个变量里: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { var that = this; // Store the context of this return arr.map(function (character) { return that.name + character; }); }; ``` 我们也可以把 `this` 通过属性传进去: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }, this); }; ``` 还可以直接使用 `bind`: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }.bind(this)); }; ``` 使用 **箭头函数**,`this` 的值不用我们再做如上几段代码的特殊处理,直接使用即可。 上面的代码可以重写为下面这样: ```javascript function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(character => this.name + character); }; ``` > **最佳实践**:使用箭头函数,再也不用考虑 `this` 的问题了。 当我们编写只返回一个表达式值的简单函数时,也可以使用箭头函数,如下: ```javascript var squares = arr.map(function (x) { return x * x }); // Function Expression ``` ```javascript const arr = [1, 2, 3, 4, 5]; const squares = arr.map(x => x * x); // Arrow Function for terser implementation ``` > **最佳实践**:尽可能地多使用 **箭头函数**。 [(回到目录)](#table-of-contents) ## Strings 在ES6中,标准库也被同样增强了,像字符串对象就新增了 `.includes()` 和 `.repeat()` 方法。 ### .includes( ) ```javascript var string = 'food'; var substring = 'foo'; console.log(string.indexOf(substring) > -1); ``` 现在,我们可以使用 `.inclues()` 方法,替代以往判断内容 `> -1` 的方式。 `.includes()` 方法会极简地返回一个布尔值结果。 ```javascript const string = 'food'; const substring = 'foo'; console.log(string.includes(substring)); // true ``` ### .repeat( ) ```javascript function repeat(string, count) { var strings = []; while(strings.length < count) { strings.push(string); } return strings.join(''); } ``` 在ES6中,我们可以使用一个极简的方法来实现重复字符: ```javascript // String.repeat(numberOfRepetitions) 'meow'.repeat(3); // 'meowmeowmeow' ``` ### Template Literals 使用 **字符串模板字面量**,我可以在字符串中直接使用特殊字符,而不用转义。 ```javascript var text = "This string contains \"double quotes\" which are escaped."; ``` ```javascript let text = `This string contains "double quotes" which don't need to be escaped anymore.`; ``` **字符串模板字面量** 还支持直接插入变量,可以实现字符串与变量的直接连接输出。 ```javascript var name = 'Tiger'; var age = 13; console.log('My cat is named ' + name + ' and is ' + age + ' years old.'); ``` 更简单的版本: ```javascript const name = 'Tiger'; const age = 13; console.log(`My cat is named ${name} and is ${age} years old.`); ``` ES5中,我们要这样生成多行文本: ```javascript var text = ( 'cat\n' + 'dog\n' + 'nickelodeon' ); ``` 或者: ```javascript var text = [ 'cat', 'dog', 'nickelodeon' ].join('\n'); ``` **字符串模板字面量** 让我们不必特别关注多行字符串中的换行转义符号,直接换行即可: ```javascript let text = ( `cat dog nickelodeon` ); ``` **字符串模板字面量** 内部可以使用表达式,像这样: ```javascript let today = new Date(); let text = `The time and date is ${today.toLocaleString()}`; ``` [(回到目录)](#table-of-contents) ## Destructuring 解构让我们可以使用非常便捷的语法,直接将数组或者对象中的值直接分别导出到多个变量中, ### Destructuring Arrays **解构数组** ```javascript var arr = [1, 2, 3, 4]; var a = arr[0]; var b = arr[1]; var c = arr[2]; var d = arr[3]; ``` ```javascript let [a, b, c, d] = [1, 2, 3, 4]; console.log(a); // 1 console.log(b); // 2 ``` ### Destructuring Objects **解构对象** ```javascript var luke = { occupation: 'jedi', father: 'anakin' }; var occupation = luke.occupation; // 'jedi' var father = luke.father; // 'anakin' ``` ```javascript let luke = { occupation: 'jedi', father: 'anakin' }; let {occupation, father} = luke; console.log(occupation); // 'jedi' console.log(father); // 'anakin' ``` [(回到目录)](#table-of-contents) ## Modules ES6之前,浏览器端的模块化代码,我们使用像[Browserify](http://browserify.org/)这样的库, 在 **Node.js** 中,我们则使用 [require](https://nodejs.org/api/modules.html#modules_module_require_id)。 在ES6中,我们现在可以直接使用AMD 和 CommonJS这些模块了。 ### Exporting in CommonJS ```javascript module.exports = 1; module.exports = { foo: 'bar' }; module.exports = ['foo', 'bar']; module.exports = function bar () {}; ``` ### Exporting in ES6 在ES6中,提供了多种设置模块出口的方式,比如我们要导出一个变量,那么使用 **变量名** : ```javascript export let name = 'David'; export let age = 25;​​ ``` 还可以为对象 **导出一个列表**: ```javascript function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } export { sumTwo, sumThree }; ``` 我们也可以使用简单的一个 `export` 关键字来导出一个结果值: ```javascript export function sumTwo(a, b) { return a + b; } export function sumThree(a, b, c) { return a + b + c; } ``` 最后,我们可以 **导出一个默认出口**: ```javascript function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } let api = { sumTwo, sumThree }; export default api; /* * 与以下的语句是对等的: * export { api as default }; */ ``` > **最佳实践**:总是在模块的 **最后** 使用 `export default` 方法。 它让模块的出口更清晰明了,节省了阅读整个模块来寻找出口的时间。 更多的是,在大量CommonJS模块中,通用的习惯是设置一个出口值或者出口对象。 坚持这个规则,可以让我们的代码更易读,且更方便的联合使用CommonJS和ES6模块。 ### Importing in ES6 ES6提供了好几种模块的导入方式。我们可以单独引入一个文件: ```javascript import 'underscore'; ``` > 这里需要注意的是, **整个文件的引入方式会执行该文件内的最上层代码**。 就像Python一样,我们还可以命名引用: ```javascript import { sumTwo, sumThree } from 'math/addition'; ``` 我们甚至可以使用 `as` 给这些模块重命名: ```javascript import { sumTwo as addTwoNumbers, sumThree as sumThreeNumbers } from 'math/addition'; ``` 另外,我们能 **引入所有的东西(原文:import all the things)** (也称为命名空间引入) ```javascript import * as util from 'math/addition'; ``` 最后,我们能可以从一个模块的众多值中引入一个列表: ```javascript import * as additionUtil from 'math/addtion'; const { sumTwo, sumThree } = additionUtil; ``` 像这样引用默认对象: ```javascript import api from 'math/addition'; // Same as: import { default as api } from 'math/addition'; ``` 我们建议一个模块导出的值应该越简洁越好,不过有时候有必要的话命名引用和默认引用可以混着用。如果一个模块是这样导出的: ```javascript // foos.js export { foo as default, foo1, foo2 }; ``` 那我们可以如此导入这个模块的值: ```javaqscript import foo, { foo1, foo2 } from 'foos'; ``` 我们还可以导入commonjs模块,例如React: ```javascript import React from 'react'; const { Component, PropTypes } = React; ``` 更简化版本: ```javascript import React, { Component, PropTypes } from 'react'; ``` > **注意**:被导出的值是被 **绑定的(原文:bingdings)**,而不是引用。 所以,改变一个模块中的值的话,会影响其他引用本模块的代码,一定要避免此种改动发生。 [(回到目录)](#table-of-contents) ## Parameters 在ES5中,许多种方法来处理函数的 **参数默认值(default values)**,**参数数量(indefinite arguments)**,**参数命名(named parameters)**。 ES6中,我们可以使用非常简洁的语法来处理上面提到的集中情况。 ### Default Parameters ```javascript function addTwoNumbers(x, y) { x = x || 0; y = y || 0; return x + y; } ``` ES6中,我们可以简单为函数参数启用默认值: ```javascript function addTwoNumbers(x=0, y=0) { return x + y; } ``` ```javascript addTwoNumbers(2, 4); // 6 addTwoNumbers(2); // 2 addTwoNumbers(); // 0 ``` ### Rest Parameters ES5中,遇到参数数量不确定时,我们只能如此处理: ```javascript function logArguments() { for (var i=0; i < arguments.length; i++) { console.log(arguments[i]); } } ``` 使用 **rest** 操作符,我们可以给函数传入一个不确定数量的参数列表: ```javascript function logArguments(...args) { for (let arg of args) { console.log(arg); } } ``` ### Named Parameters 命名函数 ES5中,当我们要处理多个 **命名参数** 时,通常会传入一个 **选项对象** 的方式,这种方式被jQuery采用。 ```javascript function initializeCanvas(options) { var height = options.height || 600; var width = options.width || 400; var lineStroke = options.lineStroke || 'black'; } ``` 我们可以利用上面提到的新特性 **解构** ,来完成与上面同样功能的函数: We can achieve the same functionality using destructuring as a formal parameter to a function: ```javascript function initializeCanvas( { height=600, width=400, lineStroke='black'}) { // ... } // Use variables height, width, lineStroke here ``` 如果我们需要把这个参数变为可选的,那么只要把该参数解构为一个空对象就好了: ```javascript function initializeCanvas( { height=600, width=400, lineStroke='black'} = {}) { // ... } ``` ### Spread Operator 我们可以利用展开操作符(Spread Operator)来把一组数组的值,当作参数传入: ```javascript Math.max(...[-1, 100, 9001, -32]); // 9001 ``` [(回到目录)](#table-of-contents) ## Classes 在ES6以前,我们实现一个类的功能的话,需要首先创建一个构造函数,然后扩展这个函数的原型方法,就像这样: ```javascript function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } Person.prototype.incrementAge = function () { return this.age += 1; }; ``` 继承父类的子类需要这样: ```javascript function Personal(name, age, gender, occupation, hobby) { Person.call(this, name, age, gender); this.occupation = occupation; this.hobby = hobby; } Personal.prototype = Object.create(Person.prototype); Personal.prototype.constructor = Personal; Personal.prototype.incrementAge = function () { return Person.prototype.incrementAge.call(this) += 20; }; ``` ES6提供了一些语法糖来实现上面的功能,我们可以直接创建一个类: ```javascript class Person { constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } incrementAge() { this.age += 1; } } ``` 继承父类的子类只要简单的使用 `extends` 关键字就可以了: ```javascript class Personal extends Person { constructor(name, age, gender, occupation, hobby) { super(name, age, gender); this.occupation = occupation; this.hobby = hobby; } incrementAge() { super.incrementAge(); this.age += 20; console.log(this.age); } } ``` > **最佳实践**:ES6新的类语法把我们从晦涩难懂的实现和原型操作中解救出来,这是个非常适合初学者的功能,而且能让我们写出更干净整洁的代码。 [(回到目录)](#table-of-contents) ## Symbols Symbols在ES6版本之前就已经存在了,但现在我们拥有一个公共的接口来直接使用它们。 Symbols是不可更改的(immutable)并且唯一的(unique),它可用作任何hash数据类型中的键。 ### Symbol( ) 调用 `Symbol()` 或者 `Symbol(描述文本)` 会创建一个唯一的、在全局中不可以访问的Symbol对象。 一个 `Symbol()` 的应用场景是:在自己的项目中使用第三方代码库,且你需要给他们的对象或者命名空间打补丁代码,又不想改动或升级第三方原有代码的时候。 例如,如果你想给 `React.Component` 这个类添加一个 `refreshComponent` 方法,但又确定不了这个方法会不会在下个版本中加入,你可以这么做: ```javascript const refreshComponent = Symbol(); React.Component.prototype[refreshComponent] = () => { // do something } ``` ### Symbol.for(key) 使用 `Symbol.for(key)` 也是会创建一个不可改变的Symbol对象,但区别于上面的创建方法,这个对象是在全局中可以被访问到的。 两次相同的 `Symbol.for(key)` 调用会返回相同的Symbol实例。 **提示**:这并不同于 `Symbol(description)`。 ```javascript Symbol('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol.for('foo') // true ``` Symbols常用的一个使用场景,尤其是使用 `Symbol.for(key)` 方法,是用于实现代码间的互操作。 在你的代码中,通过在包含一些已知接口的第三方库的对象参数中查找Symbol成员,你可以实现这种互操作。 例如: ```javascript function reader(obj) { const specialRead = Symbol.for('specialRead'); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader } else { throw new TypeError('object cannot be read'); } } ``` 之后在另一个库中: ```javascript const specialRead = Symbol.for('specialRead'); class SomeReadableType { [specialRead]() { const reader = createSomeReaderFrom(this); return reader; } } ``` > **注意**:关于Symbol互操作的使用,一个值得一提的例子是`Symbol.iterable` 。`Symbol.iterable`存在ES6的所有可枚举对象中:数组(Arrays)、 > 字符串(strings)、生成器(Generators)等等。当它作为一个方法被调用时,它将会返回一个带有枚举接口的对象。 [(回到目录)](#table-of-contents) ## Maps **Maps** 是一个JavaScript中很重要(迫切需要)的数据结构。 在ES6之前,我们创建一个 **hash** 通常是使用一个对象: ```javascript var map = new Object(); map[key1] = 'value1'; map[key2] = 'value2'; ``` 但是,这样的代码无法避免函数被特别的属性名覆盖的意外情况: ```javascript > getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned'); > TypeError: Property 'hasOwnProperty' is not a function ``` **Maps** 让我们使用 `set`,`get` 和 `search` 操作数据。 ```javascript let map = new Map(); > map.set('name', 'david'); > map.get('name'); // david > map.has('name'); // true ``` Maps最强大的地方在于我们不必只能使用字符串来做key了,现在可以使用任何类型来当作key,而且key不会被强制类型转换为字符串。 ```javascript let map = new Map([ ['name', 'david'], [true, 'false'], [1, 'one'], [{}, 'object'], [function () {}, 'function'] ]); for (let key of map.keys()) { console.log(typeof key); // > string, boolean, number, object, function } ``` > **提示**:当使用 `map.get()` 判断值是否相等时,非基础类型比如一个函数或者对象,将不会正常工作。 有鉴于此,还是建议使用字符串,布尔和数字类型的数据类型。 我们还可以使用 `.entries()` 方法来遍历整个map对象: ```javascript for (let [key, value] of map.entries()) { console.log(key, value); } ``` [(回到目录)](#table-of-contents) ## WeakMaps 在ES5之前的版本,我们为了存储私有数据,有好几种方法。像使用这种下划线命名约定: ```javascript class Person { constructor(age) { this._age = age; } _incrementAge() { this._age += 1; } } ``` 在一个开源项目中,命名规则很难维持得一直很好,这样经常会造成一些困扰。 此时,我们可以选择使用WeakMaps来替代Maps来存储我们的数据: ```javascript let _age = new WeakMap(); class Person { constructor(age) { _age.set(this, age); } incrementAge() { let age = _age.get(this) + 1; _age.set(this, age); if (age > 50) { console.log('Midlife crisis'); } } } ``` 使用WeakMaps来保存我们私有数据的理由之一是不会暴露出属性名,就像下面的例子中的 `Reflect.ownKeys()`: ```javascript > const person = new Person(50); > person.incrementAge(); // 'Midlife crisis' > Reflect.ownKeys(person); // [] ``` 一个使用WeakMaps存储数据更实际的例子,是存储与DOM元素相关联的数据,而这不会对DOM元素本身产生污染: ```javascript let map = new WeakMap(); let el = document.getElementById('someElement'); // Store a weak reference to the element with a key map.set(el, 'reference'); // Access the value of the element let value = map.get(el); // 'reference' // Remove the reference el.parentNode.removeChild(el); el = null; value = map.get(el); // undefined ``` 上面的例子中,一旦对象被垃圾回收器给销毁了,WeakMaps会自动的把这个对象所对应的键值对数据同时销毁。 > **提示**:结合这个例子,再考虑下jQuery是如何实现缓存带有引用的DOM元素这个功能的。使用WeakMaps的话,当被缓存的DOM元素被移除的时,jQuery可以自动释放相应元素的内存。 通常情况下,在涉及DOM元素存储和缓存的情况下,使用WeakMaps是非常有效的。 [(回到目录)](#table-of-contents) ## Promises Promises让我们把多缩进难看的代码(回调地狱): ```javascript func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }); }); }); }); }); ``` 写成这样: ```javascript func1(value1) .then(func2) .then(func3) .then(func4) .then(func5, value5 => { // Do something with value 5 }); ``` 在ES6之前,我们使用[bluebird](https://github.com/petkaantonov/bluebird) 或者 [Q](https://github.com/kriskowal/q)。现在我们有了原生版本的 Promises: ```javascript new Promise((resolve, reject) => reject(new Error('Failed to fulfill Promise'))) .catch(reason => console.log(reason)); ``` 这里有两个处理函数,**resolve**(当Promise执行成功完毕时调用的回调函数) 和 **reject** (当Promise执行不接受时调用的回调函数) > **Promises的好处**:大量嵌套错误处理回调函数会使代码变得难以阅读理解。 使用Promises,我们可以通过清晰的路径将错误事件让上传递,并且适当地处理它们。 此外,Promise处理后的值,无论是解决(resolved)还是拒绝(rejected)的结果值,都是不可改变的。 下面是一些使用Promises的实际例子: ```javascript var request = require('request'); return new Promise((resolve, reject) => { request.get(url, (error, response, body) => { if (body) { resolve(JSON.parse(body)); } else { resolve({}); } }); }); ``` 我们还可以使用 `Promise.all()` 来 **并行化** 的处理一组异步的操作。 ```javascript let urls = [ '/api/commits', '/api/issues/opened', '/api/issues/assigned', '/api/issues/completed', '/api/issues/comments', '/api/pullrequests' ]; let promises = urls.map((url) => { return new Promise((resolve, reject) => { $.ajax({ url: url }) .done((data) => { resolve(data); }); }); }); Promise.all(promises) .then((results) => { // Do something with results of all our promises }); ``` [(回到目录)](#table-of-contents) ## Generators 就像[Promises](https://github.com/DrkSephy/es6-cheatsheet#promises)如何让我们避免[回调地狱](http://callbackhell.com/)一样,Generators也可以使我们的代码扁平化,同时给予我们开发者像开发同步代码一样的感觉来写异步代码。Generators本质上是一种支持的函数,随后返回表达式的值。 Generators实际上是支持[暂停运行](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield),随后根据上一步的返回值再继续运行的一种函数。 下面代码是一个使用generators函数的简单例子: ```javascript function* sillyGenerator() { yield 1; yield 2; yield 3; yield 4; } var generator = sillyGenerator(); > console.log(generator.next()); // { value: 1, done: false } > console.log(generator.next()); // { value: 2, done: false } > console.log(generator.next()); // { value: 3, done: false } > console.log(generator.next()); // { value: 4, done: false } ``` 就像上面的例子,当[next](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next)运行时,它会把我们的generator向前“推动”,同时执行新的表达式。 我们能利用Generators来像书写同步代码一样书写异步代码。 ```javascript // Hiding asynchronousity with Generators function request(url) { getJSON(url, function(response) { generator.next(response); }); } ``` 这里我们写个generator函数将要返回我们的数据: ```javascript function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); } ``` 借助于 `yield`,我们可以保证 `entry1` 确实拿到数据并转换后再赋值给 `data1`。 当我们使用generators来像书写同步代码一样书写我们的异步代码逻辑时,没有一种清晰简单的方式来处理期间可能会产生的错误或者异常。在这种情况下,我们可以在我们的generator中引入Promises来处理,就像下面这样: ```javascript function request(url) { return new Promise((resolve, reject) => { getJSON(url, resolve); }); } ``` 我们再写一个函数,其中使用 `next` 来步进我们的generator的同事,再利用我们上面的 `request` 方法来产生(yield)一个Promise。 ```javascript function iterateGenerator(gen) { var generator = gen(); var ret; (function iterate(val) { ret = generator.next(); if(!ret.done) { ret.value.then(iterate); } })(); } ``` 在Generator中引入了Promises后,我们就可以通过Promise的 `.catch` 和 `reject` 来捕捉和处理错误了。 使用了我们新版的Generator后,新版的调用就像老版本一样简单可读(译者注:有微调): ```javascript iterateGenerator(function* getData() { var entry1 = yield request('http://some_api/item1'); var data1 = JSON.parse(entry1); var entry2 = yield request('http://some_api/item2'); var data2 = JSON.parse(entry2); }); ``` 在使用Generator后,我们可以重用我们的老版本代码实现,以此展示了Generator的力量。 当使用Generators和Promises后,我们可以像书写同步代码一样书写异步代码的同时优雅地解决了错误处理问题。 此后,我们实际上可以开始利用更简单的一种方式了,它就是[async-await](https://github.com/DrkSephy/es6-cheatsheet#async-await)。 [(回到目录)](#table-of-contents) ## Async Await `async await` 随着ES2016版本就要发布了,它给我们提供了一种更轻松的、更简单的可以替代的实现上面 Generators 配合 Promises 组合代码的一种编码方式,让我们来看看例子: ```javascript var request = require('request'); function getJSON(url) { return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body); }); }); } async function main() { var data = await getJSON(); console.log(data); // NOT undefined! } main(); ``` 它们看上去和Generators很像。我(作者)强烈推荐使用 `async await` 来替代Generators + Promises的写法。 [这里](http://masnun.com/2015/11/11/using-es7-asyncawait-today-with-babel.html)是个很好的学习资源,让我们学习和使用这项ES7中的新功能。 [(回到目录)](#table-of-contents) ## License The MIT License (MIT) Copyright (c) 2015 David Leonard Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [(回到目录)](#table-of-contents) ================================================ FILE: src/convert.js ================================================ console.log('Converting repository to JavaScript');