├── README.md ├── chapter-01 └── README.md ├── chapter-02 └── README.md ├── chapter-03 └── README.md ├── chapter-04 └── README.md ├── chapter-05 └── README.md ├── chapter-06 └── README.md ├── chapter-07 └── README.md ├── chapter-09 └── README.md ├── chapter-13 └── README.md ├── chapter-14 └── README.md ├── chapter-15 └── README.md ├── chapter-17 └── README.md └── introducton └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # 歡迎加入 ES6+ 讀書會 2 | 本讀書會採用自助式共同協作 3 | > es6+ 是泛指 es6 以上本版的 ECMAscript 4 | 5 | ## 導讀內容: 6 | * [第一章 ECMAScript 6 簡介 - polo](chapter-01) 7 | * [第二章 const 和 let - jawayang](chapter-02) / [投影片](https://hackmd.io/p/rJvZCMUM#/) / [線上導讀影片](https://youtu.be/mzQTOIaEqMs) 8 | * [第三章 解構賦值 Destructuring assignment](chapter-03) / [投影片](https://hackmd.io/p/HJcb_nKf#/) 9 | * [第四章 String - Peng Jie](chapter-04) / [投影片](https://hackmd.io/p/S10A0p5G#/) / [線上導讀影片](https://www.youtube.com/watch?v=7LOpj19J4nw) 10 | * [第五章 RegExp - cashbook](chapter-05) / [投影片](https://hackmd.io/p/rkHAATpM#/) / [線上導讀影片](https://www.youtube.com/watch?v=eDvxODarM3U) 11 | * [第六章 數值的拓展 - RayWay](chapter-06) / [投影片](https://hackmd.io/p/SkIRuHSB#/) / [線上導讀影片](https://www.youtube.com/watch?v=vJxlYXu1Dvc) 12 | * [第七章 陣列的拓展 - AllenHsieh](chapter-07) / [投影片](https://hackmd.io/p/S1Jll4LH#/) / [線上導讀影片](https://youtu.be/1eugOttKFfA) 13 | * [第十五章 Generator 函數 - shiningjason](chapter-15) / [投影片](https://hackmd.io/p/r1_8z2Z_#/) / [線上導讀影片](https://youtu.be/5_mUqevT3cM) 14 | * [第十七章 異步操作和 Async 函數 - shiningjason](chapter-17) / [線上導讀影片](https://youtu.be/RvMeDS2yjzU) 15 | 16 | ## 讀書會閱讀工具: 17 | 18 | * [hackmd.io](https://hackmd.io/) [教學影片-Jacky](https://www.youtube.com/watch?v=8maKJ6CJ9no) 19 | * [新同文堂](https://chrome.google.com/webstore/detail/new-tong-wen-tang/ldmgbgaoglmaiblpnphffibpbfchjaeg?hl=zh-TW) 簡體繁體轉換工具 20 | * [hangout 直播](https://plus.google.com/hangouts/onair) 導讀錄影 21 | * markdown 編輯工具 22 | 1. [mac](http://superuser.com/questions/616899/github-flavored-markdown-editor-for-osx) 23 | 2. [atom](https://atom.io/) 24 | 25 | ## 導讀的製作流程 26 | 1. 挑選書中的某個單元 27 | 2. 研讀後將重點透過 github 記下來。 28 | 1. 建立單元 29 | 2. 撰寫內容 30 | 3. 錄製影片 & 線上導讀 31 | 4. 將相關檔案根據單元分類上傳 32 | 33 | ## 書籍內容 34 | * [ECMAScript 6 入门 - 阮一峰](http://es6.ruanyifeng.com/) 35 | 36 | ## 入會方式 37 | * [說明連結](https://softnshare.wordpress.com/portfolio/ecmascript-6%E5%85%A5%E9%96%80%E8%AE%80%E6%9B%B8%E6%9C%83/) 38 | 39 | ## 相關書籍 40 | 41 | * [Understanding ECMAScript 6](https://leanpub.com/understandinges6/read) 42 | 43 | ## ES6 相關資源 44 | 45 | ### 線上課程 46 | 47 | * [ES6 Katas](http://es6katas.org/) - Learn ES6 by doing it. Fix failing tests. Keep all learnings. 48 | * [Let's Learn ES6](https://www.youtube.com/playlist?list=PL57atfCFqj2h5fpdZD-doGEIs0NZxeJTX) Youtube 影片 49 | 50 | 51 | ### 文章整理及投影片 52 | 53 | * [ES6-In-Depth](http://www.infoq.com/cn/es6-in-depth/) - 本迷你书是 InfoQ 特别推出的深入浅出 ES6 专栏合集迷你书,欢迎各位 ES6 爱好者收集下载。 54 | * [ES6-Learning](https://github.com/ericdouglas/ES6-Learning) - List of resources to learn ECMAScript 6! 55 | * [Strikingly ES6 JavaScript Style Guide](https://github.com/strikingly/javascript) - A mostly reasonable approach to JavaScript. 56 | * [ECMAScript 6 簡介](https://hackmd.io/p/4JlFcJKOe#/) - by [jackymaxj](https://twitter.com/jackymaxj) 57 | * [ES6-Cheatsheet](http://slides.com/drksephy/ecmascript-2015) 58 | * [ES6-Feature](http://es6-features.org/#Constants) - New Features: Overview & Comparison. 59 | * [ECMAScript 6 compatibility table](http://kangax.github.io/compat-table/es6/) 60 | * [Computer Science in JavaScript (ES5 and ES6)](https://github.com/benoitvallon/computer-science-in-javascript) 61 | 62 | ### ES6 VS ES5 63 | 64 | * [ES6 equivalents in ES5](https://github.com/addyosmani/es6-equivalents-in-es5) 65 | * [React/React Native 的 ES5 ES6 寫法對照表](http://bbs.reactnative.cn/topic/15/react-react-native-%E7%9A%84es5-es6%E5%86%99%E6%B3%95%E5%AF%B9%E7%85%A7%E8%A1%A8) 66 | 67 | ### 開發工具 68 | 69 | * [ES6 tools](https://github.com/addyosmani/es6-tools) 70 | -------------------------------------------------------------------------------- /chapter-01/README.md: -------------------------------------------------------------------------------- 1 | 2 | 讀書會第一章內容 請參考 http://es6.ruanyifeng.com/#docs/intro 阮一峰大大 3 | 4 | # 筆記整理 5 | * 主講 polo 6 | * 文字資料補充 james yang 7 | 8 | ## 第1節 ECMAScript和JavaScript的關係 9 | ECMAScript和JavaScript的關係是,前者是後者的規格,後者是前者的一種實現 10 | 11 | * 導讀影片 : https://youtu.be/sqh41P1FMbs 12 | 13 | ## 第2節 ECMAScript的歷史 14 | 15 | 2000開始 -> 2013凍結 -> 20015正式通過 16 | 17 | ![參考圖片](http://image.slidesharecdn.com/es6-140516103511-phpapp02/95/ecmascript-6-the-future-is-here-4-638.jpg?cb=1400236673) 18 | 19 | * 導讀影片 : https://youtu.be/aPVJ-8dHSns 20 | 21 | ## 第3節 部署進度 22 | 23 | 這個章節主要是講各平台目前對es6的支援度 24 | 25 | * Node.js 26 | 27 | * npm install 跟 使用 28 | ``` bash 29 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash 30 | nvm ls-remote 31 | nvm ls 32 | nvm use 33 | ``` 34 | 35 | * 導讀影片 : https://youtu.be/RRN7yiGX9Ec 36 | 37 | ## 第4節 Babel 轉碼器 38 | 39 | * 配置文件 `.babelrc` 40 | 41 | * 各種轉換方式: 42 | 43 | * 命令行轉碼 `babel-cli` 44 | - babel-node (babel-cli安裝時就會裝) 45 | - babel-register (程式直接轉不用另外產出) 46 | - babel-core (使用Babel的API轉碼) 47 | - babel-polyfill (讓 es6 api 與 es5 相容) 48 | > 由於 babel 預設不轉換 es6 相關的物件, 只轉語法 , 透過 import babel-polyfill 可以讓 es5 執行環境支援相關的物件,像是 generator , promise...) 49 | 50 | 51 | * 瀏覽器環境 52 | - 在線轉換 https://babeljs.io/repl/ 53 | 54 | 55 | * 導讀影片 : https://youtu.be/eOw7cp_JXTQ 56 | 57 | ## 第5節 Traceur 轉碼器 58 | 59 | * 安裝 `npm install -g traceur` 60 | * 使用 `traceur calc.js` 61 | * `node` 中使用 `traceur` 62 | 63 | * 導讀影片 : https://youtu.be/Cjb4-3r4DLk 64 | 65 | ## 第6節 ECMAScript 7 66 | 任何人都可以向TC39提案,從提案到變成正式標準,需要經歷五個階段。每個階段的變動都需要由TC39委員會批准。 67 | 68 | * Stage 0 - Strawman(展示階段) 69 | * Stage 1 - Proposal(徵求意見階段) 70 | * Stage 2 - Draft(草案階段) 71 | * Stage 3 - Candidate(候選人階段) 72 | * Stage 4 - Finished(定案階段) 73 | 74 | > 一個提案只要能進入Stage 2,就差不多等於肯定會包括在ES7裡面。 75 | -------------------------------------------------------------------------------- /chapter-02/README.md: -------------------------------------------------------------------------------- 1 | 2 | # ES6 讀書會 3 | 4 | let 與 const 5 | 6 | ###### tags: `let` `const` 7 | 8 | ---- 9 | 10 | ## 關於我 11 | 12 | * James Yang (jawayang) 13 | * ES6 讀書會召集人 14 | * blog : [jamestw.logdown.com](jamestw.logdown.com) 15 | 16 | 17 | ---- 18 | 19 | 20 | ## let 與 const 21 | 22 | ES6 入門 第二章 23 | 作者:阮一峰 24 | http://es6.ruanyifeng.com/#docs/let 25 | 26 | 27 | ---- 28 | 29 | let 指令 30 | ===== 31 | 32 | 基本用法 33 | 34 | ``` 35 | 使用 `let` 陳述式來宣告變數,其範圍限於宣告所在的`區塊` 36 | 在 `javascript` 中 我們使用 {} 來代表`區塊` 37 | ``` 38 | 39 | 40 | 41 | ---- 42 | 43 | ## var 與 let 的差別 44 | 45 | ``` js 46 | { 47 | let a = 10; 48 | var b = 1; 49 | } 50 | 51 | console.log(a) // ReferenceError: a is not defined. 52 | console.log(b) // 1 53 | 54 | ``` 55 | ``` text 56 | 區塊中,分別用 var 與 let 宣告了兩個不同的變數 57 | 在區塊外部,很明確地可以看出差異,使用`let`的變數,只能用在區塊內部。 58 | ``` 59 | 60 | ---- 61 | 62 | ## 使用時機 63 | 64 | let 適合用在 for 迴圈 65 | ``` text 66 | 與 var 比較 67 | ``` 68 | ``` js 69 | var a = []; 70 | for (var i = 0; i < 10; i++) { 71 | a[i] = function () { 72 | console.log(i); 73 | }; 74 | } 75 | a[1](); //10 76 | a[6](); //10 77 | ``` 78 | 79 | ``` text 80 | 使用 var 的時候,i 會不斷地被覆蓋,結果永遠等於 10 81 | ``` 82 | 83 | ---- 84 | 85 | ## 使用時機 86 | 87 | 在迴圈中使用 let 88 | 89 | ``` js 90 | var a = []; 91 | for (let i = 0; i < 10; i++) { 92 | a[i] = function () { 93 | console.log(i); 94 | }; 95 | } 96 | a[1](); // 1 97 | a[6](); // 6 98 | 99 | ``` 100 | ``` text 101 | 使用 let 的時候,i 不會被覆蓋,每次循環都會是一個新的宣告 102 | ``` 103 | 104 | ---- 105 | 106 | ## Hoisting 提升 107 | 108 | 宣告提升到其所在區域內頂端的行為 109 | ``` text 110 | 與 var 不同,不會有變數提升的現象。 111 | ``` 112 | 113 | ``` js 114 | console.log(foo); //顯示 undefined; 115 | console.log(bar); //報錯 ReferenceError 116 | var foo = 2; 117 | let bar = 2; 118 | ``` 119 | 120 | ---- 121 | 122 | ## 暫時性死區 123 | 124 | ``` text 125 | ES6 明確規定,區塊中如果存在 `let` 或 `const` 宣告、這個區塊對這些宣告 126 | 會產生封閉的作用區。 127 | 簡單說:在宣告 let 跟 const 變數之前,該變數都是不可用的。 128 | 這在語法上,稱為「暫時性死區」(temporal dead zone,簡稱TDZ)。 129 | ``` 130 | 131 | 132 | 133 | ``` js 134 | if (true) { 135 | // TDZ開始 136 | tmp = 'abc'; // ReferenceError 137 | console.log(tmp); // ReferenceError 138 | 139 | let tmp; // TDZ結束 140 | console.log(tmp); // undefined 141 | 142 | tmp = 123; 143 | console.log(tmp); // 123 144 | } 145 | ``` 146 | 147 | 148 | ``` text 149 | 這樣的設計是為了讓大家養成良好的編程習慣,變量一定要在宣告之後使用 150 | ``` 151 | 152 | 153 | --- 154 | 155 | ``` text 156 | 實際測試的時候,測試環境結果有所不同 157 | chrome 50 版本會根據本書上的敘述,表示錯誤。 158 | 若用 `Babel` 轉譯則不會顯示錯誤 159 | 支援程度可以參考:http://kangax.github.io/compat-table/es6/ 160 | ``` 161 | 162 | ---- 163 | 164 | ## 暫時性死區 165 | 166 | ``` text 167 | 這也表示使用`typeof`來檢查使用`let`宣告的變數,可能會出現錯誤 168 | ``` 169 | ``` text 170 | 範例: 171 | ``` 172 | 173 | ``` js 174 | typeof x;// 出現錯誤 175 | let x; 176 | ``` 177 | ``` 178 | 因為在 let 宣告 x 之前,無法使用 x 這個變數 179 | ``` 180 | 181 | 182 | 183 | ---- 184 | 185 | ## 暫時性死區 186 | 187 | 較為不容易察覺的`死區` 188 | 189 | ``` js 190 | function bar (x=y,y=2){ 191 | return [x,y]; 192 | } 193 | // 參數 x 預設為 y 194 | // 參數 y 預設為 2 195 | ``` 196 | ``` text 197 | 因為此時 y 尚未被宣告,所以會報錯 198 | ``` 199 | 200 | 修正的方式 201 | ``` js 202 | function bar (x=2,y=x){ 203 | return [x,y]; 204 | } 205 | // 先宣告 x 再宣告 y 206 | ``` 207 | 208 | 209 | ---- 210 | 211 | ## let 不允許重複 212 | ``` text 213 | `let` 不允許在同一個區塊內宣告同一個變數 214 | ``` 215 | 216 | ``` js 217 | function(){ 218 | let a = 10; 219 | let a = 9 // 發生錯誤 220 | } 221 | ``` 222 | ``` text 223 | 所以不能再函式內部重新宣告參數 224 | ``` 225 | 226 | ``` js 227 | function func(arg){ 228 | let art; // 發生錯誤 229 | } 230 | function func(arg){ 231 | { 232 | let arg; // 不發生錯誤 233 | } 234 | } 235 | ``` 236 | 237 | ---- 238 | 239 | ## 為什麼需要區塊範圍變數 ? 240 | 241 | ``` text 242 | 在`ES5`中只有全域變數與函式變數,沒有區塊範圍變數。 243 | 因此造成了很多不合理的狀況。 244 | ``` 245 | 246 | ### 狀況一 247 | ``` js 248 | var tmp = new Data(); 249 | 250 | function f(){ 251 | console.log(tmp); // undefined 252 | if(false){ 253 | var tmp = "hello world"; 254 | } 255 | } 256 | f(); 257 | ``` 258 | ``` text 259 | 在 `es5` 執行之後,tmp 在函數內輸出的結果會是 `undefined`, 260 | 由於 `tmp` 變數提升,導致函式內部的變數,覆蓋了外部的 `tmp` 261 | ``` 262 | 263 | 264 | 265 | ---- 266 | 267 | ## 狀況二 268 | 269 | 270 | ``` js 271 | var s = 'hello'; 272 | for(var i=0 ; i< s.length; i++){ 273 | console.log(s[i]); 274 | } 275 | console.log(i) //5 276 | ``` 277 | ``` text 278 | 變數 i 只用來提供迴圈執行,執行結束後,並沒有消失,變成了全域變數。 279 | ``` 280 | 281 | ---- 282 | 283 | ## ES6 區塊作用域 284 | 285 | let 提供了新的變數作用域 286 | 287 | ``` js 288 | function f1() { 289 | let n = 5; //5 290 | if (true) { 291 | let n = 10; //10 292 | } 293 | console.log(n); // 5 294 | } 295 | ``` 296 | ``` 297 | 若使用 var 最後輸出結果會變成 10 298 | ``` 299 | 300 | ---- 301 | 302 | ## let 限制變數作用區域 303 | 304 | 305 | ``` js 306 | {{{ 307 | { 308 | let a = 1; 309 | {let b = 1}; 310 | console.log(b); //報錯 311 | } 312 | console.log(a); //報錯 313 | }}} 314 | ``` 315 | 316 | ---- 317 | 318 | 過去我們希望隔離不同的變數區域時, 319 | 會需要用到立即執行匿名函式 320 | 像這樣: 321 | 322 | ``` js 323 | (function(){ 324 | var a = 0; 325 | })(); 326 | //確保 a 不會污染全域 327 | 328 | console.log(a); // undefined 329 | 330 | //在ES6中可以改成用區塊撰寫 331 | { 332 | let b = 0; 333 | } 334 | console.log(b); 335 | 336 | ``` 337 | 338 | ---- 339 | 340 | ## 區塊中的函式 341 | 342 | ``` text 343 | ES6 中規定 函數本身的作用域,在其所在的塊級作用域之內。 344 | ``` 345 | ``` js 346 | function f(){ console.log('我在外面');} 347 | (function(){ 348 | if(true){ 349 | function f(){ console.log('我在裡面');} 350 | } 351 | f(); 352 | })(); 353 | ``` 354 | ``` 355 | 若由 es5 的執行環境執行時,結果會是'我在裡面' 356 | 若由 es6 執行環境執行,結果會是'我在外面' 357 | ``` 358 | ``` test 359 | 在ES6中內部宣告的函數皆不會影響到作用域的外部 360 | ``` 361 | 362 | ---- 363 | 364 | ## 嚴格模式 use strict 365 | ``` text 366 | 在ES5嚴格模式規定,函式只能在頂層作用域或是函式內宣告, 367 | 其他狀況(例如`if`指令、循環指令區)的宣告都會報錯。 368 | ``` 369 | ```js 370 | //ES5 371 | `use strict` 372 | if(true){ 373 | function f(){ } //報錯 374 | } 375 | 376 | 377 | ``` 378 | 379 | 380 | ---- 381 | 382 | ## ES6 嚴格模式 383 | ``` text 384 | ES6由於引入了塊級作用域,這種情況可以理解成函數在塊級作用域內聲明 385 | ``` 386 | 387 | ```js 388 | // 不報錯 389 | 'use strict'; 390 | if (true) { 391 | function f() {} 392 | } 393 | 394 | // 報錯 不能缺少 { } 395 | 'use strict'; 396 | if (true) 397 | function f() {} 398 | 399 | f(); // 無法執行因為在區塊外部 400 | ``` 401 | 402 | 403 | ---- 404 | 405 | 406 | 407 | # Const 命令 408 | 409 | 基本用法 410 | 411 | ---- 412 | 413 | ## 宣告常數 414 | 415 | const 是一個唯讀的常數。 416 | 一旦被宣告,常數的值就不能變。 417 | 418 | ``` text 419 | 嚴格模式 420 | ``` 421 | ``` js 422 | 'use strict'; 423 | const PI = 3.1415; // 3.1415 424 | PI = 3; // TypeError: "PI" is read-only 425 | 426 | ``` 427 | ``` text 428 | 非嚴格模式,不報錯,且PI的值不變。 429 | ``` 430 | ``` 431 | const PI = 3.1415; // 3.1415 432 | PI = 3; // 3.1415 433 | ``` 434 | 435 | ---- 436 | 437 | ## 宣告常數 438 | ``` text 439 | 嚴格模式'use strict'; 440 | ``` 441 | ``` js 442 | 'use strict'; 443 | const foo; 444 | // SyntaxError: missing = in const declaration 445 | 446 | ``` 447 | ``` text 448 | 非嚴格模式 449 | ``` 450 | ``` js 451 | const foo; 452 | foo = 1; // 重新賦值無效 453 | foo // undefined 454 | ``` 455 | 456 | ---- 457 | 458 | ## const 作用域 459 | ``` text 460 | 作用域與 let 相同,同樣存在暫時性死區,只能在宣告後面的位置使用。 461 | ``` 462 | ``` js 463 | if(true){ 464 | console.log(MAX); //錯誤參照 465 | const MAX = 5; 466 | } 467 | MAX // 未定義 468 | ``` 469 | 470 | ---- 471 | 472 | ## 宣告不可重複 473 | ``` text 474 | 與 let 相同,同一個變數名稱在同一區塊不得重複宣告。 475 | ``` 476 | ``` 477 | var message = "Hello!"; 478 | let age = 25; 479 | 480 | // 以下兩行都會報錯 481 | const message = "Goodbye!"; 482 | const age = 30; 483 | ``` 484 | 485 | ---- 486 | 487 | ## const 宣告物件及陣列 488 | ``` text 489 | 對於複合資料類型之數據,像是 array 或 obj, 490 | 宣告時指向的是數據所在的參考,無法保證變數內的數據不變。 491 | 所以當宣告一個物件為常數的時候要很小心。 492 | ``` 493 | ``` js 494 | const foo = {}; //foo 指向物件位置 495 | foo.prop = 123; //物件內容可以被修改 496 | 497 | foo.prop; //123 498 | ``` 499 | 500 | ---- 501 | 502 | ## 避免常數物件或陣列異動 503 | ``` text 504 | 為了避免宣告的物件或陣列被修改, 505 | 可以使用 Object.freeze(); 506 | 方法凍結該物件或陣列。 507 | 508 | ``` 509 | ``` js 510 | const obj = Object.freeze({}); //宣告一個不能異動的常數物件 511 | const arr = Object.freeze([]); //宣告一個不能異動的陣列物件 512 | // 下面的指令沒有作用 513 | // 嚴格模式下會顯示錯誤 514 | obj.prop = 123; 515 | arr[0] = 123; 516 | console.log(obj.prop); //無法執行 517 | console.log(arr); //無法執行 518 | ``` 519 | ``` text 520 | 在 http://www.es6fiddle.net/ 中執行會出錯,必須拿掉修改才會正常執行 521 | ``` 522 | 523 | ---- 524 | 525 | ## 凍結屬性 526 | ``` text 527 | 除了物件本身應該要凍結之外,物件的屬性若指向物件時應該也要凍結,凍結的方法 528 | ``` 529 | ``` js 530 | var constantize = (obj) => { 531 | Object.freeze(obj); 532 | Object.keys(obj).forEach( (key, value) => { 533 | if ( typeof obj[key] === 'object' ) { 534 | constantize( obj[key] ); 535 | } 536 | }); 537 | }; 538 | ``` 539 | 540 | ---- 541 | 542 | ## 變數宣告 543 | 544 | ES5只有兩種聲明變量的方法: 545 | `var` 和 `function` 546 | 547 | ``` text 548 | 除了原本的 var 跟 function 之外 549 | ``` 550 | ES6有 `let` 跟 `const` 551 | 還有 `import` 與 `class` 552 | ``` text 553 | 共六種變數宣告方式 554 | ``` 555 | 556 | ---- 557 | 558 | ## 全域物件 559 | ``` text 560 | 全域物件,是指最上層的物件, 561 | 在瀏覽器中指的是 `window` 物件 562 | 在`Node.js`指的是`global` 物件 563 | ``` 564 | ``` js 565 | //在 ES5 中 全域物件的屬性,就等於全域變數 566 | window.a = 1; 567 | console.log(a); // 1 568 | 569 | a = 2; 570 | console.log(window.a); //2; 571 | 572 | b = 3; //沒有宣告 573 | console.log(window.b); //3; 574 | ``` 575 | 576 | ``` text 577 | 在 ES5 中沒有宣告的全域變數,自動變成為全域物件window的屬性, 578 | 不容易除錯,很容易就產生問題。 579 | ``` 580 | 581 | ---- 582 | 583 | ## 全域物件 584 | 585 | 從ES6開始,全域變數將逐步與全域物件的屬性脫鉤。 586 | 587 | ``` text 588 | ES6為了改變這一點,但仍然保持兼容性, 589 | var 和 function 宣告的全域變數,依舊是全域物件的屬性; 590 | 另一方面規定,let命令、const命令、class命令聲明的全域變數, 591 | 不屬於全域物件的屬性。 592 | ``` 593 | ``` js 594 | //如果是在最外層宣告 595 | var a = 1; 596 | window.a; //1 597 | 598 | let b = 1; 599 | window.b // undefined 600 | ``` 601 | 602 | ---- 603 | 604 | 結論 605 | ==== 606 | 607 | 608 | let 與 const 是用來宣告區塊中的變數 609 | 這些變數在區塊外部是無效的 610 | 並且要注意宣告與使用的順序 611 | 612 | 613 | 614 | 謝謝指教 615 | 期待下次的分享 616 | 617 | 618 | -------------------------------------------------------------------------------- /chapter-03/README.md: -------------------------------------------------------------------------------- 1 | # ES6 讀書會 2 | 3 | 解構賦值 4 | 5 | 6 | ###### tags: `解構賦值` 7 | 8 | ---- 9 | 10 | ## 關於我 11 | 12 | * James Yang (jawayang) 13 | * ES6 讀書會召集人 14 | * blog : [jamestw.logdown.com](jamestw.logdown.com) 15 | 16 | 17 | ###### tags: `解構`,`Destructuring` 18 | 19 | ---- 20 | 21 | ## 解構賦值 / 分割代入 22 | 23 | Destructuring assignment 24 | 25 | 基本用法 26 | 27 | 透過 `解構賦值` 來映射 `物件` 或 `陣列` 中的資料 28 | 29 | 30 | --- 31 | 32 | ## 陣列的解構賦值 33 | Array destructuring 34 | 35 | ``` text 36 | es5 寫法: 37 | ``` 38 | 39 | ``` js 40 | var a = 1; 41 | var b = 2; 42 | var c = 3; 43 | ``` 44 | 45 | 46 | 47 | 48 | ``` text 49 | es6 寫法: 50 | ``` 51 | 52 | 53 | 54 | ``` js 55 | var [a, b, c] = [1, 2, 3]; 56 | ``` 57 | 58 | 59 | 60 | ``` text 61 | 上面代碼表示,可以從陣列中提取值,按照對應位置,對變數賦值。 62 | ``` 63 | 64 | 65 | 66 | ---- 67 | 68 | ``` text 69 | 簡單說就是一對一的關係 70 | ``` 71 | 72 | ---- 73 | 74 | ``` text 75 | 在陣列前可以使用 var 或 let 或 const 等指令.. 76 | ``` 77 | 78 | ``` js 79 | var [a1,b1,c1] =[1,2,3]; 80 | ``` 81 | 82 | ``` js 83 | let [a2,b2,c2] = [1,2,3]; 84 | ``` 85 | 86 | 87 | ``` js 88 | const [d3,e3,f3] = [1,2,3]; 89 | ``` 90 | 91 | 92 | ---- 93 | 94 | ## 格式匹配 95 | 96 | ``` text 97 | 只要等號兩邊的格式相同,左邊的變數就會被賦予對應的值 98 | ``` 99 | 100 | 101 | ``` js 102 | var [foo, [[bar], baz]] = [1, [[2], 3]]; 103 | foo //1 104 | bar //2 105 | baz //3 106 | ``` 107 | 108 | 109 | ``` text 110 | 也可保留空位 111 | ``` 112 | 113 | 114 | ``` js 115 | var [, , third] = ["foo","bar","baz"]; 116 | third // "baz" 117 | ``` 118 | 119 | 120 | ---- 121 | 122 | ## 部分匹配 123 | 124 | ``` text 125 | 如果沒有對應的結構,變數的值就會是 undefined 126 | ``` 127 | 128 | 129 | ``` js 130 | var [foo] = []; 131 | var [bar,foo] = [1]; 132 | //foo 都等於 undefined 133 | ``` 134 | 135 | 136 | ``` text 137 | 不完全對應 138 | ``` 139 | 140 | 141 | ``` js 142 | let [x,y] = [1,2,3]; 143 | x //1 144 | y //2 145 | 146 | let [a,[b],c] = [1,[2,3],4]; 147 | a //1 148 | b //2 149 | c //4 150 | ``` 151 | 152 | 153 | ``` text 154 | 雖然沒有完全解構,但是還是可以匹配部分的變數 155 | ``` 156 | 157 | 158 | 159 | ---- 160 | 161 | ## 不定量參數 "..." 162 | 163 | 不定參數 / 其餘參數 / 剩餘參數 / 殘餘引數 / 164 | Rest parameters 165 | 166 | ``` text 167 | 用法:不定量參數會回傳陣列 168 | ``` 169 | 170 | 171 | ``` js 172 | var [head, ...tail] = [1,2,3,4]; 173 | tail // [2,3,4] 174 | 175 | let [x, y, ...z] = ['a']; 176 | x // 'a' 177 | y // undefined 178 | z // [] 179 | ``` 180 | 181 | 182 | ---- 183 | 184 | ## 非陣列指派 185 | 186 | ``` 187 | 若等號的右邊不是同樣的陣列格式,就會出現錯誤 188 | ``` 189 | 190 | 191 | ``` js 192 | // 報錯 193 | let [foo] = 1; 194 | let [foo] = false; 195 | let [foo] = NaN; 196 | let [foo] = undefined; 197 | let [foo] = null; 198 | let [foo] = {}; 199 | ``` 200 | 201 | 202 | ``` 203 | 主要原因是因為不具備 Iterator 迭代介面 / 疊代 204 | ``` 205 | 206 | 207 | ---- 208 | 209 | ## 具備迭代介面的物件 210 | 211 | ``` text 212 | 若是使用 Set 物件,也可以用在陣列解構賦值 213 | ``` 214 | 215 | ``` js 216 | let [x,y,z] = new Set(["a","b","c"]); 217 | x //"a" 218 | y //"b" 219 | z //"c" 220 | ``` 221 | 222 | 223 | ``` text 224 | 可遍歷的物件,(指針物件 / 指標物件) 225 | 像陣列、物件都具備這樣的結構 226 | 而 ES6 又新增了 Map 與 Set 227 | ``` 228 | 229 | 230 | ---- 231 | 232 | ## 自建具備迭代介面的物件 233 | 234 | ``` js 235 | function* fib(){ 236 | var a = 0; 237 | var b = 1; 238 | while (true){ 239 | yield a; 240 | [a,b] = [b, a + b]; 241 | } 242 | } 243 | 244 | var [first,second,third,fourth] = fib(); 245 | first // 0 246 | second // 1 247 | third // 2 248 | ``` 249 | 250 | 251 | ``` 252 | fibs 是一個 Generator 函式,具備 Iterator 介面 253 | 解構賦值會依次從這個介面取值 254 | ``` 255 | 256 | 257 | 258 | ---- 259 | 260 | ## 陣列解構賦值的運用 261 | 262 | ``` text 263 | 用來交換變數 264 | ``` 265 | ``` js 266 | var a = 1; b = 2; 267 | [b,a] = [a,b]; 268 | a //2 269 | b //1 270 | ``` 271 | 272 | ---- 273 | 274 | ## 陣列解構賦值的運用 275 | 276 | ``` text 277 | 與函式並用,可以取得函式回傳的陣列值 278 | ``` 279 | ``` 280 | function f(){ 281 | return [1,2,3]; 282 | } 283 | var [a,,b] = f(); 284 | a //1 285 | b //3 286 | ``` 287 | 288 | ---- 289 | 290 | ## 陣列解構賦值的運用 291 | 292 | ``` text 293 | 用正則表達式匹配提取值 294 | ``` 295 | ``` js 296 | var url = "http://www.w3schools.com/svg/"; 297 | 298 | var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url); 299 | var [, protocol, fullhost, fullpath] = parsedURL; 300 | 301 | console.log(protocol); // 輸出"https:" 302 | ``` 303 | 304 | ---- 305 | 306 | ## 陣列解構賦值的運用 307 | 308 | ``` text 309 | 函式的參數也可以使用解構賦值 310 | ``` 311 | ``` js 312 | function add([x, y]){ 313 | return x + y; 314 | } 315 | 316 | add([1, 2]); // 3 317 | ``` 318 | ``` text 319 | 函式 add 的參數表面上是一個陣列,但在傳入參數的那一刻, 320 | 陣列參數就被解構成變量x和y 321 | ``` 322 | ``` text 323 | 下面是另一個例子 324 | ``` 325 | ``` js 326 | [[1, 2], [3, 4]].map(([a, b]) => a + b); 327 | // [ 3, 7 ] 328 | 329 | [[1,2],[3,4]].map(function([a,b]){ 330 | return a+b; 331 | }) 332 | 333 | ``` 334 | 335 | ---- 336 | 337 | ## 指定預設值 338 | 339 | ```text 340 | 解構賦值允許指定預設值 341 | 當對應值為 undefined 的時候,就會使用預設值 342 | ``` 343 | 344 | 345 | ```javascript 346 | var [a=0,b=0] = [1,]; 347 | a //1 348 | b //0 349 | ``` 350 | 351 | 352 | ``` text 353 | 預設值可以引用解構賦值的其他變數,但該變數必須已經宣告。 354 | ``` 355 | 356 | 357 | ``` js 358 | var [x = 1, y = x] = []; // x=1; y=1 359 | var [x = 1, y = x] = [2]; // x=2; y=2 360 | var [x = 1, y = x] = [1, 2]; // x=1; y=2 361 | var [x = y, y = 1] = []; // ReferenceError 362 | ``` 363 | 364 | 365 | 366 | --- 367 | 368 | ## 物件的解構賦值 369 | Object destructuring 370 | 371 | 372 | 373 | ``` text 374 | 解構不僅僅可以用在陣列,也可以用在物件 375 | ``` 376 | 377 | 378 | ``` js 379 | var { foo, bar } = { foo: "aaa", bar: "bbb" }; 380 | foo // "aaa" 381 | bar // "bbb" 382 | ``` 383 | 384 | 385 | ``` text 386 | 物件與陣列的差別,在於陣列是根據順序排列, 387 | 變數的內容根據位置決定,而物件並沒有順序, 388 | 變數必須與物件屬性同名,才能取得到內容 389 | ``` 390 | 391 | 392 | ``` js 393 | var { baz } = { foo: "aaa", bar: "bbb" }; 394 | baz // undefined 395 | ``` 396 | 397 | 398 | ---- 399 | 400 | ## 名稱不一致 401 | ``` text 402 | 如果變量名與屬性名不一致,必須寫成下面這樣。 403 | ``` 404 | 405 | 406 | ``` js 407 | var { foo: baz } = { foo: "aaa", bar: "bbb" }; 408 | baz // "aaa" 409 | ``` 410 | 411 | 412 | ```text 413 | 再複雜一點 414 | ``` 415 | 416 | 417 | ``` js 418 | let obj = { first: 'hello', last: 'world' }; 419 | let { first: f, last: l } = obj; 420 | f // 'hello' 421 | l // 'world' 422 | ``` 423 | 424 | 425 | ---- 426 | 427 | 428 | ## 實際的內部機制 429 | ``` js 430 | var { foo, bar } = { foo: "aaa", bar: "bbb" }; 431 | ``` 432 | 433 | ```text 434 | 上方的程式碼同等於下方的程式碼 435 | ``` 436 | 437 | ``` js 438 | var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" }; 439 | ``` 440 | 441 | ``` text 442 | 物件的解構賦值的內部機制,是先找到同名屬性,然後再賦給對應的變數。 443 | 真正被賦值的是後者,而不是前者 444 | ``` 445 | 446 | ``` js 447 | var { foo: baz } = { foo: "aaa", bar: "bbb" }; 448 | baz // "aaa" 449 | foo // error: foo is not defined 450 | ``` 451 | 452 | ``` text 453 | 上面代碼中,真正被賦值的是變數 baz,而不是模式foo。 454 | ``` 455 | 456 | 457 | ---- 458 | 459 | ## 注意事項 460 | 461 | ``` text 462 | 使用這種寫法,變數的宣告與賦值是一起的。但是對於 let 與 const 指令來說, 463 | 變數不能重新被宣告,所以一旦變數之前已經被宣告過了, 464 | 在結構賦值的時候,就會發生錯誤。 465 | ``` 466 | 467 | ```js 468 | let foo; 469 | let {foo} = {foo: 1}; 470 | // SyntaxError: Duplicate declaration "foo" 471 | 472 | let baz; 473 | let {bar: baz} = {bar: 1}; 474 | // SyntaxError: Duplicate declaration "baz" 475 | ``` 476 | 477 | ---- 478 | 479 | ## 解決方案 480 | ``` text 481 | 在解構賦值時不要使用 let 指令 482 | ``` 483 | 484 | ```js 485 | let foo; 486 | ({foo} = {foo: 1}); // 成功 487 | 488 | let baz; 489 | ({bar: baz} = {bar: 1}); // 成功 490 | ``` 491 | 492 | ``` text 493 | const只能賦值時並用,沒辦法分成兩段 494 | ``` 495 | 496 | ```js 497 | const a; //報錯 498 | const {bar: a} = {bar: 1}; // 成功 499 | ``` 500 | 501 | 502 | ---- 503 | 504 | ## 解構嵌套/巢狀解構/Nested 505 | ``` text 506 | 和陣列一樣,解構也可以用於嵌套結構的物件 507 | ``` 508 | ``` js 509 | var obj = { 510 | p: [ 511 | "Hello", 512 | { y: "World" } 513 | ] 514 | }; 515 | 516 | var { p: [x, { y }] } = obj; 517 | x // "Hello" 518 | y // "World" 519 | ``` 520 | 521 | ``` text 522 | 注意,這時p是樣式 patten ,不是變數,因此不會被賦值。 523 | ``` 524 | 525 | ---- 526 | 527 | ## 解構嵌套/巢狀解構/Nested 528 | 529 | ```js 530 | var node = { 531 | loc: { 532 | start: { 533 | line: 1, 534 | column: 5 535 | } 536 | } 537 | }; 538 | 539 | var { loc: { start: { line }} } = node; 540 | line // 1 541 | loc // error: loc is undefined 542 | start // error: start is undefined 543 | ``` 544 | ``` text 545 | 上面代碼中,只有line是變量,loc和start都是模式,不會被賦值。 546 | ``` 547 | 548 | ---- 549 | 550 | ## 解構嵌套/巢狀解構/Nested 551 | 552 | ```js 553 | let obj = {}; 554 | let arr = []; 555 | 556 | ({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true }); 557 | 558 | obj // {prop:123} 559 | arr // [true] 560 | ``` 561 | 562 | ---- 563 | 564 | ## 設定預設值 565 | ``` 566 | var {x = 3} = {}; 567 | x // 3 568 | 569 | var {x, y = 5} = {x: 1}; 570 | x // 1 571 | y // 5 572 | 573 | var { message: msg = "Something went wrong" } = {}; 574 | msg // "Something went wrong" 575 | ``` 576 | ``` text 577 | 預設值生效的條件是,物件的屬性值嚴格等於 undefined 578 | ``` 579 | ``` js 580 | var {x = 3} = {x: undefined}; 581 | x // 3 582 | 583 | var {x = 3} = {x: null}; 584 | x // null 585 | ``` 586 | 587 | ---- 588 | 589 | ## 解構失敗 590 | 591 | ``` text 592 | 如果解構失敗,變量的值等於 undefined。 593 | ``` 594 | ``` js 595 | var {foo} = {bar: 'baz'}; 596 | foo // undefined 597 | ``` 598 | 599 | ``` text 600 | 如果解構模式是嵌套的物件,而且子物件所在的父屬性不存在,那麼將會報錯。 601 | ``` 602 | ``` js 603 | var {foo: {bar}} = {baz: 'baz'}; 604 | ``` 605 | ``` text 606 | 因為 foo 這時等於 undefined , 再取子屬性就會報錯 607 | ``` 608 | 609 | ---- 610 | 611 | ## 奇怪的寫法 612 | 613 | ``` text 614 | 如果要將一個已經聲明的變數用於解構賦值,必須非常小心。 615 | ``` 616 | ``` 617 | var x; 618 | {x} = {x: 1}; 619 | ``` 620 | ``` text 621 | 把大括號放在聚首,會被誤以為是程式區塊,而發生錯誤, 622 | 避免這個問題要在外面加上圓括號 623 | ``` 624 | ```js 625 | var x; 626 | ({x} = {x: 1}); 627 | ``` 628 | 629 | ``` text 630 | 解構賦值允許,等號左邊的模式之中,不放置任何變數名 631 | ``` 632 | ``` js 633 | ({} = [true, false]); 634 | ({} = 'abc'); 635 | ({} = []); 636 | ``` 637 | ``` js 638 | 上面的表達式雖然毫無意義,但是語法是合法的,可以執行。 639 | ``` 640 | 641 | ---- 642 | 643 | ## 物件解構賦值的應用 644 | 645 | ``` text 646 | 物件的解構賦值,可以很方便地將現有物件的方法,賦值到某個變數。 647 | ``` 648 | ``` js 649 | let { log, sin, cos } = Math; 650 | ``` 651 | ``` text 652 | 上面代碼將Math對象的對數、正弦、餘弦三個方法, 653 | 賦值到對應的變量上,使用起來就會方便很多。 654 | ``` 655 | 656 | ---- 657 | 658 | ## 布林值與數值的解構 659 | 660 | ``` 661 | let {toString: s} = 123; 662 | s === Number.prototype.toString // true 663 | 664 | let {toString: s} = true; 665 | s === Boolean.prototype.toString // true 666 | ``` 667 | 668 | ``` text 669 | 解構賦值的規則是,只要等號右邊的值不是對象, 670 | 就先將其轉為對象。由於undefined和null無法轉為對象, 671 | 所以對它們進行解構賦值,都會報錯。 672 | ``` 673 | 674 | ---- 675 | 676 | ## 字串的解構賦值的應用 677 | 678 | ``` text 679 | 字串與陣列都有一個length屬性,因此還可以對這個屬性解構賦值。 680 | ``` 681 | ``` js 682 | let {length : len} = 'hello'; 683 | len // 5 684 | ``` 685 | 686 | ---- 687 | 688 | ## 函式參數解構賦值的運用 689 | 690 | ``` text 691 | 函式參數的解構可以使用預設值。 692 | ``` 693 | ``` js 694 | function move({x = 0, y = 0} = {}) { 695 | return [x, y]; 696 | } 697 | 698 | move({x: 3, y: 8}); // [3, 8] 699 | move({x: 3}); // [3, 0] 700 | move({}); // [0, 0] 701 | move(); // [0, 0] 702 | ``` 703 | ``` text 704 | 上面程式中,函式 move 的參數是一個物件,通過對這個物件進行解構, 705 | 得到變數x和y的值。如果解構失敗,x和y等於預設值。 706 | ``` 707 |
 James 解讀:
708 | 若 move() 沒有傳參數進去,參數預設就會是一個空物件,
709 | 然後 x 預設就是 0 , y 預設為 0
710 | 
711 | 712 | ---- 713 | 714 | ## 函式參數解構賦值的運用 715 | 716 | ``` text 717 | 寫法不同,結果不同 718 | ``` 719 | 720 | ``` js 721 | function move({x, y} = { x: 0, y: 0 }) { 722 | return [x, y]; 723 | } 724 | 725 | move({x: 3, y: 8}); // [3, 8] 726 | move({x: 3}); // [3, undefined] 727 | move({}); // [undefined, undefined] 728 | move(); // [0, 0] 729 | ``` 730 | 731 | ``` 732 | undefined就會觸發函式參數的預設值。 733 | ``` 734 | 735 | ---- 736 | 737 | ## 圓括號的使用 738 | 739 | ``` text 740 | 解構賦值雖然很方便,但是解析起來並不容易。 741 | 對於編譯器來說,一個`程式碼`到底是`樣式`, 742 | 還是`表達式`,沒有辦法從一開始就知道。 743 | ``` 744 | ``` text 745 | ES6 的處理規則,只要是有可能導致解構的爭議,就不得使用。 746 | 建議盡量不要在樣式中放置圓括號 747 | ``` 748 | 749 | ---- 750 | ## 不能使用圓括號的狀況 751 | 752 | 1. 變數宣告 753 | ``` js 754 | var [(a)] = [1]; 755 | 756 | var {x: (c)} = {}; 757 | var ({x: c}) = {}; 758 | var {(x: c)} = {}; 759 | var {(x): c} = {};} 760 | 761 | var { o: ({ p: p }) } = { o: { p: 2 } }; 762 | 763 | ``` 764 | 765 | 2. 函式參數中不得使用 766 | 767 | ``` js 768 | function f([(z)] reture z ); 769 | ``` 770 | 771 | 3. 解構賦值,不能將整個或某一層,放在圓括符中。 772 | ``` js 773 | ({ p: a }) = { p: 42 }; 774 | ([a]) = [5]; 775 | [({ p: a }), { x: c }] = [{}, {}]; 776 | ``` 777 | 778 | ## 可以使用圓括號的狀況 779 | 780 | 1. 賦值程式的非樣式部分,可以使用圓括號。 781 | ``` js 782 | [(b)] = [3]; // 正確 783 | ({ p: (d) } = {}); // 正確 784 | [(parseInt.prop)] = [3]; // 正確 785 | ``` 786 | ``` 787 | 全是賦值,而非宣告,圓括號全都不是模式的部分。 788 | ``` 789 | 790 | ---- 791 | 792 | ## 解構的用途 793 | 794 | 1.交換變數 795 | 796 | 797 | ``` js 798 | [x,y] = [y,x]; 799 | ``` 800 | 801 | ---- 802 | 803 | 2.從函式返回多個值 804 | 805 | ``` text 806 | 函式只能返回一個值,如果要返回多個值, 807 | 只能將它們放在陣列或物件裡返回。 808 | 有了解構賦值,取出這些值就非常方便。 809 | ``` 810 | ``` js 811 | // 返回一個陣列 812 | function example() { 813 | return [1, 2, 3]; 814 | } 815 | var [a, b, c] = example(); 816 | ``` 817 | ``` js 818 | // 返回一個物件 819 | function example() { 820 | return { 821 | foo: 1, 822 | bar: 2 823 | }; 824 | } 825 | var { foo, bar } = example(); 826 | 827 | ``` 828 | 829 | ---- 830 | 831 | 3.函式參數定義 832 | 833 | ``` text 834 | 解構賦值可以方便地將一組參數與變數名稱對應 835 | ``` 836 | 837 | ``` js 838 | // 參數是一組有次序的值 839 | function f([x, y, z]) { ... } 840 | f([1, 2, 3]); 841 | 842 | // 參數是一組無次序的值 843 | function f({x, y, z}) { ... } 844 | f({z: 3, y: 2, x: 1}); 845 | ``` 846 | 847 | ---- 848 | 849 | 4.取出 JSON 數據 850 | 851 | ``` text 852 | 解構賦值對提取JSON對象中的數據,尤其有用。 853 | ``` 854 | ``` js 855 | var jsonData = { 856 | id: 42, 857 | status: "OK", 858 | data: [867, 5309] 859 | }; 860 | 861 | let { id, status, data: number } = jsonData; 862 | 863 | console.log(id, status, number); 864 | // 42, "OK", [867, 5309] 865 | ``` 866 | 867 | ---- 868 | 869 | 5.設定參數的預設值 870 | ``` js 871 | jQuery.ajax = function (url, { 872 | async = true, 873 | beforeSend = function () {}, 874 | cache = true, 875 | complete = function () {}, 876 | crossDomain = false, 877 | global = true, 878 | // ... more config 879 | }) { 880 | // ... do stuff 881 | }; 882 | ``` 883 | ``` 884 | 指定參數的預設值,就避免了在函式內部寫 885 | var foo = config.foo || 'default foo'; 886 | 這樣的程式碼。 887 | ``` 888 | 889 | ---- 890 | 891 | 6.遍歷map結構 892 | ``` text 893 | 任何部署了Iterator介面的物件, 894 | 都可以用for...of循環遍歷。 895 | Map結構原生支持Iterator介面, 896 | 配合變數的解構賦值,獲取鍵名和鍵值就非常方便。 897 | ``` 898 | 899 | ```js 900 | var map = new Map(); 901 | map.set('first', 'hello'); 902 | map.set('second', 'world'); 903 | 904 | for (let [key, value] of map) { 905 | console.log(key + " is " + value); 906 | } 907 | 908 | // first is hello 909 | // second is world 910 | 911 | ``` 912 | 913 | ---- 914 | 915 | ``` text 916 | 如果只想獲取鍵名,或者只想獲取鍵值,可以寫成下面這樣。 917 | ``` 918 | 919 | ```js 920 | // 獲取鍵名 921 | for (let [key] of map) { 922 | // ... 923 | } 924 | 925 | // 獲取鍵值 926 | for (let [,value] of map) { 927 | // ... 928 | } 929 | ``` 930 | 931 | ---- 932 | 933 | 7.輸入模組的指定方法 934 | ``` text 935 | 加載模組時,往往需要指定輸入那些方法。解構賦值使得輸入語句非常清晰。 936 | ``` 937 | ```js 938 | const { SourceMapConsumer, SourceNode } = require("source-map"); 939 | ``` 940 | 941 | 942 | --- 943 | -------------------------------------------------------------------------------- /chapter-04/README.md: -------------------------------------------------------------------------------- 1 | # ES6 讀書會 - ECMAScript 6 入門 2 | 3 | ### Chapter 4 - String 4 | 5 | _Prepared By [Peng Jie](https://github.com/neighborhood999)_ 6 | 7 | ## Table Of Content 8 | 9 | - [使用 unicode 表示字串](#使用-unicode-表示字串) 10 | - [在 JavaScript 中的 String](#在-javascript-中的-string) 11 | - [codePointAt 方法](#codepointat-方法) 12 | - [String.fromCodePoint 方法](#stringfromcodepoint-方法) 13 | - [String 的 iterator](#string-的-iterator) 14 | - [at 方法](#at-方法) 15 | - [normalize 方法](#normalize-方法) 16 | - [字串確認](#字串確認) 17 | - [includes 方法](#includes-方法) 18 | - [startsWith 方法](#startswith-方法) 19 | - [endsWith 方法](#endswith-方法) 20 | - [repeat 方法](#repeat-方法) 21 | - [字串補全](#字串補全) 22 | - [padStart 方法](#padstart-方法) 23 | - [padEnd 方法](#padend-方法) 24 | - [Template Strings](#template-strings) 25 | - [raw 方法](#raw-方法) 26 | 27 | ## 使用 unicode 表示字串 28 | 29 | **TL;DR** 30 | 31 | - Unicode 解決了不同文字編碼的問題。 32 | - 每個文字和符號都對應到一組 Code Point。 33 | - 十六進制格式:`\u0000` - `\uFFFF`。 34 | 35 | _延伸閱讀:_ [Wiki - Unicode](https://zh.wikipedia.org/wiki/Unicode)、[Unicode字元平面對映](http://bit.ly/1TIEHvG) 36 | 37 | ### 在 JavaScript 中的 String 38 | 39 | - UCS-2 encode。 40 | - Support **UTF-16** encode。 41 | - Every string is **2 Byte**。 42 | 43 | 在 JavaScript 中,我們可以透過這樣的方式來表示字串: 44 | 45 | ```js 46 | const a = '\x61'; //a 47 | const b = '\u0061'; // a 48 | 49 | '\x61' === '\u0061'; // true 50 | ``` 51 | 52 | 由**十六進制格式的兩個位元**組成,代表一個編碼位置。 53 | 超出 `\u0000` - `\uFFFF` 範圍必須要用**兩個** Code Ponit 來表示。 54 | 55 | 在 ES6 中,改善了這個問題,將超出範圍的 Code Point 放入 **{ }** 就可以了。 56 | 57 | ```js 58 | // ES5 59 | 60 | const a = '\uD842\uDFB7'; // '𠮷' (4 Bytes) 61 | const b = '\u20BB7'; // ₻7 (\u20BB+7) 62 | 63 | // ES6 64 | 65 | const c = '\u{20BB7}'; // '𠮷' 66 | ``` 67 | 68 | ### codePointAt 方法 69 | 70 | 簡單來說,透過 `codePointAt()` 可以正確處理 **4 Byte** 的字元。 71 | 72 | ```js 73 | const emoji = '\uD83D\uDE02'; // 😂 74 | 75 | const tmp = emoji.codePointAt(0); // 128514 (十進制) 76 | tmp.toString(16); //1f602 (十六進制) 77 | 78 | emoji.codePointAt(1); // 56834 (十進制) 79 | 80 | ``` 81 | 82 | `codePointAt()` 方法回傳一個非負整數,它是 Unicode Code Point 的值。 83 | 84 | ### String.fromCodePoint 方法 85 | 86 | _ES5 和 ES6 方法比較:_ 87 | 88 | > ES5:`String.fromCharCode()` < _(0xFFFF)_ 89 | 90 | > ES6:`String.fromCodePoint()` > _(0xFFFF)_ 91 | 92 | `String.fromCharCode()` 會省略超過 `0xFFFF` 最高位元: 93 | 94 | ```js 95 | String.fromCharCode(0x1f602); //  => Actual:0xf602 96 | String.fromCodePoint(0x1f602); // 😂 => success 97 | ``` 98 | 99 | ## String 的 iterator 100 | 101 | ES6 的 `for..of` 在可以 **Iterable** 的物件上建立一個 loop。 102 | 103 | _(Iterator 的詳細介紹請期待下次的讀書會)_ :wink: 104 | 105 | ```js 106 | const text = 'ES6 線上讀書會'; 107 | 108 | for (t of text) { 109 | console.log(t); 110 | } 111 | 112 | // 113 | 114 | const emoji = String.fromCodePoint(0x1f602); 115 | 116 | for (e of emoji) { 117 | console.log(e); // 😂 118 | } 119 | ``` 120 | 121 | ## at 方法 122 | 123 | _at 方法目前還在 [Proposal stage 0](https://github.com/tc39/proposals/blob/master/stage-0-proposals.md) 階段_ 124 | 125 | 在前面 [codePointAt()](#codepointat-方法) 提到了這個方法可以正確的處理 **4 Byte** 的字元,因為實際上它是被拆成兩組。 126 | 在這裡我們比較一下 `charAt` 和 `at` 方法差異: 127 | 128 | ```js 129 | const text = 'abc'; 130 | const emoji = '😂'; 131 | 132 | console.log(text.charAt(0)); // a 133 | console.log(emoji.charAt(0)); // � 134 | console.log(emoji.at(0)); // 😂 135 | ``` 136 | 137 | **at()** 可以正確的回傳大於 `0XFFFF` 的字元。 138 | 139 | ## normalize 方法 140 | 141 | 帶有**語調和重音符號**的字元,Unicode 有**兩種**方式可以表示: 142 | 143 | 例如 `Ǒ` 符號可以表達成: 144 | 145 | 1. `Ǒ(\u01D1)` // 直接表達 146 | 2. `O(\u004F)` + `ˇ(\u030C)` // 組合字元 147 | 148 | --- 149 | 150 | _所以**直接表達**和**組合字元**是等價的嗎?_ 151 | 152 | ```js 153 | console.log('\u01D1' === '\u004f\u030C'); // false 154 | ``` 155 | --- 156 | 157 | `normalize()` 會根據你所指定的 Unicode 正規形式,將字符串正規化。 158 | 159 | > - NFC:預設參數,標準形式規範組成。 160 | > - NFD:標準形式規範分解。 161 | > - NFKC:標準形式兼容組成。 162 | > - NFKD:標準形式兼容分解。 163 | 164 | _更多參考 MDN:[String.prototype.normalize()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)_。 165 | 166 | 加入 `normalize()` 後的比較結果: 167 | 168 | ```js 169 | console.log( 170 | '\u01D1'.normalize() === '\u004f\u030C'.normalize() 171 | ); // true 172 | ``` 173 | 174 | ## 字串確認 175 | 176 | 我們會使用 `indexOf` 來尋找字串首次出現的位置,`lastIndex` 來尋找字串最後出現的位置。 177 | 178 | ```js 179 | const text = 'ES6 Study'; 180 | 181 | console.log(text.indexOf('E')); // 0 182 | console.log(text.indexOf('I')); // 不存在回傳 -1 183 | 184 | // 185 | 186 | const text2 = 'Happy Birthday'; 187 | 188 | console.log(text2.lastIndexOf('y')); // 13 189 | ``` 190 | 191 | ### includes 方法 192 | 193 | 確認字串是否包含如你參數中所指定的,回傳 `boolean` 值: 194 | 195 | ```js 196 | // Syntax:str.includes(searchString[, position]) 197 | 198 | const text = 'ES6 Study'; 199 | 200 | console.log(text.includes('ES6')); // true 201 | ``` 202 | 203 | ### startsWith 方法 204 | 205 | 確認字串的起始是否如你參數中所指定的,回傳 `boolean` 值: 206 | 207 | ```js 208 | // Syntax:str.startsWith(searchString[, position]) 209 | 210 | const text = 'ES6 Study'; 211 | 212 | console.log(text.startsWith('ES6')); // true 213 | ``` 214 | 215 | ### endsWith 方法 216 | 217 | 確認字串的尾部是否如你參數中所指定的,回傳 `boolean` 值: 218 | 219 | ```js 220 | // Syntax:str.endsWith(searchString[, position]) 221 | 222 | const text = 'ES6 Study'; 223 | 224 | console.log(text.endsWith('Study')); // true 225 | ``` 226 | 227 | ## repeat 方法 228 | 229 | `repeat()` 方法回傳一個新字串。 230 | 231 | ```js 232 | const A = 'A'; 233 | 234 | console.log(A.repeat(2)); // AA 235 | console.log(A.repeat(3.6)); // AAA 236 | console.log(A.repeat(-1.2)); // RangeError 237 | ``` 238 | 239 | ## 字串補全 240 | 241 | 目前 `padStart()` 和 `padEnd()` 還在 **Proposal** [stage-3](https://github.com/tc39/proposal-string-pad-start-end) 階段。 242 | 243 | 這兩個方法可以自動幫我們補全字串的長度,可以在 **start** 或是 **end** 的地方做補全。 244 | 245 | ### padStart 方法 246 | 247 | ```js 248 | // Syntax:str.padStart(targetLength [, padString]) 249 | 250 | 'o'.padStart(5, 'Hell'); // Hello 251 | ``` 252 | 253 | ### padEnd 方法 254 | 255 | ```js 256 | // Syntax:str.padEnd(targetLength [, padString]) 257 | 258 | '123'.padEnd(6, 'x'); // 123xxx 259 | ``` 260 | 261 | ## Template Strings 262 | 263 | 終於來到我們最常使用的 **Template String** 啦! 264 | 265 | ![](http://i.giphy.com/3NtY188QaxDdC.gif) 266 | 267 | 我們以前可能都是這樣輸出 HTML template 的: 268 | 269 | ```js 270 | const t = 'My name is ' + name + ', Nice to meet you'; // 不易閱讀! 271 | ``` 272 | 273 | _Template Strings - Example 1:_ 274 | 275 | ```js 276 | const name = 'PJ'; 277 | const t = `My name is ${name}, Nice to meet you`; // 變數的部份寫在 `${}` 內來顯示 278 | ``` 279 | 280 | _Template Strings - Example 2:_ 281 | 282 | ```js 283 | const t1 = `Hello\nWorld`; 284 | const t2 = `Hello, World 285 | Hello ECMAScript 6 286 | `; 287 | 288 | console.log(t1); // 透過 \n 做換行 289 | console.log(t2); // 換行 290 | ```` 291 | 292 | ![超方便的!](http://i.giphy.com/bN5n5Yy3up5vO.gif) 293 | 294 | 也可以用在 HTML 的 style 設定: 295 | 296 | ```js 297 | const style = { 298 | color: 'red', 299 | size: 4 300 | }; 301 | let temp = ` 302 | 303 | Hi! 304 | 305 | `; 306 | 307 | console.log(temp); 308 | ``` 309 | 310 | ## Tagged template literals 311 | 312 | function 可以處理跟在後面的 template strings: 313 | 314 | ```js 315 | function tag(str, ...values) { 316 | console.log(str); 317 | console.log(values[0]); 318 | console.log(values[1]); 319 | } 320 | 321 | const a = 5; 322 | const b = 10; 323 | 324 | tag`I have ${a} and ${b} number.` 325 | 326 | // result => 327 | // [I have, and, number.] 328 | // 5 329 | // 10 330 | ``` 331 | 332 | ## raw 方法 333 | 334 | `raw` 是 ES6 String 原生的方法,可以用來處理 template stirngs: 335 | 336 | ```js 337 | const text = String.raw`ECMAScript 6: \n`; 338 | console.log(text === 'ECMAScript 6: \\n') // true 339 | ``` 340 | 341 | ## Reference 342 | - [Wiki - Unicode](https://zh.wikipedia.org/wiki/Unicode) 343 | - [Unicode 字元平面對映](http://bit.ly/1TIEHvG) 344 | - [MDN - String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) 345 | - [Unicode 与 JavaScript详解](http://www.ruanyifeng.com/blog/2014/12/unicode.html) 346 | - [Javascript Unicode](http://andyyou.github.io/javascript/2015/05/21/js-unicode-issue.html) 347 | -------------------------------------------------------------------------------- /chapter-05/README.md: -------------------------------------------------------------------------------- 1 | # Expansion of RegExp in ES6 2 | 3 | 4 | 5 | #### 資料來源:[ECMAScript 6 入門](http://es6.ruanyifeng.com/#docs/regex) 6 | #### 資料整理:[cashbook](https://github.com/cashbooktw/) 7 | 8 | --- 9 | 10 | ## Outline 11 | - RegExp Constructor 12 | - RegExp Methods 13 | - The Flag of "u" (Unicode) 14 | - The Flag of "y" (Sticky) 15 | - The "sticky" Property 16 | - The "flags" Property 17 | - RegExp.escape() 18 | - Lookbehind 19 | 20 | --- 21 | 22 | ## RegExp Constructor 23 | 24 | ES5,在 RegExp 建構子內使用正則表達式,無法使用第二個參數 25 | ```js 26 | let regex = /xyz/i; 27 | let regex = new RegExp('xyz', 'i'); 28 | let regex = new RegExp(/xyz/i); 29 | 30 | let regex = new RegExp(/xyz/, 'i'); 31 | //error 32 | ``` 33 | ES6,可使用第二個參數,且 flag 會覆蓋 34 | ```js 35 | let regex = new RegExp(/xyz/ig, 'i'); 36 | let foo = regex.flags; 37 | //i 38 | ``` 39 | 40 | --- 41 | 42 | ## RegExp Methods 43 | 44 | `RegExp.prototype[Symbol.match]`於內部被 `String.prototype.match()`呼叫
45 | 其餘方法如`replace`、`search`、`split`同上 46 | ```js 47 | 'abc'.match(/a/); 48 | /a/[Symbol.match]('abc'); 49 | //return same result 50 | ``` 51 | 52 | --- 53 | 54 | ## The Flag of "u" (Unicode) 55 | 56 | 加上 `u` flag 正確處理 UTF-16 編碼 57 | ```js 58 | /^\uD83D/.test('\uD83D\uDC2A'); 59 | // true 60 | /^\uD83D/u.test('\uD83D\uDC2A'); 61 | // false 62 | ``` 63 | 加上 `u` flag,讓以下正則表達式的內容可以正確處理 64 | - The Character "." 65 | - Unicode Representation 66 | - Occurrences 67 | - Predefined Character Classes 68 | - The Flag of "i" (ignore case) 69 | 70 | ---- 71 | 72 | ### The Character "." 73 | 74 | 加上 `u` flag,讓 "." 可以正確處理 UTF-16 編碼 75 | ```js 76 | let s = '𠮷'; 77 | 78 | /^.$/.test(s); // false 79 | /^.$/u.test(s); // true 80 | ``` 81 | 82 | ---- 83 | 84 | ### Unicode Representation 85 | 86 | `\u{n}`用來表示Unicode
87 | 加上 `u` flag,處理大括號內表示的 Unicode 字元 88 | ```js 89 | /\u{61}/.test('a'); // false 90 | /\u{61}/u.test('a'); // true 91 | ``` 92 | 93 | ---- 94 | 95 | ### Occurrences 96 | 97 | 加上 `u` flag,讓 `{n}` (出現次數)正確處理 98 | ```js 99 | /a{2}/.test('aa'); // true 100 | /a{2}/u.test('aa'); // true 101 | /𠮷{2}/.test('𠮷𠮷'); // false 102 | /𠮷{2}/u.test('𠮷𠮷'); // true 103 | ``` 104 | 第三個式子等於 105 | ```js 106 | /\uD842\uDFB7{2}/.test('\uD842\uDFB7\uD842\uDFB7'); 107 | ``` 108 | 109 | ---- 110 | 111 | ### Predefined Character Classes 112 | 113 | 加上 `u` flag,讓預定義模式正確處理 114 | ```js 115 | /^\S$/.test('𠮷') // false 116 | /^\S$/u.test('𠮷') // true 117 | ``` 118 | 119 | ---- 120 | 121 | ### The Flag of "i" (ignore case) 122 | 123 | 有些Unicode字元編碼不同,但長得幾乎一樣,例如 124 | >`\u004B` = K
125 | >`\u212A` = K 126 | 127 | 加上 `u` flag,可識別非規範內的字元 128 | ```js 129 | /[a-z]/i.test('\u212A') // false 130 | /[a-z]/iu.test('\u212A') // true 131 | ``` 132 | 133 | --- 134 | 135 | ## The Flag of "y" (Sticky) 136 | 137 | 針對目標字串,只會從`lastIndex`屬性指定的索引位置開始尋找 138 | ```js 139 | let s = 'aaa_aa_a'; 140 | let r1 = /a+/g; 141 | let r2 = /a+/y; 142 | 143 | r1.exec(s) // ["aaa"] 144 | r2.exec(s) // ["aaa"] 145 | 146 | r1.exec(s) // ["aa"] 147 | r2.exec(s) // null 148 | ``` 149 | 150 | ---- 151 | 152 | 在`split`方法中使用 flag `y`,若匹配成功,則回傳矩陣第一個值一定為空字串
153 | 目標值必須相鄰才會被匹配成功 154 | ```js 155 | '#x#'.split(/#/y);// [ '', 'x#' ] 156 | '##'.split(/#/y);// [ '', '', '' ] 157 | ``` 158 | 159 | ---- 160 | 161 | 使用replace方法的例子 162 | ```js 163 | const REGEX = /a/gy; 164 | 'aaxa'.replace(REGEX, '-'); // '--xa' 165 | ``` 166 | 167 | ---- 168 | 169 | 使用`match`方法只能返回第一個匹配,必須與flag `g`並用才能回傳所有匹配 170 | ```js 171 | 'a1a2a3'.match(/a\d/y); // ["a1"] 172 | 'a1a2a3'.match(/a\d/gy); // ["a1", "a2", "a3"] 173 | ``` 174 | 175 | ---- 176 | 177 | flag y的一個應用,是從字串中提取 token,確保每一個匹配。若有非pattern的字出現則停止匹配。 178 | ```js 179 | const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y; 180 | const TOKEN_G = /\s*(\+|[0-9]+)\s*/g; 181 | 182 | tokenize(TOKEN_Y, '3x + 4'); 183 | // [ '3' ] 184 | tokenize(TOKEN_G, '3x + 4'); 185 | // [ '3', '+', '4' ] 186 | 187 | function tokenize(TOKEN_REGEX, str) { 188 | let result = []; 189 | let match; 190 | while (match = TOKEN_REGEX.exec(str)) { 191 | result.push(match[1]); 192 | } 193 | return result; 194 | } 195 | ``` 196 | 197 | --- 198 | 199 | ## The "sticky" Property 200 | 201 | `sticky`屬性為布林,若正則表達式物件使用flag `y`則為`true` 202 | ```js 203 | let r = (/hello\d/y).sticky; 204 | // true 205 | ``` 206 | 207 | --- 208 | 209 | ## The "flags" Property 210 | 211 | `flags`屬性為字串,回傳正則表達式使用的 flags 212 | ```js 213 | // ES5的source property 214 | // return pattern of RegExp 215 | let foo = /abc/ig.source; 216 | // "abc" 217 | 218 | // ES6的flags property 219 | // return flags of RegExp 220 | let bar = /abc/ig.flags; 221 | // 'gi' 222 | ``` 223 | 224 | --- 225 | 226 | ## RegExp.escape() 227 | 228 | > 未成為ES6標準 229 | 230 | 處理需要被跳脫的特殊字元 231 | ```js 232 | RegExp.escape("The Quick Brown Fox"); // "The Quick Brown Fox" 233 | RegExp.escape("Buy it. use it. break it. fix it."); 234 | // "Buy it\. use it\. break it\. fix it\." 235 | RegExp.escape("(*.*)"); // "\(\*\.\*\)" 236 | ``` 237 | 238 | --- 239 | 240 | ## Lookbehind 241 | 242 | > 未成為ES6標準 243 | 244 | lookahead 245 | ```js 246 | /\d+(?=%)/.exec('100% of US presidents have been male'); 247 | // ["100"] 248 | ``` 249 | negative lookahead 250 | ```js 251 | /\d+(?!%)/.exec('that’s all 44 of them'); // ["44"] 252 | ``` 253 | 254 | ---- 255 | 256 | lookbehind 257 | ```js 258 | /(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill'); 259 | // ["100"] 260 | ``` 261 | negative lookbehind 262 | ```js 263 | /\d+(?!%)/.exec('that’s all 44 of them'); // ["44"] 264 | ``` 265 | 266 | ---- 267 | 268 | 不符合預期的行為 269 | >capturing group 內容不同 270 | ```js 271 | /^(\d+)(\d+)$/.exec('1053'); 272 | // ["1053", "105", "3"] 273 | /(?<=(\d+)(\d+))$/.exec('1053'); 274 | // ["", "1", "053"] 275 | ``` 276 | capturing group 順序不同 277 | ```js 278 | /(?<=(o)d\1)r/.exec('hodor') // null 279 | /(?<=\1d(o))r/.exec('hodor') // ["r", "o"] 280 | ``` 281 | 282 | --- 283 | 284 | ## Reference 285 | 286 | [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
287 | [ECMA-262 Ed.6](http://www.ecma-international.org/ecma-262/6.0/) 288 | 289 | 使用工具 290 | >[es6fiddle](http://www.es6fiddle.net/)
291 | [jsbin](http://jsbin.com/?js,console)
292 | [Scratch JS](https://chrome.google.com/webstore/detail/scratch-js/alploljligeomonipppgaahpkenfnfkn)
293 | [regex101](https://regex101.com/#javascript)
294 | -------------------------------------------------------------------------------- /chapter-06/README.md: -------------------------------------------------------------------------------- 1 | # ES6讀書會 2 | - [數值的擴展-作者:阮一峰](http://es6.ruanyifeng.com/#docs/number) 3 | - 6/21(二) RayWay 4 | 5 | --- 6 | 7 | ## 二進制和八進制表示法 8 | 9 | ---- 10 | 11 | ### 二進制和八進制表示法 (ES6) 12 | - 二進制(Binary) 13 | - `OB` or `ob` 14 | 15 | ```js 16 | 0b111110111 === 503 // true 17 | ``` 18 | 19 | - 八進制(Octal) 20 | - `0O` or `0o` 21 | 22 | ```js 23 | 0o767 === 503 // true 24 | ``` 25 | 26 | - 轉成十進制 27 | 28 | ```js 29 | Number('0b111') // 7 30 | Number('0o10') // 8 31 | ``` 32 | 33 | ---- 34 | 35 | ### 二進制和八進制表示法 (ES5) 36 | - 0開始表示八進位 37 | ```js 38 | (function(){ 39 | console.log(0o11 === 011); 40 | })() // true 41 | ``` 42 | - strict mode禁用 43 | ```js 44 | (function(){ 45 | 'use strict'; 46 | console.log(0o11 === 011); 47 | })() 48 | // Uncaught SyntaxError: Octal literals are not allowed in strict mode. 49 | ``` 50 | 51 | --- 52 | 53 | ## Number.isFinite(), Number.isNaN() 54 | 55 | ---- 56 | 57 | ### ES6 58 | 59 | ---- 60 | 61 | #### Number.isFinite() 62 | - Infinity, -Infinity 63 | 64 | ```js 65 | Number.isFinite(15); // true 66 | Number.isFinite(0.8); // true 67 | Number.isFinite(NaN); // false 68 | Number.isFinite(Infinity); // false 69 | Number.isFinite(-Infinity); // false 70 | Number.isFinite('foo'); // false 71 | Number.isFinite('15'); // false 72 | Number.isFinite(true); // false 73 | Number.isFinite(1/0); // false 74 | Number.isFinite(-1/0); // false 75 | ``` 76 | 77 | ---- 78 | 79 | #### Number.isNaN() 80 | 81 | ```js 82 | Number.isNaN(NaN) // true 83 | Number.isNaN(15) // false 84 | Number.isNaN('15') // false 85 | Number.isNaN(true) // false 86 | Number.isNaN(9/NaN) // true 87 | Number.isNaN('true'/0) // true 88 | Number.isNaN('true'/'true') // true 89 | NaN === NaN // false 90 | ``` 91 | 92 | ---- 93 | 94 | ### ES5 95 | 96 | ---- 97 | 98 | #### Number.isFinite() 99 | 100 | ```js 101 | (function (global) { 102 | var global_isFinite = global.isFinite; 103 | 104 | Object.defineProperty(Number, 'isFinite', { 105 | value: function isFinite(value) { 106 | return typeof value === 'number' && global_isFinite(value); 107 | }, 108 | configurable: true, 109 | enumerable: false, 110 | writable: true 111 | }); 112 | })(this); 113 | ``` 114 | 115 | ---- 116 | 117 | #### Number.isNaN() 118 | 119 | ```js 120 | (function (global) { 121 | var global_isNaN = global.isNaN; 122 | 123 | Object.defineProperty(Number, 'isNaN', { 124 | value: function isNaN(value) { 125 | return typeof value === 'number' && global_isNaN(value); 126 | }, 127 | configurable: true, 128 | enumerable: false, 129 | writable: true 130 | }); 131 | })(this); 132 | ``` 133 | 134 | ---- 135 | 136 | #### 比較 137 | 138 | ```js 139 | isFinite(25) // true 140 | isFinite("25") // true 141 | Number.isFinite(25) // true 142 | Number.isFinite("25") // false 143 | 144 | isNaN(NaN) // true 145 | isNaN("NaN") // true 146 | Number.isNaN(NaN) // true 147 | Number.isNaN("NaN") // false 148 | ``` 149 | 150 | --- 151 | 152 | ## Number.parseInt(), Number.parseFloat() 153 | 154 | ---- 155 | 156 | - Global -> Number 157 | 158 | ```js 159 | // ES5 160 | parseInt('12.34') // 12 161 | parseFloat('123.45#') // 123.45 162 | 163 | // ES6 164 | Number.parseInt('12.34') // 12 165 | Number.parseFloat('123.45#') // 123.45 166 | ``` 167 | 168 | - 行為不變 169 | 170 | ```js 171 | Number.parseInt === parseInt // true 172 | Number.parseFloat === parseFloat // true 173 | ``` 174 | 175 | --- 176 | 177 | ## Number.isInteger() 178 | 179 | ---- 180 | 181 | - integer和float在js中是一樣的貯存方式 182 | 183 | ```js 184 | Number.isInteger(25) // true 185 | Number.isInteger(25.0) // true 186 | Number.isInteger(25.1) // false 187 | Number.isInteger("15") // false 188 | Number.isInteger(true) // false 189 | ``` 190 | 191 | ---- 192 | 193 | ### ES5 194 | 195 | ```js 196 | 197 | (function (global) { 198 | var floor = Math.floor, 199 | isFinite = global.isFinite; 200 | 201 | Object.defineProperty(Number, 'isInteger', { 202 | value: function isInteger(value) { 203 | return typeof value === 'number' && isFinite(value) && 204 | value > -9007199254740992 && value < 9007199254740992 && 205 | floor(value) === value; 206 | }, 207 | configurable: true, 208 | enumerable: false, 209 | writable: true 210 | }); 211 | })(this); 212 | ``` 213 | 214 | --- 215 | 216 | ## Number.EPSILON 217 | 218 | ---- 219 | 220 | ### IEEE 754 造成的誤差 221 | - JS沒有所謂的Integer 222 | 223 | ```js 224 | 0.1 + 0.2 225 | // 0.30000000000000004 226 | 227 | 0.1 + 0.2 - 0.3 228 | // 5.551115123125783e-17 229 | 230 | 5.551115123125783e-17.toFixed(20) 231 | // '0.00000000000000005551' 232 | ``` 233 | 234 | ---- 235 | 236 | ### Number.EPSILON 237 | - ES6引進之極小常量 238 | 239 | ```js 240 | Number.EPSILON 241 | // 2.220446049250313e-16 242 | Number.EPSILON.toFixed(20) 243 | // '0.00000000000000022204' 244 | ``` 245 | 246 | ---- 247 | 248 | ### 可接受之誤差 249 | 250 | ```js 251 | function withinErrorMargin (left, right) { 252 | return Math.abs(left - right) < Number.EPSILON; 253 | } 254 | withinErrorMargin(0.1 + 0.2, 0.3) 255 | // true 256 | withinErrorMargin(0.2 + 0.2, 0.3) 257 | // false 258 | ``` 259 | 260 | --- 261 | 262 | ## 安全整數和Number.isSafeInteger() 263 | 264 | ---- 265 | 266 | ### JS的數值範圍 267 | - -2^53^~2^53^ (不包含) 268 | 269 | ```js 270 | Math.pow(2, 53) // 9007199254740992 271 | 272 | 9007199254740992 // 9007199254740992 273 | 9007199254740993 // 9007199254740992 274 | 275 | Math.pow(2, 53) === Math.pow(2, 53) + 1 276 | // true 277 | ``` 278 | 279 | ---- 280 | 281 | ### 安全整數 282 | - Number.MAX_SAFE_INTEGER 283 | - Number.MIN_SAFE_INTEGER 284 | 285 | ```js 286 | Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 287 | // true 288 | Number.MAX_SAFE_INTEGER === 9007199254740991 289 | // true 290 | 291 | Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER 292 | // true 293 | Number.MIN_SAFE_INTEGER === -9007199254740991 294 | // true 295 | ``` 296 | 297 | ---- 298 | 299 | - Number.isSafeInteger() 300 | - 若拿來計算時,參與運算的每個數都要檢查! 301 | 302 | ```js 303 | Number.isSafeInteger('a') // false 304 | Number.isSafeInteger(null) // false 305 | Number.isSafeInteger(NaN) // false 306 | Number.isSafeInteger(Infinity) // false 307 | Number.isSafeInteger(-Infinity) // false 308 | 309 | Number.isSafeInteger(3) // true 310 | Number.isSafeInteger(1.2) // false 311 | Number.isSafeInteger(9007199254740990) // true 312 | Number.isSafeInteger(9007199254740992) // false 313 | 314 | Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false 315 | Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true 316 | Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true 317 | Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false 318 | ``` 319 | 320 | 321 | 322 | --- 323 | 324 | ## Math物件的擴展 325 | 326 | ---- 327 | 328 | ### 一般 329 | 330 | ---- 331 | 332 | #### Math.trunc() 333 | - 去除小數 334 | 335 | ```js 336 | Math.trunc(4.1) // 4 337 | Math.trunc(4.9) // 4 338 | Math.trunc(-4.1) // -4 339 | Math.trunc(-4.9) // -4 340 | Math.trunc(-0.1234) // -0 341 | ``` 342 | 343 | - 非數值先型態轉換 344 | 345 | ```js 346 | Math.trunc('123.456') // 123 347 | ``` 348 | 349 | - 無法處理就回傳NaN 350 | 351 | ```js 352 | Math.trunc(NaN); // NaN 353 | Math.trunc('foo'); // NaN 354 | Math.trunc(); // NaN 355 | 356 | ``` 357 | 358 | ---- 359 | 360 | #### Math.sign() 361 | - 正數, +1 362 | - 負數, -1 363 | - 零 364 | - +0 365 | - -0 366 | - NaN 367 | 368 | ```js 369 | 370 | Math.sign(-5) // -1 371 | Math.sign(5) // +1 372 | Math.sign(0) // +0 373 | Math.sign(-0) // -0 374 | Math.sign(NaN) // NaN 375 | Math.sign('foo'); // NaN 376 | Math.sign(); // NaN 377 | 378 | ``` 379 | 380 | ---- 381 | 382 | #### Math.cbrt() 383 | 384 | - 立方根 385 | 386 | ```js 387 | Math.cbrt(-1) // -1 388 | Math.cbrt(0) // 0 389 | Math.cbrt(1) // 1 390 | Math.cbrt(2) // 1.2599210498948734 391 | ``` 392 | 393 | ---- 394 | 395 | #### Math.clz32() 396 | - 回傳整數以32bit二進位顯示之前導`0`個數 397 | - 浮點數只取整數部分,非數值會先轉為數值。 398 | 399 | ```js 400 | Math.clz32(0) // 32 401 | Math.clz32(1) // 31 402 | Math.clz32(1000) // 22 403 | Math.clz32(0b01000000000000000000000000000000) // 1 404 | Math.clz32(0b00100000000000000000000000000000) // 2 405 | Math.clz32(1 << 1) // 30 406 | Math.clz32(1 << 2) // 29 407 | Math.clz32(1 << 29) // 2 408 | Math.clz32(3.2) // 30 409 | Math.clz32() // 32 410 | Math.clz32(NaN) // 32 411 | Math.clz32(Infinity) // 32 412 | Math.clz32(null) // 32 413 | Math.clz32('foo') // 32 414 | Math.clz32([]) // 32 415 | Math.clz32({}) // 32 416 | Math.clz32(true) // 31 417 | ``` 418 | 419 | ---- 420 | 421 | #### Math.imul() 422 | - 32-bit 整數相乘 423 | 424 | ```js 425 | Math.imul(2, 4) // 8 426 | Math.imul(-1, 8) // -8 427 | Math.imul(-2, -2) // 4 428 | ``` 429 | 430 | - 可以返回正確低位數值 431 | 432 | ```js 433 | (0x7fffffff * 0x7fffffff) | 0 // 0 434 | Math.imul(0x7fffffff, 0x7fffffff) // 1 435 | ``` 436 | 437 | ---- 438 | 439 | #### Math.fround() 440 | - 返回單精度浮點數 441 | 442 | ```js 443 | Math.fround(0) // 0 444 | Math.fround(1) // 1 445 | Math.fround(1.337) // 1.3370000123977661 446 | Math.fround(1.5) // 1.5 447 | Math.fround(NaN) // NaN 448 | ``` 449 | 450 | ---- 451 | 452 | #### Math.hypot() 453 | - 返回所有參數的平方和之平方根 454 | 455 | ```js 456 | Math.hypot(3, 4); // 5 457 | Math.hypot(3, 4, 5); // 7.0710678118654755 458 | Math.hypot(); // 0 459 | Math.hypot(NaN); // NaN 460 | Math.hypot(3, 4, 'foo'); // NaN 461 | Math.hypot(3, 4, '5'); // 7.0710678118654755 462 | Math.hypot(-3); // 3 463 | ``` 464 | 465 | ---- 466 | 467 | ### 對數 468 | 469 | ---- 470 | 471 | #### Math.expm1() 472 | - Math.expm1(x)返回e^x^ - 1,即Math.exp(x) - 1。 473 | 474 | ```js 475 | Math.expm1(-1) // -0.6321205588285577 476 | Math.expm1(0) // 0 477 | Math.expm1(1) // 1.718281828459045 478 | ``` 479 | 480 | ---- 481 | 482 | #### Math.log1p() 483 | - Math.log1p(x)方法返回1 + x的自然對數 484 | - Math.log(1 + x) 485 | - x < -1 486 | - NaN 487 | 488 | ```js 489 | Math.log1p(1) // 0.6931471805599453 490 | Math.log1p(0) // 0 491 | Math.log1p(-1) // -Infinity 492 | Math.log1p(-2) // NaN 493 | ``` 494 | 495 | ---- 496 | 497 | #### Math.log10() 498 | - 以10為底 499 | 500 | #### Math.log2() 501 | - 以2為底 502 | 503 | ```js 504 | Math.log10(2) // 0.3010299956639812 505 | Math.log10(1) // 0 506 | Math.log10(0) // -Infinity 507 | Math.log10(-2) // NaN 508 | Math.log10(100000) // 5 509 | //--- 510 | Math.log2(3) // 1.584962500721156 511 | Math.log2(2) // 1 512 | Math.log2(1) // 0 513 | Math.log2(0) // -Infinity 514 | Math.log2(-2) // NaN 515 | Math.log2(1024) // 10 516 | Math.log2(1 << 29) // 29 517 | ``` 518 | 519 | ---- 520 | 521 | ### 三角函数 522 | 523 | ```js 524 | Math.sinh(x) 525 | Math.cosh(x) 526 | Math.tanh(x) 527 | Math.asinh(x) 528 | Math.acosh(x) 529 | Math.atanh(x) 530 | ``` 531 | 532 | --- 533 | 534 | ## 指數運算符 535 | - ES7 (Babel已支援) 536 | - ** 運算子 537 | - 可以與等號連用 538 | 539 | ```js 540 | 2 ** 2 // 4 541 | 2 ** 3 // 8 542 | //--- 543 | let a = 2; 544 | a **= 2; // a = a * a; 545 | 546 | let b = 3; 547 | b **= 3; // b = b * b * b; 548 | ``` 549 | 550 | -------------------------------------------------------------------------------- /chapter-07/README.md: -------------------------------------------------------------------------------- 1 | ## ES6 讀書會 - 關於「陣列」的擴充 2 | 3 | 4 | 5 | 6 | ### 資料來源 7 | * [ECMAScript 6 入门](http://es6.ruanyifeng.com/#docs/array) (阮一峰) 8 | * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 9 | 10 | 11 | 12 | 13 | ### 關於我 14 | ``` 15 | { 16 | name: "Allen Hsieh", 17 | roles: [ 18 | "Front-end Engineer", 19 | "Java Programmer" 20 | ], 21 | worksAt: "達暉資訊", 22 | email: "allen@lustertech.net" 23 | } 24 | ``` 25 | 26 | 27 | 28 | 29 | ## 大綱 30 | * Array.from() 31 | * Array.of() 32 | * copyWithin() 33 | * find() 和 findIndex() 34 | * fill() 35 | * entries(),keys() 和 values() 36 | * includes() 37 | * 空位 38 | 39 | 40 | 41 | 42 | 43 | 44 | ## Array.from() 45 | ### 將物件轉換成陣列 46 | 47 | 48 | 49 | 50 | ### 語法 51 | ```javascript 52 | Array.from(arrayLike[, mapFn[, thisArg]]) 53 | 54 | 用途: 55 | 將「物件」轉換為「陣列」 56 | 57 | 參數: 58 | arrayLike - 物件,ArrayLike 或 Iterable 59 | mapFn - map function 60 | thisArg - map function 執行時的 this 物件 61 | ``` 62 | 63 | 64 | 65 | 66 | ### Array-like 轉換範例 67 | 68 | ```javascript 69 | let arrayLike = { 70 | '0': 'a', 71 | '1': 'b', 72 | '2': 'c', 73 | length: 3 74 | }; 75 | 76 | // ES5的写法 77 | var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] 78 | 79 | // ES6的写法 80 | let arr2 = Array.from(arrayLike); // ['a', 'b', 'c'] 81 | ``` 82 | 83 | 84 | 85 | 86 | ### Array-like 轉換範例(續) 87 | ```javascript 88 | // NodeList对象 89 | let ps = document.querySelectorAll('p'); 90 | Array.from(ps).forEach(function (p) { 91 | console.log(p); 92 | }); 93 | 94 | // arguments对象 95 | function foo() { 96 | var args = Array.from(arguments); 97 | // ... 98 | } 99 | ``` 100 | 101 | 102 | 103 | 104 | ### Array-like 必要條件 105 | ```javascript 106 | // Array-like 必要條件,必須有 length 屬性 107 | 108 | Array.from({ length: 3 }); 109 | // [ undefined, undefined, undefinded ] 110 | 111 | Array.from({ 0: 'a', 1: 'b' }) 112 | // [] 113 | ``` 114 | 115 | 116 | 117 | 118 | ### Iterable 轉換範例 119 | ```javascript 120 | Array.from('hello') 121 | // ['h', 'e', 'l', 'l', 'o'] 122 | 123 | let namesSet = new Set(['a', 'b']) 124 | Array.from(namesSet) // ['a', 'b'] 125 | ``` 126 | 127 | 128 | 129 | 130 | ### 擴展運算符號 131 | ```javascript 132 | // 擴展運算符號,也可以將物件轉換成數據 133 | 134 | // arguments对象 135 | function foo() { 136 | var args = [...arguments]; 137 | } 138 | 139 | // NodeList对象 140 | [...document.querySelectorAll('div')] 141 | 142 | // 要注意「擴展運算符號」是透過 Symbol.iterator 來進行轉換 143 | // 所以無法轉換 { length: 3 } 這樣的 array-like 物件 144 | // 這時必須透過 Array.from() 來處理 145 | ``` 146 | 147 | 148 | 149 | 150 | ### 加上 map funciton 的範例 151 | ```javascript 152 | Array.from(arrayLike).map(x => x * x); 153 | 154 | // 等同於 155 | Array.from(arrayLike, x => x * x); 156 | 157 | Array.from([1, 2, 3], x => x * x); 158 | // [1, 4, 9] 159 | 160 | 161 | let spans = document.querySelectorAll('span.name'); 162 | let names1 = Array.prototype.map.call(spans, s => s.textContent); 163 | 164 | // 等同於 165 | let names2 = Array.from(spans, s => s.textContent) 166 | ``` 167 | 168 | 169 | 170 | 171 | ### 更多範例 172 | ```javascript 173 | Array.from([1, , 2, , 3], (n) => n || 0) 174 | // [1, 0, 2, 0, 3] 175 | 176 | 177 | function typesOf () { 178 | return Array.from(arguments, value => typeof value) 179 | } 180 | typesOf(null, [], NaN) 181 | // ['object', 'object', 'number'] 182 | ``` 183 | 184 | 185 | 186 | 187 | ### 另一個用途,計算字串長度 188 | ```javascript 189 | // 因為 from 函數可以正確處理各種 Unicode 字, 190 | // 因此可避免 JavaScript 將大於 \uFFFF 的 Unicode 字, 191 | // 算成兩個字的 bug. 192 | 193 | function countSymbols(string) { 194 | return Array.from(string).length; 195 | } 196 | ``` 197 | 198 | 199 | 200 | 201 | 202 | 203 | ## Array.of() 204 | ### 建立陣列,彌補建構子的不足 205 | 206 | 207 | 208 | 209 | ### 傳統建構子的問題 210 | ```javascript 211 | // Array() 遞入沒有、一個、三個參數,所代表意思為皆不同。 212 | 213 | Array() // [] 214 | 215 | Array(3) // [, , ,] 216 | 217 | Array(3, 11, 8) // [3, 11, 8] 218 | ``` 219 | 220 | 221 | 222 | 223 | ### of 函式的好處 224 | ```javascript= 225 | // Array.of() 統一參數所代表的意思 226 | 227 | Array.of() // [] 228 | 229 | Array.of(undefined) // [undefined] 230 | 231 | Array.of(1) // [1] 232 | 233 | Array.of(1, 2) // [1, 2] 234 | ``` 235 | 236 | 237 | 238 | 239 | 240 | 241 | ## copyWithin() 242 | ### 複製陣列元素 243 | 244 | 245 | 246 | 247 | ### copyWithin 語法 248 | ```javascript 249 | arr.copyWithin(target[, start[, end]]) 250 | 251 | 參數: 252 | target - 替換的起始點 253 | start - 來源的起始點 254 | end - 來源的結束點 255 | 3 個參數都是數字,array index 256 | 從 0 開始,負數代表從後面開始算 257 | 258 | ``` 259 | 260 | 261 | 262 | 263 | ### 範例 264 | ```javascript 265 | [1, 2, 3, 4, 5].copyWithin(0, 3) 266 | // [4, 5, 3, 4, 5] 267 | 268 | // 将3号位复制到0号位 269 | [1, 2, 3, 4, 5].copyWithin(0, 3, 4) 270 | // [4, 2, 3, 4, 5] 271 | 272 | // -2相当于3号位,-1相当于4号位 273 | [1, 2, 3, 4, 5].copyWithin(0, -2, -1) 274 | // [4, 2, 3, 4, 5] 275 | ``` 276 | 277 | 278 | 279 | 280 | ### 範例(續.) 281 | ```javascript 282 | // 将3号位复制到0号位 283 | [].copyWithin.call({length: 5, 3: 1}, 0, 3) 284 | // {0: 1, 3: 1, length: 5} 285 | 286 | // 将2号位到数组结束,复制到0号位 287 | var i32a = new Int32Array([1, 2, 3, 4, 5]); 288 | i32a.copyWithin(0, 2); 289 | // Int32Array [3, 4, 5, 4, 5] 290 | 291 | // 对于没有部署TypedArray的copyWithin方法的平台 292 | // 需要采用下面的写法 293 | [].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4); 294 | // Int32Array [4, 2, 3, 4, 5] 295 | ``` 296 | 297 | 298 | 299 | 300 | 301 | 302 | ## find() 和 findIndex() 303 | ### 尋找陣列元素 304 | 305 | 306 | 307 | 308 | ### find 語法 309 | ```javascript 310 | arr.find(callback[, thisArg]) 311 | 312 | 參數: 313 | callback - 判斷函式,可以接到三個參數 314 | * element - 目前元素 315 | * index - 目前 array index 316 | * array - 目前 array 317 | 318 | thisArg - 給 callback 函式用的 this 319 | 320 | 回傳: 321 | 回傳第一個符合條件的元素,反之則回傳 undefined 322 | ``` 323 | 324 | 325 | 326 | 327 | ### find 範例 328 | ```javascript= 329 | [1, 4, -5, 10].find((n) => n < 0) 330 | // -5 331 | 332 | [1, 5, 10, 15].find(function(value, index, arr) { 333 | return value > 9; 334 | }) 335 | // 10 336 | 337 | // 上面的例子也可以寫成 338 | [1, 5, 10, 15].find(value => value > 9); 339 | ``` 340 | 341 | 342 | 343 | 344 | ### findIndex 語法 345 | ```javascript 346 | arr.findIndex(callback[, thisArg]) 347 | 348 | 參數: 349 | callback - 判斷函式,可以接到三個參數 350 | * element - 目前元素 351 | * index - 目前 array index 352 | * array - 目前 array 353 | 354 | thisArg - 給 callback 函式用的 this 355 | 356 | 回傳: 357 | 回傳第一個符合條件的位置,反之則回傳 -1 358 | ``` 359 | 360 | 361 | 362 | 363 | ### findIndex 範例 364 | ```javascript 365 | [1, 5, 10, 15].findIndex(function(value, index, arr) { 366 | return value > 9; 367 | }) // 2 368 | 369 | [NaN].indexOf(NaN) 370 | // -1 371 | 372 | [NaN].findIndex(y => Object.is(NaN, y)) 373 | // 0 374 | ``` 375 | 376 | 377 | 378 | 379 | 380 | 381 | ## fill() 382 | ### 填入陣列元素 383 | 384 | 385 | 386 | 387 | 388 | ### 語法 389 | ```javascript 390 | arr.fill(value[, start = 0[, end = this.length]]) 391 | 392 | 參數: 393 | value - 填入的值 394 | start - 起始點,array index,起始為 0 395 | end - 結束點,array index,不包含該點 396 | ``` 397 | 398 | 399 | 400 | 401 | ### 範例 402 | ```javascript 403 | ['a', 'b', 'c'].fill(7) 404 | // [7, 7, 7] 405 | 406 | new Array(3).fill(7) 407 | // [7, 7, 7] 408 | 409 | ['a', 'b', 'c'].fill(7, 1, 2) 410 | // ['a', 7, 'c'] 411 | ``` 412 | 413 | 414 | 415 | 416 | 417 | 418 | ## entries()、Keys() 和 values() 419 | ### 取得陣列 key, value or both 420 | 421 | 422 | 423 | 424 | ### 範例 425 | ```javascript 426 | for (let index of ['a', 'b'].keys()) { 427 | console.log(index); 428 | } 429 | // 0 430 | // 1 431 | 432 | for (let elem of ['a', 'b'].values()) { 433 | console.log(elem); 434 | } 435 | // "a" 436 | // "b" 437 | 438 | for (let [index, elem] of ['a', 'b'].entries()) { 439 | console.log(index, elem); 440 | } 441 | // 0 "a" 442 | // 1 "b" 443 | ``` 444 | 445 | 446 | 447 | 448 | ### 手動用 next 取資料 449 | ```javascript 450 | // 如不想使用 for...of 迴圈,也可改用 next 方法 451 | 452 | let letter = ['a', 'b', 'c']; 453 | let entries = letter.entries(); 454 | 455 | console.log(entries.next().value); // [0, "a"] 456 | console.log(entries.next().value); // [1, "b"] 457 | console.log(entries.next().value); // [2, "c"] 458 | ``` 459 | 460 | 461 | 462 | 463 | 464 | 465 | ## includes() 466 | ### 判斷陣列是否包含該元素 467 | 468 | 469 | 470 | 471 | ### 語法 472 | ```javascript 473 | var boolean = array.includes(searchElement[, fromIndex]) 474 | 475 | 參數: 476 | searchElement - 搜尋物 477 | fromIndex - 搜尋起點,array index,起始為 0 478 | 479 | 回傳: 480 | boolean,找到回傳 true,反之 false 481 | ``` 482 | 483 | 484 | 485 | 486 | ### 範例 487 | ```javascript 488 | [1, 2, 3].includes(2); // true 489 | 490 | [1, 2, 3].includes(4); // false 491 | 492 | [1, 2, 3].includes(3, 3); // false 493 | 494 | [1, 2, 3].includes(3, -1); // true 495 | 496 | [1, 2, NaN].includes(NaN); // true 497 | ``` 498 | 499 | 500 | 501 | 502 | ### 以前判斷的方式 503 | ```javascript 504 | if (arr.indexOf(el) !== -1) { 505 | // ... 506 | } 507 | 508 | // 缺點: 509 | // 1. 不直覺 510 | // 2. NaN 會出現誤判 511 | [NaN].indexOf(NaN) // -1 512 | 513 | // includes 沒有 NaN 的問題 514 | [NaN].includes(NaN) // true 515 | ``` 516 | 517 | 518 | 519 | 520 | ### 支援 521 | * [屬於 ES7 規範](https://tc39.github.io/ecma262/#sec-array.prototype.includes) 522 | * [Babel 已經支援](http://kangax.github.io/compat-table/esnext/) 523 | 524 | 525 | 526 | 527 | ### 補充 528 | ```javascript 529 | Map 和 Set 有一個 has 方法,需要注意與 includes 區分。 530 | 531 | Map 的 has 方法,是用来找 key 的: 532 | * Map.prototype.has(key) 533 | * WeakMap.prototype.has(key) 534 | * Reflect.has(target, propertyKey) 535 | 536 | Set 的 has 方法,是用来找 value 的: 537 | * Set.prototype.has(value) 538 | * WeakSet.prototype.has(value) 539 | ``` 540 | 541 | 542 | 543 | 544 | 545 | 546 | ## 空位 547 | 548 | 549 | 550 | 551 | ### 先說結論 552 | > 由於處理規則不統一,陣列 Array 應盡量「避免出現空位」。 553 | 554 | 555 | 556 | 557 | 558 | ### 空位代表該位置沒有任何值 559 | ```javascript 560 | // 例如宣告一個長度 3 的陣列 561 | Array(3) 562 | 563 | // 因為沒有初始值,所以裡面都是「空位」 564 | // [, , ,] 565 | ``` 566 | 567 | 568 | 569 | 570 | ### 注意! undefined 是有值的 571 | 572 | ```javascript 573 | // 透過 in 運算子來判斷陣列0號位子有沒有值 574 | 575 | // 回傳 true 576 | 0 in [undefined, undefined, undefined] 577 | 578 | // 回傳 false 579 | 0 in [, , ,] 580 | ``` 581 | 582 | 583 | 584 | 585 | 586 | ### ES5 對空位處理的方式不一 587 | ```javascript 588 | // 大多數都是跳過空位 589 | 590 | // forEach - 跳過空位 591 | [,'a'].forEach((x,i) => console.log(i)); // 1 592 | 593 | // filter - 跳過空位 594 | ['a',,'b'].filter(x => true) // ['a', 'b'] 595 | 596 | // every - 跳過空位 597 | [,'a'].every(x => x==='a') // true 598 | 599 | // some - 跳過空位 600 | [,'a'].some(x => x !== 'a') // false 601 | ``` 602 | 603 | 604 | 605 | 606 | ### ES5 對空位處理的方式不一(續.) 607 | ```javascript 608 | // map - 跳過空位,回傳值 undefined 609 | [,'a'].map(x => 1) // [undefined, 1] 610 | 611 | // join - 將空位視為 undefined,並被處理成空字串 612 | [,'a',undefined,null].join('#') // "#a##" 613 | 614 | // toString - 將空位視為 undefined,並被處理成空字串 615 | [,'a',undefined,null].toString() // ",a,," 616 | ``` 617 | 618 | 619 | 620 | 621 | ### ES6 把空位當 undefined 處理 622 | ```javascript 623 | Array.from(['a',,'b']) // [ "a", undefined, "b" ] 624 | 625 | [...['a',,'b']] // [ "a", undefined, "b" ] 626 | 627 | [,'a','b',,].copyWithin(2,0) // [undefined, "a", undefined, "a"] 628 | 629 | new Array(3).fill('a') // ["a","a","a"] 630 | 631 | let arr = [, ,]; 632 | for (let i of arr) { 633 | console.log(1); 634 | } 635 | // 輸出兩次 1 636 | ``` 637 | 638 | 639 | 640 | 641 | ### 以下函式也將空位處理成 undefined 642 | ```javascript 643 | // entries() 644 | [...[,'a'].entries()] // [[0, undefined], [1, "a"]] 645 | 646 | // keys() 647 | [...[,'a'].keys()] // [0, 1] 648 | 649 | // values() 650 | [...[,'a'].values()] // [undefined, "a"] 651 | 652 | // find() 653 | [,'a'].find(x => true) // undefined 654 | 655 | // findIndex() 656 | [,'a'].findIndex(x => true) // 0 657 | ``` 658 | -------------------------------------------------------------------------------- /chapter-09/README.md: -------------------------------------------------------------------------------- 1 | ES6 讀書會 2 | == 3 | ![alt](http://ryanchristiani.com/wp-content/uploads/2015/06/js-logo.png) 4 | 5 | ## 對象的擴展 6 | 7 | Ellen 8 | 9 | ---- 10 | 11 | 參考資料 12 | - [ECMAScript 6 入門](http://es6.ruanyifeng.com/#docs/object) 13 | 14 | - [MDN document](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) 15 | 16 | --- 17 | 18 | ## 1. 屬性的簡潔表示法 19 | 20 | ---- 21 | 22 | 物件屬性使用變數簡潔表示 23 | 24 | ``` javascript 25 | var name = 'Anna', 26 | age = 25, 27 | gender = 'female'; 28 | 29 | 30 | var es5Obj = { name: name, age: age, gender: gender }; 31 | var es6Obj = { name, age, gender }; 32 | 33 | console.log(es5Obj); 34 | console.log(es6Obj); 35 | // { name: 'Anna', age: 25, gender: 'female' } 36 | ``` 37 | 38 | ---- 39 | 40 | 物件方法使用函數簡潔表示 41 | 42 | ```javascript 43 | var es5Obj = { 44 | foo: function() {}, 45 | bar: function() {} 46 | }; 47 | 48 | var es6Obj = { 49 | foo() {}, 50 | bar() {} 51 | }; 52 | ``` 53 | 54 | ---- 55 | 56 | 範例 1 57 | ``` javascript 58 | const obj = { 59 | get phone() { 60 | return this._phone; 61 | }, 62 | set phone(phone) { 63 | if (!this.validatePhone(phone)) { 64 | this._phone = '+886' + phone.slice(2, 10); 65 | } 66 | }, 67 | validatePhone(phone) { 68 | return (phone.indexOf("+886") > 0) ? true : false; 69 | }, 70 | } 71 | obj.phone = '0931123456'; 72 | console.log(obj.phone); // +88631123456 73 | ``` 74 | 75 | ---- 76 | 77 | 範例 2 78 | ``` javascript 79 | function cube(value) { 80 | return Math.pow(value, 3); 81 | } 82 | 83 | function cubeRoot(value) { 84 | return Math.pow(value, 1/3); 85 | } 86 | 87 | module.exports = { cube, cubeRoot }; 88 | // 同下 89 | module.exports = { 90 | cube: cube, 91 | cubeRoot: cubeRoot 92 | }; 93 | ``` 94 | 95 | --- 96 | 97 | ## 2. 屬性名表達式 98 | 99 | ---- 100 | 101 | ``` javascript 102 | // ES6 103 | function getCar(make, model, value) { 104 | return { 105 | ['make' + make]: true 106 | }; 107 | } 108 | 109 | // before ES6 110 | function getCar(make, model, value) { 111 | var car = {}; 112 | 113 | // 只能在個別指定值時使用表達式 114 | car['make' + make] = true; 115 | 116 | return car; 117 | } 118 | ``` 119 | 120 | ---- 121 | 122 | 注意:屬性名表達式與簡潔表示法不能同時使用 123 | 124 | ``` javascript 125 | let key = 'value', 126 | 127 | value = 'hello', 128 | 129 | // 語法錯誤 130 | // Uncaught SyntaxError: Unexpected token } 131 | errObj = { [key] }; 132 | 133 | // 可寫為 134 | obj = { [key] : value } 135 | ``` 136 | 137 | --- 138 | 139 | ## 3. 方法的 name 屬性 140 | 141 | ---- 142 | 143 | name 屬性回傳函數的名稱 144 | 145 | ``` javascriptvar 146 | var object = { 147 | someMethod: function test() {} 148 | }; 149 | 150 | console.log(object.someMethod.name); // 印出 "test" 151 | 152 | object.someMethod.name = 'hello' // 僅供讀取 153 | 154 | console.log(object.someMethod.name); // 依舊印出 "test" 155 | 156 | ``` 157 | 158 | ---- 159 | 160 | 匿名函數 name 屬性 161 | 162 | ``` javascript 163 | var object = { 164 | someMethod: function() {} 165 | }; 166 | 167 | console.log(object.someMethod.name); // ES6: someMethod 168 | ``` 169 | 170 | --- 171 | 172 | ## 4. Object . is ( ) 173 | 174 | ---- 175 | 176 | 同值相等 177 | 178 | ``` javascript 179 | Object.is('foo', 'bar'); // false 180 | Object.is([], []); // false 181 | 182 | var test = { a: 1 }; 183 | Object.is(test, test); // true 184 | 185 | Object.is(null, null); // true 186 | 187 | // 特殊例子 188 | Object.is(0, -0); // false 189 | Object.is(-0, -0); // true 190 | Object.is(NaN, 0/0); // true 191 | ``` 192 | 193 | ---- 194 | 195 | 改善 `==` & `===` 比較運算符 196 | 197 | - 相等運算符 (`==`) 198 | 199 | :::danger 200 | `"" == false // true` 201 | ::: 202 | 203 | - 嚴格運算符 (`===`) 204 | 205 | :::danger 206 | `-0 === +0 // true` 207 | ::: 208 | 209 | --- 210 | 211 | ## 5. Object . assign () 212 | 213 | ---- 214 | 215 | 將來源物件的所有可枚舉屬性,複製到目標物件 216 | 217 | ``` javascript 218 | Object.assign(target, ...sources) 219 | ``` 220 | 221 | ---- 222 | 223 | - 物件的每個屬性都有一個描述對象(Descriptor),控制該屬性的行為 224 | 225 | ```javascript 226 | let obj = { foo: 123 }; 227 | Object.getOwnPropertyDescriptor(obj, 'foo') 228 | // { 229 | // value: 123, 230 | // writable: true, 231 | // enumerable: true, 232 | // configurable: true 233 | // } 234 | ``` 235 | 236 | ---- 237 | 238 | 常見用法 239 | 240 | ---- 241 | 242 | 拷貝物件 243 | 244 | ```javascript 245 | var obj = { a: 1 }; 246 | 247 | var copy = Object.assign({}, obj); 248 | 249 | console.log(copy); // { a: 1 } 250 | ``` 251 | 252 | ---- 253 | 254 | 合併多個物件 255 | 256 | ```javascript 257 | var o1 = { a: 1 }; 258 | var o2 = { b: 2 }; 259 | var o3 = { c: 3 }; 260 | 261 | var obj = Object.assign(o1, o2, o3); 262 | console.log(obj); // { a: 1, b: 2, c: 3 } 263 | console.log(o1); // { a: 1, b: 2, c: 3 }, 目標物件已被改變 264 | ``` 265 | 266 | ---- 267 | 268 | 複製 Symbol 型別屬性 269 | 270 | ```javascript 271 | var o1 = { a: 1 }; 272 | var o2 = { [Symbol('foo')]: 2 }; 273 | 274 | var obj = Object.assign({}, o1, o2); 275 | console.log(obj); // { a: 1, [Symbol("foo")]: 2 } 276 | ``` 277 | 278 | ---- 279 | 280 | - 限制: 281 | 只拷貝物件自身屬性 ( 不拷貝繼承屬性 ) 282 | 不拷貝不可枚舉 ( `enumerable: false` ) 的屬性 283 | 284 | ```javascript 285 | var obj = Object.assign({b: 'c'}, 286 | Object.defineProperty({}, 'invisible', { 287 | enumerable: false, 288 | value: 'hello' 289 | }) 290 | ) 291 | // { b: 'c' } 292 | ``` 293 | 294 | ---- 295 | 296 | - 注意: 297 | Primitives 型別會被包裹成物件 298 | (string, number, boolean, null, undefined, symbol) 299 | ```javascript 300 | var v1 = 'abc'; 301 | var v2 = true; 302 | var v3 = 10; 303 | var v4 = Symbol('foo'); 304 | 305 | var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); 306 | // null & undefined 會被忽略 307 | // 字串會以數組形式,複製進目標物件 308 | console.log(obj); // { "0": "a", "1": "b", "2": "c" } 309 | ``` 310 | 311 | ```javascript 312 | Object(true) // {[[PrimitiveValue]]: true} 313 | Object(10) // {[[PrimitiveValue]]: 10} 314 | Object('abc') 315 | // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"} 316 | ``` 317 | 318 | --- 319 | 320 | ## 6. 屬性的遍例 321 | 322 | ---- 323 | 324 | ES6 有五種方式可遍歷物件屬性: 325 | 326 | 1. for...in 327 | 328 | 2. Object.keys(obj) 329 | 330 | 3. Object.getOwnPropertyNames(obj) 331 | 332 | 4. Object.getOwnPropertySymbols(obj) 333 | 334 | 5. Reflect.ownKeys(obj) 335 | 336 | ---- 337 | 338 | 1. `for...in` 339 | 循環遍歷物件本身的屬性和繼承的可枚舉屬性 340 | (不含Symbol屬性) 341 | 342 | ``` 343 | 注意:操作中引入繼承的屬性會讓問題複雜化,通常我們只關心物件本身的屬性 344 | 儘量少用for...in循環,而用 Object.keys() 代替 345 | ``` 346 | 347 | ---- 348 | 349 | 2. `Object.keys(obj)` 350 | 返回一個陣列 351 | 包括物件本身的(不含繼承的)所有可枚舉屬性 352 | (不含Symbol屬性) 353 | 354 | ```javascript 355 | var an_obj = { 100: 'a', 2: 'b', 7: 'c' }; 356 | 357 | console.log(Object.keys(an_obj)); // console: ['2', '7', '100'] 358 | ``` 359 | 360 | ---- 361 | 362 | 將非物件傳入函式 363 | 364 | ```javascript 365 | Object.keys("foo"); 366 | // TypeError: "foo" is not an object (ES5 code) 367 | 368 | Object.keys("foo"); 369 | // ["0", "1", "2"] (ES6 code) 370 | ``` 371 | 372 | ---- 373 | 374 | 3. `Object.getOwnPropertyNames(obj)` 375 | 返回一陣列,包含對象自身的所有屬性 376 | (不含Symbol屬性,但包括不可枚舉屬性) 377 | 378 | ```javascript 379 | var my_obj = Object.create({}, { 380 | getFoo: { 381 | value: function() { return this.foo; }, 382 | enumerable: false 383 | } 384 | }); 385 | my_obj.foo = 1; 386 | 387 | console.log(Object.getOwnPropertyNames(my_obj).sort()); 388 | // logs ["foo", "getFoo"] 389 | ``` 390 | 391 | ---- 392 | 393 | 將非物件傳入函式 394 | 395 | ```javascript 396 | Object.getOwnPropertyNames('foo'); 397 | // TypeError: "foo" is not an object (ES5 code) 398 | 399 | Object.getOwnPropertyNames('foo'); 400 | // ["0", "1", "2", "length"] (ES6 code) 401 | ``` 402 | 403 | ---- 404 | 405 | 4. `Object.getOwnPropertySymbols(obj)` 406 | 返回一陣列,包含物件本身的所有Symbol屬性 407 | 408 | ```javascript 409 | var obj = {}; 410 | var a = Symbol('a'); 411 | var b = Symbol.for('b'); 412 | 413 | obj[a] = 'localSymbol'; 414 | obj[b] = 'globalSymbol'; 415 | 416 | var objectSymbols = Object.getOwnPropertySymbols(obj); 417 | 418 | console.log(objectSymbols.length); // 2 419 | console.log(objectSymbols); // [Symbol(a), Symbol(b)] 420 | ``` 421 | 422 | ---- 423 | 424 | 5. `Reflect.ownKeys(obj)` 425 | 返回一陣列,包含物件本身的所有屬性,無論屬性名是Symbol或字符串,以及是否可枚舉 426 | 427 | ```javascript 428 | Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) 429 | // ['2', '10', 'b', 'a', Symbol()] 430 | ``` 431 | 432 | --- 433 | 434 | ## 7. Object Prototype 435 | 436 | ---- 437 | 438 | 1. __proto__屬性 439 | 440 | 2. Object.setPrototypeOf() 441 | 442 | 3. Object.getPrototypeOf() 443 | 444 | ---- 445 | 446 | 1. `__proto__` 屬性 447 | 用來讀取或設置當前物件的 `prototype` 物件 448 | 449 | ```javascript 450 | var shape = {}; 451 | var circle = new Circle(); 452 | 453 | // 設定物件原型 454 | // 不建議正式使用 455 | shape.__proto__ = circle; 456 | 457 | // 拿取物件原型 458 | console.log(shape.__proto__ === circle); // true 459 | ``` 460 | 461 | ---- 462 | 463 | - 為一內部屬性,不正式對外 464 | 由於瀏覽器廣泛支持,才被加入了ES6 465 | 466 | ---- 467 | 468 | 2. Object.setPrototypeOf() 469 | 同`__proto__`,用來設置一物件的 prototype 物件 470 | 471 | ```javascript 472 | Object.setPrototypeOf(obj, prototype); 473 | ``` 474 | 475 | ---- 476 | 477 | 範例 478 | ```javascript 479 | var o = Object.setPrototypeOf({}, null); 480 | 481 | // 同下 482 | function (obj, proto) { 483 | obj.__proto__ = proto; 484 | return obj; 485 | } 486 | ``` 487 | 488 | ---- 489 | 490 | :exclamation: 491 | 492 | 改變一物件的 [[Prototype]] 需較長作業時間 493 | 如有性能考量建議使用 `Object.create()` 建一新物件設定相關 [[Prototype]] 494 | 495 | ---- 496 | 497 | 3. Object.getPrototypeOf() 498 | 用於讀取物件的原型 499 | 500 | ```javascript 501 | var proto = {}; 502 | var obj = Object.create(proto); 503 | Object.getPrototypeOf(obj) === proto; // true 504 | ``` 505 | 506 | ---- 507 | 508 | 傳入非物件參數 509 | ```javascript 510 | Object.getPrototypeOf("foo"); 511 | // TypeError: "foo" is not an object (ES5 code) 512 | Object.getPrototypeOf("foo"); 513 | // String.prototype (ES6 code) 514 | ``` 515 | 516 | --- 517 | 518 | :microscope: ES7 519 | 520 | --- 521 | 522 | ## 8. Object.value() & Object.entries() 523 | 524 | ---- 525 | 526 | ES7提議引入 Object.keys() 的配套 527 | 528 | Object.value() 529 | ```javascript 530 | var an_obj = { 100: 'a', 2: 'b', 7: 'c' }; 531 | 532 | console.log(Object.values(an_obj)); 533 | // ['b', 'c', 'a'] 534 | ``` 535 | 536 | Object.entries() 537 | ```javascript 538 | var an_obj = { 100: 'a', 2: 'b', 7: 'c' }; 539 | 540 | console.log(Object.entries(an_obj)); 541 | // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ] 542 | ``` 543 | 544 | ---- 545 | 546 | - Object.entries() 547 | 可用於將物件轉換為 Map 結構 548 | 549 | ```javascript 550 | var obj = { foo: "bar", baz: 42 }; 551 | 552 | var map = new Map(Object.entries(obj)); 553 | console.log(map); // Map { foo: "bar", baz: 42 } 554 | ``` 555 | 556 | --- 557 | 558 | ## 9. 物件的擴展運算符 559 | 560 | ---- 561 | 562 | ES7 提案 563 | 將 Rest 解構賦值 / 擴展運算符 (...) 引入物件 564 | 565 | ---- 566 | 567 | 1. Rest 解構賦值 568 | 從一個物件取值,將所有可遍歷的、但尚未被讀取的屬性,分配到指定的物件上面。所有的鍵和它們的值,都會拷貝到新物件上面 569 | 570 | ```javascript 571 | let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; 572 | x; // 1 573 | y; // 2 574 | z; // { a: 3, b: 4 } 575 | ``` 576 | 577 | ---- 578 | 579 | - Rest 解構賦值的拷貝為淺拷貝 580 | 581 | ```javascript 582 | let obj = { a: { b: 1 } }; 583 | let { ...x } = obj; 584 | obj.a.b = 2; 585 | x.a.b // 2 586 | ``` 587 | 588 | 如果一個鍵的值是復合類型的值(陣列、物件、函數) 589 | 那麼Rest解構賦值拷貝的是這個值的引用,而不是這個值的副本 590 | 591 | ---- 592 | 593 | - Rest 解構賦值不會拷貝繼承自原型物件的屬性 594 | 595 | ```javascript 596 | let o1 = { a: 1 }; 597 | 598 | let o2 = { b: 2 }; 599 | o2.__proto__ = o1; 600 | 601 | let o3 = { ...o2 }; 602 | o3 // { b: 2 } 603 | ``` 604 | 605 | ---- 606 | 607 | 2. 擴展運算符 (...) 608 | 用於取出參數物件的所有可遍歷屬性,拷貝到當前物件中 609 | 610 | ```javascript 611 | let n = { x, y, ...z }; 612 | n; // { x: 1, y: 2, a: 3, b: 4 } 613 | ``` 614 | 615 | :arrow_right: 相當於使用 Object.assign() 616 | 617 | ---- 618 | 619 | 可用於合併兩物件 620 | 621 | ```javascript 622 | let ab = { ...a, ...b }; 623 | 624 | // 同下 625 | let ab = Object.assign({}, a, b); 626 | ``` 627 | 628 | --- 629 | 630 | ### 10. Object.getOwnPropertyDescriptors() 631 | 632 | ---- 633 | 634 | ES7 提案新增一方法: 635 | 返回指定物件所有自身屬性(非繼承屬性)的描述物件 636 | 637 | ```javascript 638 | const obj = { 639 | foo: 123, 640 | get bar() { return 'abc' } 641 | }; 642 | 643 | Object.getOwnPropertyDescriptors(obj) 644 | // { foo: 645 | // { value: 123, 646 | // writable: true, 647 | // enumerable: true, 648 | // configurable: true }, 649 | // bar: 650 | // { get: [Function: bar], 651 | // set: undefined, 652 | // enumerable: true, 653 | // configurable: true } } 654 | ``` 655 | 656 | ---- 657 | 658 | - 目的:為解決 Object.assign() 659 | 無法正確拷貝get屬性和set屬性的問題 660 | 661 | - 配合Object.defineProperties方法實現正確拷貝 662 | ```javascript 663 | const source = { 664 | set foo(value) { 665 | console.log(value); 666 | } 667 | }; 668 | 669 | const target = {}; 670 | Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); 671 | Object.getOwnPropertyDescriptor(target, 'foo') 672 | // { get: undefined, 673 | // set: [Function: foo], 674 | // enumerable: true, 675 | // configurable: true } 676 | ``` 677 | 678 | ---- 679 | 680 | - 可配合 Object.create(),將物件屬性複製到一個新物件(屬於淺拷貝) 681 | 682 | 683 | ```javascript 684 | Object.create( 685 | Object.getPrototypeOf(obj), 686 | Object.getOwnPropertyDescriptors(obj) 687 | ); 688 | ``` 689 | 690 | --- 691 | 692 | Thanks :bow: 693 | -------------------------------------------------------------------------------- /chapter-13/README.md: -------------------------------------------------------------------------------- 1 | ES6 讀書會 2 | ==== 3 | Set 和 Map 數據結構 4 | 5 | Doris 6 | 7 | --- 8 | 9 | ## 導讀內容 10 | - Set 11 | - Map 12 | - WeakSet 13 | - WeakMap 14 | 15 | --- 16 | 17 | ## Set 18 | - 有序列表集合(a list of values) 19 | - 類似 Array ,但成員值都是唯一的。 20 | - 是構造函數,可以用 new 來創建 Set 資料結構。 21 | - 可接受的參數型別:Array (或類似 Array 的物件)。 22 | - 加入值時,不會轉換型別;5 和 “5” 視為不同。 23 | - 內部判斷重複值可視為使用“===”,但有些不同: 24 | - NaN 只能加入一次 25 | - “-0”和“0 或 +0”可同時存在 26 | - 兩個物件總是不相等。 27 | 28 | ---- 29 | 30 | ## Set 程式範例 31 | ```javascript= 32 | //構造函數 33 | var s = new Set([1,2,3,4,4,5,"5",NaN,NaN,0,-0]); 34 | [...s]; //[1,2,3,4,5,"5",NaN,0,-0] 將重複值剔除 35 | 36 | //類似 Array 的物件 37 | function divs () { 38 | return [...document.querySelectorAll('div')]; 39 | } 40 | 41 | var set = new Set(divs()); 42 | set.size; 43 | ``` 44 | 45 | ---- 46 | 47 | ## Set 實做的屬性與方法 48 | - 屬性 49 | - Set.prototype.constructor 50 | - Set.prototype.size 51 | - 方法 52 | - add(value):新增某個值,返回 Set 結構本身 53 | - delete(value):刪除某個值,返回 Boolean 54 | - has(value):返回 Boolean,表示是否為 Set 的成員 55 | - clear():清除所有值 56 | 57 | ---- 58 | 59 | ## Set 實做的屬性與方法程式範例 60 | ```javascript= 61 | //add(value) 因為回傳 Set 結構本身,可用 Cascading 62 | var s = new Set(); 63 | s.add('a') 64 | .add('b') 65 | .add('c'); 66 | 67 | //delete(value) 68 | s.delete('b'); 69 | 70 | //has(value) 71 | s.has('a'); 72 | 73 | //clear() 74 | s.clear(); 75 | ``` 76 | 77 | ---- 78 | 79 | ## Set 結構轉為 Array 80 | - Array.from() 81 | - Set 與 Array.from() 提供 Array 除去重複值的另一種方法 82 | ```javascript= 83 | var array2 = Array.from(new Set([1,1,2,2,3])); 84 | [...array2] 85 | ``` 86 | 87 | ---- 88 | 89 | ## Set 的遍歴操作 90 | - Set 遍歴順序就是插入順序,這個特性保證使用 Set 保存一個回調函數列表,調用時可以保證按照新增順序調用。 91 | - keys():返回鍵名 92 | - values():返回值 93 | - entries():返回鍵值對 94 | - forEach():利用回調函數,遍歴所有成員 95 | 96 | ---- 97 | 98 | ## keys(),values(),entries() 99 | - Set 結構只有鍵值,或說鍵名與鍵值是同一個值,所以 keys() 與 values() 行為相同。 100 | - Set 結構實作預設遍歷器生成函數就是 values(),表示可省略 values(),直接使用 for...of 歷遍 Set 101 | 102 | ---- 103 | 104 | ## 程式範例 105 | ```javascript= 106 | let set = new Set(['red', 'green', 'blue']); 107 | for (let item of set.keys()) { 108 | console.log(item); 109 | } 110 | for (let item of set.values()) { 111 | console.log(item); 112 | } 113 | for (let item of set.entries()) { 114 | console.log(item); 115 | } 116 | for (let item of set) { 117 | console.log(item); 118 | } 119 | ``` 120 | 121 | ---- 122 | 123 | ## forEach() 124 | ```javascript= 125 | let set = new Set([1, 2, 3]); 126 | set.forEach((v, k) => console.log(v * 2)); 127 | ``` 128 | 129 | ---- 130 | 131 | ## 遍歷的應用 132 | - 擴展運算符(...) 133 | - 去除 Array 重複的成員 134 | - Array 的 filter 與 map 可用於 Set 135 | - 實現聯集(Union),交集(Intersect),差集(Defference) 136 | - 遍歷操作中,同步改變 Set 結構 137 | - 利用 Set 映射出新的 Set 結構,再賦值給原本的 Set 138 | - 利用 Array.from() 139 | 140 | ---- 141 | 142 | ## 程式範例 - 擴展運算符(...) 143 | ```javascript= 144 | let set = new Set([1, 2, 3]); 145 | set = new Set([...set].map(x => x * 2)); 146 | 147 | let set = new Set([1, 2, 3, 4, 5]); 148 | set = new Set([...set].filter(x => (x % 2) == 0)); 149 | ``` 150 | 151 | ---- 152 | 153 | ## 程式範例 - 遍歷操作中,同步改變 Set 結構 154 | ```javascript= 155 | // 映射新的 Set 再賦值給原本的 Set 156 | let set = new Set([1, 2, 3]); 157 | set = new Set([...set].map(val => val * 2)); 158 | 159 | // Array.from() 160 | let set = new Set([1, 2, 3]); 161 | set = new Set(Array.from(set, val => val * 2)); 162 | ``` 163 | 164 | --- 165 | 166 | ## Map 167 | - 有序鍵值對集合(a collection of keys that correspond to specific values) 168 | - 類似物件,也是鍵值對的集合。 169 | - 鍵的範圍不限於字串。 170 | - 是構造函數,可以用 new 來創建 Map 資料結構。 171 | - 可接受成員為鍵值對的 Array 作為參數。 172 | - 對同一個鍵多次賦值,後面的值會覆蓋前面的。 173 | - key 是物件時,對同一物件的引用才會視為同一個。 174 | - 若兩個鍵嚴格相等視為同一個鍵,但有例外 175 | - NaN 視為同一個鍵 176 | - “-0”和“0 或 +0”視為同一個鍵 177 | 178 | ---- 179 | 180 | ## Map 程式範例 181 | ```javascript= 182 | //Array 中有兩個鍵值對的 Array 183 | var map = new Map([['name', 'Jhon'], ['title', 'test']]); 184 | console.log(map); //Map {"name" => "Jhon", "title" => "test"} 185 | 186 | //對同一物件的引用 187 | var map1 = new Map(); 188 | var obj = ['a']; 189 | map1.set(obj, 555); 190 | map1.get(obj); 191 | ``` 192 | 193 | ---- 194 | 195 | ## Map 實做的屬性與方法 196 | - 屬性 197 | - size 198 | - 方法 199 | - set(key, value):設置 key 對應的 value,返回整個 Map 資料結構。 200 | - get(key):讀取 key 對應的 value。 201 | - has(key):返回 Boolean,表示某個 key 是否在 Map 結構中。 202 | - delete(key):返回 Boolean,刪除某個 key。 203 | - clear():清除所有成員。 204 | 205 | ---- 206 | 207 | ## Map 實做的屬性與方法程式範例 208 | ```javascript= 209 | let m = new Map(); 210 | m.set("edition", 6) // 鍵是字符串 211 | m.set(262, "standard") // 鍵是數值 212 | m.set(undefined, "nah") // 鍵是undefined 213 | 214 | var hello = function() {console.log("hello");} 215 | m.set(hello, "Hello ES6!") // 鍵是函數 216 | 217 | m.size // 4 218 | m.get(hello); // Hello ES6! 219 | m.has(262); // true 220 | m.has(123); // false 221 | m.delete(262); // true 222 | m.clear(); 223 | ``` 224 | 225 | ---- 226 | 227 | ## Map 的遍歴操作 228 | - Map 遍歴順序就是插入順序。 229 | - keys():返回鍵名 230 | - values():返回值 231 | - entries():返回鍵值對 232 | - forEach():利用回調函數,遍歴所有成員 233 | 234 | ---- 235 | 236 | ## entries() 237 | - Map 結構實作預設遍歷器生成函數就是 entries(),表示可省略 entries(),直接使用 for...of 歷遍 Map 238 | ```javascript= 239 | let map = new Map([ 240 | ['F', 'no'], 241 | ['T', 'yes'], 242 | ]); 243 | 244 | for (let item of map.entries()) { 245 | console.log(item[0], item[1]); 246 | } 247 | 248 | // 或者 249 | for (let [key, value] of map.entries()) { 250 | console.log(key, value); 251 | } 252 | 253 | // 等同于使用map.entries() 254 | for (let [key, value] of map) { 255 | console.log(key, value); 256 | } 257 | ``` 258 | 259 | ---- 260 | 261 | ## forEach 262 | ```javascript= 263 | let map = new Map([ 264 | ['F', 'no'], 265 | ['T', 'yes'], 266 | ]); 267 | 268 | map.forEach(function(value, key, map){ 269 | console.log("Key: %s, Value: %s", key, value); 270 | }); 271 | 272 | //可以接受第二個參數,用來綁定 this 273 | var map2 = new Map().set(1, 'a').set(2, 'b').set(3, 'c'); 274 | var reporter = { 275 | report: function(x, y){ 276 | return x + y; 277 | } 278 | } 279 | map2.forEach(function(v, k){ 280 | console.log(this.report(1, 2)); //this 指向 reporter 281 | }, reporter); 282 | ``` 283 | 284 | ---- 285 | 286 | ## 與其他資料結構的互相轉換 287 | - Map 與 Array 288 | - Map 與 Object 289 | - Map 與 JSON 290 | 291 | ---- 292 | 293 | ## Map 與 Array 294 | - Map 轉為 Array >> 使用擴展運算符(...) 295 | - 結合 Array 的 map() 與 filter(),可實現 Map 的遍歷(map)與過濾(filter) 296 | 297 | ---- 298 | 299 | ## Map 轉為 Array 300 | ```javascript= 301 | let map = new Map([ 302 | [1, 'a'], 303 | [2, 'b'], 304 | [3, 'c'], 305 | ]); 306 | 307 | // 產生 Map 結構 {1 => 'a', 2 => 'b'} 308 | let map1 = new Map( 309 | [...map].filter(([k, v]) => k < 3) 310 | ); 311 | 312 | // 產生 Map 結構 {2 => '_a', 4 => '_b', 6 => '_c'} 313 | let map2 = new Map( 314 | [...map].map(([k, v]) => [k * 2, '_' + v]) 315 | ); 316 | ``` 317 | 318 | ---- 319 | 320 | ## Array 轉為 Map 321 | - 將 Array 轉入 Map 構造函數即可 322 | ```javascript= 323 | var map3 = new Map([[true, 7], [{foo: 3}, ['abc']]]); 324 | ``` 325 | 326 | ---- 327 | 328 | ## Map 與 Object 329 | - 若 Map 的 key 都是字串,可以轉為 Object;key 不為 字串,轉換後的物件 key 會不正確。 330 | ```javascript= 331 | function strMapToObj(strMap) { 332 | let obj = Object.create(null); 333 | for (let [k,v] of strMap) { 334 | obj[k] = v; 335 | } 336 | return obj; 337 | } 338 | 339 | let myMap = new Map().set('yes', true).set('no', false); 340 | strMapToObj(myMap) 341 | ``` 342 | 343 | ---- 344 | 345 | ## Map 與 JSON 346 | - Map 轉 JSON (JSON.stringify) 347 | - Map 的 key 都是字串符,可轉為物件 JSON。 348 | - Map 的 key 有非字串符,可轉為陣列 JSON。 349 | ```javascript= 350 | //key 都是字串 351 | function strMapToJson(strMap) { 352 | return JSON.stringify(strMapToObj(strMap)); 353 | } 354 | 355 | let myMap = new Map().set('yes', true).set('no', false); 356 | strMapToJson(myMap) 357 | 358 | //key 有非字串符 359 | function mapToArrayJson(map) { 360 | return JSON.stringify([...map]); 361 | } 362 | 363 | let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']); 364 | mapToArrayJson(myMap) 365 | ``` 366 | 367 | --- 368 | 369 | ## WeakSet 370 | - 與 Set 結構相似,也是不重複的值的集合。 371 | - 區別: 372 | - 成員只能是物件類型 373 | - 成員都是弱引用,這個特性意味 WeakSet 不可遍歷 374 | - 可用在 DOM 節點的儲存,不需擔心節點被刪除,引發記憶體洩漏(Memory Leak) 375 | ```javascript= 376 | var ws = new WeakSet(); 377 | ws.add(1) // TypeError: Invalid value used in weak set 378 | 379 | //以下範例,a 是陣列,有兩個成員,都是陣列 380 | //a 的成員會成為 WeakSet 的成員,不是 a 陣列本身 381 | //這意味陣列的成員只能是物件類型 382 | var a = [[1,2],[3,4]]; 383 | ws.add(a); 384 | ``` 385 | 386 | ---- 387 | 388 | ## WeakSet 方法 389 | - WeakSet.prototype.add(value) 390 | - WeakSet.prototype.delete(value) 391 | - WeakSet.prototype.has(value) 392 | 393 | 394 | --- 395 | 396 | ## WeakMap 397 | - 與 Map 結構相似。 398 | - 區別: 399 | - 只接受非空物件 (non-null object) 為 key 400 | - key 指向的物件不計入垃圾回收機制。 401 | - 設計目的: 402 | - key 是物件的弱引用,物件回收後 WeakMap 自動移除對應的鍵值對 403 | - 典型應用於一個 DOM 元素的 WeakMap 結構。 404 | - 有助於防止記憶體洩漏(Memory Leak)。 405 | 406 | ---- 407 | 408 | ## WeakMap 與 Map 在 API 上的區別 409 | - 沒有遍歷操作(沒有 keys(), values(), entries()),也沒有 size 屬性 410 | - 無法清空,不支持 clear() 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | -------------------------------------------------------------------------------- /chapter-14/README.md: -------------------------------------------------------------------------------- 1 | ES6 讀書會 2 | == 3 | ![alt](http://ryanchristiani.com/wp-content/uploads/2015/06/js-logo.png) 4 | 5 | ## Iterator & for...of 循環 6 | 7 | ---- 8 | 9 | 參考資料 10 | - [ECMAScript 6 入門](http://es6.ruanyifeng.com/#docs/iterator) 11 | 12 | - [MDN document](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) 13 | 14 | - [Exploring ES6](http://exploringjs.com/es6/ch_iteration.html) 15 | 16 | --- 17 | 18 | # Iterator Concepts 19 | 20 | ---- 21 | 22 | ## Usage 23 | 24 | * 為各種數據結構,提供一個統一的、簡便的訪問接口 25 | 26 | * 任何數據結構只要部署 Iterator 接口,就可依次處理該數據結構的所有成員 27 | 28 | * ES6 創造了一種新的遍歷命令 for...of 循環 29 | 30 | 31 | ---- 32 | 33 | ## Iterator 遍歷過程 34 | 35 | ``` javascript 36 | var someString = "hi"; 37 | 38 | var iterator = someString[Symbol.iterator](); 39 | iterator + ""; // "[object String Iterator]" 40 | 41 | iterator.next(); // { value: "h", done: false } 42 | iterator.next(); // { value: "i", done: false } 43 | iterator.next(); // { value: undefined, done: true } 44 | ``` 45 | * `next` 方法返回一個對象,表示當前數據成員的信息 46 | * `value` 屬性返回當前位置的成員 47 | * `done` 屬性是一個布林值,表示遍歷是否結束 48 | 49 | ---- 50 | 51 | ## Builtin Iterables 52 | 53 | * 陣列 54 | * 字串 55 | * Sets 56 | * Maps 57 | 58 | :exclamation: 物件原生不具備遍歷器接口 59 | 60 | ---- 61 | 62 | - 不用任何處理,就可以被 `for...of` 循環遍歷 63 | 64 | - 這些數據結構原生部署了 `Symbol.iterator` 屬性 65 | 66 | --- 67 | 68 | # Iterator Protocal 69 | 70 | ---- 71 | 72 | ## Iterable Protocol 73 | 74 | * 部署在數據結構的 `Symbol.iterator` 屬性 75 | 76 | :arrow_right: 具有 `Symbol.iterator` 屬性,就可以認為是 `iterable` 77 | 78 | ---- 79 | 80 | ```javascript 81 | let arr = ['a', 'b', 'c']; 82 | let iter = arr[Symbol.iterator](); 83 | 84 | iter.next() // { value: 'a', done: false } 85 | iter.next() // { value: 'b', done: false } 86 | iter.next() // { value: 'c', done: false } 87 | iter.next() // { value: undefined, done: true } 88 | ``` 89 | 90 | ---- 91 | 92 | ```javascript 93 | var someArray = [1, 5, 7]; 94 | var someArrayEntries = someArray.entries(); 95 | 96 | someArrayEntries.toString(); // "[object Array Iterator]" 97 | someArrayEntries === someArrayEntries[Symbol.iterator](); // true 98 | typeof someArrayEntries[Symbol.iterator]; // "function" 99 | ``` 100 | 101 | --- 102 | 103 | # Constucts Supporting Iteration 104 | 105 | ---- 106 | 107 | 1. for-of loop 108 | 109 | ```javascript 110 | let iterable = [10, 20, 30]; 111 | 112 | for (let value of iterable) { 113 | console.log(value); 114 | } 115 | 116 | // 10 117 | // 20 118 | // 30 119 | ``` 120 | 121 | ---- 122 | 123 | 2. Spread operator (...) 124 | 125 | ```javascript 126 | var someString = "hi"; 127 | 128 | [...someString] // ["h", "i"] 129 | ``` 130 | 131 | :+1: 可以將任何部署了Iterator接口的數據結構,轉為陣列 132 | 133 | ---- 134 | 135 | 3. yield* 136 | 137 | ```javascript 138 | let generator = function* () { 139 | yield 1; 140 | yield* [2,3,4]; 141 | yield 5; 142 | }; 143 | 144 | var iterator = generator(); 145 | 146 | iterator.next() // { value: 1, done: false } 147 | iterator.next() // { value: 2, done: false } 148 | iterator.next() // { value: 3, done: false } 149 | iterator.next() // { value: 4, done: false } 150 | iterator.next() // { value: 5, done: false } 151 | iterator.next() // { value: undefined, done: true } 152 | ``` 153 | 154 | ---- 155 | 156 | ## Others 157 | 158 | * Array.from(): 159 | `const arr = Array.from(new Set(['a', 'b', 'c']));` 160 | 161 | * Constructors of Maps and Sets: 162 | `const map = new Map([[false, 'no'], [true, 'yes']]);` 163 | `const set = new Set(['a', 'b', 'c']);` 164 | 165 | * Promise.all(), Promise.race(): 166 | `Promise.all(iterableOverPromises).then(···);` 167 | `Promise.race(iterableOverPromises).then(···);` 168 | 169 | --- 170 | 171 | # Iterators and Generators 172 | 173 | ---- 174 | 175 | ```javascript 176 | var myIterable = {} 177 | myIterable[Symbol.iterator] = function* () { 178 | yield 1; 179 | yield 2; 180 | yield 3; 181 | }; 182 | [...myIterable] // [1, 2, 3] 183 | ``` 184 | 185 | --- 186 | 187 | # return() and throw() Methods 188 | 189 | ---- 190 | 191 | ## return() 192 | 193 | * 使用場合: 如果for...of循環提前退出(通常是因為出錯,或者有break語句或continue語句) 194 | * 如果一個對象在完成遍歷前,需要清理或釋放資源,就可以部署return方法 195 | 196 | ---- 197 | 198 | ```javascript 199 | function* gen() { 200 | yield 1; 201 | yield 2; 202 | yield 3; 203 | } 204 | 205 | var g = gen(); 206 | 207 | g.next() // { value: 1, done: false } 208 | g.return('foo') // { value: "foo", done: true } 209 | g.next() // { value: undefined, done: true } 210 | ``` 211 | 212 | ---- 213 | 214 | ## throw() 215 | 216 | * 主要是配合Generator函數使用,一般的遍歷器對象用不到這個方法 217 | 218 | --- 219 | 220 | # for...of 循環 221 | 222 | ---- 223 | 224 | ## 使用的範圍包括: 225 | 226 | * Array 227 | * Set 228 | * Map 229 | * DOM NodeList ...... 230 | 231 | ---- 232 | 233 | ```javascript 234 | // DOM NodeList obj 235 | let paras = document.querySelectorAll("p"); 236 | 237 | for (let p of paras) { 238 | p.classList.add("test"); 239 | } 240 | ``` 241 | 242 | ---- 243 | 244 | ## 與 for...in 的差異 - 1 245 | 246 | ```javascript 247 | var arr = ['a', 'b', 'c', 'd']; 248 | 249 | for (let a in arr) { 250 | console.log(a); // 0 1 2 3 251 | } 252 | 253 | // 只能獲得對象的鍵名,不能直接獲取鍵值 254 | 255 | for (let a of arr) { 256 | console.log(a); // a b c d 257 | } 258 | 259 | // 允許遍歷獲得鍵值 260 | ``` 261 | 262 | ---- 263 | 264 | ## 與 for...in 的差異 - 2 265 | 266 | ```javascript 267 | let arr = [3, 5, 7]; 268 | arr.foo = 'hello'; 269 | 270 | for (let i in arr) { 271 | console.log(i); // "0", "1", "2", "foo" 272 | } 273 | 274 | for (let i of arr) { 275 | console.log(i); // "3", "5", "7" 276 | } 277 | 278 | // 數組的遍歷器接口只返回具有數字索引的屬性 279 | ``` 280 | 281 | ---- 282 | 283 | ## for...in 循環缺點: 284 | 285 | * 陣列的鍵名是數字,但是for...in循環是以字符串作為鍵名“0”、“1”、“2”等等 286 | * for...in循環不僅遍歷數字鍵名,還會遍歷手動添加的其他鍵,甚至包括原型鏈上的鍵 287 | * 某些情況下,for...in循環會以任意順序遍歷鍵名 288 | 289 | :exclamation: 主要是為遍歷物件而設計的,不適用於遍歷陣列 290 | 291 | ---- 292 | 293 | ## for...of 與其他遍歷語法的比較之優點 294 | 295 | * 有著同for...in一樣的簡潔語法,但是沒有for...in的缺點 296 | * 不同用於forEach方法,它可以與break、continue和return配合使用 297 | * 提供了遍歷所有數據結構的統一操作接口 298 | 299 | ---- 300 | 301 | ## forEach() 302 | 303 | ```javascript 304 | myArray.forEach(function (value) { 305 | console.log(value); 306 | }); 307 | ``` 308 | 309 | * 無法中途跳出forEach循環 310 | 311 | --- 312 | 313 | Thanks :bow: 314 | -------------------------------------------------------------------------------- /chapter-15/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 15. Generator function 2 | 3 | 以下章節 **由淺入深** 帶大家探索 Generator function! 4 | :seedling: :herb: :deciduous_tree: 5 | 6 | ### 大綱 7 | - [一. 淺入淺出 Generator function](#ㄧ-淺入淺出-generator-function) 8 | - [1. 它是一個狀態機](#1-它是一個狀態機) 9 | - [2. 它會回傳一個遍歷器](#2-它會回傳一個遍歷器) 10 | - [3. 使用 `next()` 遍歷狀態](#3-使用-next-遍歷狀態) 11 | - [4. 在 `next()` 中傳入參數,與 generator 互動](#4-在-next-中傳入參數與-generator-互動) 12 | - [二. 中入淺出 Generator function](#二-中入淺出-generator-function) 13 | - [1. for…of](#1-forof) 14 | - [2. Generator.prototype.throw](#2-generatorprototypethrow) 15 | - [3. Generator.prototype.return](#3-generatorprototypereturn) 16 | - [4. yield*](#4-yield) 17 | - [5. 當物件屬性是 generator](#5-當物件屬性是-generator) 18 | - [三. 深入淺出 Generator function](#三-深入淺出-generator-function) 19 | - [1. generator 的 this](#1-generator-的-this) 20 | - [2. generator 的應用](#2-generator-的應用) 21 | - [四. 參考連結](#四-參考連結) 22 | 23 | :seedling: 24 | ## ㄧ. 淺入淺出 Generator function 25 | 26 | ### 1. 它是一個狀態機 27 | 28 | 它封裝多個內部狀態,而 ***yield 是一個暫停標誌***。 29 | 30 | ```js 31 | function* generator() { 32 | yield 'state1'; 33 | yield 'state2'; 34 | } 35 | ``` 36 | 37 | ### 2. 它會回傳一個遍歷器 38 | 39 | ```js 40 | function* speak() { 41 | yield 'hello'; 42 | yield 'world'; 43 | } 44 | 45 | const iterator = speak(); 46 | ``` 47 | 48 | ### 3. 使用 `next()` 遍歷狀態 49 | 50 | ```js 51 | function* speak() { 52 | yield 'hello'; 53 | yield 'world'; 54 | } 55 | 56 | const i = speak(); 57 | 58 | i.next(); // { value: 'hello', done: false } 59 | i.next(); // { value: 'world', done: false } 60 | i.next(); // { value: undefined, done: true } 61 | ``` 62 | 63 | #### 如果 generator 有 `return` 值 64 | 65 | ```js 66 | function* speak() { 67 | yield 'hello'; 68 | yield 'world'; 69 | return '!'; 70 | } 71 | 72 | const i = speak(); 73 | 74 | i.next(); // { value: 'hello', done: false } 75 | i.next(); // { value: 'world', done: false } 76 | i.next(); // { value: '!', done: true } -> done 已經是 true,而 value 取得的是 return 的值 77 | i.next(); // { value: undefined, done: true } 78 | ``` 79 | 80 | #### `yield` 後面的表達式,只有被呼叫 `next()` 才會執行 81 | 82 | ```js 83 | function getName() { 84 | console.log('My name is world.'); 85 | return 'world'; 86 | } 87 | 88 | function* speak() { 89 | yield 'hello ' + getName(); 90 | } 91 | 92 | const i = speak(); 93 | 94 | i.next(); // My name is world. 95 | // { value: 'hello world', done: false } 96 | ``` 97 | 98 | #### 更多語法特性 99 | 100 | ```js 101 | // 不一定要使用 yield 102 | function* speak() {} 103 | 104 | // 但是 yield 一定要在 generator function 中使用 105 | function speak() { 106 | yield 'hello world'; // Error! 107 | } 108 | 109 | // 在表達式中必須加括弧 110 | console.log('hello' + (yield 'world')); 111 | 112 | // 在參數中不用 113 | getName(yield 'Who are you?'); 114 | ``` 115 | 116 | ### 4. 在 `next()` 中傳入參數,與 generator 互動 117 | 118 | ```js 119 | function* conversation() { 120 | const name = yield 'What\'s your name?'; // name 會取得第二個 next 回傳的參數 121 | yield `Ok, your name is ${name}, right?`; 122 | } 123 | 124 | const i = conversation(); 125 | i.next().value; // What's your name? 126 | i.next('Jason').value; // Ok, your name is Jason, right? 127 | ``` 128 | 129 | #### 第一個 `next()` 是不能給參數的 130 | 131 | ```js 132 | const i = conversation(); 133 | i.next().value; 134 | i.next('Jason').value; 135 | ``` 136 | 137 | #### 小試題:下方例子會分別印出什麼呢? 138 | 139 | ```js 140 | function* foo(x) { 141 | const y = 2 * (yield (x + 1)); 142 | const z = yield (y / 3); 143 | return (x + y + z); 144 | } 145 | 146 | const i = foo(5); 147 | console.log(i.next().value); 148 | console.log(i.next(12).value); 149 | console.log(i.next(13).value); 150 | ``` 151 | 152 | ```js 153 | function* foo(x) { 154 | const y = 2 * (yield (x + 1)); 155 | const z = yield (y / 3); 156 | return (x + y + z); 157 | } 158 | 159 | const i = foo(5); // x=5 160 | console.log(i.next().value); // yield x+1 -> 6 161 | console.log(i.next(12).value); // y=2*12 -> yield 24/3 -> 8 162 | console.log(i.next(13).value); // z=13 -> 5+24+13 -> 42 163 | ``` 164 | 165 | ### 5. 淺入淺出小結 166 | 167 | 1. 它是一個狀態機 168 | 2. 它會回傳一個遍歷器 169 | 3. 使用 `next()` 遍歷狀態 170 | 4. 在 `next()` 中傳入參數,與 generator 互動 171 | 172 | :herb: 173 | ## 二. 中入淺出 Generator function 174 | 175 | ### 1. for...of 176 | 177 | ```js 178 | function *foo() { 179 | yield 1; 180 | yield 2; 181 | yield 3; 182 | yield 4; 183 | yield 5; 184 | return 6; 185 | } 186 | 187 | for (let v of foo()) { 188 | console.log(v); 189 | } 190 | // 1 2 3 4 5 191 | // 注意:沒有 `return` 的值 192 | ``` 193 | 194 | #### 一個 fibonacci 例子 195 | 196 | ```js 197 | function* fibonacci() { 198 | let [prev, curr] = [0, 1]; 199 | while (true) { 200 | [prev, curr] = [curr, prev + curr]; 201 | yield curr; 202 | } 203 | } 204 | 205 | for (let n of fibonacci()) { 206 | if (n > 1000) break; 207 | console.log(n); 208 | } 209 | ``` 210 | 211 | #### 使用 `Array.form()`、擴展和解構運算式 212 | 213 | ```js 214 | function* numbers () { 215 | yield 1 216 | yield 2 217 | return 3 218 | yield 4 219 | } 220 | 221 | Array.from(numbers()) // [1, 2] 222 | 223 | [...numbers()] // [1, 2] 224 | 225 | let [x, y] = numbers(); // x=1, y=2 226 | 227 | for (let n of numbers()) { 228 | console.log(n); 229 | } 230 | // 1 2 231 | ``` 232 | 233 | ### 2. Generator.prototype.throw 234 | 235 | ```js 236 | function* g() { 237 | try { 238 | yield; 239 | } catch (e) { 240 | console.log('内部捕獲', e); 241 | } 242 | } 243 | 244 | var i = g(); 245 | i.next(); 246 | 247 | try { 248 | i.throw('a'); 249 | i.throw('b'); 250 | } catch (e) { 251 | console.log('外部捕獲', e); 252 | } 253 | 254 | // 內部捕獲, a 255 | // 外部補獲, b 256 | ``` 257 | 258 | #### `throw` 被捕獲以後,會附帶執行下一條 `yield` 語句 259 | 260 | ```js 261 | function* g() { 262 | try { 263 | yield console.log('a'); 264 | } catch (e) { 265 | // ... 266 | } 267 | yield console.log('b'); 268 | yield console.log('c'); 269 | } 270 | 271 | var i = g(); 272 | i.next(); // a 273 | i.throw(); // b 274 | i.next(); // c 275 | ``` 276 | 277 | ### 3. Generator.prototype.return 278 | 279 | ```js 280 | function* g() { 281 | yield 1; 282 | yield 2; 283 | yield 3; 284 | } 285 | 286 | var i = g(); 287 | 288 | i.next(); // { value: 1, done: false } 289 | i.return('foo'); // { value: 'foo', done: true } 290 | i.next(); // { value: undefined, done: true } 291 | ``` 292 | 293 | #### 如果使用 `finally` 294 | 295 | ```js 296 | function* numbers() { 297 | yield 1; 298 | try { 299 | yield 2; 300 | yield 3; 301 | } finally { 302 | yield 4; 303 | yield 5; 304 | } 305 | yield 6; 306 | } 307 | var i = numbers() 308 | i.next(); // { done: false, value: 1 } 309 | i.next(); // { done: false, value: 2 } 310 | i.return(7); // { done: false, value: 4 } 311 | i.next(); // { done: false, value: 5 } 312 | i.next(); // { done: true, value: 7 } 313 | ``` 314 | 315 | ### 4. yield* 316 | 317 | ```js 318 | function* foo() { 319 | yield 'a'; 320 | yield 'b'; 321 | } 322 | 323 | function* bar() { 324 | yield 'x'; 325 | yield* foo(); 326 | yield 'y'; 327 | } 328 | 329 | for (let v of bar()) { 330 | console.log(v); 331 | } 332 | // x a b y 333 | ``` 334 | 335 | #### 陣列是 iterator 336 | 337 | ```js 338 | function* g() { 339 | yield* ['a', 'b', 'c']; 340 | } 341 | 342 | g().next() // { value: 'a', done: false } 343 | ``` 344 | 345 | #### 字串也是 iterator 346 | 347 | ```js 348 | let read = (function* () { 349 | yield 'hello'; 350 | yield* 'hello'; 351 | })(); 352 | 353 | read.next().value // 'hello' 354 | read.next().value // 'h' 355 | ``` 356 | 357 | #### 接收 `yield*` 的 `return` 358 | 359 | ``` 360 | function* foo() { 361 | yield 2; 362 | yield 3; 363 | return 'foo'; 364 | } 365 | 366 | function* bar() { 367 | yield 1; 368 | var v = yield* foo(); 369 | console.log('v: ' + v); 370 | yield 4; 371 | } 372 | 373 | var i = bar(); 374 | 375 | i.next(); // { value: 1, done: false } 376 | i.next(); // { value: 2, done: false } 377 | i.next(); // { value: 3, done: false } 378 | i.next(); // 'v: foo' 379 | // { value: 4, done: false } 380 | i.next(); // { value: undefined, done: true } 381 | ``` 382 | 383 | ### 5. 當物件屬性是 generator 384 | 385 | ```js 386 | const obj = { 387 | * myGeneratorMethod() { 388 | ··· 389 | } 390 | }; 391 | ``` 392 | 393 | ### 6. 中入淺出小結 394 | 395 | 我們介紹了以下語法: 396 | 397 | 1. for…of 398 | 2. Generator.prototype.throw 399 | 3. Generator.prototype.return 400 | 4. yield* 401 | 5. 當物件屬性是 generator 402 | 403 | :deciduous_tree: 404 | ## 三. 深入淺出 Generator function 405 | 406 | ### 1. generator 的 this 407 | 408 | #### gernerator 回傳的是 gernerator 實例 409 | 410 | ```js 411 | function* g() {} 412 | 413 | g.prototype.hello = function () { 414 | return 'hi!'; 415 | }; 416 | 417 | const obj = g(); 418 | 419 | obj instanceof g; // true 420 | obj.hello(); // 'hi!' 421 | ``` 422 | 423 | #### generator 返回的是遍歷器,不是 this 424 | 425 | ```js 426 | function* g() { 427 | this.a = 11; 428 | } 429 | 430 | let obj = g(); 431 | obj.a; // undefined 432 | ``` 433 | 434 | #### 不能使用 new 435 | 436 | ```js 437 | function* F() { 438 | yield this.x = 2; 439 | yield this.y = 3; 440 | } 441 | 442 | new F() 443 | // TypeError: F is not a constructor 444 | ``` 445 | 446 | ### 2. generator 的應用 447 | 448 | 1. 以同步化的方式撰寫異步操作 449 | 2. 控制流管理 450 | 3. 部署 iterator 接口 451 | 4. 作為數據結構 452 | 453 | :mortar_board: 454 | 455 | ## 四. 參考連結 456 | 457 | 1. [ECMAScript 6 入门 | 阮一峰](http://es6.ruanyifeng.com/#docs/generator) 458 | 2. [Egghead](https://egghead.io/lessons/ecmascript-6-generators?course=learn-es6-ecmascript-2015) 459 | 3. [淺入淺出 Generator Function](http://abalone0204.github.io/2016/05/08/es6-generator-func/) 460 | 4. [Promise, generator, async 與 ES6](http://huli.logdown.com/posts/292655-javascript-promise-generator-async-es6) 461 | 5. [Airbnb style guide](https://github.com/airbnb/javascript#iterators-and-generators) 462 | -------------------------------------------------------------------------------- /chapter-17/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 17. 異步操作和 Async 函数 2 | 3 | ### 大綱 4 | 5 | - [一. Blocking and waiting](#ㄧ-blocking-and-waiting) 6 | - [二. Callback function](#二-callback-function) 7 | - [三. ES2015 Promise](#三-es2015-promise) 8 | - [四. ES2015 Generator function](#四-es2015-generator-function) 9 | - [五. ES2016 Async function](#五-es2016-async-function) 10 | - [六. More Async programming](#六-more-async-programming) 11 | - [七. 參考資源](#七-參考資源) 12 | - [八. Curators](#八-curators) 13 | 14 | ## ㄧ. Blocking and waiting 15 | 16 | #### Blocking (pulling): 17 | 18 | ```js 19 | function login() { 20 | const fbUser = fbLogin(); 21 | const user = getCurrentUser(fbUser); 22 | return user; 23 | } 24 | ``` 25 | 26 | 在 Jafar Husain 的影片([連結](https://www.youtube.com/watch?v=lil4YCCXRYc))中,他提到的 Pulling 概念可以用下面這一句來想像: 27 | `fbUser <- fbLogin()` 28 | 29 | 換成白話文的意思就是 - 我們調用 fbLogin 這個函數,並將 fbUser 的值從中拉 (pull) 出來。 30 | 31 | #### Waiting (pushing): 32 | 33 | ```js 34 | function login() { 35 | fbLogin((fbUser) => { 36 | getCurrentUser(fbUser, (user) => { 37 | }); 38 | }); 39 | } 40 | ``` 41 | 42 | 如果 fbLogin 是非同步的請求(例如 AJAX),我們沒辦法馬上得到資料,也不能讓程式 blocking 在這一行(如果使用者點了登入按鈕,卻看到頁面卡在那裡是會抓狂的)。 43 | 44 | 所以我們可以使用 Pushing 的概念,用下面這一行想像: 45 | `fbLogin( -> fbUser)` 46 | 47 | 白話文 - 我們調用 fbLogin,並將結果值推 (push) 給 fbUser。 48 | 49 | ## 二. Callback function 50 | 51 | 如果瞭解了 blocking 和 waiting 的概念,在 ES6 以前,如果我們要寫 Async 的邏輯,勢必一定要用 callback。 52 | 53 | ```js 54 | function login(callback) { 55 | fbLogin((err, fbUser) => { 56 | if (err) { 57 | callback(err); 58 | return; 59 | } 60 | getCurrentUser(fbUser, (err, user) => { 61 | callback(err, user); 62 | }); 63 | }); 64 | } 65 | ``` 66 | 67 | 為了不讓我們的程式停在非同步請求,我們將邏輯拆分成兩段: 68 | 一段是調用請求的主程式; 69 | 一段是處理請求結果的 callback 函數。 70 | 71 | 不過使用 Callback 處理 Async 的隱憂就是: 72 | 73 | ![Callback hell](http://icompile.eladkarako.com/wp-content/uploads/2016/01/icompile.eladkarako.com_callback_hell.gif) 74 | 75 | ## 三. ES2015 Promise 76 | 77 | 到了 ES6,想必你也用過 Promise 來避免 Callback hell: 78 | 79 | ```js 80 | fbLogin() 81 | .then(getCurrentUser) 82 | .then((user) => console.log(user)) 83 | .catch((err) => console.log(err)); 84 | ``` 85 | 86 | ## 四. ES2015 Generator function 87 | 88 | ES6 提供了我們另外一個語法糖 - [Generator](../chapter-15): 89 | 90 | ```js 91 | function* login() { 92 | const fbUser = yield fbLogin(); 93 | const user = yield getCurrentUser(fbUser); 94 | return user; 95 | } 96 | ``` 97 | 98 | 這語法糖讓我們可以使用 [Pulling](#blocking-pulling) 的方式撰寫非同步操作,讓編寫同步和非同步程式的思維一致化。 99 | 100 | > PS. 如果你把上面那一段程式的 `*` 與 `yield` 拿掉,是不是就跟同步邏輯一樣了呢! 101 | 102 | #### 使用 `spawn` 103 | 104 | 不過 Generator 是一個狀態機,我們必須調用 login 函數後拿到最後 user 的值,而不是停在每一個 `yield` 的中斷點上;因此我們需要一個執行器。 105 | 106 | ```js 107 | const promise = spawn(login()); 108 | 109 | promise 110 | .then((user) => console.log(user)) 111 | .catch((err) => console.log(err)); 112 | ``` 113 | 114 | `spawn.js` 幫我們解決了這個問題,我們將 Generator 回傳的 iterator 遞給 spawn,它會幫我們執行每一個非同步操作,直到那到最後的值,並 push 到你的程式中。 115 | 116 | #### `spawn.js` 是如何執行這些非同步的? 117 | 118 | ```js 119 | function spawn(iterator) { 120 | return new Promise((resolve, reject) => { 121 | const onResult = (data) => { 122 | const { value, done } = iterator.next(data); 123 | if (!done) value.then(onResult); 124 | else resolve(data); 125 | }; 126 | onResult(); 127 | }); 128 | } 129 | ``` 130 | 131 | 不過使用 Generator 來撰寫 push 風格的非同步操作,卻需要額外的執行器(spawn.js);想像以後我們需要專們處理 Async 的語法糖,不再需要使用者些執行器... 132 | 133 | ## 五. ES2016 Async function 134 | 135 | ES7 的 Async/Await 就是專門解決非同步操作的語法糖,我們不再需要引用額外的程式。 136 | 137 | ```js 138 | async function login() { 139 | const fbUser = await fbLogin(); 140 | const user = await getCurrentUser(fbUser); 141 | return user; 142 | } 143 | 144 | login() 145 | .then((user) => console.log(user)) 146 | .catch((err) => console.log(err)); 147 | ``` 148 | 149 | ## 六. More Async programming 150 | 151 | 在 Jafar Husain 的影片末也有提到未來 ES Async programming 的走向,例如 Observable interface: 152 | 153 | ```js 154 | async function getReplies() { 155 | for (let reply on new WebSocket('/posts/1/replies')) { 156 | console.log(reply); 157 | } 158 | } 159 | ``` 160 | 161 | 這部影片值得去看一看,可以了解整個 Async programming 的來龍去脈([連結](https://www.youtube.com/watch?v=lil4YCCXRYc))! 162 | 163 | ## 七. 參考資源 164 | 165 | 1. [Jafar Husain: Async Programming in ES7 | JSConf US 2015](https://www.youtube.com/watch?v=lil4YCCXRYc) 166 | 2. [异步操作和 Async 函数](http://es6.ruanyifeng.com/#docs/async) 167 | 3. [Callback Hell - A guide to writing asynchronous JavaScript programs](http://callbackhell.com/) 168 | 169 | ## 八. Curators 170 | 171 | - [shiningjason](http://shiningjason1989.github.io/): Hope this article will inspire you 🍾🍾🍾 172 | 173 | -------------------------------------------------------------------------------- /introducton/README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | # 成立讀書會的目的 4 | 讀書會的成員,有老手,也有初學者。 5 | 由於程式語言的進步非常的快速,不管是從事開發十年以上的老手, 6 | 或是剛入門的初學者,都必須重新適應新的開發語言,新的特性。 7 | 所以我們希望透過一個平台 8 | 打造一個學習與實作的環境, 9 | 共同成長和學習。 10 | 11 | # 練習環境 12 | 1. 需要一個可以編譯 es6 成 es5 的測試環境。 13 | 2. 驗證語法是否正確。 14 | 15 | # 工具 16 | 17 | * 簡體轉繁體 18 | 19 | - 特色 : 網頁內容簡體中文轉繁體中文 20 | - 安裝 : [Chrome 線上應用程式商店](https://chrome.google.com/webstore/detail/new-tong-wen-tang/ldmgbgaoglmaiblpnphffibpbfchjaeg?hl=zh-TW) 21 | 22 | 23 | * 編輯器 24 | - 線上編輯 25 | - [jsbin](http://jsbin.com/) 26 | - [codepen](http://codepen.io/) 27 | - [jsfiddle](https://jsfiddle.net/) 28 | 29 | - 線上即時轉譯 30 | - [babel](https://babeljs.io/repl/) 31 | - [traceur](https://google.github.io/traceur-compiler/demo/repl.html#) 32 | 33 | - ATOM 34 | - 特色 : 免費跨平台使用,可以提供語法檢查、高亮語法顯示、語法提示... 35 | - 安裝 : [官網](https://atom.io/) 36 | - 外掛 : 37 | - [es6-javascript](https://atom.io/packages/es6-javascript) 38 | - [linter-eslint](https://atom.io/packages/linter-eslint) 39 | 40 | * 錄影工具 41 | 42 | - 線上 43 | - [youtube 螢幕錄影](http://www.playpcesor.com/2016/01/youtube-desktop-screen-record.html) 44 | - windows 45 | - [ActivePresenter](http://www.playpcesor.com/2016/01/activepresenter.html) 46 | - mac 47 | - [QuickTime Player](https://support.apple.com/zh-tw/HT201066) 48 | 49 | 50 | 51 | 歡迎其他成員協助撰寫內容補充資料 52 | --------------------------------------------------------------------------------