├── README.md
├── README_ko.md
├── README_zhCn.md
└── src
└── convert.js
/README.md:
--------------------------------------------------------------------------------
1 | # es6-cheatsheet
2 |
3 | A cheatsheet containing ES2015 [ES6] tips, tricks, best practices and code
4 | snippet examples for your day to day workflow. Contributions are welcome!
5 |
6 | ## Table of Contents
7 |
8 | - [var versus let / const](#var-versus-let--const)
9 | - [Replacing IIFEs with Blocks](#replacing-iifes-with-blocks)
10 | - [Arrow Functions](#arrow-functions)
11 | - [Strings](#strings)
12 | - [Destructuring](#destructuring)
13 | - [Modules](#modules)
14 | - [Parameters](#parameters)
15 | - [Classes](#classes)
16 | - [Symbols](#symbols)
17 | - [Maps](#maps)
18 | - [WeakMaps](#weakmaps)
19 | - [Promises](#promises)
20 | - [Generators](#generators)
21 | - [Async Await](#async-await)
22 | - [Getter/Setter functions](#getter-and-setter-functions)
23 | - [License](#license)
24 |
25 | ## var versus let / const
26 |
27 | > Besides `var`, we now have access to two new identifiers for storing values
28 | —`let` and `const`. Unlike `var`, `let` and `const` statements are not hoisted
29 | to the top of their enclosing scope.
30 |
31 | An example of using `var`:
32 |
33 | ```javascript
34 | var snack = 'Meow Mix';
35 |
36 | function getFood(food) {
37 | if (food) {
38 | var snack = 'Friskies';
39 | return snack;
40 | }
41 | return snack;
42 | }
43 |
44 | getFood(false); // undefined
45 | ```
46 |
47 | However, observe what happens when we replace `var` using `let`:
48 |
49 | ```javascript
50 | let snack = 'Meow Mix';
51 |
52 | function getFood(food) {
53 | if (food) {
54 | let snack = 'Friskies';
55 | return snack;
56 | }
57 | return snack;
58 | }
59 |
60 | getFood(false); // 'Meow Mix'
61 | ```
62 |
63 | This change in behavior highlights that we need to be careful when refactoring
64 | legacy code which uses `var`. Blindly replacing instances of `var` with `let`
65 | may lead to unexpected behavior.
66 |
67 | > **Note**: `let` and `const` are block scoped. Therefore, referencing
68 | block-scoped identifiers before they are defined will produce
69 | a `ReferenceError`.
70 |
71 | ```javascript
72 | console.log(x); // ReferenceError: x is not defined
73 |
74 | let x = 'hi';
75 | ```
76 |
77 | > **Best Practice**: Leave `var` declarations inside of legacy code to denote
78 | that it needs to be carefully refactored. When working on a new codebase, use
79 | `let` for variables that will change their value over time, and `const` for
80 | variables which cannot be reassigned.
81 |
82 | [(back to table of contents)](#table-of-contents)
83 |
84 | ## Replacing IIFEs with Blocks
85 |
86 | > A common use of **Immediately Invoked Function Expressions** is to enclose
87 | values within its scope. In ES6, we now have the ability to create block-based
88 | scopes and therefore are not limited purely to function-based scope.
89 |
90 | ```javascript
91 | (function () {
92 | var food = 'Meow Mix';
93 | }());
94 |
95 | console.log(food); // Reference Error
96 | ```
97 |
98 | Using ES6 Blocks:
99 |
100 | ```javascript
101 | {
102 | let food = 'Meow Mix';
103 | };
104 |
105 | console.log(food); // Reference Error
106 | ```
107 |
108 | [(back to table of contents)](#table-of-contents)
109 |
110 | ## Arrow Functions
111 |
112 | Often times we have nested functions in which we would like to preserve the
113 | context of `this` from its lexical scope. An example is shown below:
114 |
115 | ```javascript
116 | function Person(name) {
117 | this.name = name;
118 | }
119 |
120 | Person.prototype.prefixName = function (arr) {
121 | return arr.map(function (character) {
122 | return this.name + character; // Cannot read property 'name' of undefined
123 | });
124 | };
125 | ```
126 |
127 | One common solution to this problem is to store the context of `this` using
128 | a variable:
129 |
130 | ```javascript
131 | function Person(name) {
132 | this.name = name;
133 | }
134 |
135 | Person.prototype.prefixName = function (arr) {
136 | var that = this; // Store the context of this
137 | return arr.map(function (character) {
138 | return that.name + character;
139 | });
140 | };
141 | ```
142 |
143 | We can also pass in the proper context of `this`:
144 |
145 | ```javascript
146 | function Person(name) {
147 | this.name = name;
148 | }
149 |
150 | Person.prototype.prefixName = function (arr) {
151 | return arr.map(function (character) {
152 | return this.name + character;
153 | }, this);
154 | };
155 | ```
156 |
157 | As well as bind the context:
158 |
159 | ```javascript
160 | function Person(name) {
161 | this.name = name;
162 | }
163 |
164 | Person.prototype.prefixName = function (arr) {
165 | return arr.map(function (character) {
166 | return this.name + character;
167 | }.bind(this));
168 | };
169 | ```
170 |
171 | Using **Arrow Functions**, the lexical value of `this` isn't shadowed and we
172 | can re-write the above as shown:
173 |
174 | ```javascript
175 | function Person(name) {
176 | this.name = name;
177 | }
178 |
179 | Person.prototype.prefixName = function (arr) {
180 | return arr.map(character => this.name + character);
181 | };
182 | ```
183 |
184 | > **Best Practice**: Use **Arrow Functions** whenever you need to preserve the
185 | lexical value of `this`.
186 |
187 | Arrow Functions are also more concise when used in function expressions which
188 | simply return a value:
189 |
190 | ```javascript
191 | var squares = arr.map(function (x) { return x * x }); // Function Expression
192 | ```
193 |
194 | ```javascript
195 | const arr = [1, 2, 3, 4, 5];
196 | const squares = arr.map(x => x * x); // Arrow Function for terser implementation
197 | ```
198 |
199 | > **Best Practice**: Use **Arrow Functions** in place of function expressions
200 | when possible.
201 |
202 | [(back to table of contents)](#table-of-contents)
203 |
204 | ## Strings
205 |
206 | With ES6, the standard library has grown immensely. Along with these changes
207 | are new methods which can be used on strings, such as `.includes()` and
208 | `.repeat()`.
209 |
210 | ### .includes( )
211 |
212 | ```javascript
213 | var string = 'food';
214 | var substring = 'foo';
215 |
216 | console.log(string.indexOf(substring) > -1);
217 | ```
218 |
219 | Instead of checking for a return value `> -1` to denote string containment,
220 | we can simply use `.includes()` which will return a boolean:
221 |
222 | ```javascript
223 | const string = 'food';
224 | const substring = 'foo';
225 |
226 | console.log(string.includes(substring)); // true
227 | ```
228 |
229 | ### .repeat( )
230 |
231 | ```javascript
232 | function repeat(string, count) {
233 | var strings = [];
234 | while(strings.length < count) {
235 | strings.push(string);
236 | }
237 | return strings.join('');
238 | }
239 | ```
240 |
241 | In ES6, we now have access to a terser implementation:
242 |
243 | ```javascript
244 | // String.repeat(numberOfRepetitions)
245 | 'meow'.repeat(3); // 'meowmeowmeow'
246 | ```
247 |
248 | ### Template Literals
249 |
250 | Using **Template Literals**, we can now construct strings that have special
251 | characters in them without needing to escape them explicitly.
252 |
253 | ```javascript
254 | var text = "This string contains \"double quotes\" which are escaped.";
255 | ```
256 |
257 | ```javascript
258 | let text = `This string contains "double quotes" which don't need to be escaped anymore.`;
259 | ```
260 |
261 | **Template Literals** also support interpolation, which makes the task of
262 | concatenating strings and values:
263 |
264 | ```javascript
265 | var name = 'Tiger';
266 | var age = 13;
267 |
268 | console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
269 | ```
270 |
271 | Much simpler:
272 |
273 | ```javascript
274 | const name = 'Tiger';
275 | const age = 13;
276 |
277 | console.log(`My cat is named ${name} and is ${age} years old.`);
278 | ```
279 |
280 | In ES5, we handled new lines as follows:
281 |
282 | ```javascript
283 | var text = (
284 | 'cat\n' +
285 | 'dog\n' +
286 | 'nickelodeon'
287 | );
288 | ```
289 |
290 | Or:
291 |
292 | ```javascript
293 | var text = [
294 | 'cat',
295 | 'dog',
296 | 'nickelodeon'
297 | ].join('\n');
298 | ```
299 |
300 | **Template Literals** will preserve new lines for us without having to
301 | explicitly place them in:
302 |
303 | ```javascript
304 | let text = ( `cat
305 | dog
306 | nickelodeon`
307 | );
308 | ```
309 |
310 | **Template Literals** can accept expressions, as well:
311 |
312 | ```javascript
313 | let today = new Date();
314 | let text = `The time and date is ${today.toLocaleString()}`;
315 | ```
316 |
317 | [(back to table of contents)](#table-of-contents)
318 |
319 | ## Destructuring
320 |
321 | Destructuring allows us to extract values from arrays and objects (even deeply
322 | nested) and store them in variables with a more convenient syntax.
323 |
324 | ### Destructuring Arrays
325 |
326 | ```javascript
327 | var arr = [1, 2, 3, 4];
328 | var a = arr[0];
329 | var b = arr[1];
330 | var c = arr[2];
331 | var d = arr[3];
332 | ```
333 |
334 | ```javascript
335 | let [a, b, c, d] = [1, 2, 3, 4];
336 |
337 | console.log(a); // 1
338 | console.log(b); // 2
339 | ```
340 |
341 | ### Destructuring Objects
342 |
343 | ```javascript
344 | var luke = { occupation: 'jedi', father: 'anakin' };
345 | var occupation = luke.occupation; // 'jedi'
346 | var father = luke.father; // 'anakin'
347 | ```
348 |
349 | ```javascript
350 | let luke = { occupation: 'jedi', father: 'anakin' };
351 | let {occupation, father} = luke;
352 |
353 | console.log(occupation); // 'jedi'
354 | console.log(father); // 'anakin'
355 | ```
356 |
357 | [(back to table of contents)](#table-of-contents)
358 |
359 | ## Modules
360 |
361 | Prior to ES6, we used libraries such as [Browserify](http://browserify.org/)
362 | to create modules on the client-side, and [require](https://nodejs.org/api/modules.html#modules_module_require_id)
363 | in **Node.js**. With ES6, we can now directly use modules of all types
364 | (AMD and CommonJS).
365 |
366 | ### Exporting in CommonJS
367 |
368 | ```javascript
369 | module.exports = 1;
370 | module.exports = { foo: 'bar' };
371 | module.exports = ['foo', 'bar'];
372 | module.exports = function bar () {};
373 | ```
374 |
375 | ### Exporting in ES6
376 |
377 | With ES6, we have various flavors of exporting. We can perform
378 | **Named Exports**:
379 |
380 | ```javascript
381 | export let name = 'David';
382 | export let age = 25;
383 | ```
384 |
385 | As well as **exporting a list** of objects:
386 |
387 | ```javascript
388 | function sumTwo(a, b) {
389 | return a + b;
390 | }
391 |
392 | function sumThree(a, b, c) {
393 | return a + b + c;
394 | }
395 |
396 | export { sumTwo, sumThree };
397 | ```
398 |
399 | We can also export functions, objects and values (etc.) simply by using the `export` keyword:
400 |
401 | ```javascript
402 | export function sumTwo(a, b) {
403 | return a + b;
404 | }
405 |
406 | export function sumThree(a, b, c) {
407 | return a + b + c;
408 | }
409 | ```
410 |
411 | And lastly, we can **export default bindings**:
412 |
413 | ```javascript
414 | function sumTwo(a, b) {
415 | return a + b;
416 | }
417 |
418 | function sumThree(a, b, c) {
419 | return a + b + c;
420 | }
421 |
422 | let api = {
423 | sumTwo,
424 | sumThree
425 | };
426 |
427 | export default api;
428 |
429 | /* Which is the same as
430 | * export { api as default };
431 | */
432 | ```
433 |
434 | > **Best Practices**: Always use the `export default` method at **the end** of
435 | the module. It makes it clear what is being exported, and saves time by having
436 | to figure out what name a value was exported as. More so, the common practice
437 | in CommonJS modules is to export a single value or object. By sticking to this
438 | paradigm, we make our code easily readable and allow ourselves to interpolate
439 | between CommonJS and ES6 modules.
440 |
441 | ### Importing in ES6
442 |
443 | ES6 provides us with various flavors of importing. We can import an entire file:
444 |
445 | ```javascript
446 | import 'underscore';
447 | ```
448 |
449 | > It is important to note that simply **importing an entire file will execute
450 | all code at the top level of that file**.
451 |
452 | Similar to Python, we have named imports:
453 |
454 | ```javascript
455 | import { sumTwo, sumThree } from 'math/addition';
456 | ```
457 |
458 | We can also rename the named imports:
459 |
460 | ```javascript
461 | import {
462 | sumTwo as addTwoNumbers,
463 | sumThree as sumThreeNumbers
464 | } from 'math/addition';
465 | ```
466 |
467 | In addition, we can **import all the things** (also called namespace import):
468 |
469 | ```javascript
470 | import * as util from 'math/addition';
471 | ```
472 |
473 | Lastly, we can import a list of values from a module:
474 |
475 | ```javascript
476 | import * as additionUtil from 'math/addition';
477 | const { sumTwo, sumThree } = additionUtil;
478 | ```
479 | Importing from the default binding like this:
480 |
481 | ```javascript
482 | import api from 'math/addition';
483 | // Same as: import { default as api } from 'math/addition';
484 | ```
485 |
486 | While it is better to keep the exports simple, but we can sometimes mix default import and mixed import if needed.
487 | When we are exporting like this:
488 |
489 | ```javascript
490 | // foos.js
491 | export { foo as default, foo1, foo2 };
492 | ```
493 |
494 | We can import them like the following:
495 |
496 | ```javascript
497 | import foo, { foo1, foo2 } from 'foos';
498 | ```
499 |
500 | When importing a module exported using commonjs syntax (such as React) we can do:
501 |
502 | ```javascript
503 | import React from 'react';
504 | const { Component, PropTypes } = React;
505 | ```
506 |
507 | This can also be simplified further, using:
508 |
509 | ```javascript
510 | import React, { Component, PropTypes } from 'react';
511 | ```
512 |
513 | > **Note**: Values that are exported are **bindings**, not references.
514 | Therefore, changing the binding of a variable in one module will affect the
515 | value within the exported module. Avoid changing the public interface of these
516 | exported values.
517 |
518 | [(back to table of contents)](#table-of-contents)
519 |
520 | ## Parameters
521 |
522 | In ES5, we had varying ways to handle functions which needed **default values**,
523 | **indefinite arguments**, and **named parameters**. With ES6, we can accomplish
524 | all of this and more using more concise syntax.
525 |
526 | ### Default Parameters
527 |
528 | ```javascript
529 | function addTwoNumbers(x, y) {
530 | x = x || 0;
531 | y = y || 0;
532 | return x + y;
533 | }
534 | ```
535 |
536 | In ES6, we can simply supply default values for parameters in a function:
537 |
538 | ```javascript
539 | function addTwoNumbers(x=0, y=0) {
540 | return x + y;
541 | }
542 | ```
543 |
544 | ```javascript
545 | addTwoNumbers(2, 4); // 6
546 | addTwoNumbers(2); // 2
547 | addTwoNumbers(); // 0
548 | ```
549 |
550 | ### Rest Parameters
551 |
552 | In ES5, we handled an indefinite number of arguments like so:
553 |
554 | ```javascript
555 | function logArguments() {
556 | for (var i=0; i < arguments.length; i++) {
557 | console.log(arguments[i]);
558 | }
559 | }
560 | ```
561 |
562 | Using the **rest** operator, we can pass in an indefinite amount of arguments:
563 |
564 | ```javascript
565 | function logArguments(...args) {
566 | for (let arg of args) {
567 | console.log(arg);
568 | }
569 | }
570 | ```
571 |
572 | ### Named Parameters
573 |
574 | One of the patterns in ES5 to handle named parameters was to use the **options
575 | object** pattern, adopted from jQuery.
576 |
577 | ```javascript
578 | function initializeCanvas(options) {
579 | var height = options.height || 600;
580 | var width = options.width || 400;
581 | var lineStroke = options.lineStroke || 'black';
582 | }
583 | ```
584 |
585 | We can achieve the same functionality using destructuring as a formal parameter
586 | to a function:
587 |
588 | ```javascript
589 | function initializeCanvas(
590 | { height=600, width=400, lineStroke='black'}) {
591 | // Use variables height, width, lineStroke here
592 | }
593 | ```
594 |
595 | If we want to make the entire value optional, we can do so by destructuring an
596 | empty object:
597 |
598 | ```javascript
599 | function initializeCanvas(
600 | { height=600, width=400, lineStroke='black'} = {}) {
601 | // ...
602 | }
603 | ```
604 |
605 | ### Spread Operator
606 |
607 | In ES5, we could find the max of values in an array by using the `apply` method on `Math.max` like this:
608 | ```javascript
609 | Math.max.apply(null, [-1, 100, 9001, -32]); // 9001
610 | ```
611 |
612 | In ES6, we can now use the spread operator to pass an array of values to be used as
613 | parameters to a function:
614 |
615 | ```javascript
616 | Math.max(...[-1, 100, 9001, -32]); // 9001
617 | ```
618 |
619 | We can concat array literals easily with this intuitive syntax:
620 |
621 | ```javascript
622 | let cities = ['San Francisco', 'Los Angeles'];
623 | let places = ['Miami', ...cities, 'Chicago']; // ['Miami', 'San Francisco', 'Los Angeles', 'Chicago']
624 | ```
625 |
626 | [(back to table of contents)](#table-of-contents)
627 |
628 | ## Classes
629 |
630 | Prior to ES6, we implemented Classes by creating a constructor function and
631 | adding properties by extending the prototype:
632 |
633 | ```javascript
634 | function Person(name, age, gender) {
635 | this.name = name;
636 | this.age = age;
637 | this.gender = gender;
638 | }
639 |
640 | Person.prototype.incrementAge = function () {
641 | return this.age += 1;
642 | };
643 | ```
644 |
645 | And created extended classes by the following:
646 |
647 | ```javascript
648 | function Personal(name, age, gender, occupation, hobby) {
649 | Person.call(this, name, age, gender);
650 | this.occupation = occupation;
651 | this.hobby = hobby;
652 | }
653 |
654 | Personal.prototype = Object.create(Person.prototype);
655 | Personal.prototype.constructor = Personal;
656 | Personal.prototype.incrementAge = function () {
657 | Person.prototype.incrementAge.call(this);
658 | this.age += 20;
659 | console.log(this.age);
660 | };
661 | ```
662 |
663 | ES6 provides much needed syntactic sugar for doing this under the hood. We can
664 | create Classes directly:
665 |
666 | ```javascript
667 | class Person {
668 | constructor(name, age, gender) {
669 | this.name = name;
670 | this.age = age;
671 | this.gender = gender;
672 | }
673 |
674 | incrementAge() {
675 | this.age += 1;
676 | }
677 | }
678 | ```
679 |
680 | And extend them using the `extends` keyword:
681 |
682 | ```javascript
683 | class Personal extends Person {
684 | constructor(name, age, gender, occupation, hobby) {
685 | super(name, age, gender);
686 | this.occupation = occupation;
687 | this.hobby = hobby;
688 | }
689 |
690 | incrementAge() {
691 | super.incrementAge();
692 | this.age += 20;
693 | console.log(this.age);
694 | }
695 | }
696 | ```
697 |
698 | > **Best Practice**: While the syntax for creating classes in ES6 obscures how
699 | implementation and prototypes work under the hood, it is a good feature for
700 | beginners and allows us to write cleaner code.
701 |
702 | [(back to table of contents)](#table-of-contents)
703 |
704 | ## Symbols
705 |
706 | Symbols have existed prior to ES6, but now we have a public interface to using
707 | them directly. Symbols are immutable and unique and can be used as keys in any hash.
708 |
709 | ### Symbol( )
710 |
711 | Calling `Symbol()` or `Symbol(description)` will create a unique symbol that cannot be looked up
712 | globally. A Use case for `Symbol()` is to patch objects or namespaces from third parties with your own
713 | logic, but be confident that you won't collide with updates to that library. For example,
714 | if you wanted to add a method `refreshComponent` to the `React.Component` class, and be certain that
715 | you didn't trample a method they add in a later update:
716 |
717 | ```javascript
718 | const refreshComponent = Symbol();
719 |
720 | React.Component.prototype[refreshComponent] = () => {
721 | // do something
722 | }
723 | ```
724 |
725 |
726 | ### Symbol.for(key)
727 |
728 | `Symbol.for(key)` will create a Symbol that is still immutable and unique, but can be looked up globally.
729 | Two identical calls to `Symbol.for(key)` will return the same Symbol instance. NOTE: This is not true for
730 | `Symbol(description)`:
731 |
732 | ```javascript
733 | Symbol('foo') === Symbol('foo') // false
734 | Symbol.for('foo') === Symbol('foo') // false
735 | Symbol.for('foo') === Symbol.for('foo') // true
736 | ```
737 |
738 | A common use case for Symbols, and in particular with `Symbol.for(key)` is for interoperability. This can be
739 | achieved by having your code look for a Symbol member on object arguments from third parties that contain some
740 | known interface. For example:
741 |
742 | ```javascript
743 | function reader(obj) {
744 | const specialRead = Symbol.for('specialRead');
745 | if (obj[specialRead]) {
746 | const reader = obj[specialRead]();
747 | // do something with reader
748 | } else {
749 | throw new TypeError('object cannot be read');
750 | }
751 | }
752 | ```
753 |
754 | And then in another library:
755 |
756 | ```javascript
757 | const specialRead = Symbol.for('specialRead');
758 |
759 | class SomeReadableType {
760 | [specialRead]() {
761 | const reader = createSomeReaderFrom(this);
762 | return reader;
763 | }
764 | }
765 | ```
766 |
767 | > A notable example of Symbol use for interoperability is `Symbol.iterator` which exists on all iterable
768 | types in ES6: Arrays, strings, generators, etc. When called as a method it returns an object with an Iterator
769 | interface.
770 |
771 | [(back to table of contents)](#table-of-contents)
772 |
773 | ## Maps
774 |
775 | **Maps** is a much needed data structure in JavaScript. Prior to ES6, we created
776 | **hash** maps through objects:
777 |
778 | ```javascript
779 | var map = new Object();
780 | map[key1] = 'value1';
781 | map[key2] = 'value2';
782 | ```
783 |
784 | However, this does not protect us from accidentally overriding functions with
785 | specific property names:
786 |
787 | ```javascript
788 | > getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
789 | > TypeError: Property 'hasOwnProperty' is not a function
790 | ```
791 |
792 | Actual **Maps** allow us to `set`, `get` and `search` for values (and much more).
793 |
794 | ```javascript
795 | let map = new Map();
796 | > map.set('name', 'david');
797 | > map.get('name'); // david
798 | > map.has('name'); // true
799 | ```
800 |
801 | The most amazing part of Maps is that we are no longer limited to just using
802 | strings. We can now use any type as a key, and it will not be type-cast to
803 | a string.
804 |
805 | ```javascript
806 | let map = new Map([
807 | ['name', 'david'],
808 | [true, 'false'],
809 | [1, 'one'],
810 | [{}, 'object'],
811 | [function () {}, 'function']
812 | ]);
813 |
814 | for (let key of map.keys()) {
815 | console.log(typeof key);
816 | // > string, boolean, number, object, function
817 | }
818 | ```
819 |
820 | > **Note**: Using non-primitive values such as functions or objects won't work
821 | when testing equality using methods such as `map.get()`. As such, stick to
822 | primitive values such as Strings, Booleans and Numbers.
823 |
824 | We can also iterate over maps using `.entries()`:
825 |
826 | ```javascript
827 | for (let [key, value] of map.entries()) {
828 | console.log(key, value);
829 | }
830 | ```
831 |
832 | [(back to table of contents)](#table-of-contents)
833 |
834 | ## WeakMaps
835 |
836 | In order to store private data versions < ES6, we had various ways of doing this.
837 | One such method was using naming conventions:
838 |
839 | ```javascript
840 | class Person {
841 | constructor(age) {
842 | this._age = age;
843 | }
844 |
845 | _incrementAge() {
846 | this._age += 1;
847 | }
848 | }
849 | ```
850 |
851 | But naming conventions can cause confusion in a codebase and are not always
852 | going to be upheld. Instead, we can use WeakMaps to store our values:
853 |
854 | ```javascript
855 | let _age = new WeakMap();
856 | class Person {
857 | constructor(age) {
858 | _age.set(this, age);
859 | }
860 |
861 | incrementAge() {
862 | let age = _age.get(this) + 1;
863 | _age.set(this, age);
864 | if (age > 50) {
865 | console.log('Midlife crisis');
866 | }
867 | }
868 | }
869 | ```
870 |
871 | The cool thing about using WeakMaps to store our private data is that their
872 | keys do not give away the property names, which can be seen by using
873 | `Reflect.ownKeys()`:
874 |
875 | ```javascript
876 | > const person = new Person(50);
877 | > person.incrementAge(); // 'Midlife crisis'
878 | > Reflect.ownKeys(person); // []
879 | ```
880 |
881 | A more practical example of using WeakMaps is to store data which is associated
882 | to a DOM element without having to pollute the DOM itself:
883 |
884 | ```javascript
885 | let map = new WeakMap();
886 | let el = document.getElementById('someElement');
887 |
888 | // Store a weak reference to the element with a key
889 | map.set(el, 'reference');
890 |
891 | // Access the value of the element
892 | let value = map.get(el); // 'reference'
893 |
894 | // Remove the reference
895 | el.parentNode.removeChild(el);
896 | el = null;
897 |
898 | // map is empty, since the element is destroyed
899 | ```
900 |
901 | As shown above, once the object is destroyed by the garbage collector,
902 | the WeakMap will automatically remove the key-value pair which was identified
903 | by that object.
904 |
905 | > **Note**: To further illustrate the usefulness of this example, consider how
906 | jQuery stores a cache of objects corresponding to DOM elements which have
907 | references. Using WeakMaps, jQuery can automatically free up any memory that
908 | was associated with a particular DOM element once it has been removed from the
909 | document. In general, WeakMaps are very useful for any library that wraps DOM
910 | elements.
911 |
912 | [(back to table of contents)](#table-of-contents)
913 |
914 | ## Promises
915 |
916 | Promises allow us to turn our horizontal code (callback hell):
917 |
918 | ```javascript
919 | func1(function (value1) {
920 | func2(value1, function (value2) {
921 | func3(value2, function (value3) {
922 | func4(value3, function (value4) {
923 | func5(value4, function (value5) {
924 | // Do something with value 5
925 | });
926 | });
927 | });
928 | });
929 | });
930 | ```
931 |
932 | Into vertical code:
933 |
934 | ```javascript
935 | func1(value1)
936 | .then(func2)
937 | .then(func3)
938 | .then(func4)
939 | .then(func5, value5 => {
940 | // Do something with value 5
941 | });
942 | ```
943 |
944 | Prior to ES6, we used [bluebird](https://github.com/petkaantonov/bluebird) or
945 | [Q](https://github.com/kriskowal/q). Now we have Promises natively:
946 |
947 | ```javascript
948 | new Promise((resolve, reject) =>
949 | reject(new Error('Failed to fulfill Promise')))
950 | .catch(reason => console.log(reason));
951 | ```
952 |
953 | Where we have two handlers, **resolve** (a function called when the Promise is
954 | **fulfilled**) and **reject** (a function called when the Promise is **rejected**).
955 |
956 | > **Benefits of Promises**: Error Handling using a bunch of nested callbacks
957 | can get chaotic. Using Promises, we have a clear path to bubbling errors up
958 | and handling them appropriately. Moreover, the value of a Promise after it has
959 | been resolved/rejected is immutable - it will never change.
960 |
961 | Here is a practical example of using Promises:
962 |
963 | ```javascript
964 | var request = require('request');
965 |
966 | return new Promise((resolve, reject) => {
967 | request.get(url, (error, response, body) => {
968 | if (body) {
969 | resolve(JSON.parse(body));
970 | } else {
971 | resolve({});
972 | }
973 | });
974 | });
975 | ```
976 |
977 | We can also **parallelize** Promises to handle an array of asynchronous
978 | operations by using `Promise.all()`:
979 |
980 | ```javascript
981 | let urls = [
982 | '/api/commits',
983 | '/api/issues/opened',
984 | '/api/issues/assigned',
985 | '/api/issues/completed',
986 | '/api/issues/comments',
987 | '/api/pullrequests'
988 | ];
989 |
990 | let promises = urls.map((url) => {
991 | return new Promise((resolve, reject) => {
992 | $.ajax({ url: url })
993 | .done((data) => {
994 | resolve(data);
995 | });
996 | });
997 | });
998 |
999 | Promise.all(promises)
1000 | .then((results) => {
1001 | // Do something with results of all our promises
1002 | });
1003 | ```
1004 |
1005 | [(back to table of contents)](#table-of-contents)
1006 |
1007 | ## Generators
1008 |
1009 | Similar to how [Promises](https://github.com/DrkSephy/es6-cheatsheet#promises) allow us to avoid
1010 | [callback hell](http://callbackhell.com/), Generators allow us to flatten our code - giving our
1011 | asynchronous code a synchronous feel. Generators are essentially functions which we can
1012 | [pause their execution](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield)
1013 | and subsequently return the value of an expression.
1014 |
1015 | A simple example of using generators is shown below:
1016 |
1017 | ```javascript
1018 | function* sillyGenerator() {
1019 | yield 1;
1020 | yield 2;
1021 | yield 3;
1022 | yield 4;
1023 | }
1024 |
1025 | var generator = sillyGenerator();
1026 | > console.log(generator.next()); // { value: 1, done: false }
1027 | > console.log(generator.next()); // { value: 2, done: false }
1028 | > console.log(generator.next()); // { value: 3, done: false }
1029 | > console.log(generator.next()); // { value: 4, done: false }
1030 | ```
1031 |
1032 | Where [next](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next)
1033 | will allow us to push our generator forward and evaluate a new expression. While the above example is extremely
1034 | contrived, we can utilize Generators to write asynchronous code in a synchronous manner:
1035 |
1036 | ```javascript
1037 | // Hiding asynchronousity with Generators
1038 |
1039 | function request(url) {
1040 | getJSON(url, function(response) {
1041 | generator.next(response);
1042 | });
1043 | }
1044 | ```
1045 |
1046 | And here we write a generator function that will return our data:
1047 |
1048 | ```javascript
1049 | function* getData() {
1050 | var entry1 = yield request('http://some_api/item1');
1051 | var data1 = JSON.parse(entry1);
1052 | var entry2 = yield request('http://some_api/item2');
1053 | var data2 = JSON.parse(entry2);
1054 | }
1055 | ```
1056 |
1057 | By the power of `yield`, we are guaranteed that `entry1` will have the data needed to be parsed and stored
1058 | in `data1`.
1059 |
1060 | While generators allow us to write asynchronous code in a synchronous manner, there is no clear
1061 | and easy path for error propagation. As such, as we can augment our generator with Promises:
1062 |
1063 | ```javascript
1064 | function request(url) {
1065 | return new Promise((resolve, reject) => {
1066 | getJSON(url, resolve);
1067 | });
1068 | }
1069 | ```
1070 |
1071 | And we write a function which will step through our generator using `next` which in turn will utilize our
1072 | `request` method above to yield a Promise:
1073 |
1074 | ```javascript
1075 | function iterateGenerator(gen) {
1076 | var generator = gen();
1077 | (function iterate(val) {
1078 | var ret = generator.next();
1079 | if(!ret.done) {
1080 | ret.value.then(iterate);
1081 | }
1082 | })();
1083 | }
1084 | ```
1085 |
1086 | By augmenting our Generator with Promises, we have a clear way of propagating errors through the use of our
1087 | Promise `.catch` and `reject`. To use our newly augmented Generator, it is as simple as before:
1088 |
1089 | ```javascript
1090 | iterateGenerator(function* getData() {
1091 | var entry1 = yield request('http://some_api/item1');
1092 | var data1 = JSON.parse(entry1);
1093 | var entry2 = yield request('http://some_api/item2');
1094 | var data2 = JSON.parse(entry2);
1095 | });
1096 | ```
1097 |
1098 | We were able to reuse our implementation to use our Generator as before, which shows their power. While Generators
1099 | and Promises allow us to write asynchronous code in a synchronous manner while retaining the ability to propagate
1100 | errors in a nice way, we can actually begin to utilize a simpler construction that provides the same benefits:
1101 | [async-await](https://github.com/DrkSephy/es6-cheatsheet#async-await).
1102 |
1103 | [(back to table of contents)](#table-of-contents)
1104 |
1105 | ## Async Await
1106 |
1107 | While this is actually an upcoming ES2016 feature, `async await` allows us to perform the same thing we accomplished
1108 | using Generators and Promises with less effort:
1109 |
1110 | ```javascript
1111 | var request = require('request');
1112 |
1113 | function getJSON(url) {
1114 | return new Promise(function(resolve, reject) {
1115 | request(url, function(error, response, body) {
1116 | resolve(body);
1117 | });
1118 | });
1119 | }
1120 |
1121 | async function main() {
1122 | var data = await getJSON();
1123 | console.log(data); // NOT undefined!
1124 | }
1125 |
1126 | main();
1127 | ```
1128 |
1129 | Under the hood, it performs similarly to Generators. I highly recommend using them over Generators + Promises. A great resource
1130 | 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).
1131 |
1132 | [(back to table of contents)](#table-of-contents)
1133 | ## Getter and setter functions
1134 |
1135 | ES6 has started supporting getter and setter functions within classes. Using the following example:
1136 |
1137 | ```javascript
1138 | class Employee {
1139 |
1140 | constructor(name) {
1141 | this._name = name;
1142 | }
1143 |
1144 | get name() {
1145 | if(this._name) {
1146 | return 'Mr. ' + this._name.toUpperCase();
1147 | } else {
1148 | return undefined;
1149 | }
1150 | }
1151 |
1152 | set name(newName) {
1153 | if (newName == this._name) {
1154 | console.log('I already have this name.');
1155 | } else if (newName) {
1156 | this._name = newName;
1157 | } else {
1158 | return false;
1159 | }
1160 | }
1161 | }
1162 |
1163 | var emp = new Employee("James Bond");
1164 |
1165 | // uses the get method in the background
1166 | if (emp.name) {
1167 | console.log(emp.name); // Mr. JAMES BOND
1168 | }
1169 |
1170 | // uses the setter in the background
1171 | emp.name = "Bond 007";
1172 | console.log(emp.name); // Mr. BOND 007
1173 | ```
1174 |
1175 | 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:
1176 |
1177 | ```javascript
1178 | var person = {
1179 | firstName: 'James',
1180 | lastName: 'Bond',
1181 | get fullName() {
1182 | console.log('Getting FullName');
1183 | return this.firstName + ' ' + this.lastName;
1184 | },
1185 | set fullName (name) {
1186 | console.log('Setting FullName');
1187 | var words = name.toString().split(' ');
1188 | this.firstName = words[0] || '';
1189 | this.lastName = words[1] || '';
1190 | }
1191 | }
1192 |
1193 | person.fullName; // James Bond
1194 | person.fullName = 'Bond 007';
1195 | person.fullName; // Bond 007
1196 | ```
1197 | [(back to table of contents)](#table-of-contents)
1198 |
1199 | ## License
1200 |
1201 | The MIT License (MIT)
1202 |
1203 | Copyright (c) 2015 David Leonard
1204 |
1205 | Permission is hereby granted, free of charge, to any person obtaining a copy
1206 | of this software and associated documentation files (the "Software"), to deal
1207 | in the Software without restriction, including without limitation the rights
1208 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1209 | copies of the Software, and to permit persons to whom the Software is
1210 | furnished to do so, subject to the following conditions:
1211 |
1212 | The above copyright notice and this permission notice shall be included in all
1213 | copies or substantial portions of the Software.
1214 |
1215 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1216 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1217 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1218 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1219 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1220 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1221 | SOFTWARE.
1222 |
1223 | [(back to table of contents)](#table-of-contents)
--------------------------------------------------------------------------------
/README_ko.md:
--------------------------------------------------------------------------------
1 | # es6-cheatsheet
2 |
3 | ES2015(ES6)의 Tip & Tricks, 좋은 활용사례들과 코드 예제들이 포함된 cheatsheet입니다.
4 | 이 문서는 한국어 번역 버전입니다. 오역 제보와 더 좋은 번역을 기다리고 있습니다!
5 |
6 | ## 목차
7 |
8 | - [var VS let / const](#var-vs-let--const)
9 | - [IIFE를 블록으로 교체하기](#iife를-블록으로-교체하기)
10 | - [애로우 펑션](#애로우-펑션)
11 | - [문자열](#문자열)
12 | - [Destructuring](#destructuring)
13 | - [모듈](#모듈)
14 | - [파라미터(Parameter)](#파라미터parameter)
15 | - [클래스](#클래스)
16 | - [심볼(Symbol)](#심볼symbol)
17 | - [맵(Map)](#맵map)
18 | - [위크맵(WeakMap)](#위크맵weakmap)
19 | - [Promise](#promise)
20 | - [제너레이터(Generator)](#제너레이터generator)
21 | - [Async Await](#async-await)
22 | - [Getter/Setter 함수](#getter와-setter-함수)
23 | - [License](#license)
24 |
25 | ## var VS let / const
26 |
27 | > 기존의 `var`에 더해 `let`과 `const`라는 값을 저장하기 위한 두 개의 새로운 식별자가 추가되었습니다. `var`와는 다르게, `let`과 `const` 상태는 스코프 내 최상단으로 호이스팅되지 않습니다.
28 |
29 | 다음은 `var`를 활용한 예제입니다.
30 |
31 | ```javascript
32 | var snack = '허니버터칩';
33 |
34 | function getFood(food) {
35 | if (food) {
36 | var snack = '스윙칩';
37 | return snack;
38 | }
39 | return snack;
40 | }
41 |
42 | getFood(false); // undefined
43 | ```
44 |
45 | 그러나, `var` 대신 `let`을 사용하면 다음과 같이 동작합니다.
46 |
47 | ```javascript
48 | let snack = '허니버터칩';
49 |
50 | function getFood(food) {
51 | if (food) {
52 | let snack = '스윙칩';
53 | return snack;
54 | }
55 | return snack;
56 | }
57 |
58 | getFood(false); // '허니버터칩'
59 | ```
60 |
61 | 이런 변경점으로 인해 `var`를 사용했던 레거시 코드를 리팩토링할 때 더욱 조심해야 합니다. 무턱대고 `var` 대신 `let`을 사용하면 예상치 못한 동작을 할 수도 있습니다.
62 |
63 | > **Note**: `let`과 `const`는 블록 스코프 식별자입니다. 따라서, 블록 스코프 식별자로 정의하기 전에 참조하게 되면 `ReferenceError`를 발생시킵니다.
64 |
65 | ```javascript
66 | console.log(x);
67 |
68 | let x = 'hi'; // ReferenceError: x is not defined
69 | ```
70 |
71 | > **Best Practice**: 더 조심스럽게 리팩토링하기 위해서 레거시 코드 내에 `var`선언을 남겨두세요. 새로운 코드베이스에서 작업하게 될 때, 변수 사용을 위해서 `let`을 사용하고, 상수 사용을 위해서 `const`를 사용하세요.
72 |
73 | [(목차로 돌아가기)](#목차)
74 |
75 | ## IIFE를 블록으로 교체하기
76 |
77 | > **Immediately Invoked Function Expressions(IIFE)**는 일반적으로 변수들을 별도의 스코프 안에서만 쓰기 위해서 사용되었습니다. ES6에서는 블록을 기반으로 스코프를 만들 수 있게 되었으므로, 더 이상 함수 기반으로 스코프를 만들지 않아도 됩니다.
78 |
79 | ```javascript
80 | (function () {
81 | var food = '허니버터칩';
82 | }());
83 |
84 | console.log(food); // Reference Error
85 | ```
86 |
87 | ES6 블록을 사용하는 경우,
88 |
89 | ```javascript
90 | {
91 | let food = '허니버터칩';
92 | };
93 |
94 | console.log(food); // Reference Error
95 | ```
96 |
97 | [(목차로 돌아가기)](#목차)
98 |
99 | ## 애로우 펑션
100 |
101 | 종종 다음과 같이 중첩된 함수 안에서 `this`의 문맥(context)를 보존해야 할 일이 있습니다.
102 |
103 | ```javascript
104 | function Person(name) {
105 | this.name = name;
106 | }
107 |
108 | Person.prototype.prefixName = function (arr) {
109 | return arr.map(function (character) {
110 | return this.name + character; // Cannot read property 'name' of undefined
111 | });
112 | };
113 | ```
114 |
115 | 보통 이 문제를 해결하기 위해 다음과 같이 별도의 변수를 사용해서 `this`의 문맥을 저장합니다.
116 |
117 | ```javascript
118 | function Person(name) {
119 | this.name = name;
120 | }
121 |
122 | Person.prototype.prefixName = function (arr) {
123 | var that = this; // this의 문맥을 저장합니다.
124 | return arr.map(function (character) {
125 | return that.name + character;
126 | });
127 | };
128 | ```
129 |
130 | 또는, 다음과 같은 방법으로 `this` 문맥을 통과시킬 수도 있습니다.
131 |
132 | ```javascript
133 | function Person(name) {
134 | this.name = name;
135 | }
136 |
137 | Person.prototype.prefixName = function (arr) {
138 | return arr.map(function (character) {
139 | return this.name + character;
140 | }, this);
141 | };
142 | ```
143 |
144 | 이 뿐만 아니라 문맥을 bind 할 수도 있습니다.
145 |
146 | ```javascript
147 | function Person(name) {
148 | this.name = name;
149 | }
150 |
151 | Person.prototype.prefixName = function (arr) {
152 | return arr.map(function (character) {
153 | return this.name + character;
154 | }.bind(this));
155 | };
156 | ```
157 |
158 | **애로우 펑션**을 사용하면, `this`의 문맥 값이 사라지지 않기 때문에 위의 코드는 다음과 같이 다시 쓸 수 있습니다.
159 |
160 | ```javascript
161 | function Person(name) {
162 | this.name = name;
163 | }
164 |
165 | Person.prototype.prefixName = function (arr) {
166 | return arr.map(character => this.name + character);
167 | };
168 | ```
169 |
170 | > **Best Practice**: `this`의 문맥 값을 보존해야할 때마다 **애로우 펑션**을 사용하세요.
171 |
172 | 또한 애로우 펑션은 간단한 값을 리턴하는 함수(함수 표현식)가 필요할 때 사용하면 더욱 간결합니다.
173 |
174 | ```javascript
175 | var squares = arr.map(function (x) { return x * x }); // 함수 표현식
176 | ```
177 |
178 | ```javascript
179 | const arr = [1, 2, 3, 4, 5];
180 | const squares = arr.map(x => x * x); // 간결한 구현을 위한 애로우 펑션
181 | ```
182 |
183 | > **Best Practice**: 가능하다면 함수 표현식 대신 **애로우 펑션**을 활용하세요.
184 |
185 | [(목차로 돌아가기)](#목차)
186 |
187 | ## 문자열
188 |
189 | ES6에서는 표준 라이브러리가 크게 확장되었습니다. 이러한 변경에 맞춰 문자열에도 `.includes()`와 `.repeat()` 같은 새로운 메소드가 추가되었습니다.
190 |
191 | ### .includes( )
192 |
193 | ```javascript
194 | var string = 'food';
195 | var substring = 'foo';
196 |
197 | console.log(string.indexOf(substring) > -1);
198 | ```
199 |
200 | 문자열 포함 여부를 구현하기 위해서 리턴 값이 `-1`보다 큰 지 체크하는 것 대신, 간단하게 불린 값을 리턴하는 `.includes()` 메소드를 사용할 수 있습니다.
201 |
202 | ```javascript
203 | const string = 'food';
204 | const substring = 'foo';
205 |
206 | console.log(string.includes(substring)); // true
207 | ```
208 |
209 | ### .repeat( )
210 |
211 | ```javascript
212 | function repeat(string, count) {
213 | var strings = [];
214 | while(strings.length < count) {
215 | strings.push(string);
216 | }
217 | return strings.join('');
218 | }
219 | ```
220 |
221 | ES6에서는 이제 간결하게 구현할 수 있습니다.
222 |
223 | ```javascript
224 | // String.repeat(numberOfRepetitions)
225 | '야옹'.repeat(3); // '야옹야옹야옹'
226 | ```
227 |
228 | ### 템플릿 리터럴
229 |
230 | **템플릿 리터럴**을 사용하면 명시적인 문자열 이스케이프를 사용하지 않아도 특수문자를 포함한 문자열을 구축할 수 있습니다.
231 |
232 | ```javascript
233 | var text = "이 문자열은 이스케이프 된 \"큰 따옴표\"를 포함합니다.";
234 | ```
235 |
236 | ```javascript
237 | let text = `이 문자열은 이스케이프 된 "큰 따옴표"를 포함합니다.`;
238 | ```
239 |
240 | **템플릿 리터럴**은 문자열과 값을 연결시키는 문자열 Interpolation도 지원합니다.
241 |
242 | ```javascript
243 | var name = '나비';
244 | var age = 13;
245 |
246 | console.log('제 고양이의 이름은 ' + name + '이고, 나이는 ' + age + '살 입니다.');
247 | ```
248 |
249 | 더 간단하게 구현하면,
250 |
251 | ```javascript
252 | const name = '나비';
253 | const age = 13;
254 |
255 | console.log(`제 고양이의 이름은 ${name}이고, 나이는 ${age}살 입니다.`);
256 | ```
257 |
258 | ES5에서는 개행을 구현하기 위해서 다음과 같이 했습니다.
259 |
260 | ```javascript
261 | var text = (
262 | '고양이\n' +
263 | '강아지\n' +
264 | '투니버스'
265 | );
266 | ```
267 |
268 | 혹은 이렇게,
269 |
270 | ```javascript
271 | var text = [
272 | '고양이',
273 | '강아지',
274 | '투니버스'
275 | ].join('\n');
276 | ```
277 |
278 | **템플릿 리터럴**은 명시적으로 표시하지 않아도 개행을 보존합니다.
279 |
280 | ```javascript
281 | let text = ( `고양이
282 | 강아지
283 | 투니버스`
284 | );
285 | ```
286 |
287 | 뿐만 아니라, **템플릿 리터럴**은 표현식에도 접근할 수 있습니다.
288 |
289 | ```javascript
290 | let today = new Date();
291 | let text = `현재 시각은 ${today.toLocaleString()}입니다.`;
292 | ```
293 |
294 | [(목차로 돌아가기)](#목차)
295 |
296 | ## Destructuring
297 |
298 | Destructuring은 배열 혹은 객체(깊게 중첩된 것도 포함하여)에서 편리한 문법을 이용해 값을 추출하고 저장하는데에 활용됩니다.
299 |
300 | ### 배열 Destructuring
301 |
302 | ```javascript
303 | var arr = [1, 2, 3, 4];
304 | var a = arr[0];
305 | var b = arr[1];
306 | var c = arr[2];
307 | var d = arr[3];
308 | ```
309 |
310 | ```javascript
311 | let [a, b, c, d] = [1, 2, 3, 4];
312 |
313 | console.log(a); // 1
314 | console.log(b); // 2
315 | ```
316 |
317 | ### 객체 Destructuring
318 |
319 | ```javascript
320 | var luke = { occupation: 'jedi', father: 'anakin' };
321 | var occupation = luke.occupation; // 'jedi'
322 | var father = luke.father; // 'anakin'
323 | ```
324 |
325 | ```javascript
326 | let luke = { occupation: 'jedi', father: 'anakin' };
327 | let {occupation, father} = luke;
328 |
329 | console.log(occupation); // 'jedi'
330 | console.log(father); // 'anakin'
331 | ```
332 |
333 | [(목차로 돌아가기)](#목차)
334 |
335 | ## 모듈
336 |
337 | ES6 이전엔, 클라이언트 단은 [Browserify](http://browserify.org/), **Node.js**에서는 [require](https://nodejs.org/api/modules.html#modules_module_require_id)같은 라이브러리를 사용했습니다. 이제 ES6에서는 모든 종류(AMD와 CommonJS)의 모듈을 직접적으로 사용할 수 있습니다.
338 |
339 | ### CommonJS의 모듈 내보내기(Export)
340 |
341 | ```javascript
342 | module.exports = 1;
343 | module.exports = { foo: 'bar' };
344 | module.exports = ['foo', 'bar'];
345 | module.exports = function bar () {};
346 | ```
347 |
348 | ### ES6의 모듈 내보내기
349 |
350 | ES6에서는, 다양한 방식으로 모듈을 내보낼 수 있습니다. 그 중 **지정 내보내기(named export)**방식은 다음과 같습니다.
351 |
352 | ```javascript
353 | export let name = 'David';
354 | export let age = 25;
355 | ```
356 |
357 | 또한 객체를 이용한 **리스트 내보내기(exporting a list)**방식도 있습니다.
358 |
359 | ```javascript
360 | function sumTwo(a, b) {
361 | return a + b;
362 | }
363 |
364 | function sumThree(a, b, c) {
365 | return a + b + c;
366 | }
367 |
368 | export { sumTwo, sumThree };
369 | ```
370 |
371 | 간단하게 `export` 키워드만 활용하면 함수, 객체, 값 등을 내보낼 수 있습니다.
372 |
373 | ```javascript
374 | export function sumTwo(a, b) {
375 | return a + b;
376 | }
377 |
378 | export function sumThree(a, b, c) {
379 | return a + b + c;
380 | }
381 | ```
382 |
383 | 마지막으로, **디폴트 모듈로 내보내기(default binding export)**도 가능합니다.
384 |
385 | ```javascript
386 | function sumTwo(a, b) {
387 | return a + b;
388 | }
389 |
390 | function sumThree(a, b, c) {
391 | return a + b + c;
392 | }
393 |
394 | let api = {
395 | sumTwo,
396 | sumThree
397 | };
398 |
399 | export default api;
400 |
401 | /* 위 코드는 아래와 같습니다.
402 | * export { api as default };
403 | */
404 | ```
405 |
406 | > **Best Practice**: `export default`메소드는 항상 모듈 코드의 **마지막**에 위치해야 합니다. 그래야 내보내는 것이 무엇인지 분명해지며, 내보내는 값의 이름을 확인하는 시간을 절약할 수 있습니다. 그 이상으로, CommonJS의 일반적인 관행은 단일 값이나 객체를 내보내는 것입니다. 이런 컨벤션을 따름으로서, 코드의 가독성을 좋게 만들 수 있고 CommonJS와 ES6 모듈을 모두 사용할 수 있게 됩니다.
407 |
408 |
409 | ### ES6의 모듈 불러오기(import)
410 |
411 | ES6에서는 다양한 방식으로 모듈을 불러올 수 있습니다. 다음과 같이 파일 전체를 불러올 수 있습니다.
412 |
413 | ```javascript
414 | import 'underscore';
415 | ```
416 |
417 | > 이렇게 단순히 파일 전체를 불러오면 그 파일의 최상단에서 불러온 모든 코드가 실행된다는 점에 유의하시기 바랍니다.
418 |
419 | 파이썬하고 유사한 지정 불러오기(named import)를 사용할 수 있습니다.
420 |
421 | ```javascript
422 | import { sumTwo, sumThree } from 'math/addition';
423 | ```
424 |
425 | 다음과 같이 불러온 모듈의 이름을 새로 작성할 수도 있습니다.
426 |
427 | ```javascript
428 | import {
429 | sumTwo as addTwoNumbers,
430 | sumThree as sumThreeNumbers
431 | } from 'math/addition';
432 | ```
433 |
434 | 거기에 더해서, **모두 불러오기**(네임스페이스 불러오기)도 가능합니다.
435 |
436 | ```javascript
437 | import * as util from 'math/addition';
438 | ```
439 |
440 | 마지막으로, 모듈에서 값들의 리스트를 불러올 수도 있습니다.
441 |
442 | ```javascript
443 | import * as additionUtil from 'math/addition';
444 | const { sumTwo, sumThree } = additionUtil;
445 | ```
446 |
447 | 디폴트 모듈은 다음과 같이 불러올 수 있습니다.
448 |
449 | ```javascript
450 | import api from 'math/addition';
451 | // 위 코드는 이렇게 표현할 수도 있습니다: import { default as api } from 'math/addition';
452 | ```
453 |
454 | 가급적 간단한 형태로 모듈을 내보내는 것이 좋지만, 필요하다면 때때로 디폴트 모듈을 포함해 여러 이름을 섞어서 내보낼 수도 있습니다.
455 |
456 | ```javascript
457 | // foos.js
458 | export { foo as default, foo1, foo2 };
459 | ```
460 |
461 | 이 모듈은 아래와 같이 불러올 수 있습니다.
462 |
463 | ```javascript
464 | import foo, { foo1, foo2 } from 'foos';
465 | ```
466 |
467 | React처럼 CommonJS 문법을 사용해 내보낸 모듈을 불러올 때는 다음과 같이 쓰면 됩니다.
468 |
469 | ```javascript
470 | import React from 'react';
471 | const { Component, PropTypes } = React;
472 | ```
473 |
474 | 다음과 같이 더욱 간결해질 수도 있습니다.
475 |
476 | ```javascript
477 | import React, { Component, PropTypes } from 'react';
478 | ```
479 |
480 | > **Note**: 내보내지는 값은 참조되는 것이 아니라 **바인딩**되는 것입니다. 그러므로, 어떤 모듈의 변수 바인딩을 바꾸게 되면 내보낸 모듈 내에서만 바뀌게 됩니다. 이렇게 내보낸 모듈의 값의 인터페이스를 바꾸는 것은 피하세요.
481 |
482 | [(목차로 돌아가기)](#목차)
483 |
484 | ## 파라미터(Parameter)
485 |
486 | ES5에서는 **디폴트 값(default values)**이나 **정의되지 않은 인자(indefinite arguments)** 혹은 **네임드 파라미터(named parameters)**를 다루는 함수를 구현하는 방법이 너무 많았습니다. ES6에서는 더욱 간결한 문법을 통해 이것들을 모두 다룰 수 있습니다.
487 |
488 | ### 디폴트 파라미터(Default Parameter)
489 |
490 | ```javascript
491 | function addTwoNumbers(x, y) {
492 | x = x || 0;
493 | y = y || 0;
494 | return x + y;
495 | }
496 | ```
497 |
498 | ES6에서는 함수 내 파라미터의 디폴트 값을 간단하게 설정할 수 있습니다.
499 |
500 | ```javascript
501 | function addTwoNumbers(x=0, y=0) {
502 | return x + y;
503 | }
504 | ```
505 |
506 | ```javascript
507 | addTwoNumbers(2, 4); // 6
508 | addTwoNumbers(2); // 2
509 | addTwoNumbers(); // 0
510 | ```
511 |
512 | ### 레스트 파라미터(Rest Parameter)
513 |
514 | ES5에서는 인수의 숫자가 가변적인 경우 다음과 같이 처리했습니다.
515 |
516 | ```javascript
517 | function logArguments() {
518 | for (var i=0; i < arguments.length; i++) {
519 | console.log(arguments[i]);
520 | }
521 | }
522 | ```
523 |
524 | **레스트(rest)**연산자를 사용하면, 다음과 같이 가변적인 숫자의 인수를 넘길 수 있습니다.
525 |
526 | ```javascript
527 | function logArguments(...args) {
528 | for (let arg of args) {
529 | console.log(arg);
530 | }
531 | }
532 | ```
533 |
534 | ### 네임드 파라미터(Named Parameter)
535 |
536 | ES5의 네임드 파라미터를 처리하는 방법 중 하나는 jQuery에서 차용된 **options object** 패턴을 사용하는 것입니다.
537 |
538 | ```javascript
539 | function initializeCanvas(options) {
540 | var height = options.height || 600;
541 | var width = options.width || 400;
542 | var lineStroke = options.lineStroke || 'black';
543 | }
544 | ```
545 |
546 | 파라미터에 destructuring을 사용하면 같은 기능을 구현할 수 있습니다.
547 |
548 | ```javascript
549 | function initializeCanvas(
550 | { height=600, width=400, lineStroke='black'}) {
551 | // 여기에서 height, width, lineStroke 변수를 사용합니다.
552 | }
553 | ```
554 |
555 | 만약 모든 파라미터를 선택적으로 넘기고 싶다면, 다음과 같이 빈 객체로 destructuring 하면 됩니다.
556 |
557 | ```javascript
558 | function initializeCanvas(
559 | { height=600, width=400, lineStroke='black'} = {}) {
560 | // ...
561 | }
562 | ```
563 |
564 | ### 전개 연산자(Spread Operator)
565 |
566 | ES5에서는 배열 내 숫자들의 최대 값을 찾기 위해서 `Math.max`에 `apply` 메소드를 사용했습니다.
567 |
568 | ```javascript
569 | Math.max.apply(null, [-1, 100, 9001, -32]); // 9001
570 | ```
571 |
572 | ES6에서는 이제 전개 연산자를 이용해서 함수에 파라미터로 배열을 넘길 수 있습니다.
573 |
574 | ```javascript
575 | Math.max(...[-1, 100, 9001, -32]); // 9001
576 | ```
577 |
578 | 다음과 같이 직관적인 문법을 통해 쉽게 배열 리터럴을 합칠 수도 있습니다.
579 |
580 | ```javascript
581 | let cities = ['서울', '부산'];
582 | let places = ['여수', ...cities, '제주']; // ['여수', '서울', '부산', '제주']
583 | ```
584 |
585 | [(목차로 돌아가기)](#목차)
586 |
587 | ## 클래스
588 |
589 | ES6 이전에는, 생성자 함수(constructor)를 만들고 프로토타입을 확장해서 프로퍼티를 추가하여 클래스를 구현했습니다.
590 |
591 | ```javascript
592 | function Person(name, age, gender) {
593 | this.name = name;
594 | this.age = age;
595 | this.gender = gender;
596 | }
597 |
598 | Person.prototype.incrementAge = function () {
599 | return this.age += 1;
600 | };
601 | ```
602 |
603 | 그리고 다음과 같이 클래스를 상속했습니다.
604 |
605 | ```javascript
606 | function Personal(name, age, gender, occupation, hobby) {
607 | Person.call(this, name, age, gender);
608 | this.occupation = occupation;
609 | this.hobby = hobby;
610 | }
611 |
612 | Personal.prototype = Object.create(Person.prototype);
613 | Personal.prototype.constructor = Personal;
614 | Personal.prototype.incrementAge = function () {
615 | Person.prototype.incrementAge.call(this);
616 | this.age += 20;
617 | console.log(this.age);
618 | };
619 | ```
620 |
621 | ES6는 이런 간단한 구현을 위해 편리한 문법을 제공합니다. 이제 클래스를 직접 만들 수 있습니다.
622 |
623 | ```javascript
624 | class Person {
625 | constructor(name, age, gender) {
626 | this.name = name;
627 | this.age = age;
628 | this.gender = gender;
629 | }
630 |
631 | incrementAge() {
632 | this.age += 1;
633 | }
634 | }
635 | ```
636 |
637 | 그리고 `extends` 키워드를 사용해서 상속할 수 있습니다.
638 |
639 | ```javascript
640 | class Personal extends Person {
641 | constructor(name, age, gender, occupation, hobby) {
642 | super(name, age, gender);
643 | this.occupation = occupation;
644 | this.hobby = hobby;
645 | }
646 |
647 | incrementAge() {
648 | super.incrementAge();
649 | this.age += 20;
650 | console.log(this.age);
651 | }
652 | }
653 | ```
654 |
655 | > **Best Practice**: 클래스를 만들기 위한 ES6의 문법은 내부적으로 어떻게 프로토타입으로 구현되는지 모호하지만, 초보자들에게 좋은 기능이고 더 깨끗한 코드를 작성하도록 도와줍니다.
656 |
657 | [(목차로 돌아가기)](#목차)
658 |
659 | ## 심볼(Symbol)
660 |
661 | 심볼은 ES6 이전에도 존재했지만, 이제 직접적으로 심볼을 사용할 수 있는 공식 인터페이스가 제공됩니다. 심볼은 고유하고 수정 불가능한 데이터 타입이고 모든 객체의 식별자로 활용할 수 있습니다.
662 |
663 | ### Symbol()
664 |
665 | `Symbol()` 혹은 `Symbol(description)` 메소드를 호출하면 전역적으로 사용할 수 없는 고유한 심볼이 생성될 것입니다. `Symbol()`은 써드 파티 라이브러리의 객체 혹은 네임스페이스에 충돌할 염려가 없는 새로운 코드를 덧입히는데 종종 쓰입니다. 예를 들어, 나중에 라이브러리가 업데이트 되더라도 겹칠 우려가 없이 `React.Component` 클래스에 `refreshComponent` 메소드를 추가하고 싶다면 다음과 같이 할 수 있습니다.
666 |
667 | ```javascript
668 | const refreshComponent = Symbol();
669 |
670 | React.Component.prototype[refreshComponent] = () => {
671 | // do something
672 | }
673 | ```
674 |
675 |
676 | ### Symbol.for(key)
677 |
678 | `Symbol.for(key)`는 여전히 고유하고 수정 불가능한 심볼을 생성하지만, 전역적으로 사용 가능합니다. `Symbol.for(key)`를 두 번 호출하면 두 번 다 같은 심볼 인스턴스를 반환합니다. 주의하세요. `Symbol(description)`에서는 그렇지 않습니다.
679 |
680 | ```javascript
681 | Symbol('foo') === Symbol('foo') // false
682 | Symbol.for('foo') === Symbol('foo') // false
683 | Symbol.for('foo') === Symbol.for('foo') // true
684 | ```
685 |
686 | 일반적으로 심볼, 특히 `Symbol.for(key)`은 상호 운용성을 위해 사용합니다. 상호 운용성은 몇 가지 알려진 인터페이스를 포함하는 서드 파티 라이브러리의 객체에 인자로 심볼 멤버 형태의 코드를 사용함으로서 만족될 수 있습니다. 예를 들면,
687 |
688 | ```javascript
689 | function reader(obj) {
690 | const specialRead = Symbol.for('specialRead');
691 | if (obj[specialRead]) {
692 | const reader = obj[specialRead]();
693 | // do something with reader
694 | } else {
695 | throw new TypeError('객체를 읽을 수 없습니다.');
696 | }
697 | }
698 | ```
699 |
700 | 또 다른 라이브러리에선 이렇게 할 수 있습니다.
701 |
702 | ```javascript
703 | const specialRead = Symbol.for('specialRead');
704 |
705 | class SomeReadableType {
706 | [specialRead]() {
707 | const reader = createSomeReaderFrom(this);
708 | return reader;
709 | }
710 | }
711 | ```
712 |
713 | > 상호 운용성을 위해 심볼을 사용하는 주목할 만한 예는 모든 반복 가능한(iterable) 타입 혹은 반복자(iterator)에 존재하는 `Symbol.iterator`입니다. 배열, 문자열, 생성자, 등 반복 가능한 타입은 이 메소드를 통해 호출되면 반복자 인터페이스를 포함한 객체 형태로 리턴됩니다.
714 |
715 | [(목차로 돌아가기)](#목차)
716 |
717 | ## 맵(Map)
718 | **맵**은 자바스크립트에서 자주 필요한 데이터 구조입니다. ES6 이전엔 객체를 이용해서 **해시** 맵을 생성했습니다.
719 |
720 | ```javascript
721 | var map = new Object();
722 | map[key1] = 'value1';
723 | map[key2] = 'value2';
724 | ```
725 |
726 | 하지만, 이 방법은 특정 프로퍼티 이름으로 인한 예상치 못한 함수 오버라이드(override)로부터 안전하지 않습니다.
727 |
728 | ```javascript
729 | > getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
730 | > TypeError: Property 'hasOwnProperty' is not a function
731 | ```
732 |
733 | 실제로 **맵**은 값을 위해 `get`, `set` 그리고 `search` 등의 메소드를 제공합니다.
734 |
735 | ```javascript
736 | let map = new Map();
737 | > map.set('name', '현섭');
738 | > map.get('name'); // 현섭
739 | > map.has('name'); // true
740 | ```
741 |
742 | 맵의 가장 놀라운 점은 더 이상 키 값으로 문자열만 쓰지 않아도 된다는 것입니다. 이제 키 값으로 어떤 타입을 전달해도 문자열로 형변환되지 않습니다.
743 |
744 | ```javascript
745 | let map = new Map([
746 | ['이름', '현섭'],
747 | [true, 'false'],
748 | [1, '하나'],
749 | [{}, '객체'],
750 | [function () {}, '함수']
751 | ]);
752 |
753 | for (let key of map.keys()) {
754 | console.log(typeof key);
755 | // > string, boolean, number, object, function
756 | }
757 | ```
758 |
759 | > **Note**: 함수나 객체처럼 기본형 데이터 타입이 아닌 타입을 사용하면 `map.get()`같은 메소드를 사용할 때 비교 연산자가 제대로 동작하지 않습니다. 따라서, 문자열, 불린, 숫자 같은 기본형 데이터 타입을 계속 쓰는 것이 좋습니다.
760 |
761 | 또한 `.entries()`를 사용하면 맵을 순회할 수 있습니다.
762 |
763 | ```javascript
764 | for (let [key, value] of map.entries()) {
765 | console.log(key, value);
766 | }
767 | ```
768 |
769 | [(목차로 돌아가기)](#목차)
770 |
771 | ## 위크맵(WeakMap)
772 |
773 | ES6 이전에는 private 데이터를 저장하기 위해서 많은 방법을 사용했습니다. 그 중 한가지가 네이밍 컨벤션을 이용한 방법이죠.
774 |
775 | ```javascript
776 | class Person {
777 | constructor(age) {
778 | this._age = age;
779 | }
780 |
781 | _incrementAge() {
782 | this._age += 1;
783 | }
784 | }
785 | ```
786 |
787 | 그러나 네이밍 컨벤션은 코드베이스에 대해 혼란을 일으킬 수 있고, 항상 유지된다는 보장을 할 수 없었습니다. 이제 위크맵으로 이런 값을 저장할 수 있습니다.
788 |
789 | ```javascript
790 | let _age = new WeakMap();
791 | class Person {
792 | constructor(age) {
793 | _age.set(this, age);
794 | }
795 |
796 | incrementAge() {
797 | let age = _age.get(this) + 1;
798 | _age.set(this, age);
799 | if (age > 50) {
800 | console.log('중년의 위기');
801 | }
802 | }
803 | }
804 | ```
805 |
806 | Private 데이터를 저장하기 위해 위크맵을 사용해서 좋은 점은 `Reflect.ownKeys()`를 사용해도 프로퍼티 이름을 드러내지 않는다는 것입니다.
807 |
808 | ```javascript
809 | > const person = new Person(50);
810 | > person.incrementAge(); // '중년의 위기'
811 | > Reflect.ownKeys(person); // []
812 | ```
813 |
814 | 위크맵을 사용하는 더욱 실질적인 예는 DOM 요소 자체를 훼손시키지 않고도 DOM 요소에 관련된 데이터를 저장하는 것입니다.
815 |
816 | ```javascript
817 | let map = new WeakMap();
818 | let el = document.getElementById('someElement');
819 |
820 | // 요소에 대한 약한 참조(weak reference)를 저장
821 | map.set(el, '참조');
822 |
823 | // 요소의 값에 접근
824 | let value = map.get(el); // '참조'
825 |
826 | // 참조 제거
827 | el.parentNode.removeChild(el);
828 | el = null;
829 |
830 | value = map.get(el); // undefined
831 | ```
832 |
833 | 위에서 보여준 대로, 객체가 가비지 콜렉터에 의해 한 번 제거된 다음에는 위크맵이 자동적으로 해당 객체에 의해 식별되는 key-value 쌍을 제거합니다.
834 |
835 | > **Note**: 더 나아가, 이 예제의 유용함을 보여주기 위해 jQuery가 참조를 가진 DOM 요소에 대응되는 객체의 캐시를 저장하는 방법을 생각해보세요. 위크맵을 사용하면, jQuery는 문서에서 지워진 특정 DOM 요소에 관련된 모든 메모리를 자동적으로 절약할 수 있습니다. 전반적으로, 위크맵은 DOM 요소를 감싸는 모든 라이브러리에 매우 유용합니다.
836 |
837 | [(목차로 돌아가기)](#목차)
838 |
839 | ## Promise
840 |
841 | Promise는 다음과 같이 수평적인 코드(콜백 지옥)의 형태를 바꿀 수 있게 해줍니다.
842 |
843 | ```javascript
844 | func1(function (value1) {
845 | func2(value1, function (value2) {
846 | func3(value2, function (value3) {
847 | func4(value3, function (value4) {
848 | func5(value4, function (value5) {
849 | // Do something with value 5
850 | });
851 | });
852 | });
853 | });
854 | });
855 | ```
856 |
857 | 수직적인 코드로 바꾸면,
858 |
859 | ```javascript
860 | func1(value1)
861 | .then(func2)
862 | .then(func3)
863 | .then(func4)
864 | .then(func5, value5 => {
865 | // Do something with value 5
866 | });
867 | ```
868 |
869 | ES6 이전엔, [bluebird](https://github.com/petkaantonov/bluebird) 혹은 [Q](https://github.com/kriskowal/q)같은 라이브러리를 사용했었습니다. 이제는 Promise가 네이티브로 지원됩니다.
870 |
871 | ```javascript
872 | new Promise((resolve, reject) =>
873 | reject(new Error('Promise가 제대로 동작하지 않았습니다!')))
874 | .catch(reason => console.log(reason));
875 | ```
876 |
877 | Promise가 제대로 동작(**fulfill**)했을 때 호출되는 **resolve** 메소드와 Promise가 제대로 동작하지 않(**rejected**)았을 때 호출되는 **reject** 메소드를 이용해 Promise를 다룰 수 있습니다.
878 |
879 | > **Promise의 장점**: 중첩된 콜백 코드에서는 에러 핸들링하기가 혼란스럽습니다. Promise를 사용하면 에러를 적절히 위로 깨끗하게 전파할 수 있습니다. 게다가, resolve/reject된 후의 Promise의 값은 불변입니다.
880 |
881 | 아래는 Promise를 사용하는 실질적인 예제입니다.
882 |
883 | ```javascript
884 | var request = require('request');
885 |
886 | return new Promise((resolve, reject) => {
887 | request.get(url, (error, response, body) => {
888 | if (body) {
889 | resolve(JSON.parse(body));
890 | } else {
891 | resolve({});
892 | }
893 | });
894 | });
895 | ```
896 |
897 | 또한 `Promise.all()`을 사용해서 비동기 동작들의 배열을 다루는 Promise를 **병렬화**할 수 있습니다.
898 |
899 | ```javascript
900 | let urls = [
901 | '/api/commits',
902 | '/api/issues/opened',
903 | '/api/issues/assigned',
904 | '/api/issues/completed',
905 | '/api/issues/comments',
906 | '/api/pullrequests'
907 | ];
908 |
909 | let promises = urls.map((url) => {
910 | return new Promise((resolve, reject) => {
911 | $.ajax({ url: url })
912 | .done((data) => {
913 | resolve(data);
914 | });
915 | });
916 | });
917 |
918 | Promise.all(promises)
919 | .then((results) => {
920 | // Do something with results of all our promises
921 | });
922 | ```
923 |
924 | [(목차로 돌아가기)](#목차)
925 |
926 | ## 제너레이터(Generator)
927 |
928 | [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)하고 나중에 표현식의 값을 돌려주는 함수입니다.
929 |
930 | 다음은 제너레이터를 사용하는 간단한 예입니다.
931 |
932 | ```javascript
933 | function* sillyGenerator() {
934 | yield 1;
935 | yield 2;
936 | yield 3;
937 | yield 4;
938 | }
939 |
940 | var generator = sillyGenerator();
941 | > console.log(generator.next()); // { value: 1, done: false }
942 | > console.log(generator.next()); // { value: 2, done: false }
943 | > console.log(generator.next()); // { value: 3, done: false }
944 | > console.log(generator.next()); // { value: 4, done: false }
945 | ```
946 | [next](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next)를 사용하면 제너레이터를 전진시키고 새로운 표현식을 계산합니다. 위의 예제는 극히 부자연스럽지만, 다음과 같이 제너레이터는 비동기적인 코드를 동기적인 방식으로 작성하는데에 활용할 수 있습니다.
947 |
948 | ```javascript
949 | // 제너레이터를 이용해 비동기 동작을 숨김
950 |
951 | function request(url) {
952 | getJSON(url, function(response) {
953 | generator.next(response);
954 | });
955 | }
956 | ```
957 |
958 | 그리고 데이터를 돌려줄 제너레이터 함수를 작성합니다.
959 |
960 | ```javascript
961 | function* getData() {
962 | var entry1 = yield request('http://some_api/item1');
963 | var data1 = JSON.parse(entry1);
964 | var entry2 = yield request('http://some_api/item2');
965 | var data2 = JSON.parse(entry2);
966 | }
967 | ```
968 |
969 | `yield` 덕분에, `data1`에 데이터가 파싱될 필요가 있을 때에만 `entry1`이 데이터를 가질 것임을 보장할 수 있습니다.
970 |
971 | 제너레이터는 비동기적인 코드를 동기적인 방식으로 작성하는데 도움을 주지만, 에러 전파는 깨끗하지 않고 쉽지 않은 경로를 통해 해야 합니다. 그렇기 때문에, Promise를 통해 제너레이터를 보완할 수 있습니다.
972 |
973 | ```javascript
974 | function request(url) {
975 | return new Promise((resolve, reject) => {
976 | getJSON(url, resolve);
977 | });
978 | }
979 | ```
980 |
981 | 그리고 `next`를 사용하는 제너레이터를 통해 단계별로 진행하는 함수를 작성합니다. `next`는 `request` 메소드를 사용하여 위의 Promise를 돌려줍니다.
982 |
983 | ```javascript
984 | function iterateGenerator(gen) {
985 | var generator = gen();
986 | (function iterate(val) {
987 | var ret = generator.next();
988 | if(!ret.done) {
989 | ret.value.then(iterate);
990 | }
991 | })();
992 | }
993 | ```
994 |
995 | Promise를 통해 제너레이터를 보완함으로서, Promise의 `.catch`와 `reject`를 활용해 에러 전파를 깨끗하게 할 수 있게 되었습니다. 다음과 같이 새롭게 개선된 제너레이터를 사용하면 전보다 더 간단합니다.
996 |
997 | ```javascript
998 | iterateGenerator(function* getData() {
999 | var entry1 = yield request('http://some_api/item1');
1000 | var data1 = JSON.parse(entry1);
1001 | var entry2 = yield request('http://some_api/item2');
1002 | var data2 = JSON.parse(entry2);
1003 | });
1004 | ```
1005 |
1006 | 구현했던 코드는 전처럼 제너레이터를 사용하기 위해서 재활용할 수 있습니다. 제너레이터와 Promise가 비동기적인 코드를 동기적인 방식으로 작성하면서도 에러 전파를 좋은 방법으로 하도록 유지시킬 수 있도록 도와줬지만, 사실, [async-await](https://github.com/DrkSephy/es6-cheatsheet/blob/master/README_ko.md#async-await)는 같은 이점을 더 간단한 형태로 활용할 수 있도록 제공합니다.
1007 |
1008 | [(목차로 돌아가기)](#목차)
1009 |
1010 | ## Async Await
1011 |
1012 | 사실 `async await`는 곧 나올 ES2016의 기능이지만, 제너레이터와 Promise를 같이써야 할 수 있었던 것들을 더 적은 노력으로 가능하게 합니다.
1013 |
1014 | ```javascript
1015 | var request = require('request');
1016 |
1017 | function getJSON(url) {
1018 | return new Promise(function(resolve, reject) {
1019 | request(url, function(error, response, body) {
1020 | resolve(body);
1021 | });
1022 | });
1023 | }
1024 |
1025 | async function main() {
1026 | var data = await getJSON();
1027 | console.log(data); // undefined 값이 아님!
1028 | }
1029 |
1030 | main();
1031 | ```
1032 |
1033 | 간단한 구현이지만, 제너레이터와 비슷하게 동작하는 코드입니다. 저는 제너레이터 + Promise보다는 `async-await`를 사용하는 것을 강력하게 추천드립니다. [여기](http://masnun.com/2015/11/11/using-es7-asyncawait-today-with-babel.html)에서 ES7과 Babel을 통해 실행할 수 있는 좋은 예제를 얻을 수 있습니다.
1034 |
1035 | [(목차로 돌아가기)](#목차)
1036 |
1037 | ## Getter와 Setter 함수
1038 |
1039 | ES6는 getter와 setter 함수를 지원하기 시작했습니다. 다음 예제를 보세요.
1040 |
1041 | ```javascript
1042 | class Employee {
1043 |
1044 | constructor(name) {
1045 | this._name = name;
1046 | }
1047 |
1048 | get name() {
1049 | if(this._name) {
1050 | return this._name.toUpperCase() + ' 양';
1051 | } else {
1052 | return undefined;
1053 | }
1054 | }
1055 |
1056 | set name(newName) {
1057 | if (newName == this._name) {
1058 | console.log('이미 같은 이름을 쓰고 있습니다.');
1059 | } else if (newName) {
1060 | this._name = newName;
1061 | } else {
1062 | return false;
1063 | }
1064 | }
1065 | }
1066 |
1067 | var emp = new Employee("솔지");
1068 |
1069 | // 내부적으로 get 메소드를 활용
1070 | if (emp.name) {
1071 | console.log(emp.name); // 솔지 양
1072 | }
1073 |
1074 | // 내부적으로 setter를 활용
1075 | emp.name = "EXID 솔지";
1076 | console.log(emp.name); // EXID 솔지 양
1077 | ```
1078 |
1079 | 가장 최근의 브라우저들은 객체의 getter와 setter 함수를 지원합니다. set/get 전에 리스너와 전처리작업을 추가하여 계산된 프로퍼티를 위해 getter와 settter를 활용할 수 있습니다.
1080 |
1081 | ```javascript
1082 | var person = {
1083 | firstName: '솔지',
1084 | lastName: '허',
1085 | get fullName() {
1086 | console.log('이름 Get');
1087 | return this.lastName + ' ' + this.firstName;
1088 | },
1089 | set fullName (name) {
1090 | console.log('이름 Set');
1091 | var words = name.toString().split(' ');
1092 | this.lastName = words[0] || '';
1093 | this.firstName = words[1] || '';
1094 | }
1095 | }
1096 |
1097 | person.fullName; // 허 솔지
1098 | person.fullName = 'EXID 솔지';
1099 | person.fullName; // EXID 솔지
1100 | ```
1101 | [(목차로 돌아가기)](#목차)
1102 |
1103 | ## License
1104 |
1105 | The MIT License (MIT)
1106 |
1107 | Copyright (c) 2015 David Leonard
1108 |
1109 | Permission is hereby granted, free of charge, to any person obtaining a copy
1110 | of this software and associated documentation files (the "Software"), to deal
1111 | in the Software without restriction, including without limitation the rights
1112 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1113 | copies of the Software, and to permit persons to whom the Software is
1114 | furnished to do so, subject to the following conditions:
1115 |
1116 | The above copyright notice and this permission notice shall be included in all
1117 | copies or substantial portions of the Software.
1118 |
1119 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1120 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1121 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1122 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1123 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1124 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1125 | SOFTWARE.
1126 |
1127 | [(목차로 돌아가기)](#목차)
--------------------------------------------------------------------------------
/README_zhCn.md:
--------------------------------------------------------------------------------
1 | # es6-cheatsheet
2 |
3 | 这是一个 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳实践和一些代码片段,帮助你
4 | 完成日复一日的开发工作。
5 |
6 | ## Table of Contents
7 |
8 | - [var 与 let / const 声明](#var-versus-let--const)
9 | - [代码执行块替换立即执行函数](#replacing-iifes-with-blocks)
10 | - [箭头函数](#arrow-functions)
11 | - [字符串](#strings)
12 | - [解构](#destructuring)
13 | - [模块](#modules)
14 | - [参数](#parameters)
15 | - [类](#classes)
16 | - [Symbols](#symbols)
17 | - [Maps](#maps)
18 | - [WeakMaps](#weakmaps)
19 | - [Promises](#promises)
20 | - [Generators](#generators)
21 | - [Async Await](#async-await)
22 | - [License](#license)
23 |
24 | ## var versus let / const
25 |
26 | > 除了 `var` 以外,我们现在多了两个新的标识符来声明变量的存储,它们就是 `let` 和 `const`。
27 | 不同于 `var` ,`let` 和 `const` 语句不会造成声明提升。
28 |
29 | 一个 `var` 的例子:
30 |
31 | ```javascript
32 | var snack = 'Meow Mix';
33 |
34 | function getFood(food) {
35 | if (food) {
36 | var snack = 'Friskies';
37 | return snack;
38 | }
39 | return snack;
40 | }
41 |
42 | getFood(false); // undefined
43 | ```
44 |
45 | 让我们再观察下面语句中,使用 `let` 替换了 `var` 后的表现:
46 |
47 | ```javascript
48 | let snack = 'Meow Mix';
49 |
50 | function getFood(food) {
51 | if (food) {
52 | let snack = 'Friskies';
53 | return snack;
54 | }
55 | return snack;
56 | }
57 |
58 | getFood(false); // 'Meow Mix'
59 | ```
60 |
61 | 当我们重构使用 `var` 的老代码时,一定要注意这种变化。盲目使用 `let` 替换 `var` 后可能会导致预期意外的结果。
62 |
63 | > **注意**:`let` 和 `const` 是块级作用域语句。所以在语句块以外引用这些变量时,会造成引用错误 `ReferenceError`。
64 |
65 | ```javascript
66 | console.log(x);
67 |
68 | let x = 'hi'; // ReferenceError: x is not defined
69 | ```
70 |
71 | > **最佳实践**: 在重构老代码时,`var` 声明需要格外的注意。在创建一个新项目时,使用 `let` 声明一个变量,使用 `const` 来声明一个不可改变的常量。
72 |
73 | [(回到目录)](#table-of-contents)
74 |
75 | ## Replacing IIFEs with Blocks
76 |
77 | 我们以往创建一个 **立即执行函数** 时,一般是在函数最外层包裹一层括号。
78 | ES6支持块级作用域(更贴近其他语言),我们现在可以通过创建一个代码块(Block)来实现,不必通过创建一个函数来实现,
79 |
80 | ```javascript
81 | (function () {
82 | var food = 'Meow Mix';
83 | }());
84 |
85 | console.log(food); // Reference Error
86 | ```
87 |
88 | 使用支持块级作用域的ES6的版本:
89 |
90 | ```javascript
91 | {
92 | let food = 'Meow Mix';
93 | };
94 |
95 | console.log(food); // Reference Error
96 | ```
97 |
98 | [(回到目录)](#table-of-contents)
99 |
100 | ## Arrow Functions
101 |
102 | 一些时候,我们在函数嵌套中需要访问上下文中的 `this`。比如下面的例子:
103 |
104 | ```javascript
105 | function Person(name) {
106 | this.name = name;
107 | }
108 |
109 | Person.prototype.prefixName = function (arr) {
110 | return arr.map(function (character) {
111 | return this.name + character; // Cannot read property 'name' of undefined
112 | });
113 | };
114 | ```
115 |
116 | 一种通用的方式是把上下文中的 `this` 保存在一个变量里:
117 |
118 | ```javascript
119 | function Person(name) {
120 | this.name = name;
121 | }
122 |
123 | Person.prototype.prefixName = function (arr) {
124 | var that = this; // Store the context of this
125 | return arr.map(function (character) {
126 | return that.name + character;
127 | });
128 | };
129 | ```
130 |
131 | 我们也可以把 `this` 通过属性传进去:
132 |
133 | ```javascript
134 | function Person(name) {
135 | this.name = name;
136 | }
137 |
138 | Person.prototype.prefixName = function (arr) {
139 | return arr.map(function (character) {
140 | return this.name + character;
141 | }, this);
142 | };
143 | ```
144 |
145 | 还可以直接使用 `bind`:
146 |
147 | ```javascript
148 | function Person(name) {
149 | this.name = name;
150 | }
151 |
152 | Person.prototype.prefixName = function (arr) {
153 | return arr.map(function (character) {
154 | return this.name + character;
155 | }.bind(this));
156 | };
157 | ```
158 |
159 | 使用 **箭头函数**,`this` 的值不用我们再做如上几段代码的特殊处理,直接使用即可。
160 | 上面的代码可以重写为下面这样:
161 |
162 | ```javascript
163 | function Person(name) {
164 | this.name = name;
165 | }
166 |
167 | Person.prototype.prefixName = function (arr) {
168 | return arr.map(character => this.name + character);
169 | };
170 | ```
171 |
172 | > **最佳实践**:使用箭头函数,再也不用考虑 `this` 的问题了。
173 |
174 | 当我们编写只返回一个表达式值的简单函数时,也可以使用箭头函数,如下:
175 |
176 | ```javascript
177 | var squares = arr.map(function (x) { return x * x }); // Function Expression
178 | ```
179 |
180 | ```javascript
181 | const arr = [1, 2, 3, 4, 5];
182 | const squares = arr.map(x => x * x); // Arrow Function for terser implementation
183 | ```
184 |
185 | > **最佳实践**:尽可能地多使用 **箭头函数**。
186 |
187 | [(回到目录)](#table-of-contents)
188 |
189 | ## Strings
190 |
191 | 在ES6中,标准库也被同样增强了,像字符串对象就新增了 `.includes()` 和 `.repeat()` 方法。
192 |
193 | ### .includes( )
194 |
195 | ```javascript
196 | var string = 'food';
197 | var substring = 'foo';
198 |
199 | console.log(string.indexOf(substring) > -1);
200 | ```
201 |
202 | 现在,我们可以使用 `.inclues()` 方法,替代以往判断内容 `> -1` 的方式。
203 | `.includes()` 方法会极简地返回一个布尔值结果。
204 |
205 | ```javascript
206 | const string = 'food';
207 | const substring = 'foo';
208 |
209 | console.log(string.includes(substring)); // true
210 | ```
211 |
212 | ### .repeat( )
213 |
214 | ```javascript
215 | function repeat(string, count) {
216 | var strings = [];
217 | while(strings.length < count) {
218 | strings.push(string);
219 | }
220 | return strings.join('');
221 | }
222 | ```
223 |
224 | 在ES6中,我们可以使用一个极简的方法来实现重复字符:
225 |
226 | ```javascript
227 | // String.repeat(numberOfRepetitions)
228 | 'meow'.repeat(3); // 'meowmeowmeow'
229 | ```
230 |
231 | ### Template Literals
232 |
233 | 使用 **字符串模板字面量**,我可以在字符串中直接使用特殊字符,而不用转义。
234 |
235 | ```javascript
236 | var text = "This string contains \"double quotes\" which are escaped.";
237 | ```
238 |
239 | ```javascript
240 | let text = `This string contains "double quotes" which don't need to be escaped anymore.`;
241 | ```
242 |
243 | **字符串模板字面量** 还支持直接插入变量,可以实现字符串与变量的直接连接输出。
244 |
245 | ```javascript
246 | var name = 'Tiger';
247 | var age = 13;
248 |
249 | console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
250 | ```
251 |
252 | 更简单的版本:
253 |
254 | ```javascript
255 | const name = 'Tiger';
256 | const age = 13;
257 |
258 | console.log(`My cat is named ${name} and is ${age} years old.`);
259 | ```
260 |
261 | ES5中,我们要这样生成多行文本:
262 |
263 | ```javascript
264 | var text = (
265 | 'cat\n' +
266 | 'dog\n' +
267 | 'nickelodeon'
268 | );
269 | ```
270 |
271 | 或者:
272 |
273 | ```javascript
274 | var text = [
275 | 'cat',
276 | 'dog',
277 | 'nickelodeon'
278 | ].join('\n');
279 | ```
280 |
281 | **字符串模板字面量** 让我们不必特别关注多行字符串中的换行转义符号,直接换行即可:
282 |
283 | ```javascript
284 | let text = ( `cat
285 | dog
286 | nickelodeon`
287 | );
288 | ```
289 |
290 | **字符串模板字面量** 内部可以使用表达式,像这样:
291 |
292 | ```javascript
293 | let today = new Date();
294 | let text = `The time and date is ${today.toLocaleString()}`;
295 | ```
296 |
297 | [(回到目录)](#table-of-contents)
298 |
299 | ## Destructuring
300 |
301 | 解构让我们可以使用非常便捷的语法,直接将数组或者对象中的值直接分别导出到多个变量中,
302 |
303 | ### Destructuring Arrays
304 |
305 | **解构数组**
306 |
307 | ```javascript
308 | var arr = [1, 2, 3, 4];
309 | var a = arr[0];
310 | var b = arr[1];
311 | var c = arr[2];
312 | var d = arr[3];
313 | ```
314 |
315 | ```javascript
316 | let [a, b, c, d] = [1, 2, 3, 4];
317 |
318 | console.log(a); // 1
319 | console.log(b); // 2
320 | ```
321 |
322 | ### Destructuring Objects
323 |
324 | **解构对象**
325 |
326 | ```javascript
327 | var luke = { occupation: 'jedi', father: 'anakin' };
328 | var occupation = luke.occupation; // 'jedi'
329 | var father = luke.father; // 'anakin'
330 | ```
331 |
332 | ```javascript
333 | let luke = { occupation: 'jedi', father: 'anakin' };
334 | let {occupation, father} = luke;
335 |
336 | console.log(occupation); // 'jedi'
337 | console.log(father); // 'anakin'
338 | ```
339 |
340 | [(回到目录)](#table-of-contents)
341 |
342 | ## Modules
343 |
344 | ES6之前,浏览器端的模块化代码,我们使用像[Browserify](http://browserify.org/)这样的库,
345 | 在 **Node.js** 中,我们则使用 [require](https://nodejs.org/api/modules.html#modules_module_require_id)。
346 | 在ES6中,我们现在可以直接使用AMD 和 CommonJS这些模块了。
347 |
348 | ### Exporting in CommonJS
349 |
350 | ```javascript
351 | module.exports = 1;
352 | module.exports = { foo: 'bar' };
353 | module.exports = ['foo', 'bar'];
354 | module.exports = function bar () {};
355 | ```
356 |
357 | ### Exporting in ES6
358 |
359 | 在ES6中,提供了多种设置模块出口的方式,比如我们要导出一个变量,那么使用 **变量名** :
360 |
361 | ```javascript
362 | export let name = 'David';
363 | export let age = 25;
364 | ```
365 |
366 | 还可以为对象 **导出一个列表**:
367 |
368 | ```javascript
369 | function sumTwo(a, b) {
370 | return a + b;
371 | }
372 |
373 | function sumThree(a, b, c) {
374 | return a + b + c;
375 | }
376 |
377 | export { sumTwo, sumThree };
378 | ```
379 |
380 | 我们也可以使用简单的一个 `export` 关键字来导出一个结果值:
381 |
382 | ```javascript
383 | export function sumTwo(a, b) {
384 | return a + b;
385 | }
386 |
387 | export function sumThree(a, b, c) {
388 | return a + b + c;
389 | }
390 | ```
391 |
392 | 最后,我们可以 **导出一个默认出口**:
393 |
394 | ```javascript
395 | function sumTwo(a, b) {
396 | return a + b;
397 | }
398 |
399 | function sumThree(a, b, c) {
400 | return a + b + c;
401 | }
402 |
403 | let api = {
404 | sumTwo,
405 | sumThree
406 | };
407 |
408 | export default api;
409 |
410 | /*
411 | * 与以下的语句是对等的:
412 | * export { api as default };
413 | */
414 | ```
415 |
416 | > **最佳实践**:总是在模块的 **最后** 使用 `export default` 方法。
417 | 它让模块的出口更清晰明了,节省了阅读整个模块来寻找出口的时间。
418 | 更多的是,在大量CommonJS模块中,通用的习惯是设置一个出口值或者出口对象。
419 | 坚持这个规则,可以让我们的代码更易读,且更方便的联合使用CommonJS和ES6模块。
420 |
421 | ### Importing in ES6
422 |
423 | ES6提供了好几种模块的导入方式。我们可以单独引入一个文件:
424 |
425 | ```javascript
426 | import 'underscore';
427 | ```
428 |
429 | > 这里需要注意的是, **整个文件的引入方式会执行该文件内的最上层代码**。
430 |
431 | 就像Python一样,我们还可以命名引用:
432 |
433 | ```javascript
434 | import { sumTwo, sumThree } from 'math/addition';
435 | ```
436 |
437 | 我们甚至可以使用 `as` 给这些模块重命名:
438 |
439 | ```javascript
440 | import {
441 | sumTwo as addTwoNumbers,
442 | sumThree as sumThreeNumbers
443 | } from 'math/addition';
444 | ```
445 |
446 | 另外,我们能 **引入所有的东西(原文:import all the things)** (也称为命名空间引入)
447 |
448 | ```javascript
449 | import * as util from 'math/addition';
450 | ```
451 |
452 | 最后,我们能可以从一个模块的众多值中引入一个列表:
453 |
454 | ```javascript
455 | import * as additionUtil from 'math/addtion';
456 | const { sumTwo, sumThree } = additionUtil;
457 | ```
458 | 像这样引用默认对象:
459 |
460 | ```javascript
461 | import api from 'math/addition';
462 | // Same as: import { default as api } from 'math/addition';
463 | ```
464 |
465 | 我们建议一个模块导出的值应该越简洁越好,不过有时候有必要的话命名引用和默认引用可以混着用。如果一个模块是这样导出的:
466 |
467 | ```javascript
468 | // foos.js
469 | export { foo as default, foo1, foo2 };
470 | ```
471 | 那我们可以如此导入这个模块的值:
472 |
473 | ```javaqscript
474 | import foo, { foo1, foo2 } from 'foos';
475 | ```
476 |
477 | 我们还可以导入commonjs模块,例如React:
478 |
479 | ```javascript
480 | import React from 'react';
481 | const { Component, PropTypes } = React;
482 | ```
483 |
484 | 更简化版本:
485 |
486 | ```javascript
487 | import React, { Component, PropTypes } from 'react';
488 | ```
489 |
490 | > **注意**:被导出的值是被 **绑定的(原文:bingdings)**,而不是引用。
491 | 所以,改变一个模块中的值的话,会影响其他引用本模块的代码,一定要避免此种改动发生。
492 |
493 | [(回到目录)](#table-of-contents)
494 |
495 | ## Parameters
496 |
497 | 在ES5中,许多种方法来处理函数的 **参数默认值(default values)**,**参数数量(indefinite arguments)**,**参数命名(named parameters)**。
498 | ES6中,我们可以使用非常简洁的语法来处理上面提到的集中情况。
499 |
500 | ### Default Parameters
501 |
502 | ```javascript
503 | function addTwoNumbers(x, y) {
504 | x = x || 0;
505 | y = y || 0;
506 | return x + y;
507 | }
508 | ```
509 |
510 | ES6中,我们可以简单为函数参数启用默认值:
511 |
512 | ```javascript
513 | function addTwoNumbers(x=0, y=0) {
514 | return x + y;
515 | }
516 | ```
517 |
518 | ```javascript
519 | addTwoNumbers(2, 4); // 6
520 | addTwoNumbers(2); // 2
521 | addTwoNumbers(); // 0
522 | ```
523 |
524 | ### Rest Parameters
525 |
526 | ES5中,遇到参数数量不确定时,我们只能如此处理:
527 |
528 | ```javascript
529 | function logArguments() {
530 | for (var i=0; i < arguments.length; i++) {
531 | console.log(arguments[i]);
532 | }
533 | }
534 | ```
535 |
536 | 使用 **rest** 操作符,我们可以给函数传入一个不确定数量的参数列表:
537 |
538 | ```javascript
539 | function logArguments(...args) {
540 | for (let arg of args) {
541 | console.log(arg);
542 | }
543 | }
544 | ```
545 |
546 | ### Named Parameters
547 |
548 | 命名函数
549 | ES5中,当我们要处理多个 **命名参数** 时,通常会传入一个 **选项对象** 的方式,这种方式被jQuery采用。
550 |
551 | ```javascript
552 | function initializeCanvas(options) {
553 | var height = options.height || 600;
554 | var width = options.width || 400;
555 | var lineStroke = options.lineStroke || 'black';
556 | }
557 | ```
558 |
559 | 我们可以利用上面提到的新特性 **解构** ,来完成与上面同样功能的函数:
560 | We can achieve the same functionality using destructuring as a formal parameter
561 | to a function:
562 |
563 | ```javascript
564 | function initializeCanvas(
565 | { height=600, width=400, lineStroke='black'}) {
566 | // ...
567 | }
568 | // Use variables height, width, lineStroke here
569 | ```
570 |
571 | 如果我们需要把这个参数变为可选的,那么只要把该参数解构为一个空对象就好了:
572 |
573 | ```javascript
574 | function initializeCanvas(
575 | { height=600, width=400, lineStroke='black'} = {}) {
576 | // ...
577 | }
578 | ```
579 |
580 | ### Spread Operator
581 |
582 | 我们可以利用展开操作符(Spread Operator)来把一组数组的值,当作参数传入:
583 |
584 | ```javascript
585 | Math.max(...[-1, 100, 9001, -32]); // 9001
586 | ```
587 |
588 | [(回到目录)](#table-of-contents)
589 |
590 | ## Classes
591 |
592 | 在ES6以前,我们实现一个类的功能的话,需要首先创建一个构造函数,然后扩展这个函数的原型方法,就像这样:
593 |
594 | ```javascript
595 | function Person(name, age, gender) {
596 | this.name = name;
597 | this.age = age;
598 | this.gender = gender;
599 | }
600 |
601 | Person.prototype.incrementAge = function () {
602 | return this.age += 1;
603 | };
604 | ```
605 |
606 | 继承父类的子类需要这样:
607 |
608 | ```javascript
609 | function Personal(name, age, gender, occupation, hobby) {
610 | Person.call(this, name, age, gender);
611 | this.occupation = occupation;
612 | this.hobby = hobby;
613 | }
614 |
615 | Personal.prototype = Object.create(Person.prototype);
616 | Personal.prototype.constructor = Personal;
617 | Personal.prototype.incrementAge = function () {
618 | return Person.prototype.incrementAge.call(this) += 20;
619 | };
620 | ```
621 |
622 | ES6提供了一些语法糖来实现上面的功能,我们可以直接创建一个类:
623 |
624 | ```javascript
625 | class Person {
626 | constructor(name, age, gender) {
627 | this.name = name;
628 | this.age = age;
629 | this.gender = gender;
630 | }
631 |
632 | incrementAge() {
633 | this.age += 1;
634 | }
635 | }
636 | ```
637 |
638 | 继承父类的子类只要简单的使用 `extends` 关键字就可以了:
639 |
640 | ```javascript
641 | class Personal extends Person {
642 | constructor(name, age, gender, occupation, hobby) {
643 | super(name, age, gender);
644 | this.occupation = occupation;
645 | this.hobby = hobby;
646 | }
647 |
648 | incrementAge() {
649 | super.incrementAge();
650 | this.age += 20;
651 | console.log(this.age);
652 | }
653 | }
654 | ```
655 |
656 | > **最佳实践**:ES6新的类语法把我们从晦涩难懂的实现和原型操作中解救出来,这是个非常适合初学者的功能,而且能让我们写出更干净整洁的代码。
657 |
658 | [(回到目录)](#table-of-contents)
659 |
660 | ## Symbols
661 |
662 | Symbols在ES6版本之前就已经存在了,但现在我们拥有一个公共的接口来直接使用它们。
663 | Symbols是不可更改的(immutable)并且唯一的(unique),它可用作任何hash数据类型中的键。
664 |
665 | ### Symbol( )
666 | 调用 `Symbol()` 或者 `Symbol(描述文本)` 会创建一个唯一的、在全局中不可以访问的Symbol对象。
667 | 一个 `Symbol()` 的应用场景是:在自己的项目中使用第三方代码库,且你需要给他们的对象或者命名空间打补丁代码,又不想改动或升级第三方原有代码的时候。
668 | 例如,如果你想给 `React.Component` 这个类添加一个 `refreshComponent` 方法,但又确定不了这个方法会不会在下个版本中加入,你可以这么做:
669 |
670 | ```javascript
671 | const refreshComponent = Symbol();
672 |
673 | React.Component.prototype[refreshComponent] = () => {
674 | // do something
675 | }
676 | ```
677 |
678 | ### Symbol.for(key)
679 |
680 | 使用 `Symbol.for(key)` 也是会创建一个不可改变的Symbol对象,但区别于上面的创建方法,这个对象是在全局中可以被访问到的。
681 | 两次相同的 `Symbol.for(key)` 调用会返回相同的Symbol实例。
682 |
683 | **提示**:这并不同于 `Symbol(description)`。
684 |
685 | ```javascript
686 | Symbol('foo') === Symbol('foo') // false
687 | Symbol.for('foo') === Symbol('foo') // false
688 | Symbol.for('foo') === Symbol.for('foo') // true
689 | ```
690 |
691 | Symbols常用的一个使用场景,尤其是使用 `Symbol.for(key)` 方法,是用于实现代码间的互操作。
692 | 在你的代码中,通过在包含一些已知接口的第三方库的对象参数中查找Symbol成员,你可以实现这种互操作。
693 | 例如:
694 |
695 | ```javascript
696 | function reader(obj) {
697 | const specialRead = Symbol.for('specialRead');
698 | if (obj[specialRead]) {
699 | const reader = obj[specialRead]();
700 | // do something with reader
701 | } else {
702 | throw new TypeError('object cannot be read');
703 | }
704 | }
705 | ```
706 |
707 | 之后在另一个库中:
708 |
709 | ```javascript
710 | const specialRead = Symbol.for('specialRead');
711 |
712 | class SomeReadableType {
713 | [specialRead]() {
714 | const reader = createSomeReaderFrom(this);
715 | return reader;
716 | }
717 | }
718 | ```
719 |
720 | > **注意**:关于Symbol互操作的使用,一个值得一提的例子是`Symbol.iterable` 。`Symbol.iterable`存在ES6的所有可枚举对象中:数组(Arrays)、
721 | > 字符串(strings)、生成器(Generators)等等。当它作为一个方法被调用时,它将会返回一个带有枚举接口的对象。
722 |
723 | [(回到目录)](#table-of-contents)
724 |
725 | ## Maps
726 |
727 | **Maps** 是一个JavaScript中很重要(迫切需要)的数据结构。
728 | 在ES6之前,我们创建一个 **hash** 通常是使用一个对象:
729 |
730 | ```javascript
731 | var map = new Object();
732 | map[key1] = 'value1';
733 | map[key2] = 'value2';
734 | ```
735 |
736 | 但是,这样的代码无法避免函数被特别的属性名覆盖的意外情况:
737 |
738 | ```javascript
739 | > getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
740 | > TypeError: Property 'hasOwnProperty' is not a function
741 | ```
742 |
743 | **Maps** 让我们使用 `set`,`get` 和 `search` 操作数据。
744 |
745 | ```javascript
746 | let map = new Map();
747 | > map.set('name', 'david');
748 | > map.get('name'); // david
749 | > map.has('name'); // true
750 | ```
751 |
752 | Maps最强大的地方在于我们不必只能使用字符串来做key了,现在可以使用任何类型来当作key,而且key不会被强制类型转换为字符串。
753 |
754 | ```javascript
755 | let map = new Map([
756 | ['name', 'david'],
757 | [true, 'false'],
758 | [1, 'one'],
759 | [{}, 'object'],
760 | [function () {}, 'function']
761 | ]);
762 |
763 | for (let key of map.keys()) {
764 | console.log(typeof key);
765 | // > string, boolean, number, object, function
766 | }
767 | ```
768 |
769 | > **提示**:当使用 `map.get()` 判断值是否相等时,非基础类型比如一个函数或者对象,将不会正常工作。
770 | 有鉴于此,还是建议使用字符串,布尔和数字类型的数据类型。
771 |
772 | 我们还可以使用 `.entries()` 方法来遍历整个map对象:
773 |
774 | ```javascript
775 | for (let [key, value] of map.entries()) {
776 | console.log(key, value);
777 | }
778 | ```
779 |
780 | [(回到目录)](#table-of-contents)
781 |
782 | ## WeakMaps
783 |
784 | 在ES5之前的版本,我们为了存储私有数据,有好几种方法。像使用这种下划线命名约定:
785 |
786 | ```javascript
787 | class Person {
788 | constructor(age) {
789 | this._age = age;
790 | }
791 |
792 | _incrementAge() {
793 | this._age += 1;
794 | }
795 | }
796 | ```
797 |
798 | 在一个开源项目中,命名规则很难维持得一直很好,这样经常会造成一些困扰。
799 | 此时,我们可以选择使用WeakMaps来替代Maps来存储我们的数据:
800 |
801 | ```javascript
802 | let _age = new WeakMap();
803 | class Person {
804 | constructor(age) {
805 | _age.set(this, age);
806 | }
807 |
808 | incrementAge() {
809 | let age = _age.get(this) + 1;
810 | _age.set(this, age);
811 | if (age > 50) {
812 | console.log('Midlife crisis');
813 | }
814 | }
815 | }
816 | ```
817 |
818 | 使用WeakMaps来保存我们私有数据的理由之一是不会暴露出属性名,就像下面的例子中的 `Reflect.ownKeys()`:
819 |
820 | ```javascript
821 | > const person = new Person(50);
822 | > person.incrementAge(); // 'Midlife crisis'
823 | > Reflect.ownKeys(person); // []
824 | ```
825 |
826 | 一个使用WeakMaps存储数据更实际的例子,是存储与DOM元素相关联的数据,而这不会对DOM元素本身产生污染:
827 |
828 | ```javascript
829 | let map = new WeakMap();
830 | let el = document.getElementById('someElement');
831 |
832 | // Store a weak reference to the element with a key
833 | map.set(el, 'reference');
834 |
835 | // Access the value of the element
836 | let value = map.get(el); // 'reference'
837 |
838 | // Remove the reference
839 | el.parentNode.removeChild(el);
840 | el = null;
841 |
842 | value = map.get(el); // undefined
843 | ```
844 |
845 | 上面的例子中,一旦对象被垃圾回收器给销毁了,WeakMaps会自动的把这个对象所对应的键值对数据同时销毁。
846 |
847 | > **提示**:结合这个例子,再考虑下jQuery是如何实现缓存带有引用的DOM元素这个功能的。使用WeakMaps的话,当被缓存的DOM元素被移除的时,jQuery可以自动释放相应元素的内存。
848 | 通常情况下,在涉及DOM元素存储和缓存的情况下,使用WeakMaps是非常有效的。
849 |
850 | [(回到目录)](#table-of-contents)
851 |
852 | ## Promises
853 |
854 | Promises让我们把多缩进难看的代码(回调地狱):
855 |
856 | ```javascript
857 | func1(function (value1) {
858 | func2(value1, function (value2) {
859 | func3(value2, function (value3) {
860 | func4(value3, function (value4) {
861 | func5(value4, function (value5) {
862 | // Do something with value 5
863 | });
864 | });
865 | });
866 | });
867 | });
868 | ```
869 |
870 | 写成这样:
871 |
872 | ```javascript
873 | func1(value1)
874 | .then(func2)
875 | .then(func3)
876 | .then(func4)
877 | .then(func5, value5 => {
878 | // Do something with value 5
879 | });
880 | ```
881 |
882 | 在ES6之前,我们使用[bluebird](https://github.com/petkaantonov/bluebird) 或者
883 | [Q](https://github.com/kriskowal/q)。现在我们有了原生版本的 Promises:
884 |
885 | ```javascript
886 | new Promise((resolve, reject) =>
887 | reject(new Error('Failed to fulfill Promise')))
888 | .catch(reason => console.log(reason));
889 | ```
890 |
891 | 这里有两个处理函数,**resolve**(当Promise执行成功完毕时调用的回调函数) 和 **reject** (当Promise执行不接受时调用的回调函数)
892 |
893 | > **Promises的好处**:大量嵌套错误处理回调函数会使代码变得难以阅读理解。
894 | 使用Promises,我们可以通过清晰的路径将错误事件让上传递,并且适当地处理它们。
895 | 此外,Promise处理后的值,无论是解决(resolved)还是拒绝(rejected)的结果值,都是不可改变的。
896 |
897 | 下面是一些使用Promises的实际例子:
898 |
899 | ```javascript
900 | var request = require('request');
901 |
902 | return new Promise((resolve, reject) => {
903 | request.get(url, (error, response, body) => {
904 | if (body) {
905 | resolve(JSON.parse(body));
906 | } else {
907 | resolve({});
908 | }
909 | });
910 | });
911 | ```
912 |
913 | 我们还可以使用 `Promise.all()` 来 **并行化** 的处理一组异步的操作。
914 |
915 | ```javascript
916 | let urls = [
917 | '/api/commits',
918 | '/api/issues/opened',
919 | '/api/issues/assigned',
920 | '/api/issues/completed',
921 | '/api/issues/comments',
922 | '/api/pullrequests'
923 | ];
924 |
925 | let promises = urls.map((url) => {
926 | return new Promise((resolve, reject) => {
927 | $.ajax({ url: url })
928 | .done((data) => {
929 | resolve(data);
930 | });
931 | });
932 | });
933 |
934 | Promise.all(promises)
935 | .then((results) => {
936 | // Do something with results of all our promises
937 | });
938 | ```
939 |
940 | [(回到目录)](#table-of-contents)
941 |
942 | ## Generators
943 |
944 | 就像[Promises](https://github.com/DrkSephy/es6-cheatsheet#promises)如何让我们避免[回调地狱](http://callbackhell.com/)一样,Generators也可以使我们的代码扁平化,同时给予我们开发者像开发同步代码一样的感觉来写异步代码。Generators本质上是一种支持的函数,随后返回表达式的值。
945 | Generators实际上是支持[暂停运行](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield),随后根据上一步的返回值再继续运行的一种函数。
946 |
947 | 下面代码是一个使用generators函数的简单例子:
948 |
949 | ```javascript
950 | function* sillyGenerator() {
951 | yield 1;
952 | yield 2;
953 | yield 3;
954 | yield 4;
955 | }
956 |
957 | var generator = sillyGenerator();
958 | > console.log(generator.next()); // { value: 1, done: false }
959 | > console.log(generator.next()); // { value: 2, done: false }
960 | > console.log(generator.next()); // { value: 3, done: false }
961 | > console.log(generator.next()); // { value: 4, done: false }
962 | ```
963 |
964 | 就像上面的例子,当[next](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next)运行时,它会把我们的generator向前“推动”,同时执行新的表达式。
965 | 我们能利用Generators来像书写同步代码一样书写异步代码。
966 |
967 | ```javascript
968 | // Hiding asynchronousity with Generators
969 |
970 | function request(url) {
971 | getJSON(url, function(response) {
972 | generator.next(response);
973 | });
974 | }
975 | ```
976 |
977 | 这里我们写个generator函数将要返回我们的数据:
978 |
979 | ```javascript
980 | function* getData() {
981 | var entry1 = yield request('http://some_api/item1');
982 | var data1 = JSON.parse(entry1);
983 | var entry2 = yield request('http://some_api/item2');
984 | var data2 = JSON.parse(entry2);
985 | }
986 | ```
987 |
988 | 借助于 `yield`,我们可以保证 `entry1` 确实拿到数据并转换后再赋值给 `data1`。
989 |
990 | 当我们使用generators来像书写同步代码一样书写我们的异步代码逻辑时,没有一种清晰简单的方式来处理期间可能会产生的错误或者异常。在这种情况下,我们可以在我们的generator中引入Promises来处理,就像下面这样:
991 |
992 | ```javascript
993 | function request(url) {
994 | return new Promise((resolve, reject) => {
995 | getJSON(url, resolve);
996 | });
997 | }
998 | ```
999 |
1000 | 我们再写一个函数,其中使用 `next` 来步进我们的generator的同事,再利用我们上面的 `request` 方法来产生(yield)一个Promise。
1001 |
1002 | ```javascript
1003 | function iterateGenerator(gen) {
1004 | var generator = gen();
1005 | var ret;
1006 | (function iterate(val) {
1007 | ret = generator.next();
1008 | if(!ret.done) {
1009 | ret.value.then(iterate);
1010 | }
1011 | })();
1012 | }
1013 | ```
1014 |
1015 | 在Generator中引入了Promises后,我们就可以通过Promise的 `.catch` 和 `reject` 来捕捉和处理错误了。
1016 | 使用了我们新版的Generator后,新版的调用就像老版本一样简单可读(译者注:有微调):
1017 |
1018 | ```javascript
1019 | iterateGenerator(function* getData() {
1020 | var entry1 = yield request('http://some_api/item1');
1021 | var data1 = JSON.parse(entry1);
1022 | var entry2 = yield request('http://some_api/item2');
1023 | var data2 = JSON.parse(entry2);
1024 | });
1025 | ```
1026 |
1027 | 在使用Generator后,我们可以重用我们的老版本代码实现,以此展示了Generator的力量。
1028 | 当使用Generators和Promises后,我们可以像书写同步代码一样书写异步代码的同时优雅地解决了错误处理问题。
1029 | 此后,我们实际上可以开始利用更简单的一种方式了,它就是[async-await](https://github.com/DrkSephy/es6-cheatsheet#async-await)。
1030 |
1031 | [(回到目录)](#table-of-contents)
1032 |
1033 | ## Async Await
1034 |
1035 | `async await` 随着ES2016版本就要发布了,它给我们提供了一种更轻松的、更简单的可以替代的实现上面 Generators 配合 Promises 组合代码的一种编码方式,让我们来看看例子:
1036 |
1037 | ```javascript
1038 | var request = require('request');
1039 |
1040 | function getJSON(url) {
1041 | return new Promise(function(resolve, reject) {
1042 | request(url, function(error, response, body) {
1043 | resolve(body);
1044 | });
1045 | });
1046 | }
1047 |
1048 | async function main() {
1049 | var data = await getJSON();
1050 | console.log(data); // NOT undefined!
1051 | }
1052 |
1053 | main();
1054 | ```
1055 |
1056 | 它们看上去和Generators很像。我(作者)强烈推荐使用 `async await` 来替代Generators + Promises的写法。
1057 | [这里](http://masnun.com/2015/11/11/using-es7-asyncawait-today-with-babel.html)是个很好的学习资源,让我们学习和使用这项ES7中的新功能。
1058 |
1059 | [(回到目录)](#table-of-contents)
1060 |
1061 | ## License
1062 |
1063 | The MIT License (MIT)
1064 |
1065 | Copyright (c) 2015 David Leonard
1066 |
1067 | Permission is hereby granted, free of charge, to any person obtaining a copy
1068 | of this software and associated documentation files (the "Software"), to deal
1069 | in the Software without restriction, including without limitation the rights
1070 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1071 | copies of the Software, and to permit persons to whom the Software is
1072 | furnished to do so, subject to the following conditions:
1073 |
1074 | The above copyright notice and this permission notice shall be included in all
1075 | copies or substantial portions of the Software.
1076 |
1077 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1078 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1079 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1080 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1081 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1082 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1083 | SOFTWARE.
1084 |
1085 | [(回到目录)](#table-of-contents)
--------------------------------------------------------------------------------
/src/convert.js:
--------------------------------------------------------------------------------
1 | console.log('Converting repository to JavaScript');
--------------------------------------------------------------------------------