├── 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'); --------------------------------------------------------------------------------