├── _config.yml ├── LICENSE └── translations ├── zh-CN.md ├── pt-BR.md ├── zh-TW.md ├── th-TH.md └── ja-JP.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | title: Modern JS Cheatsheet 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 BEAUDRU Manuel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /translations/zh-CN.md: -------------------------------------------------------------------------------- 1 | # Modern JavaScript Cheatsheet 简体中文版 2 | 3 | ![Modern JavaScript cheatsheet](https://i.imgur.com/aexPxMb.png) 4 | Image Credits: [Ahmad Awais ⚡️](https://github.com/ahmadawais) 5 | 6 | ## 简介 7 | 8 | ### 初心 9 | 10 | 这份文档整理了在当前前端项目中经常需要查阅的内容,并给出了最新的代码示例。 11 | 12 | 你或许会因为不熟悉当前一些新的代码库(例如 React)所用到的 JavaScript 概念,而很难上手这些新框架。所以本文档的目的并非从零教你 JavaScript,而是帮助已经有一定编程基础的你。 13 | 14 | 除此之外,我(作者:[Manuel Beaudru](https://github.com/mbeaudru))偶尔会写上一些我的小技巧,也会注意提示这只是我的个人提议。 15 | 16 | > **注:** 这篇文档里提到的大多数概念来自于目前最新的 JavaScript(ES2015,即 ES6),你可以在[这里](http://es6-features.org)查看新增的特性,网站做得很棒。 17 | 18 | ### 参考材料 19 | 20 | 当你觉得有的概念不容易理解时,你可以在下面的链接里面寻找答案。 21 | 22 | - [MDN (Mozilla Developer Network)](https://developer.mozilla.org/zh-CN/search?q=) 23 | - [You don't know JS(书)](https://github.com/getify/You-Dont-Know-JS) 24 | - [ES6 新特性和例子](http://es6-features.org) 25 | - [WesBos 博客中 ES6 类别](http://wesbos.com/category/es6/) 26 | - [Reddit (JavaScript)](https://www.reddit.com/r/javascript/) 27 | - [Google](https://www.google.com/) 可直接查找特定的博客和资源 28 | - [StackOverflow](https://stackoverflow.com/questions/tagged/javascript) 29 | 30 | ## 目录 31 | 32 | - [Modern JavaScript Cheatsheet 简体中文版](#Modern-JavaScript-Cheatsheet-简体中文版) 33 | * [简介](#简介) 34 | + [初心](#初心) 35 | + [参考材料](#参考材料) 36 | * [目录](#目录) 37 | * [正文](#正文) 38 | + [变量声明: var, const, let](#变量声明-var-const-let) 39 | - [简述](#简述) 40 | - [代码示例](#代码示例) 41 | - [详述](#详述) 42 | - [延伸资料](#延伸资料) 43 | + [箭头函数](#箭头函数) 44 | - [简述](#简述-1) 45 | - [详述](#详述-1) 46 | * [简洁性](#简洁性) 47 | * [*this* 关键字](#this-关键字) 48 | - [相关资料](#相关资料) 49 | + [方法默认参数值](#方法默认参数值) 50 | 51 | ## 正文 52 | 53 | ### 变量声明: var, const, let 54 | 55 | 在 JavaScript 中,声明变量时可以用三个不同的关键词,分别是 `var`,`let` 以及 `const` ,它们各有异同。 56 | 57 | #### 简述 58 | 59 | 用 `const` 声明的变量,不能被重新赋值,而另两个 `var` 和 `let` 是可以的。 60 | 61 | 所以我建议默认情况下你都用 `const` 来声明变量,在你需要 *改变* 或是声明之后再重新指派它的时候,才用 `let` 来声明变量。 62 | 63 | 64 | | - | 作用域 | 是否可重新赋值 | 是否可变 | [暂存死区](#tdz_sample) | 65 | | ----- | ---- | ------- | -------------------------- | ------------------- | 66 | | const | 块级 | × | [√](#const_mutable_sample) | √ | 67 | | let | 块级 | √ | √ | √ | 68 | | var | 函数 | √ | √ | × | 69 | 70 | #### 代码示例 71 | 72 | ```javascript 73 | const person = "Nick"; 74 | person = "John" // 因为 person 不能被重新赋值,所以会报错 75 | ``` 76 | 77 | ```javascript 78 | let person = "Nick"; 79 | person = "John"; 80 | console.log(person) // "John", 使用 let 声明的变量可以重新赋值 81 | ``` 82 | 83 | #### 详述 84 | 85 | 简单来讲,变量的作用域([*scope*](#scope_def))是指“在这部分代码中可以访问到此变量”。 86 | 87 | ##### var 88 | 89 | 使用 `var` 定义的变量,其作用域是定义它的函数内部(*function scoped*),也就是说在函数内部创建一个 `var` 变量的时候,在此函数内部可以任意访问这个变量,但在函数之外,这样的局部变量是无法被访问的。 90 | 91 | 我建议你这样理解,如果一个变量是 *X 作用域(scoped)* 类型的,那就是说这个变量是 X 的属性之一。(译注:X 有 function 和 block 两类,代表函数作用域和块级作用域。) 92 | 93 | ```javascript 94 | function myFunction() { 95 | var myVar = "Nick"; 96 | console.log(myVar); // "Nick" - 在这个函数中 myVar 可被访问到 97 | } 98 | console.log(myVar); // 抛出错误 ReferenceError, 在函数之外 myVar 则无法访问 99 | ``` 100 | 101 | 继续来看变量的作用域,下面有更多精妙的例子: 102 | 103 | ```javascript 104 | function myFunction() { 105 | var myVar = "Nick"; 106 | if (true) { 107 | var myVar = "John"; 108 | console.log(myVar); // "John" 109 | // 实际上 myVar 是函数级作用域变量,重新声明的时候,相当于用 "John" 抹去了 myVar 之前的值 "Nick" 110 | } 111 | console.log(myVar); // "John" - 可见 if 块中的代码会如何影响变量 112 | } 113 | console.log(myVar); // 抛出错误 ReferenceError, 在函数之外 myVar 则无法访问 114 | ``` 115 | 116 | 另外,*var* 声明的变量在执行的时候,就像会被移动到作用域的开始,这就是我们所说的[变量声明提升(var hoisting)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/**var)。 117 | 118 | 所以看下面这段代码: 119 | 120 | ```javascript 121 | console.log(myVar) // undefined -- 没有提示错误 122 | var myVar = 2; 123 | ``` 124 | 125 | 之所以没有发生错误,是因为它执行时会被解释为这样: 126 | 127 | ```javascript 128 | var myVar; 129 | console.log(myVar) // undefined -- 没有提示错误 130 | myVar = 2; 131 | ``` 132 | 133 | ##### let 134 | 135 | `var` 和 `let` 几乎是一样的,但是用 `let` 声明的变量有如下特性: 136 | 137 | - *块级作用域*( block scoped ) 138 | - 在被赋值之前,是**无法**访问使用的 139 | - 在同一个作用域之下,不能被重新声明 140 | 141 | 我们来看看之前例子中提到的块级作用域( block scoping )的效果: 142 | 143 | ```javascript 144 | function myFunction() { 145 | let myVar = "Nick"; 146 | if (true) { 147 | let myVar = "John"; 148 | console.log(myVar); // "John" 149 | // 实际上 myVar 是块级作用域的变量,在 if 块中,我们相当于是创建了一个新变量, 150 | // 这个变量在此块之外是无法被访问的,而且它完全区别于我们创建的第一个 myVar 变量! 151 | } 152 | console.log(myVar); // "Nick", 可见 if 块中的代码,并没有影响到这个变量的值 153 | } 154 | console.log(myVar); // 抛出错误 ReferenceError,在函数外部无法访问到 myVar。 155 | ``` 156 | 157 | 现在,来看看 *let*(和 *const* )声明的变量在赋值前无法访问是什么意思: 158 | 159 | ```javascript 160 | console.log(myVar) // 提示错误 ReferenceError ! 161 | let myVar = 2; 162 | ``` 163 | 164 | 这就是它们和 *var* 变量的区别,如果你在还未赋值给 *let* 或者 *const* 变量之前,就想读写它,是会提示错误的。这种情况常被称作暂存死区([*Temporal dead zone*](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let#let_的暂存死区与错误))或者 *TDZ*。 165 | 166 | > **注意:** 从技术上讲,*let* 和 *const* 变量声明时也存在提升,但并不代表它们的赋值也会被提升。但由于它被设计成了赋值之前无法使用,所以我们直观感觉上它没有被提升,但其实是存在提升的。如果想了解更多细节,请看[这篇文章](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified)。 167 | 168 | 169 | 另外,在同一作用域内你不能重新声明一个 *let* 变量。 170 | 171 | ```js 172 | let myVar = 2; 173 | let myVar = 3; // 提示语法错误 SyntaxError 174 | ``` 175 | 176 | ##### const 177 | 178 | `const` 声明的变量很像 `let`,但它不能被重新赋值。 179 | 180 | 总结一下 `const` 变量的特点如下: 181 | 182 | - *块级作用域* 183 | - 赋值之前无法使用 184 | - 在同一个作用域内部,你不能重新声明一个变量 185 | - 不能被重新指派 186 | 187 | ```Javascript 188 | const myVar = "Nick"; 189 | myVar = "John" // 提示错误,不允许重新赋值 const 变量 190 | ``` 191 | 192 | 但这里有一个小细节:`const` 变量并非完全[不可变](#mutation_def),如果这个变量是 `object` 和 `array` 类型的值,那它的值是**可以改变**的。assign 193 | 194 | 对于对象类型来说: 195 | 196 | ```js 197 | const person = { 198 | name: 'Nick' 199 | }; 200 | person.name = 'John' // 这会生效的!person 并非完全重新指派( reassigned ),只是值变化了( mutated ) 201 | console.log(person.name) // "John" 202 | person = "Sandra" // 提示错误,因为用 const 声明的变量不能被重新指派 203 | ``` 204 | 205 | 对于数组类型来说: 206 | 207 | ```js 208 | const person = []; 209 | person.push('John'); // 这也会生效!person 并非完全重新指派( reassigned ),只是值变化了( mutated ) 210 | console.log(person[0]) // "John" 211 | person = ["Nick"] // 提示错误,因为用 const 声明的变量不能被重新指派 212 | ``` 213 | 214 | #### 延伸资料 215 | 216 | - [How let and const are scoped in JavaScript - WesBos](http://wesbos.com/javascript-scoping/) 217 | - [Temporal Dead Zone (TDZ) Demystified](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified) 218 | 219 | ### 箭头函数 220 | 221 | ES6 JS 的最新版本已经介绍了*箭头函数*, 箭头函数是以另一种方式声明和使用函数。以下是箭头函数带来的一些好处: 222 | 223 | - 更加简洁 224 | - 从上下文获取*this* 225 | - 隐式的返回方式 226 | 227 | #### 简述 228 | 229 | - 简洁性和隐式的返回方式 230 | 231 | ```js 232 | function double(x) { return x * 2; } // 传统函数声明方式 233 | console.log(double(2)) // 4 234 | ``` 235 | 236 | ```js 237 | const double = x => x * 2; // 同样的函数,使用具有隐式返回方式的箭头函数来表示 238 | console.log(double(2)) // 4 239 | ``` 240 | - *this* 关键字 241 | 242 | 在箭头函数中, *this*的值就等于函数所处的封闭的可执行上下文的*this*。简单来说,就是在箭头函数中,当你调用一个位于函数体内部的函数时,在内部函数中,你不需要使用"that = this" 这样的声明语句。 243 | 244 | ```js 245 | function myFunc() { 246 | this.myVar = 0; 247 | setTimeout(() => { 248 | this.myVar++; 249 | console.log(this.myVar) // 1 250 | }, 0); 251 | } 252 | ``` 253 | 254 | #### 详述 255 | 256 | ##### 简洁性 257 | 258 | 箭头函数从很多方面都比传统的函数简洁。案例如下: 259 | 260 | - 隐式返回 VS 显式返回 261 | 262 | **显式返回** 指的是函数的返回语句使用了return 关键字 263 | 264 | ```js 265 | function double(x) { 266 | return x * 2; // 使用了*return*关键字,显式返回 x * 2 267 | } 268 | ``` 269 | 270 | 传统函数总是伴随着显式返回。使用箭头函数,你可以使用*隐式返回*,即不需要在函数体内使用return关键字就可以返回值。 271 | 272 | 隐式返回需要将所需代码写在一条语句中。 273 | 274 | ```js 275 | const double = (x) => { 276 | return x * 2; // 显式返回 277 | } 278 | ``` 279 | 280 | 鉴于只返回单值,我们可以使用隐式返回。 281 | 282 | ```js 283 | const double = (x) => x * 2; 284 | ``` 285 | 286 | 为实现隐式返回,我们只需要 **移除花括号** 和 **return** 关键字。之所以被称为*隐式*返回,是因为*return*关键字不存在的情况下,函数仍可返回 ```x * 2```。 287 | 288 | > **注意:** 如果你的函数不是返回一个单值(伴有*连带值*),那么既不可以使用显式返回也不可以使用隐式返回。 289 | 290 | 除此之外, 如果你想隐式返回一个*object* 则必须使用圆括号对其修饰, 291 | 292 | ```js 293 | const getPerson = () => ({ name: "Nick", age: 24 }) 294 | console.log(getPerson()) // { name: "Nick", age: 24 } -- 箭头函数返回的对象 295 | ``` 296 | 297 | - 函数只有一个参数 298 | 299 | 如果你的箭头函数只有一个参数,你可以省略修饰参数的圆括号,重新观察上面的代码: 300 | 301 | ```js 302 | const double = (x) => x * 2; // 箭头函数只有一个参数 303 | ``` 304 | 305 | 参数外面的圆括号可以省略: 306 | 307 | ```js 308 | const double = x => x * 2; // 箭头函数只有一个参数 309 | ``` 310 | 311 | - 函数无参数 312 | 313 | 当箭头函数无参数时,必须使用圆括号,否则会出现语法错误. 314 | 315 | ```js 316 | () => { // 必须提供圆括号 317 | const x = 2; 318 | return x; 319 | } 320 | ``` 321 | 322 | ```js 323 | => { // 无圆括号,错误! 324 | const x = 2; 325 | return x; 326 | } 327 | ``` 328 | 329 | ##### *this* 关键字 330 | 331 | 要理解箭头函数中this的微妙之处,你必须首先了解JavaScript中[this](#this_def) 的行为。 332 | 333 | 在箭头函数中, *this*的值就等于函数所处的封闭可执行上下文的*this*。这句话的意思就是箭头函数不创建一个新的*this*, 而是从其所处的上下文环境中获取。 334 | 335 | 没有箭头函数,如果你想要从*this*访问函数内部的函数中的一个变量,你必须使用*that = this*或者*self = this*这样的技巧。 336 | 337 | 例如, 使用位于myFunc内部的函数setTimeout: 338 | 339 | ```js 340 | function myFunc() { 341 | this.myVar = 0; 342 | var that = this; // that = this 343 | setTimeout( 344 | function() { // 在函数的内部创建一个新的 345 | that.myVar++; 346 | console.log(that.myVar) // 1 347 | 348 | console.log(this.myVar) // 未定义 -- 请参照上面的函数this定义 349 | }, 350 | 0 351 | ); 352 | } 353 | ``` 354 | 355 | 但是一旦使用箭头函数, *this* 将从包含这个箭头函数的上下文中获取: 356 | 357 | ```js 358 | function myFunc() { 359 | this.myVar = 0; 360 | setTimeout( 361 | () => { // 从上下文中获取this, 在这里就是 myFunc 362 | this.myVar++; 363 | console.log(this.myVar) // 1 364 | }, 365 | 0 366 | ); 367 | } 368 | ``` 369 | 370 | #### 相关资料 371 | 372 | - [Arrow functions introduction - WesBos](http://wesbos.com/arrow-functions/) 373 | - [JavaScript arrow function - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 374 | - [Arrow function and lexical *this*](https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4) 375 | 376 | #### 方法默认参数值 377 | 378 | 从 ES2015 以后开始,你可以使用下面的语法,给你的方法参数来设置默认值 379 | 380 | ```js 381 | function myFunc(x = 10) { 382 | return x; 383 | } 384 | console.log(myFunc()) // 10 -- 没有值传入,所以默认的值10传给了myFunc 385 | console.log(myFunc(5)) // 5 -- 一个值被传入,所以x等于5 386 | console.log(myFunc(undefined)) // 10 -- undefined 值提供了,所以默认值关联了x 387 | console.log(myFunc(null)) // null -- 提供了 (null) , 见一下详细解释 388 | ``` 389 | 默认参数有且只有在以下两种情况下才会生效: 390 | - 没有参数提供的时候 391 | - *undefined* 参数被提供的时候 392 | 393 | 换句话说,如果你传入*NULL* 默认值**将不会生效** 394 | -------------------------------------------------------------------------------- /translations/pt-BR.md: -------------------------------------------------------------------------------- 1 | # Cheatsheet de JavaScript Moderno 2 | 3 | ![Modern JavaScript cheatsheet](https://i.imgur.com/aexPxMb.png) 4 | Crédito da Imagem: [Ahmad Awais ⚡️](https://github.com/ahmadawais) 5 | 6 | ## Introdução 7 | 8 | ### Motivação 9 | 10 | Esse documento é um conjunto de "cheatsheet" para javascript que você frequentemente encontrará em projetos modernos e na maioria de exemplos de códigos atuais. 11 | 12 | Esse guia não tem a intenção de te ensinar Javascript do zero, mas sim ajudar desenvolvedores com conhecimentos básicos a se familiarizarem com códigos modernos. 13 | 14 | Além disso, eu @mbeaudru ocasionalmente forneço dicas que podem ser discutidas, mas tomarei o cuidado de avisar quando eu fizer uma recomendação pessoal. 15 | 16 | > **Nota:** Muito dos conceitos apresentados aqui vem de uma atualização do Javascript (ES2015, chamada também de ES6). Você pode achar as novas funcionalidades adicionadas nessa atualização [aqui](http://es6-features.org). 17 | 18 | ### Material Complementar 19 | 20 | Se você estiver com dificuldades em entender alguma coisa, eu sugiro que você procure por respostas nos seguintes lugares: 21 | 22 | - [MDN (Mozilla Developer Network)](https://developer.mozilla.org/fr/search?q=) 23 | - [You don't know JS (livro)](https://github.com/getify/You-Dont-Know-JS) 24 | - [ES6 Funcionalidades e exemplos](http://es6-features.org) 25 | - [WesBos blog (ES6)](http://wesbos.com/category/es6/) 26 | - [Reddit (JavaScript)](https://www.reddit.com/r/javascript/) 27 | - [Google](https://www.google.com/) para encontrar blogs e recursos específicos 28 | 29 | ## Sumário 30 | 31 | - [Modern JavaScript cheatsheet](#modern-javascript-cheatsheet) 32 | * [Introdução](#Introdução) 33 | + [Motivação](#Motivação) 34 | + [Material Complementar](#Material-Complementar) 35 | * [Tabela de Conteúdos](#Sumário) 36 | * [Noções](#Noções) 37 | + [Declaração de variáveis: var, const, let](#declaração-de-variáveis-var-const-let) 38 | - [Breve explicação](#breve-explicação) 39 | - [Exemplo](#exemplo) 40 | - [Explicação Detalhada](#explicação-detalhada) 41 | - [Material Complementar](#material-complementar) 42 | + [Função de Seta](#função-de-seta) 43 | - [Exemplo](#exemplo-de-codigo) 44 | - [Explicação Detalhada](#detailed-explanation-1) 45 | * [Concisão](#concisão) 46 | * [Referência *this*](#referência-this) 47 | - [Material Útil](#material-útil) 48 | + [Parametros padrão de uma Function](#parametros-padrão-de-uma-function) 49 | - [Material Complementar](#material-complementar) 50 | + [Desestruturação de objetos e arrays](#desestruturação-de-objetos-e-arrays) 51 | - [Explicação com exemplo de código](#explicação-com-exemplo-de-código) 52 | - [Material Útil](#material-útil-1) 53 | + [Metodos de lista - map / filter / reduce](#array-methods---map--filter--reduce) 54 | - [Exemplo](#sample-code-2) 55 | - [Explicação](#explanation) 56 | * [Array.prototype.map()](#arrayprototypemap) 57 | * [Array.prototype.filter()](#arrayprototypefilter) 58 | * [Array.prototype.reduce()](#arrayprototypereduce) 59 | - [Material Complementar](#external-resource) 60 | + [Spread operator "..."](#spread-operator-) 61 | - [Exemplo](#sample-code-3) 62 | - [Explicação](#explanation-1) 63 | * [Em interações (como arrays)](#in-iterables-like-array) 64 | * [Function rest parameter](#function-rest-parameter) 65 | * [Object properties spreading](#object-properties-spreading) 66 | - [Material Complementar](#external-resources) 67 | + [Object property shorthand](#object-property-shorthand) 68 | - [Explicação](#explanation-2) 69 | - [Material Complementar](#external-resources-1) 70 | + [Promises](#promises) 71 | - [Exemplo](#sample-code-4) 72 | - [Explicação](#explanation-3) 73 | * [Criando uma promise](#create-the-promise) 74 | * [Usando uma promise](#use-the-promise) 75 | - [External Resources](#external-resources) 76 | + [Template literals](#template-literals) 77 | - [Sample code](#sample-code-5) 78 | - [External resources](#external-resources-2) 79 | + [Imports / Exports](#imports--exports) 80 | - [Explanation with sample code](#explanation-with-sample-code-1) 81 | - [External resources](#external-resources-3) 82 | + [JavaScript *this*](#-javascript-this) 83 | - [External resources](#external-resources-4) 84 | + [Class](#class) 85 | - [Samples](#samples) 86 | - [External resources](#external-resources-5) 87 | + [Async Await](#async-await) 88 | - [Sample code](#sample-code-6) 89 | - [Explanation](#explanation-4) 90 | - [External resources](#external-resources-7) 91 | * [Glossary](#glossary) 92 | + [Scope](#-scope) 93 | + [Variable mutation](#-variable-mutation) 94 | 95 | ## Noções 96 | 97 | ### Declaração de variáveis: var, const, let 98 | 99 | Em JavaScript, existem três palavras-chave disponíveis para declarar uma variável, e cada uma tem suas diferenças. São elas ```var```, ```let``` e ```const```. 100 | 101 | #### Breve explicação 102 | 103 | Variáveis declaradas com a palavra-chave ```const``` não podem ser reatribuídas, enquanto ```let``` e ```var``` podem. 104 | 105 | Eu recomendo sempre declarar suas variáveis com ```const``` por padrão, e com ```let``` se você precisar *modifica-lo* ou reatribuí-lo mais tarde. 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
EscopoReatribuívelMutávelZona Temporal Inoperante
constBlocoNãoSimSim
letBlocoSimSimSim
varFunçãoSimSimNão
137 | 138 | #### Exemplo 139 | 140 | ```javascript 141 | const person = "Nick"; 142 | person = "John" // Irá ocorrer um erro, person não pode ser reatribuída 143 | ``` 144 | 145 | ```javascript 146 | let person = "Nick"; 147 | person = "John"; 148 | console.log(person) // "John", a reatribuição é permitida com let 149 | ``` 150 | 151 | #### Explicação Detalhada 152 | 153 | O [*escopo*](#scope_def) de uma variável grosseiramente significa "onde esta variável está disponível no código". 154 | 155 | ##### var 156 | 157 | Variáveis declaradas com ```var``` são *função escopada*, significando que quando uma variável é criada em uma função, tudo naquela função pode acessar essa variável. Além disso, uma variável de *função escopada* criada em uma função não pode ser acessada fora desta função. 158 | 159 | Eu recomendo que você imagine isso, como se uma variável *X escopada* significasse que essa variável era uma propriedade de X. 160 | 161 | ```javascript 162 | function myFunction() { 163 | var myVar = "Nick"; 164 | console.log(myVar); // "Nick" - myVar é acessível dentro da função. 165 | } 166 | console.log(myVar); // Lança um ReferenceError, myVar não está acessível fora da função. 167 | ``` 168 | 169 | Ainda focado na variável de escopo, aqui está um exemplo mais sutil: 170 | 171 | ```javascript 172 | function myFunction() { 173 | var myVar = "Nick"; 174 | if (true) { 175 | var myVar = "John"; 176 | console.log(myVar); // "John" 177 | // na verdade, sendo myVar do escopo da função, nós simplesmente apagamos o valor anterior do myVar "Nick" para "John" 178 | } 179 | console.log(myVar); // "John" - veja como as instruções no bloco if afetaram esse valor 180 | } 181 | console.log(myVar); // Lança um ReferenceError, myVar não é acessível fora da função. 182 | ``` 183 | 184 | Além disso, variáveis declaradas *var* são movidas para o topo do escopo na execução. É o que chamamos de [içando a var](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting). 185 | 186 | Esta parte do código: 187 | 188 | ```js 189 | console.log(myVar) // undefined -- sem erro lançado 190 | var myVar = 2; 191 | ``` 192 | 193 | é entendido na execução como: 194 | 195 | ```js 196 | var myVar; 197 | console.log(myVar) // undefined -- sem erro lançado 198 | myVar = 2; 199 | ``` 200 | 201 | ##### let 202 | 203 | ```var``` e ```let ``` são quase os mesmos, mas variáveis declaradas com ```let``` 204 | 205 | - são *escopado em bloco* 206 | - **não** são acessíveis antes de serem atribuídas 207 | - não podem ser re-declaradas no mesmo escopo 208 | 209 | Vamos ver o impacto do escopo em bloco em nosso exemplo anterior: 210 | 211 | ```javascript 212 | function myFunction() { 213 | let myVar = "Nick"; 214 | if (true) { 215 | let myVar = "John"; 216 | console.log(myVar); // "John" 217 | // na verdade, myVar sendo escopada em bloco, nós criamos uma nova variável myVar. 218 | // essa variável não é acessível fora do bloco e é totalmente independente 219 | // da primeira myVar criada ! 220 | } 221 | console.log(myVar); // "Nick", veja como as instruções no bloco IF NÃO afetou este valor 222 | } 223 | console.log(myVar); // lançado um ReferenceError, myVar não é acessível fora da fucnção. 224 | ``` 225 | 226 | Agora, o que significa para as variáveis *let* (e *const*) não estarem acessíveis antes de serem atribuídas: 227 | 228 | ```js 229 | console.log(myVar) // lança um ReferenceError ! 230 | let myVar = 2; 231 | ``` 232 | 233 | Em contraste com as variáveis *var*, se você tentar ler ou escrever em uma variável *let* ou *const* antes de serem atribuídos, um erro será gerado. Esse fenômeno é freqüentemente chamado [*Zona Temporal Inoperante*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_Dead_Zone_and_errors_with_let) ou *TDZ*. 234 | 235 | > **Nota:** Tecnicamente, as declarações de *let* e *const* também estão sendo içadas, mas não a sua atribuição. Uma vez que elas são feitas para que elas não possam ser usados antes da atribuição, ela intuitivamente parece que não há içamento, mas existe. Saiba mais sobre isso [explicação muito detalhada aqui](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified) se quiser saber mais. 236 | 237 | Além disso, você não pode re-declarar uma variável *let*: 238 | 239 | ```js 240 | let myVar = 2; 241 | let myVar = 3; // Retorna um SyntaxError 242 | ``` 243 | 244 | ##### const 245 | 246 | Variáveis declaradas ```const``` agem como variáveis *let*, mas elas não podem ser reatribuídas. 247 | 248 | Para resumir, variáveis *const*: 249 | 250 | - são *escopado em bloco* 251 | - não são acessíveis antes de serem atribuídos 252 | - não podem ser re-declaradas no mesmo escopo 253 | - não podem ser reatribuídas 254 | 255 | ```js 256 | const myVar = "Nick"; 257 | myVar = "John" // lança um erro, reatribuição não é permitido 258 | ``` 259 | 260 | ```js 261 | const myVar = "Nick"; 262 | const myVar = "John" // lança um erro, re-declaração não é permitida 263 | ``` 264 | 265 | Mas há uma sutileza : variáveis ```const``` não são [**imutáveis**](#mutation_def) ! Concretamente, Isto significa que variáveis *objetos* e *arrays* declaradas com ```const``` **podem** ser mutadas. 266 | 267 | Para objetos: 268 | ```js 269 | const person = { 270 | name: 'Nick' 271 | }; 272 | person.name = 'John' // isto irá funcionar! A variável objeto person não é completamente reatribuída, mas mutada 273 | console.log(person.name) // "John" 274 | person = "Sandra" // lança um erro, porque a reatribuição não é permitida com variáveis declaradas com const 275 | ``` 276 | 277 | Para arrays: 278 | ```js 279 | const person = []; 280 | person.push('John'); // isto irá funcionar! A variável array person não é completamente reatribuída, mas mutada 281 | console.log(person[0]) // "John" 282 | person = ["Nick"] // lança um erro, porque a reatribuição não é permitida com variáveis declaradas com array 283 | ``` 284 | 285 | #### Material Complementar 286 | 287 | - [Como let e const são escopados em JavaScript - WesBos](http://wesbos.com/javascript-scoping/) 288 | - [Zona temporal Inoperante (TDZ) desmistificada](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified) 289 | 290 | ### Função de seta 291 | 292 | A atualização do JavaScript ES6 introduziu *funções de seta*, que é outra maneira de declarar e usar funções. Aqui estão os benefícios que elas trazem: 293 | 294 | - Mais conciso 295 | - *this* é retirado dos arredores 296 | - retorno implícito 297 | 298 | #### Exemplo de código 299 | 300 | - Concisão e retorno implícito 301 | 302 | ```js 303 | function double(x) { return x * 2; } // Forma tradicional 304 | console.log(double(2)) // 4 305 | ``` 306 | 307 | ```js 308 | const double = x => x * 2; // A mesma função escrita como uma função de seta com retorno implícito 309 | console.log(double(2)) // 4 310 | ``` 311 | 312 | - Referência *this* 313 | 314 | Em uma função de seta, *this* é igual ao valor *this* do contexto de execução envolvente. Basicamente, com as funções de seta, você não precisa fazer o truque "that/self = this" antes de chamar uma função dentro de uma função. 315 | 316 | ```js 317 | function myFunc() { 318 | this.myVar = 0; 319 | setTimeout(() => { 320 | this.myVar++; 321 | console.log(this.myVar) // 1 322 | }, 0); 323 | } 324 | ``` 325 | 326 | #### Explicação detalhada 327 | 328 | ##### Concisão 329 | 330 | As funções de seta são mais concisas do que as funções tradicionais em diversas maneiras. Vamos rever todos os casos possíveis: 331 | 332 | - Retorno implícito VS explícito 333 | 334 | Um **retorno explícito** é uma função em que a palavra-chave *return* é usada em seu corpo. 335 | 336 | ```js 337 | function double(x) { 338 | return x * 2; // esta função retorna explicitamente x * 2, a palavra-chave *retorno* é usada 339 | } 340 | ``` 341 | 342 | Na maneira tradicional de escrever funções, o retorno sempre foi explícito. Mas com funções de seta, você pode fazer *retorno implícito*, o que significa que você não precisa usar a palavra-chave *return* para retornar um valor. 343 | 344 | ```js 345 | const double = (x) => { 346 | return x * 2; // um retorno explícito aqui 347 | } 348 | ``` 349 | 350 | Uma vez que esta função apenas retorna algo (sem instruções antes da palavra-chave *return*), podemos fazer um retorno implícito. 351 | 352 | ```js 353 | const double = (x) => x * 2; // Correto, retorna x*2 354 | ``` 355 | 356 | Para fazer isso, só precisamos **remover os colchetes** e a palavra-chave **return**. É por isso que é chamado de *retorno implícito*, a palavra-chave *return* não existe, mas essa função retornará ```x * 2```. 357 | 358 | > **Nota:** Se sua função não retornar um valor (com *efeitos colaterais*), ele não faz um retorno explícito nem implícito. 359 | 360 | Além disso, se você quiser retornar implicitamente um *objeto*, você **deve ter parênteses em torno dele**, pois isso entrará em conflito com as chaves do bloco: 361 | 362 | ```js 363 | const getPerson = () => ({ name: "Nick", age: 24 }) 364 | console.log(getPerson()) // { name: "Nick", age: 24 } -- objeto implicitamente retornado pela função de seta 365 | ``` 366 | 367 | - Só um argumento 368 | 369 | Se a sua função apenas tiver um parâmetro, você pode omitir os parênteses à sua volta. Se pegarmos o código *double* acima: 370 | 371 | ```js 372 | const double = (x) => x * 2; // esta função de seta apenas leva um parâmetro 373 | ``` 374 | 375 | Parênteses ao redor do parâmetro podem ser evitados: 376 | 377 | ```js 378 | const double = x => x * 2; // esta função de seta tem apenas um parâmetro 379 | ``` 380 | 381 | - Nenhum argumento 382 | 383 | Quando não há argumento fornecido para uma função de seta, você precisa fornecer parênteses, ou não será uma sintaxe válida. 384 | 385 | ```js 386 | () => { // parênteses são fornecidos, tudo está ok 387 | const x = 2; 388 | return x; 389 | } 390 | ``` 391 | 392 | ```js 393 | => { // Sem parênteses, isso não funcionará! 394 | const x = 2; 395 | return x; 396 | } 397 | ``` 398 | 399 | ##### Referência *this* 400 | 401 | Para entender essa sutileza introduzida com funções de seta, você deve saber como [this](#this_def) se comporta em JavaScript. 402 | 403 | Em funções de seta, *this* é igual ao valor *this* do contexto de execução envolvente. O que significa que uma função de seta não cria um novo *this*, Ela pega do seu entorno em vez disso. 404 | 405 | Sem uma função de seta, se você quisesse acessar uma variável de *this* em uma função dentro de outra função, você tinha que usar *that = this* ou *self = this*. 406 | 407 | Por exemplo, usando a função setTimeout dentro de myFunc: 408 | 409 | ```js 410 | function myFunc() { 411 | this.myVar = 0; 412 | var that = this; // that = this (truque) 413 | setTimeout( 414 | function() { // Um novo *this* é criado neste escopo de função 415 | that.myVar++; 416 | console.log(that.myVar) // 1 417 | 418 | console.log(this.myVar) // undefined -- veja a declaração da função acima 419 | }, 420 | 0 421 | ); 422 | } 423 | ``` 424 | 425 | Mas com função de seta, *this* é retirado do seu entorno: 426 | 427 | ```js 428 | function myFunc() { 429 | this.myVar = 0; 430 | setTimeout( 431 | () => { // this é retirado do entorno, o que significa de myFunc aqui 432 | this.myVar++; 433 | console.log(this.myVar) // 1 434 | }, 435 | 0 436 | ); 437 | } 438 | ``` 439 | 440 | #### Material Útil 441 | 442 | - [Arrow functions introduction (Introdução à Funções de Seta) - WesBos](http://wesbos.com/arrow-functions/) 443 | - [JavaScript arrow function - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 444 | - [Arrow function and lexical *this*](https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4) 445 | 446 | ### Parametros padrão de uma Function 447 | 448 | A partir da atualização do JavaScript ES2015, você pode definir um valor padrão para os parâmetros da função usando a seguinte sintaxe: 449 | 450 | ```js 451 | function myFunc(x = 10) { 452 | return x; 453 | } 454 | console.log(myFunc()) // 10 -- nenhum valor é fornecido então o valor padrão de x que é 10 será atribuído a x em myFunc 455 | console.log(myFunc(5)) // 5 -- um valor é fornecido então x é igual a 5 em myFunc 456 | 457 | console.log(myFunc(undefined)) // 10 -- um valor undefined é fornecido então o valor padrão é atribuído para x 458 | console.log(myFunc(null)) // null -- um valor (null) é fornecido, veja abaixo para mais detalhes neste caso 459 | ``` 460 | 461 | O valor de parâmetro padrão é aplicado em duas e somente duas situações: 462 | 463 | - Nenhum parâmetro fornecido 464 | - *undefined* parâmetro fornecido 465 | 466 | Em outras palavras, se você passar um *null* o parâmetro padrão **não irá ser aplicado**. 467 | 468 | > **Nota:** Atribuição de valor padrão também pode ser usada com parâmetros desestruturados (veja o próximo conceito para ver um exemplo) 469 | 470 | #### Material Complementar 471 | 472 | - [Default parameter value - ES6 Features](http://es6-features.org/#DefaultParameterValues) 473 | - [Default parameters - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) 474 | 475 | ### Desestruturação de objetos e arrays 476 | 477 | *Desestruturação* é uma maneira conveniente de criar novas variáveis extraindo alguns valores de dados armazenados em objetos ou arrays (matrizes). 478 | 479 | Para nomear alguns casos de uso, *desestruturação* pode ser usado para desestruturar parâmetros de função ou *this.props* em projetos React, por exemplo. 480 | 481 | #### Explicação com exemplo de código 482 | 483 | - Objeto 484 | 485 | Vamos considerar o seguinte objeto para todos os exemplos: 486 | 487 | ```js 488 | const person = { 489 | firstName: "Nick", 490 | lastName: "Anderson", 491 | age: 35, 492 | sex: "M" 493 | } 494 | ``` 495 | 496 | Sem desestruturação 497 | 498 | ```js 499 | const first = person.firstName; 500 | const age = person.age; 501 | const city = person.city || "Paris"; 502 | ``` 503 | 504 | Com desestruturação, tudo em uma única linha: 505 | 506 | ```js 507 | const { firstName: first, age, city = "Paris" } = person; // É isso ! :) 508 | 509 | console.log(age) // 35 -- uma nova variável age é criada e é igual a person.age 510 | console.log(first) // "Nick" -- uma nova variável first é criada e é igual person.firstName 511 | console.log(firstName) // ReferenceError -- person.firstName existe MAS a nova variável criada é nomeada primeiro 512 | console.log(city) // "Paris" -- uma nova variável city é criada e uma vez que person.city é indefinida, city é igual ao valor padrão fornecido "Paris". 513 | ``` 514 | 515 | **Nota :** Em ```const { age } = person;```, os colchetes depois da palavra-chave *const* não são usados para declarar um objeto nem um bloco, mas é a sintaxe da *desestruturação*. 516 | 517 | - Parâmetros de função 518 | 519 | *Desestruturação* é freqüentemente usado para desestruturar parâmetros de objetos em funções. 520 | 521 | Sem desestruturação 522 | 523 | ```js 524 | function joinFirstLastName(person) { 525 | const firstName = person.firstName; 526 | const lastName = person.lastName; 527 | return firstName + '-' + lastName; 528 | } 529 | 530 | joinFirstLastName(person); // "Nick-Anderson" 531 | ``` 532 | 533 | Ao desestruturar o parâmetro de objeto *person*, obtemos uma função mais concisa: 534 | 535 | ```js 536 | function joinFirstLastName({ firstName, lastName }) { // criamos variáveis firstName e lastName por desestruturação person parameter 537 | return firstName + '-' + lastName; 538 | } 539 | 540 | joinFirstLastName(person); // "Nick-Anderson" 541 | ``` 542 | 543 | A desestruturação é ainda mais agradável para usar com [funções de seta] (#função-de-seta): 544 | 545 | ```js 546 | const joinFirstLastName = ({ firstName, lastName }) => firstName + '-' + lastName; 547 | 548 | joinFirstLastName(person); // "Nick-Anderson" 549 | ``` 550 | 551 | - Array (Matriz) 552 | 553 | Vamos considerar a seguinte array: 554 | 555 | ```js 556 | const myArray = ["a", "b", "c"]; 557 | ``` 558 | 559 | Sem desestruturação 560 | 561 | ```js 562 | const x = myArray[0]; 563 | const y = myArray[1]; 564 | ``` 565 | 566 | Com desestruturação 567 | 568 | ```js 569 | const [x, y] = myArray; // É isso ! 570 | 571 | console.log(x) // "a" 572 | console.log(y) // "b" 573 | ``` 574 | 575 | #### Material Útil 576 | 577 | - [ES6 Features - Destructuring Assignment (Funcionalidades ES6 - Atribuição de Destruturação)](http://es6-features.org/#ArrayMatching) 578 | - [Destructuring Objects - WesBos](http://wesbos.com/destructuring-objects/) 579 | - [ExploringJS - Destructuring](http://exploringjs.com/es6/ch_destructuring.html) -------------------------------------------------------------------------------- /translations/zh-TW.md: -------------------------------------------------------------------------------- 1 | 2 | # Modern JavaScript Cheatsheet 繁體中文版 3 | 4 | ![Modern JavaScript cheatsheet](https://i.imgur.com/aexPxMb.png) 5 | 圖片來源: [Ahmad Awais ⚡️](https://github.com/ahmadawais) 6 | 7 | ### 譯者的話 8 | > 原標題:[mbeaudru/modern-js-cheatsheet](https://github.com/mbeaudru/modern-js-cheatsheet) 9 | > 10 | > 原作者:[BEAUDRU Manuel](https://github.com/mbeaudru) 11 | > 12 | > 對於現代 JavaScript 開發而言,這篇文章整理了不少知識點,當作複習或是學習都很不錯。自己一直以來都是做為讀者的角色,很少主動為整個開源社群做些實際貢獻,這點一直感到蠻慚愧的,就像是 Sublime 是啟蒙你寫程式的第一個 editor,當你開始工作賺錢後卻遲遲不買 license 是類似的道理。趁著短暫的假日譯者盡可能的把翻譯做到盡善盡美,畢竟不是專業的,要做到信達雅的程度其實不太可能,但過程中確實查閱了不少相關資料,部分關鍵字因為怕超譯所以會在後頭括號保留原文。 13 | > 14 | > 另外也想藉著這回翻譯的經驗說點八股的,英文真的天殺的重要,能夠直接閱讀原文始終是最能理解原意的方式。整篇 cheatsheet 從意譯的角度出發,詞意有所疑問或是理解錯誤都煩請發個 Pull Request 謝謝。 15 | > 16 | > 2017/09/30 Update 17 | > 18 | > 昨天晚上收到簡體中文譯者的來信提醒,才發現原來 Issue 內早已有社群朋友 @BirkhoffLee 正在做繁體中文的翻譯 ([詳情可見此討論串](https://github.com/mbeaudru/modern-js-cheatsheet/issues/15)),真的很抱歉昨天才驚覺這件事,譯者在這裡推薦大家如果有空也可以多多瀏覽 @BirkhoffLee 翻譯過的 [機器學習動手玩](https://github.com/humphd/have-fun-with-machine-learning/blob/master/README_zh-tw.md) 以及相關專案,他也是位對於開源社群推廣非常積極的開發者。最後關於這份繁體中文文件,譯者會在這一兩天回顧下文件翻譯用詞有無需要調整的地方,確認過後便會 merge 回原作者的 repo,大概是醬。 19 | 20 | 21 | ## 介紹 22 | 23 | 24 | ### 動機 25 | 26 | 本文檔整理了各種現代化 JavaScript 開發過程中經常使用到的腳本。 27 | 28 | 該份指南的目標並不是放在幫助初學者從零基礎到入門,而是為了幫助那些因為 JavaScript 新式語法導致可能很難熟悉現代函數庫使用方式 (以 React 做為舉例) 的開發人員。 29 | 30 | 此外我也會偶爾提供一些個人主觀的建議和技巧,而這些建議可能會造成部分的爭議性,但請務必留意,當我做出這些舉例時這僅僅是出自於個人的推薦作法。 31 | 32 | > **注意:** 此處介紹的大部分概念出自於 JavaScript 的語言更新 (ES2015,更多人稱其作 ES6)。你可以在[這個好地方](http://es6-features.org)找到更多添加的新功能。 33 | 34 | 35 | ### 配套資源 36 | 37 | 當你在試圖理解一個新概念時,我建議你可以去瀏覽以下這些資源尋找解答: 38 | 39 | - [MDN (Mozilla Developer Network)](https://developer.mozilla.org/fr/search?q=) 40 | - [You don't know JS (book)](https://github.com/getify/You-Dont-Know-JS) 41 | - [ES6 Features with examples](http://es6-features.org) 42 | - [WesBos blog (ES6)](http://wesbos.com/category/es6/) 43 | - [Reddit (JavaScript)](https://www.reddit.com/r/javascript/) 44 | - [Google](https://www.google.com/) 搜尋特定相關主題的部落格文章和資源 45 | 46 | 47 | ## 目錄 48 | 49 | - [Modern JavaScript cheatsheet 繁體中文版](#modern-javascript-cheatsheet) 50 | * [介紹](#introduction) 51 | + [動機](#motivation) 52 | + [配套資源](#complementary-resources) 53 | * [目錄](#table-of-contents) 54 | * [概念](#notions) 55 | + [變數聲明: var, const, let](#variable-declaration-var-const-let) 56 | - [簡短解釋](#short-explanation-1) 57 | - [範例程式碼](#sample-code-2) 58 | - [詳細說明](#detailed-explanation-3) 59 | - [外部資源](#external-resource-4) 60 | + [箭頭函數](#-arrow-function-4) 61 | - [範例程式碼](#sample-code-5) 62 | - [詳細說明](#detailed-explanation-6) 63 | * [簡潔性](#concision-7) 64 | * [*this* 關鍵字參照](#this-reference-8) 65 | - [有用資源](#useful-resources-9) 66 | + [函數預設值](#function-default-parameter-value-10) 67 | - [外部資源](#external-resource-11) 68 | + [objects 和 arrays 的解構](#destructuring-objects-and-arrays-12) 69 | - [說明和範例程式碼](#explanation-with-sample-code-13) 70 | - [有用資源](#useful-resources-14) 71 | + [Array 的操作方法 - map / filter / reduce](#array-methods---map--filter--reduce-15) 72 | - [範例程式碼](#sample-code-16) 73 | - [說明](#explanation-17) 74 | * [Array.prototype.map()](#arrayprototypemap-18) 75 | * [Array.prototype.filter()](#arrayprototypefilter-19) 76 | * [Array.prototype.reduce()](#arrayprototypereduce-20) 77 | - [外部資源](#external-resource-21) 78 | + [展開運算子 "..."](#spread-operator-22) 79 | - [範例程式碼](#sample-code-23) 80 | - [說明](#explanation-24) 81 | * [迭代用法 (如同 array)](#in-iterables-like-array-25) 82 | * [不定參數](#function-rest-parameter-26) 83 | * [Object 屬性擴展](#object-properties-spreading-27) 84 | - [外部資源](#external-resources-28) 85 | + [Object 屬性簡寫](#object-property-shorthand-29) 86 | - [說明](#explanation-30) 87 | - [外部資源](#external-resources-31) 88 | + [Promises](#promises-32) 89 | - [範例程式碼](#sample-code-33) 90 | - [說明](#explanation-34) 91 | * [創造 promise](#create-the-promise-35) 92 | * [使用 promise](#use-the-promise-36) 93 | - [外部資源](#external-resources-37) 94 | + [模板字符串](#template-literals-38) 95 | - [範例程式碼](#sample-code-39) 96 | - [外部資源](#external-resources-40) 97 | + [Imports / Exports](#imports--exports-41) 98 | - [說明與範例程式碼](#explanation-with-sample-code-42) 99 | - [外部資源](#external-resources-43) 100 | + [JavaScript *this*](#-javascript-this-44) 101 | - [外部資源](#external-resources-45) 102 | + [Class](#class-46) 103 | - [範例](#samples-47) 104 | - [外部資源](#external-resources-48) 105 | + [Async Await](#async-await-49) 106 | - [範例程式碼](#sample-code-50) 107 | - [說明](#explanation-51) 108 | - [外部資源](#external-resources-52) 109 | * [術語詞彙](#glossary-53) 110 | + [作用域範圍](#-scope-54) 111 | + [變數變異](#-variable-variance-55) 112 | 113 | 114 | ## 概念 115 | 116 | 117 | ### 變數聲明: var, const, let 118 | 119 | 在 JavaScript 中有三個不同關鍵字可用於宣告一個變數,分別是 ```var```, ```let``` 和 ```const```。 120 | 121 | 122 | #### 簡短解釋 123 | 124 | 使用 ```const``` 關鍵字宣告的變數無法被重新指派, 而 ```let``` 和 ```var``` 是可以的。 125 | 126 | 我會建議在默認情況下一律使用 ```const``` ,當你需要改變它或是稍後才重新指派時才使用 ```let``` 。 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
作用域範圍是否可重新指派狀態變更暫時性死區 (Temporal Dead Zone)
const區塊不是
let區塊
var函數不是
158 | 159 | 160 | #### 範例程式碼 161 | 162 | ```javascript 163 | const person = "Nick"; 164 | person = "John" // 會有錯誤跳出,person 不能被重新指派 165 | ``` 166 | 167 | ```javascript 168 | let person = "Nick"; 169 | person = "John"; 170 | console.log(person) // "John" 在 let 的使用下允許被重新指派 171 | ``` 172 | 173 | 174 | #### 詳細說明 175 | 176 | 變數的 [*作用域範圍 (scope)*](#scope_def) 大致上意味著 "這個變數的效力可被作用在哪段程式碼 (where is this variable available in the code)"。 177 | 178 | ##### var 179 | 180 | ```var``` 宣告的變數是 *函數範圍 (function scoped)* 的,這表示當函數中創造變數的時候,該函數中的所有內容都可以訪問並使用該變數。相反的,在函數外創造的 *區塊範圍 (block scoped)* 變數則無法被使用。 181 | 182 | 我會建議你把它看作是一個 *X scoped* 範圍的變數代表著這個變數是 X 的屬性之一。 183 | 184 | ```javascript 185 | function myFunction() { 186 | var myVar = "Nick"; 187 | console.log(myVar); // "Nick" - myVar 可以在函數範圍之內被使用 188 | } 189 | console.log(myVar); // Undefined, myVar 在函數範圍外部無法被使用 190 | ``` 191 | 192 | 持續觀察變數的作用域範圍,這裡有個更細微的範例: 193 | 194 | ```javascript 195 | function myFunction() { 196 | var myVar = "Nick"; 197 | if (true) { 198 | var myVar = "John"; 199 | console.log(myVar); // "John" 200 | // actually, myVar 是函數範圍之內的,我們剛剛覆蓋了之前的 myVar 變數,值從 "Nick" 變成 "John" 201 | } 202 | console.log(myVar); // "John" - 印出來看看區塊如何影響 myVar 這個變數的值 203 | } 204 | console.log(myVar); // Undefined, myVar 在函數範圍外部無法被使用 205 | ``` 206 | 207 | 此外, *var* 宣告出來的變數在程式執行之時就會被移到作用域的頂部。這個就是我們所說的[變數提升 (var hoisting)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting)。 208 | 209 | 這段程式碼: 210 | 211 | ```js 212 | console.log(myVar) // undefined -- 沒有錯誤發生 213 | var myVar = 2; 214 | ``` 215 | 216 | 在程式執行過程中被解讀為: 217 | 218 | ```js 219 | var myVar; 220 | console.log(myVar) // undefined -- 沒有錯誤發生 221 | myVar = 2; 222 | ``` 223 | 224 | ##### let 225 | 226 | ```var``` 和 ```let ``` 大致上行為相同, ```let``` 在宣告變數時 227 | 228 | - 作用域是 *區塊範圍 (block scoped)* 229 | - 在被指派值以前 **無法** 被存取使用 230 | - 同一個作用域之下不能被重新宣告 231 | 232 | 採用我們前面的例子來看看區塊範圍 (block scoped) 的影響: 233 | 234 | ```javascript 235 | function myFunction() { 236 | let myVar = "Nick"; 237 | if (true) { 238 | let myVar = "John"; 239 | console.log(myVar); // "John" 240 | // 事實上,myVar 是區塊範圍之內的,我們剛剛創造了一個全新的 myVar 變數 241 | // 這個變數是無法從區塊範圍以外的地方存取, 242 | // 而且它也是完全獨立於我們創造的第一個 myVar 變數! 243 | } 244 | console.log(myVar); // "Nick", 查看 if 區塊中的程式會不會影響到 myVar 這個值 245 | } 246 | console.log(myVar); // Undefined, myVar 在函數範圍外部無法被使用 247 | ``` 248 | 249 | 現在我們來看看 *let* ( 和 *const* ) 變數在被賦值以前無法被使用是什麼意思: 250 | 251 | ```js 252 | console.log(myVar) // 觸發 ReferenceError 錯誤! 253 | let myVar = 2; 254 | ``` 255 | 256 | 和 *var* 變數比較之下,如果在指派 *let* 或是 *const* 變數的值之前嘗試讀取或是寫入的動作是會引發錯誤的。這種現象通常被稱之為 [*Temporal dead zone*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_Dead_Zone_and_errors_with_let) 或者是 *TDZ*。 257 | 258 | > **注意:** 技術上而言, *let* 和 *const* 變數在聲明時也是會被提升的,但並不是指它們的賦值。因為他們在被指派之前是不能使用的,所以直觀上就像是沒有提升一樣,但它們其實是有的。如果你想知道更多的話請查看 [更加詳細解釋的這篇文章](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified)。 259 | 260 | 此外,你不能重新宣告一個 *let* 變數: 261 | 262 | ```js 263 | let myVar = 2; 264 | let myVar = 3; // 跳出 SyntaxError 錯誤 265 | ``` 266 | 267 | ##### const 268 | 269 | ```const``` 宣告出來的行為如同 *let* 變數,但它們同樣都不能被重新宣告。 270 | 271 | 總結一下, *const* 變數: 272 | 273 | - 作用域是 *區塊範圍 (block scoped)* 274 | - 在被指派值以前 **無法** 被存取使用 275 | - 同一個作用域之下不能被重新宣告 276 | - 無法被重新指派新值 277 | 278 | ```js 279 | const myVar = "Nick"; 280 | myVar = "John" // 跳出錯誤,不允許重新指派新值 281 | ``` 282 | 283 | ```js 284 | const myVar = "Nick"; 285 | const myVar = "John" // 跳出錯誤, 重新宣告是不被允許的 286 | ``` 287 | 288 | 但有個精妙之處 : ```const``` 變數不是[**不可變的**](#mutation_def) ! 更具體而言,這代表著 *object* 和 *array* 中由 ```const``` 宣告出來的變數是 **可以** 被改變的。 289 | 290 | 對於 objects: 291 | 292 | ```js 293 | const person = { 294 | name: 'Nick' 295 | }; 296 | person.name = 'John' // 這完全可行! person 這個變數尚未完全被重新指派,但它確實改變了 297 | console.log(person.name) // "John" 298 | person = "Sandra" // 跳出錯誤,因為重新指派時是不允許使用 const 宣告出來的變數的 299 | ``` 300 | 301 | 對於 arrays: 302 | 303 | ```js 304 | const person = []; 305 | person.push('John'); // 這完全可行! person 這個變數尚未完全被重新指派,但它確實改變了 306 | console.log(person[0]) // "John" 307 | person = ["Nick"] // 跳出錯誤,因為重新指派時是不允許使用 const 宣告出來的變數的 308 | ``` 309 | 310 | 311 | #### 外部資源 312 | 313 | - [How let and const are scoped in JavaScript - WesBos](http://wesbos.com/javascript-scoping/) 314 | - [Temporal Dead Zone (TDZ) Demystified](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified) 315 | 316 | ### 箭頭函數 317 | 318 | ES6 的更新正式引入了 *箭頭函數 (arrow functions)*,這是另外一種宣告和使用函數的方法。以下是它們所帶來的好處: 319 | 320 | - 更為簡潔 321 | - *this* 的值繼承自外圍作用域 (*this* is picked up from surroundings) 322 | - 隱式回傳 (implicit return) 323 | 324 | 325 | #### 範例程式碼 326 | 327 | - 簡潔性和隱式回傳 (implicit return) 328 | 329 | ```js 330 | function double(x) { return x * 2; } // 傳統作法 331 | console.log(double(2)) // 4 332 | ``` 333 | 334 | ```js 335 | const double = x => x * 2; // 仍然是同樣的函數,寫成帶有隱式回傳的作法 336 | console.log(double(2)) // 4 337 | ``` 338 | 339 | - *this* 關鍵字參照 340 | 341 | 在箭頭函數中, *this* 意味著封閉執行上下文的 *這個值*。基本上,透過使用箭頭函數,在函數中調用函數之前,你不需要去使用像是 "that = this" 這樣的用法。 342 | 343 | ```js 344 | function myFunc() { 345 | this.myVar = 0; 346 | setTimeout(() => { 347 | this.myVar++; 348 | console.log(this.myVar) // 1 349 | }, 0); 350 | } 351 | ``` 352 | 353 | 354 | #### 詳細說明 355 | 356 | 357 | ##### 簡潔性 358 | 359 | 箭頭函數在諸多方面都較傳統函數來的更為簡潔。讓我們來看看所有可能的情況: 360 | 361 | - 隱式回傳 VS 顯式回傳 362 | 363 | **顯式回傳 (explicit return)** 是指在函數中明確的使用 *return* 這個關鍵字。 364 | 365 | ```js 366 | function double(x) { 367 | return x * 2; // 這個函數顯式回傳了 x * 2,並且使用了 return 這個關鍵字 368 | } 369 | ``` 370 | 371 | 以傳統的作法撰寫,return 永遠都會是顯式的。但是如果是使用箭頭函數,你可以執行隱式回傳,這同時代表著你不需要使用關鍵字 return 去取得回傳值。 372 | 373 | 要做隱式回傳,程式碼必須用一行句子撰寫。 374 | 375 | ```js 376 | const double = (x) => { 377 | return x * 2; // 此處顯示 return 值 378 | } 379 | ``` 380 | 381 | 由於這裡只有一個回傳值,我們可以做一個隱式回傳。 382 | 383 | ```js 384 | const double = (x) => x * 2; 385 | ``` 386 | 387 | 做到上述的轉換,我們只需要 **移除括號** 以及 **return** 這個關鍵字。這就是為什麼它會被稱為 *隱式* 回傳,*return* 關鍵字不在了,但是這個函數確實會回傳 ```x * 2```。 388 | 389 | > **注意:** 如果你的函數沒有回傳一個值 (這種作法有 *副作用*),那麼它將不屬於顯式或是隱式返回中的任一種。 390 | 391 | - 只有一個參數 392 | 393 | 如果你的函數只接受一個參數,你可以省略它周圍的括號。如果我們拿上述的 *double* 程式碼做為舉例: 394 | 395 | ```js 396 | const double = (x) => x * 2; // 這個箭頭函數只接受一個參數 397 | ``` 398 | 399 | 括號是可以被省略的: 400 | 401 | ```js 402 | const double = x => x * 2; // 這個箭頭函數只接受一個參數 403 | ``` 404 | 405 | - 沒有參數 406 | 407 | 當沒有為箭頭函數提供任何參數時,你就必須加上括號,否則語法將會出錯。 408 | 409 | ```js 410 | () => { // 有加上括號,一切都正常運作 411 | const x = 2; 412 | return x; 413 | } 414 | ``` 415 | 416 | ```js 417 | => { // 沒有括號,這樣的語法是行不通的! 418 | const x = 2; 419 | return x; 420 | } 421 | ``` 422 | 423 | 424 | ##### *this* 關鍵字參照 425 | 426 | 要理解箭頭函數的精妙之處,你一定要清楚 [this](#this_def) 在 JavaScript 中是如何運作的。 427 | 428 | 在一個箭頭函數當中,*this* 等同於封閉執行上下文的 *這個值* 。意思就是說,一個箭頭函數並不會創造一個新的 *this*,而是從它的外圍作用域一併抓起。 429 | 430 | 沒有箭頭函數的這項功能,如果你想要取得位於函數的函數內部由 *this* 參照的變數,你就只能使用 *that = this* 或者是 *self = this* 這樣的技巧。 431 | 432 | 舉例來說,你在 myFunc 函數中使用 setTimeout 函數: 433 | 434 | ```js 435 | function myFunc() { 436 | this.myVar = 0; 437 | var that = this; // 使用 that = this 這個技巧 438 | setTimeout( 439 | function() { // 創造了一個新的 this 440 | that.myVar++; 441 | console.log(that.myVar) // 1 442 | 443 | console.log(this.myVar) // undefined -- 詳見上述的函數宣告 444 | }, 445 | 0 446 | ); 447 | } 448 | ``` 449 | 450 | 但如果你使用了箭頭函數,*this* 的範圍將會是它的外圍作用域: 451 | 452 | ```js 453 | function myFunc() { 454 | this.myVar = 0; 455 | setTimeout( 456 | () => { // this 的值來自於它的外圍作用域,也就是 myFunc 函數 457 | this.myVar++; 458 | console.log(this.myVar) // 1 459 | }, 460 | 0 461 | ); 462 | } 463 | ``` 464 | 465 | 466 | #### 有用資源 467 | 468 | - [Arrow functions introduction - WesBos](http://wesbos.com/arrow-functions/) 469 | - [JavaScript arrow function - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 470 | - [Arrow function and lexical *this*](https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4) 471 | 472 | 473 | ### 函數預設值 474 | 475 | 從 ES2015 JavaScript 更新之後,你可以透過下列的語法為函數中的參數設定預設值: 476 | 477 | ```js 478 | function myFunc(x = 10) { 479 | return x; 480 | } 481 | console.log(myFunc()) // 10 -- 沒有提供任何值,所以 10 在 myFunc 中做為預設值指派給 x 482 | console.log(myFunc(5)) // 5 -- 有提供一個參數值,所以 x 在 myFunc 中等於 5 483 | 484 | console.log(myFunc(undefined)) // 10 -- 未定義的值,所以預設值被指派給 x 485 | console.log(myFunc(null)) // null -- 提供一個值 (null),詳細資料請見下文 486 | ``` 487 | 488 | 預設值若且為若應用在兩種情況: 489 | 490 | - 沒有傳入任何參數 491 | - 傳入 *undefined* 這個參數 492 | 493 | 換句話說,如果你傳入的是 *null* ,那麼預設值的機制是不會被觸發的。 494 | 495 | > **注意:** 預設值的指派可以搭配解構參數一同使用 (參照下一個概念的實際例子) 496 | 497 | 498 | #### 外部資源 499 | 500 | - [Default parameter value - ES6 Features](http://es6-features.org/#DefaultParameterValues) 501 | - [Default parameters - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) 502 | 503 | 504 | ### objects 和 arrays 的解構 505 | 506 | *解構 (Destructuring)* 的概念是從 objects 或是 arrays 當中提取部分用值一種相當方便的方法。 507 | 508 | 舉個簡單的實例,*destructuring* 可以被用來解構函數中的參數或者像是 React 專案中 *this.props* 這樣的用法。 509 | 510 | 511 | #### 說明和範例程式碼 512 | 513 | - Object 514 | 515 | 試著想想以下這個 object: 516 | 517 | ```js 518 | const person = { 519 | firstName: "Nick", 520 | lastName: "Anderson", 521 | age: 35, 522 | sex: "M" 523 | } 524 | ``` 525 | 526 | 沒有解構的作法,你只能這樣做: 527 | 528 | ```js 529 | const first = person.firstName; 530 | const age = person.age; 531 | const city = person.city || "Paris"; 532 | ``` 533 | 534 | 使用解構,你只需要一行: 535 | 536 | ```js 537 | const { firstName: first, age, city = "Paris" } = person; // 這樣就搞定了! 538 | 539 | console.log(age) // 35 -- 一個名為 age 的新變數被創建出來了,其值等同於 person.age 540 | console.log(first) // "Nick" -- 一個名為 first 的新變數被創建出來了,其值等同於person.firstName 541 | console.log(firstName) // ReferenceError -- person.firstName 雖然存在,但其值是存在名叫 first 的新變數 542 | console.log(city) // "Paris" -- 一個名為 city 的新變數被創建出來了,同時因為 person.city 是未被定義的,所以 city 將等同於預設值也就是 "Paris"。 543 | ``` 544 | 545 | **注意:** 在 ```const { age } = person;```當中, *const* 後的括號並不是用來宣告 object 或者是區塊,僅僅是 *解構 (destructuring)* 的使用語法。 546 | 547 | - 帶有參數的函數用法 548 | 549 | *解構 (Destructuring)* 經常被用來解 objects 中的參數。 550 | 551 | 沒有解構的作法,你只能這樣做: 552 | 553 | ```js 554 | function joinFirstLastName(person) { 555 | const firstName = person.firstName; 556 | const lastName = person.lastName; 557 | return firstName + '-' + lastName; 558 | } 559 | 560 | joinFirstLastName(person); // "Nick-Anderson" 561 | ``` 562 | 563 | 在解構 obejct 當中 *person* 這個參數時,我們可以得到一個更簡潔的函數: 564 | 565 | ```js 566 | function joinFirstLastName({ firstName, lastName }) { 567 | // 我們透過解構 person 分別創造了 firstName 和 lastName 這兩個變數 568 | return firstName + '-' + lastName; 569 | } 570 | 571 | joinFirstLastName(person); // "Nick-Anderson" 572 | ``` 573 | 574 | 解構搭配[箭頭函數](#arrow_func_concept)使得開發過程更加愉快: 575 | 576 | ```js 577 | const joinFirstLastName = ({ firstName, lastName }) => firstName + '-' + lastName; 578 | 579 | joinFirstLastName(person); // "Nick-Anderson" 580 | ``` 581 | 582 | - Array 583 | 584 | 讓我們來想想下列這個 array: 585 | 586 | ```js 587 | const myArray = ["a", "b", "c"]; 588 | ``` 589 | 590 | 沒有解構的作法,你只能這樣做: 591 | 592 | ```js 593 | const x = myArray[0]; 594 | const y = myArray[1]; 595 | ``` 596 | 597 | 使用解構的作法: 598 | 599 | ```js 600 | const [x, y] = myArray; // 就是這麼簡單! 601 | 602 | console.log(x) // "a" 603 | console.log(y) // "b" 604 | ``` 605 | 606 | 607 | #### 有用資源 608 | 609 | - [ES6 Features - Destructuring Assignment](http://es6-features.org/#ArrayMatching) 610 | - [Destructuring Objects - WesBos](http://wesbos.com/destructuring-objects/) 611 | - [ExploringJS - Destructuring](http://exploringjs.com/es6/ch_destructuring.html) 612 | 613 | 614 | ### Array 的操作方法 - map / filter / reduce 615 | 616 | *Map*,*filter* 和 *reduce* 都是 array 提供的方法,它們源自於 [*functional programming*](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-functional-programming-7f218c68b3a0) 開發範式。 617 | 618 | 總結一下: 619 | 620 | - **Array.prototype.map()** 接受一組 array,針對其中的元素進行某些操作和轉換的動作。 621 | - **Array.prototype.filter()** 接受一組 array,依照元素本身決定是否保留,並且將會回傳一個僅含有保留元素的 array 622 | - **Array.prototype.reduce()** 接受一組 array,將這些元素合併成一個值並回傳 623 | 624 | 我會建議在開發時盡可能的遵循函數式編程 (functional programming) 的原則,因為它們是可組合的,簡潔且優雅的。 625 | 626 | 透過這三種方法,你將可以避免在大多數情況下使用 *for* 和 *forEach*。當你想做一個 *for* 迴圈時,試著用 *map*,*filter* 和 *reduce* 組合看看。起初你可能會覺得窒礙難行,因為它需要你學習一種新的思維方式,但一旦你掌握它了,事情也將變得更加容易。 627 | 628 | 629 | #### 範例程式碼 630 | 631 | ```js 632 | const numbers = [0, 1, 2, 3, 4, 5, 6]; 633 | const doubledNumbers = numbers.map(n => n * 2); // [0, 2, 4, 6, 8, 10, 12] 634 | const evenNumbers = numbers.filter(n => n % 2 === 0); // [0, 2, 4, 6] 635 | const sum = numbers.reduce((prev, next) => prev + next, 0); // 21 636 | ``` 637 | 638 | 透過 map,filter 和 reduce 這幾種組合技去計算出學生成績 >= 10 的總和: 639 | 640 | ```js 641 | const students = [ 642 | { name: "Nick", grade: 10 }, 643 | { name: "John", grade: 15 }, 644 | { name: "Julia", grade: 19 }, 645 | { name: "Nathalie", grade: 9 }, 646 | ]; 647 | 648 | const aboveTenSum = students 649 | .map(student => student.grade) // map the students array to an array of their grades 650 | .filter(grade => grade >= 10) // we filter the grades array to keep those 10 or above 651 | .reduce((prev, next) => prev + next, 0); // we sum all the grades 10 or above one by one 652 | 653 | console.log(aboveTenSum) // 44 -- 10 (Nick) + 15 (John) + 19 (Julia), Nathalie below 10 is ignored 654 | ``` 655 | 656 | 657 | #### 說明 658 | 659 | 讓我們來思考下列這個 array: 660 | 661 | ```js 662 | const numbers = [0, 1, 2, 3, 4, 5, 6]; 663 | ``` 664 | 665 | 666 | ##### Array.prototype.map() 667 | 668 | ```js 669 | const doubledNumbers = numbers.map(function(n) { 670 | return n * 2; 671 | }); 672 | console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12] 673 | ``` 674 | 發生了什麼事?我們在 *numbers* 這個 array 中使用了 .map 方法,map 將會去迭代 array 中的每一個元素並且回傳給我們的函數。該函數的目標是生成並回傳一個新的值使得 map 可以替換掉原本的 array。 675 | 676 | 讓我們提取這個函數以便讓解釋更清楚: 677 | 678 | ```js 679 | const doubleN = function(n) { return n * 2; }; 680 | const doubledNumbers = numbers.map(doubleN); 681 | console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12] 682 | ``` 683 | 684 | ```numbers.map(doubleN)``` 將會產生 ```[doubleN(0), doubleN(1), doubleN(2), doubleN(3), doubleN(4), doubleN(5), doubleN(6)]``` ,而它們分別等同於 ```[0, 2, 4, 6, 8, 10, 12]```。 685 | 686 | > **注意:** 如果你不需要回傳一個新的 array 且只想實作一個帶有副作用的迴圈,使用 for / forEach 迴圈會更為符合你所需。 687 | 688 | 689 | ##### Array.prototype.filter() 690 | 691 | ```js 692 | const evenNumbers = numbers.filter(function(n) { 693 | return n % 2 === 0; // true if "n" is par, false if "n" isn't 694 | }); 695 | console.log(evenNumbers); // [0, 2, 4, 6] 696 | ``` 697 | 698 | 我們在這個充滿 *numbers* 的 array 上使用 .filter 方法,過濾器將會遍歷當中的每一個元素並回傳給我們的函數。函數的目標是回傳一個布林值,它將會確定當前值是否被保留。過濾之後回傳的是一個僅保留所需值的 array。 699 | 700 | 701 | ##### Array.prototype.reduce() 702 | 703 | reduce 方法的目標是將進行迭代的 array 中的所有元素 *減少* 到只留下單一值。計算這些元素的方式將取決於你的需求。 704 | 705 | ```js 706 | const sum = numbers.reduce( 707 | function(acc, n) { 708 | return acc + n; 709 | }, 710 | 0 // 進行迭代計算的初始值 711 | ); 712 | 713 | console.log(sum) //21 714 | ``` 715 | 716 | 就像 .map 和 .filter 方法一樣, .reduce 方法被應用在 array 上並將函數做為第一個參數。 717 | 718 | 719 | 這次有些變化了: 720 | 721 | - .reduce 接受兩個參數 722 | 723 | 第一個參數是在每個迭代步驟中調用的函數。 724 | 725 | 第二個參數是在第一個迭代步驟(讀取下一個之用)的累加器變數的值(此處是 *acc*)。 726 | 727 | - 帶有參數的函數用法 728 | 729 | 做為 .reduce 的第一個參數所傳遞的函數需要兩個參數。第一個(此處是 *acc*)是累加器變數,而第二個參數(*n*)則是當前元素。 730 | 731 | 累加器變數的值等於 **上一次** 迭代步驟中函數的回傳值。在迭代過程的第一步,*acc* 等於你做為 .reduce 時第二個參數所傳遞的值。 732 | 733 | ###### 進行第一次迭代 734 | 735 | ```acc = 0``` 因為我們把 0 做為 reduce 的第二個參數 736 | 737 | ```n = 0``` *number* array 的第一個元素 738 | 739 | 函數回傳 *acc* + *n* --> 0 + 0 --> 0 740 | 741 | ###### 進行第二次迭代 742 | 743 | ```acc = 0``` 因為它是上次迭代所回傳的值 744 | 745 | ```n = 1``` *number* array 的第二個元素 746 | 747 | 函數回傳 *acc* + *n* --> 0 + 1 --> 1 748 | 749 | ###### 進行第三次迭代 750 | 751 | ```acc = 1``` 因為它是上次迭代所回傳的值 752 | 753 | ```n = 2``` *number* array 的第三個元素 754 | 755 | 函數回傳 *acc* + *n* --> 1 + 2 --> 3 756 | 757 | ###### 進行第四次迭代 758 | 759 | ```acc = 3``` 因為它是上次迭代所回傳的值 760 | 761 | ```n = 3``` *number* array 的第四個元素 762 | 763 | 函數回傳 *acc* + *n* --> 3 + 3 --> 6 764 | 765 | ###### [...] 進行最後一次迭代 766 | 767 | ```acc = 15``` 因為它是上次迭代所回傳的值 768 | 769 | ```n = 6``` *number* array 的最後一個元素 770 | 771 | 函數回傳 *acc* + *n* --> 15 + 6 --> 21 772 | 773 | 因為它是最後一個迭代步驟了, **.reduce** 將回傳 21。 774 | 775 | 776 | #### 外部資源 777 | 778 | - [Understanding map / filter / reduce in JS](https://hackernoon.com/understanding-map-filter-and-reduce-in-javascript-5df1c7eee464) 779 | 780 | 781 | ### 展開運算子 "..." 782 | 783 | 展開運算子 ```...``` 的語法在 ES2015 之下已經支援了,而它將會被用於把可迭代的元素 (像是 array) 擴展到容納更多元素。 784 | 785 | 786 | #### 範例程式碼 787 | 788 | ```js 789 | const arr1 = ["a", "b", "c"]; 790 | const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"] 791 | ``` 792 | 793 | ```js 794 | function myFunc(x, y, ...params) { 795 | console.log(x); 796 | console.log(y); 797 | console.log(params) 798 | } 799 | 800 | myFunc("a", "b", "c", "d", "e", "f") 801 | // "a" 802 | // "b" 803 | // ["c", "d", "e", "f"] 804 | ``` 805 | 806 | ```js 807 | const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; 808 | console.log(x); // 1 809 | console.log(y); // 2 810 | console.log(z); // { a: 3, b: 4 } 811 | 812 | const n = { x, y, ...z }; 813 | console.log(n); // { x: 1, y: 2, a: 3, b: 4 } 814 | ``` 815 | 816 | 817 | #### 說明 818 | 819 | 820 | ##### 迭代用法 (如同 array) 821 | 822 | 如果我們有以下兩個 arrays: 823 | 824 | ```js 825 | const arr1 = ["a", "b", "c"]; 826 | const arr2 = [arr1, "d", "e", "f"]; // [["a", "b", "c"], "d", "e", "f"] 827 | ``` 828 | 829 | *arr2* 中的第一個元素是 array ,因為 *arr1* 是被注入到 *arr2* 之中的。但我們真正想要得到的 *arr2* 是一個純字母的 array。為了做到這點,我們可以將 *arr1* *擴展 (spread)* 到 *arr2*。 830 | 831 | 透過展開運算子 832 | 833 | ```js 834 | const arr1 = ["a", "b", "c"]; 835 | const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"] 836 | ``` 837 | 838 | 839 | ##### 不定參數 840 | 841 | 在有著不定參數的函數當中,我們可以使用 rest 運算子將參數注入到我們可以進行迴圈操作的 array。這裡已經有一個名為 **arguments** 的 object 被綁定在函數上,等同於把 array 中的所有參數都傳遞給函數。 842 | 843 | ```js 844 | function myFunc() { 845 | for (var i = 0; i < arguments.length; i++) { 846 | console.log(arguments[i]); 847 | } 848 | } 849 | 850 | myFunc("Nick", "Anderson", 10, 12, 6); 851 | // "Nick" 852 | // "Anderson" 853 | // 10 854 | // 12 855 | // 6 856 | ``` 857 | 858 | 但是如果說我們希望創造的是一個包含他的各科成績和平均成績的新學生,提取前兩個參數 (firstName 和 lastName)並把剩下的元素迭代生成一個 array 的作法是否會更有效率呢? 859 | 860 | 這正是 rest 運算子允許我們做的事! 861 | 862 | ```js 863 | function createStudent(firstName, lastName, ...grades) { 864 | // firstName = "Nick" 865 | // lastName = "Anderson" 866 | // [10, 12, 6] -- "..." 運算子會把 firstName 和 lastName 以外的參數傳入,同時創造一個包含這些元素,叫做 "grades" 的 array 867 | 868 | const avgGrade = grades.reduce((acc, curr) => acc + curr, 0) / grades.length; // 計算平均成績 869 | 870 | return { 871 | firstName: firstName, 872 | lastName: lastName, 873 | grades: grades, 874 | avgGrade: avgGrade 875 | } 876 | } 877 | 878 | const student = createStudent("Nick", "Anderson", 10, 12, 6); 879 | console.log(student); 880 | // { 881 | // firstName: "Nick", 882 | // lastName: "Anderson", 883 | // grades: [10, 12, 6], 884 | // avgGrade: 9,33 885 | // } 886 | ``` 887 | 888 | > **注意:** createStudent 這個函數的舉例其實並不太好,因為我們並沒有去檢查 grades.length 是否存在又或者它根本等於 0。但是這個例子的確能夠幫助我們更為容易理解其中運作,所以我並沒有花額外的時間處理這個情況,請見諒。 889 | 890 | 891 | ##### Object 屬性擴展 892 | 893 | 關於這點,我建議你去閱讀先前有關 rest 運算子,迭代運作和帶有不定參數的函數等相關說明。 894 | 895 | ```js 896 | const myObj = { x: 1, y: 2, a: 3, b: 4 }; 897 | const { x, y, ...z } = myObj; // object 在此處被解構 898 | console.log(x); // 1 899 | console.log(y); // 2 900 | console.log(z); // { a: 3, b: 4 } 901 | 902 | // 解構後剩餘的部分都放在 z : 也就是 myObj 這個物件經過解構後鎖剩下的東西 903 | 904 | const n = { x, y, ...z }; 905 | console.log(n); // { x: 1, y: 2, a: 3, b: 4 } 906 | 907 | // 把 z 所包含的屬性擴展到 n 當中 908 | ``` 909 | 910 | 911 | #### 外部資源 912 | 913 | - [TC39 - Object rest/spread](https://github.com/tc39/proposal-object-rest-spread) 914 | - [Spread operator introduction - WesBos](https://github.com/wesbos/es6-articles/blob/master/28%20-%20Spread%20Operator%20Introduction.md) 915 | - [JavaScript & the spread operator](https://codeburst.io/javascript-the-spread-operator-a867a71668ca) 916 | - [6 Great uses of the spread operator](https://davidwalsh.name/spread-operator) 917 | 918 | 919 | ### Object 屬性簡寫 920 | 921 | 當我們想要把某個物件屬性指派給變數,如果變數名稱等同於屬性名稱,你可以試著執行以下操作: 922 | 923 | ```js 924 | const x = 10; 925 | const myObj = { x }; 926 | console.log(myObj.x) // 10 927 | ``` 928 | 929 | 930 | #### 說明 931 | 932 | 通常 (pre-ES2015) 當你宣告一個新的 *物件實體語法 (object literal)* 並且想要使用變數做為物件屬性的值時,你可能會寫出以下類似的程式碼: 933 | 934 | ```js 935 | const x = 10; 936 | const y = 20; 937 | 938 | const myObj = { 939 | x: x, // 將變數 x 賦值給 myObj.x 940 | y: y // 將變數 y 賦值給 myObj.y 941 | }; 942 | 943 | console.log(myObj.x) // 10 944 | console.log(myObj.y) // 20 945 | ``` 946 | 947 | 你可以發現,這樣的作法其實相當繁瑣,因為 myObj 的屬性名和要指派給這些屬性的變數名稱都是相同的。 948 | 949 | 透過使用 ES2015,當變數名稱和屬性名稱相同時,你可以把程式碼這樣簡寫: 950 | 951 | ```js 952 | const x = 10; 953 | const y = 20; 954 | 955 | const myObj = { 956 | x, 957 | y 958 | }; 959 | 960 | console.log(myObj.x) // 10 961 | console.log(myObj.y) // 20 962 | ``` 963 | 964 | 965 | #### 外部資源 966 | 967 | - [Property shorthand - ES6 Features](http://es6-features.org/#PropertyShorthand) 968 | 969 | 970 | ### Promises 971 | 972 | promise 是一個可以從異步函數 ([參考](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261#3cd0)) 同步回傳的函數。 973 | 974 | Promises 可以被用來避開 [回調地獄 (callback hell)](http://callbackhell.com/),而且它們在現代 JavaScript 專案中也越來越常被使用到。 975 | 976 | 977 | #### 範例程式碼 978 | 979 | ```js 980 | const fetchingPosts = new Promise((res, rej) => { 981 | $.get("/posts") 982 | .done(posts => res(posts)) 983 | .fail(err => rej(err)); 984 | }); 985 | 986 | fetchingPosts 987 | .then(posts => console.log(posts)) 988 | .catch(err => console.log(err)); 989 | ``` 990 | 991 | 992 | #### 說明 993 | 994 | 當你在進行 *Ajax 請求* 時,回傳絕對是非同步的,因為資源請求需要時間。如果你要的資源由於某些原因 (404) 而不能使用,請求的資源可能永遠都不會出現。 995 | 996 | 為了處理這類情況,ES2015 為我們提供了 *promises*。Promises 可以有三種不同的狀態: 997 | 998 | - 等待中 (Pending) 999 | - 達成 (Fulfilled) 1000 | - 拒絕 (Rejected) 1001 | 1002 | 假設我們希望使用 promises 去進行 Ajax 請求以獲取 X 這項資源。 1003 | 1004 | 1005 | ##### 創造 promise 1006 | 1007 | 首先要創造一個 promise。我們將會使用 jQuery 的 get 方法去進行資源 X 的 Ajax 請求。 1008 | 1009 | ```js 1010 | const xFetcherPromise = new Promise( // 使用 "new" 這個關鍵字並把它存至一個變數 1011 | function(resolve, reject) { // Promise 建構子需要一個有著 resolve 和 reject 這兩個參數的函數作為參數 1012 | $.get("X") // 執行 Ajax 請求 1013 | .done(function(X) { // 一旦請求完成... 1014 | resolve(X); // ... 把 X 做為參數去 resolve promise 1015 | }) 1016 | .fail(function(error) { // 如果請求失敗... 1017 | reject(error); // ... 把 error 做為參數去 reject promise 1018 | }); 1019 | } 1020 | ) 1021 | ``` 1022 | 1023 | 如上所示,Promise 物件需要一個帶有兩個參數 ( **resolve** 以及 **reject** ) 的函數。這兩個參數會把 *pending* 狀態的 promise 分別進行 *fulfilled* 和 *rejected* 的處理。 1024 | 1025 | 但在此時此刻,promise 尚未被使用,它僅僅是被宣告並且儲存到 *xFetcherPromise* 這個變數當中!所以它並不存在當前的狀態。 1026 | 1027 | 1028 | ##### 使用 promise 1029 | 1030 | 為了使用 promise,我們可以進行以下的實作: 1031 | 1032 | ```js 1033 | xFetcherPromise 1034 | .then(function(X) { 1035 | console.log(X); 1036 | }) 1037 | .catch(function(err) { 1038 | console.log(err) 1039 | }) 1040 | ``` 1041 | 1042 | ```.then``` 是一種方法,一旦被調用將會把 xFetcherPromise 調整至 **pending** 狀態。當被調用時,promise 本體會運行,在這個範例當中,Ajax 請求正在進行中。 1043 | 1044 | 如果成功,將會調用 *resolve*,並且 ```.then``` 將會執行做為參數傳遞的函數。 1045 | 1046 | 如果失敗,將會調用 *reject*,並且 ```.catch``` 將會執行做為參數傳遞的函數。 1047 | 1048 | 1049 | #### 外部資源 1050 | 1051 | - [JavaScript Promises for dummies - Jecelyn Yeen](https://scotch.io/tutorials/javascript-promises-for-dummies) 1052 | - [JavaScript Promise API - David Walsh](https://davidwalsh.name/promises) 1053 | - [Using promises - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) 1054 | - [What is a promise - Eric Elliott](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261) 1055 | - [JavaScript Promises: an Introduction - Jake Archibald](https://developers.google.com/web/fundamentals/getting-started/primers/promises) 1056 | - [Promise documentation - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 1057 | 1058 | 1059 | ### 模板字符串 1060 | 1061 | 模板字符串是一種單行和多行字符串的 [*表達式差值 (expression interpolation)*](https://en.wikipedia.org/wiki/String_interpolation)。 1062 | 1063 | 換句話說,它是一種新的字符串語法,你可以更方便地在 JavaScript 表達式中使用 (例如變數)。 1064 | 1065 | 1066 | #### 範例程式碼 1067 | 1068 | ```js 1069 | const name = "Nick"; 1070 | `Hello ${name}, the following expression is equal to four : ${2+2}`; 1071 | 1072 | // Hello Nick, the following expression is equal to four: 4 1073 | ``` 1074 | 1075 | 1076 | #### 外部資源 1077 | 1078 | - [String interpolation - ES6 Features](http://es6-features.org/#StringInterpolation) 1079 | - [ES6 Template Strings - Addy Osmani](https://developers.google.com/web/updates/2015/01/ES6-Template-Strings) 1080 | 1081 | 1082 | ### Imports / Exports 1083 | 1084 | ES6 模組被用來存取顯式輸出 (explicitly export)的變數或是函數。 1085 | 1086 | 我強烈建議你去瀏覽 MDN 上有關 import/export (請參考下面的外部資源) 的文章,它們寫的既簡潔又完整。 1087 | 1088 | 1089 | #### 說明與範例程式碼 1090 | 1091 | - Named exports 1092 | 1093 | Named exports 被用於從模組中輸出多個值的情況。你只能命名將要輸出的變數 (不能是函數或是類別),所以當你想要輸出一個函數時,你必須先把它儲存在一個變數中。 1094 | 1095 | ```js 1096 | // mathConstants.js 1097 | export const pi = 3.14; 1098 | export const exp = 2.7; 1099 | export const alpha = 0.35; 1100 | 1101 | // ------------- 1102 | 1103 | // myFile.js 1104 | import { pi, exp } from './mathConstants.js'; // 對 import 進行解構 1105 | console.log(pi) // 3.14 1106 | console.log(exp) // 2.7 1107 | 1108 | // ------------- 1109 | 1110 | // mySecondFile.js 1111 | import * as constants from './mathConstants.js'; // 把所有的值輸出到 constants 這個變數 1112 | console.log(constants.pi) // 3.14 1113 | console.log(constants.exp) // 2.7 1114 | ``` 1115 | 1116 | - 預設 import / export 1117 | 1118 | 關於輸出,每個模組在預設下只能有一個輸出。一個預設的輸出可以是函數,類別,物件又或者是任何東西。這個值被認為是 "主要的" 輸出值,因為它將會是最簡單純粹的輸出。[參考: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#Description) 1119 | 1120 | ```js 1121 | // coolNumber.js 1122 | const ultimateNumber = 42; 1123 | export default ultimateNumber; 1124 | 1125 | // ------------ 1126 | 1127 | // myFile.js 1128 | import number from './coolNumber.js'; 1129 | // 預設輸出將獨立於其名稱, 將被自動注入到 number 這個變數; 1130 | console.log(number) // 42 1131 | ``` 1132 | 1133 | 函數輸出: 1134 | 1135 | ```js 1136 | // sum.js 1137 | export default function sum(x, y) { 1138 | return x + y; 1139 | } 1140 | // ------------- 1141 | 1142 | // myFile.js 1143 | import sum from './sum.js'; 1144 | const result = sum(1, 2); 1145 | console.log(result) // 3 1146 | ``` 1147 | 1148 | 1149 | #### 外部資源 1150 | 1151 | - [Export - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) 1152 | - [Import - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 1153 | - [Understanding ES6 Modules](https://www.sitepoint.com/understanding-es6-modules/) 1154 | - [Modules in JavaScript](http://exploringjs.com/es6/ch_modules.html#sec_modules-in-javascript) 1155 | 1156 | ### JavaScript *this* 1157 | 1158 | *this* 這個運算子的行為和其他語言是不太一樣的,在大多數情況之下是由函數的調用方式決定。([參考: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)). 1159 | 1160 | 這個概念有很多精妙之處,並不是那麼容易理解,我強烈建議你好好研讀下面的外部資源。因此,我將會提供我個人對於 *this* 的一點理解和想法。我是從 [Yehuda Katz 寫的這篇文章](http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/) 學到了這個技巧。 1161 | 1162 | ```js 1163 | function myFunc() { 1164 | ... 1165 | } 1166 | 1167 | // 在每個述句後頭,你都可以在 myFunc 中找到 this 的值 1168 | 1169 | myFunc.call("myString", "hello") // "myString" -- 首先, .call 參數的值被注入到 this 1170 | 1171 | // 非嚴格模式下 1172 | myFunc("hello") // window -- myFunc() 是 myFunc.call(window, "hello") 的語法糖 1173 | 1174 | // 嚴格模式下 1175 | myFunc("hello") // undefined -- myFunc() 是 myFunc.call(undefined, "hello") 的語法糖 1176 | ``` 1177 | 1178 | ```js 1179 | var person = { 1180 | myFunc: function() { ... } 1181 | } 1182 | 1183 | person.myFunc.call(person, "test") // person 物件 -- 調用參數注入 this 1184 | person.myFunc("test") // person Object -- person.myFunc() 是 person.myFunc.call(person, "test") 的語法糖 1185 | 1186 | var myBoundFunc = person.myFunc.bind("hello") // 創造了一個函數,並且把 "hello" 注入到 this 1187 | person.myFunc("test") // person Object -- 綁定方法對原有方法並無造成影響 1188 | myBoundFunc("test") // "hello" -- myBoundFunc 是把帶有 "hello" 的 person.myFunc 綁定到 this 1189 | ``` 1190 | 1191 | 1192 | #### 外部資源 1193 | 1194 | - [Understanding JavaScript Function Invocation and "this" - Yehuda Katz](http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/) 1195 | - [JavaScript this - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) 1196 | 1197 | 1198 | ### Class 1199 | 1200 | JavaScript 是一個 [基於原型](https://en.wikipedia.org/wiki/Prototype-based_programming) 的語言 (然而 Java 是 [基於類別](https://en.wikipedia.org/wiki/Class-based_programming) 的語言)。 ES6 引入了 JavaScript 類別,它們是基於原型繼承的語法糖,而 **不是** 真正意義上基於類別的繼承模型。([參考](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)). 1201 | 1202 | 1203 | *類別 (class)* 一詞的確容易出錯,尤其是你同時也熟悉其他語言的情況下。如果真的有此困擾,請避免在這樣的認知下思考 JavaScript 的類別行為,並把它當作一個完全不同的新概念。 1204 | 1205 | 由於此份文件的目標不在於從頭教會你 JavaScript,我相信你早已知道什麼是原型,以及它們的行為模式。不過這裡還是有一些參考連結,以方便你去理解這些概念: 1206 | 1207 | - [Understanding Prototypes in JS - Yehuda Katz](http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/) 1208 | - [A plain English guide to JS prototypes - Sebastian Porto](http://sporto.github.io/blog/2013/02/22/a-plain-english-guide-to-javascript-prototypes/) 1209 | - [Inheritance and the prototype chain - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) 1210 | 1211 | 1212 | #### 範例 1213 | 1214 | ES6 之前的原型語法: 1215 | 1216 | ```js 1217 | var Person = function(name, age) { 1218 | this.name = name; 1219 | this.age = age; 1220 | } 1221 | Person.prototype.stringSentence = function() { 1222 | return "Hello, my name is " + this.name + " and I'm " + this.age; 1223 | } 1224 | ``` 1225 | 1226 | ES6 之後的類型語法: 1227 | 1228 | ```js 1229 | class Person { 1230 | constructor(name, age) { 1231 | this.name = name; 1232 | this.age = age; 1233 | } 1234 | 1235 | stringSentence() { 1236 | return "Hello, my name is " + this.name + " and I'm " + this.age; 1237 | } 1238 | } 1239 | 1240 | const myPerson = new Person("Manu", 23); 1241 | console.log(myPerson.age) // 23 1242 | console.log(myPerson.stringSentence()) // "Hello, my name is Manu and I'm 23 1243 | ``` 1244 | 1245 | 1246 | #### 外部資源 1247 | 1248 | 更好的理解原型: 1249 | 1250 | - [Understanding Prototypes in JS - Yehuda Katz](http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/) 1251 | - [A plain English guide to JS prototypes - Sebastian Porto](http://sporto.github.io/blog/2013/02/22/a-plain-english-guide-to-javascript-prototypes/) 1252 | - [Inheritance and the prototype chain - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) 1253 | 1254 | 更好的理解類別: 1255 | 1256 | - [ES6 Classes in Depth - Nicolas Bevacqua](https://ponyfoo.com/articles/es6-classes-in-depth) 1257 | - [ES6 Features - Classes](http://es6-features.org/#ClassDefinition) 1258 | - [JavaScript Classes - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) 1259 | 1260 | 1261 | ### Async Await 1262 | 1263 | 除了 [Promises](#promises) 以外,還有一種新語法你可能會遇到,那就是被稱作非同步的 *async / await*。 1264 | 1265 | async/await 的目的在於簡化同步使用 promise 的行為,並對一組 promise 執行一些處理。正如同Promises 類似於結構化之後的回調 (callback),async/await 同樣類似於組合生成器 (combining generators) 和 promises。 ([參考: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)) 1266 | 1267 | > **注意:** 你必須先行了解到什麼是 promises 和它們是如何運作的,然後再去嘗試理解 async / await 的概念,因為後者是基於前者的進一步延伸。 1268 | 1269 | > **注意:** [*await* must be used in an *async* function](https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9#f3f0) 意味著你不能程式碼的頂部使用 await,因為它並不在異步函數之內。 1270 | 1271 | 1272 | #### 說明與範例程式碼 1273 | 1274 | *Async / Await* 是基於 promises 之上的新概念,但它們更允許你使用命令式風格 (imperative style)去撰寫程式。 1275 | 1276 | 1277 | `await` 表達式使 `async` 函數暫停執行,直到 promise 被成功解析才會繼續執行。任何 `async` 函數堆將回傳 `Promise`,並將其解析為回傳值。 1278 | 1279 | ```js 1280 | async function getGithubUser(handle) { // async 這個關鍵字允許在函數中使用 await,並且意味著函數將回傳一個 promise 1281 | try { // 這是 async / await 使用的方式 1282 | const url = `https://api.github.com/users/${handle}`; 1283 | const response = await fetch(url); // "同步" 等待 fetch 去解析 promise,然後才會跳轉到下一行 1284 | return response.json(); 1285 | } catch (err) { 1286 | alert(err); 1287 | } 1288 | } 1289 | 1290 | getGithubUser('mbeaudru').then(user => console.log(user)); // 印出 user 的值 - 不能使用 await 語法,因為此段程式碼並不在 async 函數當中 1291 | ``` 1292 | 1293 | 1294 | #### 外部資源 1295 | 1296 | - [Async/Await - JavaScript.Info](https://javascript.info/async-await) 1297 | - [ES7 Async/Await](http://rossboucher.com/await/#/) 1298 | - [6 Reasons Why JavaScript’s Async/Await Blows Promises Away](https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9) 1299 | - [JavaScript awaits](https://dev.to/kayis/javascript-awaits) 1300 | - [Using Async Await in Express with Node 8](https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016) 1301 | - [Async Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) 1302 | - [Await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) 1303 | 1304 | 1305 | ## 術語詞彙 1306 | 1307 | ### 作用域範圍 (scope) 1308 | 1309 | 在上下文之中有著 "明顯可見的 (visible)" 值和表達式,又或者是可以被參照的。如果變數或是表達式並不在 "當前作用域和範圍",那麼它將會是不能用的。 1310 | 1311 | 資料來源: [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Scope) 1312 | 1313 | ### 變數變異 (Variable mutation) 1314 | 1315 | 一個變數在被宣告之後發生初始值變化的過程。 1316 | 1317 | ```js 1318 | var myArray = []; 1319 | myArray.push("firstEl") // myArray 正在變化 1320 | ``` 1321 | 1322 | 如果變數不能被改變的話,我們會說這個變數是 *不可變的 (immutable)* 。 1323 | 1324 | [查看 MDN Mutable 文章](https://developer.mozilla.org/en-US/docs/Glossary/Mutable) 了解更多詳細資料。 1325 | -------------------------------------------------------------------------------- /translations/th-TH.md: -------------------------------------------------------------------------------- 1 | # Modern JavaScript Cheatsheet 2 | 3 | ![Modern JavaScript cheatsheet](https://i.imgur.com/aexPxMb.png) 4 | เครดิตรูปภาพ: [Ahmad Awais ⚡️](https://github.com/ahmadawais) 5 | 6 | ## เกริ่นนำ 7 | 8 | ### จุดประสงค์ 9 | 10 | เอกสารนี้เป็น cheatsheet ชุดคำสั่งของภาษา JavaScript ที่คุณได้พบเจอเป็นประจำใน​โปรเจ็คและโค้ดตัวอย่างใหม่ๆ 11 | 12 | บทความนี้ไม่ได้มีจุดประสงค์ในการสอน Javascript ตั้งแต่พื้นฐาน แต่ต้องการจะช่วยให้ Developer ที่มีพื้นฐานอยู่แล้วแต่อาจจะติดปัญหาในการเข้าใจในโค้ด Javascript สมัยใหม่ (ยกตัวอย่างเช่นกำลังเรียนรู้ React อยู่) เนื่องจากมีการใช้ concept ของ Javascript สมัยใหม่ 13 | 14 | นอกจากนี้ผมจะแนะนำเคล็ดลับส่วนตัว(ซึ่งอาจจะมีบางคนไม่เห็นด้วย)ใส่ไว้ในบางส่วน โดยจะมีหมายเหตุบอกเอาไว้ 15 | 16 | > **หมายเหตุ:** คอนเซปส่วนใหญ่ในนี้จะมาจากการอัปเดตใหม่ๆของ JavaScript (ES2015, หรือโดยทั่วไปเรียกว่า ES6). คุณสามารถดูฟีเจอร์ใหม่ๆของ Javascript ที่เพิ่มเข้ามาโดยสามารถติดตามได้จาก [ที่นี่](http://es6-features.org); ซึ่งเป็นเว็บไซต์ที่ดีทีเดียว 17 | 18 | ### แหล่งเรียนรู้ฟรีที่แนะนำเพิ่มเติม 19 | 20 | เมื่อติดปัญหาในการเข้าใจตรงจุดไหนแนะนำให้ลองหาคำตอบจากแหล่งข้อมูลเพิ่มเติมเหล่านี้ดูก่อน: 21 | 22 | - [MDN (Mozilla Developer Network)](https://developer.mozilla.org/en-US/search?q=) 23 | - [You don't know JS (book)](https://github.com/getify/You-Dont-Know-JS) 24 | - [ES6 Features with examples](http://es6-features.org) 25 | - [WesBos blog (ES6)](http://wesbos.com/category/es6/) 26 | - [Javascript Basics for Beginners](https://www.udacity.com/course/javascript-basics--ud804) - คอร์สฟรีจาก Udacity 27 | - [Reddit (JavaScript)](https://www.reddit.com/r/javascript/) 28 | - [Google](https://www.google.com/) to find specific blog and resources 29 | - [StackOverflow](https://stackoverflow.com/questions/tagged/javascript) 30 | 31 | ## สารบัญ 32 | 33 | - [Modern JavaScript cheatsheet](#modern-javascript-cheatsheet) 34 | * [เกริ่นนำ](#เกริ่นนำ) 35 | + [จุดประสงค์](#จุดประสงค์) 36 | + [แหล่งเรียนรู้ฟรีที่แนะนำเพิ่มเติม](#แหล่งเรียนรู้ฟรีที่แนะนำเพิ่มเติม) 37 | * [สารบัญ](#สารบัญ) 38 | * [เนื้อหา](#เนื้อหา) 39 | + [การประกาศตัวแปร: var, const, let](#การประกาศตัวแปร-var-const-let) 40 | - [อธิบายสั้นๆ](#อธิบายสั้นๆ) 41 | - [ตัวอย่างโค้ด](#ตัวอย่างโค้ด) 42 | - [อธิบายรายละเอียด](#อธิบายรายละเอียด) 43 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก) 44 | + [Arrow ฟังก์ชั่น](#-arrow-ฟังก์ชั่น) 45 | - [ตัวอย่างโค้ด](#ตัวอย่างโค้ด-1) 46 | - [อธิบายรายละเอียด](#อธิบายรายละเอียด-1) 47 | * [การย่อสั้น](#การย่อสั้น) 48 | * [การอ้างอิงของ *this*](#การอ้างอิงของ-this) 49 | - [แหล่งข้อมูลที่มีประโยชน์](#แหล่งข้อมูลที่มีประโยชน์) 50 | + [ค่า default parameter ของฟังก์ชั่น](#ค่า-default-parameter-ของฟังก์ชั่น) 51 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-1) 52 | + [Destructuring objects และ arrays](#destructuring-objects-และ-arrays) 53 | - [อธิบายตัวอย่างโค้ด](#อธิบายตัวอย่างโค้ด) 54 | - [แหล่งข้อมูลที่มีประโยชน์](#แหล่งข้อมูลที่มีประโยชน์-1) 55 | + [Array methods - map / filter / reduce](#array-methods---map--filter--reduce) 56 | - [ตัวอย่างโค้ด](#ตัวอย่างโค้ด-2) 57 | - [อธิบาย](#อธิบาย) 58 | * [Array.prototype.map()](#arrayprototypemap) 59 | * [Array.prototype.filter()](#arrayprototypefilter) 60 | * [Array.prototype.reduce()](#arrayprototypereduce) 61 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-2) 62 | + [Spread operator "..."](#spread-operator-) 63 | - [ตัวอย่างโค้ด](#ตัวอย่างโค้ด-3) 64 | - [อธิบาย](#อธิบาย-1) 65 | * [ใช้กับสิ่งที่สามารถวนลูปได้ (iterables) (แบบ arrays)](#ใช้กับสิ่งที่สามารถวนลูปได้-iterables-แบบ-arrays) 66 | * [Rest parameter ของฟังก์ชั่น](#rest-parameter-ของฟังก์ชั่น) 67 | * [Object properties spreading](#object-properties-spreading) 68 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก) 69 | + [Object property shorthand](#object-property-shorthand) 70 | - [อธิบาย](#อธิบาย-2) 71 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-1) 72 | + [Promises](#promises) 73 | - [ตัวอย่างโค้ด](#ตัวอย่างโค้ด-4) 74 | - [อธิบาย](#อธิบาย-3) 75 | * [การสร้าง Promise](#create-the-promise) 76 | * [การใช้งาน Promise handlers](#การใช้งาน-promise-handlers) 77 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-2) 78 | + [Template literals](#template-literals) 79 | - [ตัวอย่างโค้ด](#ตัวอย่างโค้ด-5) 80 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-3) 81 | + [Tagged Template Literals](#tagged-template-literals) 82 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-4) 83 | + [Imports / Exports](#imports--exports) 84 | - [อธิบายตัวอย่างโค้ด](#อธิบายตัวอย่างโค้ด-1) 85 | * [Named exports](#named-exports) 86 | * [Default import / export](#default-import--export) 87 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-5) 88 | + [JavaScript *this*](#-javascript-this) 89 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-6) 90 | + [Class](#class) 91 | - [ตัวอย่าง](#samples) 92 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-7) 93 | + [Async Await](#async-await) 94 | - [ตัวอย่างโค้ด](#ตัวอย่างโค้ด-6) 95 | - [อธิบายตัวอย่างโค้ด](#อธิบายตัวอย่างโค้ด-2) 96 | - [การจัดการกับ Error](#การจัดการกับ-error) 97 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-8) 98 | + [Truthy / Falsy](#truthy--falsy) 99 | + [Static Methods](#static-methods) 100 | - [อธิบายสั้นๆ](#อธิบายสั้นๆ-1) 101 | - [ตัวอย่างโค้ด](#ตัวอย่างโค้ด-7) 102 | - [อธิบายรายละเอียด](#อธิบายรายละเอียด-2) 103 | * [เรียก static methods อื่นจาก static method](#เรียก-static-methods-อื่นจาก-static-method) 104 | * [เรียก static methods จาก non-static method](#เรียก-static-methods-จาก-non-static-method) 105 | - [ข้อมูลเพิ่มเติมจากภายนอก](#ข้อมูลเพิ่มเติมจากภายนอก-9) 106 | * [คำศัพธ์](#คำศัพธ์) 107 | + [Scope](#-scope) 108 | + [Variable mutation](#-variable-mutation) 109 | 110 | ## เนื้อหา 111 | 112 | ### การประกาศตัวแปร: var, const, let 113 | 114 | ใน JavaScript มีวิธีการประกาศตัวแปรได้ 3 แบบคือ ```var```, ```let``` และ ```const``` ซึ่งแต่ละแบบมีความแตกต่างกัน 115 | 116 | #### อธิบายสั้นๆ 117 | 118 | ตัวแปรที่ประกาศโดยใช้ ```const``` จะไม่สามารถถูก assign ค่าให้กับตัวแปรใหม่ได้ ในขณะที่ ```let``` กับ ```var``` สามารถทำได้ 119 | 120 | ผมแนะนำให้ประกาศตัวแปรด้วย ```const``` เสมอและค่อยเปลี่ยนเป็น ```let``` ถ้าคุณต้องการ*เปลี่ยนแปลงค่า (mutate)* หรือ assign ค่าให้ตัวแปรในภายหลัง 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 |
Scopeสามารถ Assign ค่าใหม่ได้สามารถเปลี่ยนแปลงค่าได้Temporal Dead Zone
constBlockไม่ใช่ใช่ใช่
letBlockใช่ใช่ใช่
varFunctionใช่ใช่ไม่ใช่
152 | 153 | #### ตัวอย่างโค้ด 154 | 155 | ```javascript 156 | const person = "Nick"; 157 | person = "John" // จะ thrown error เพราะว่า person ไม่สามารถ assign ค่าใหม่ได้ 158 | ``` 159 | 160 | ```javascript 161 | let person = "Nick"; 162 | person = "John"; 163 | console.log(person) // "John", let จะยอมให้สามารถ assign ค่าใหม่ได้ 164 | ``` 165 | 166 | #### อธิบายรายละเอียด 167 | 168 | อธิบาย [*scope*](#scope_def) ของตัวแปรได้อย่างคร่าวๆ หมายถึง "ขอบเขตที่ตัวแปรสามารถใช้งานได้ภายในโค้ด" 169 | 170 | ##### var 171 | 172 | ตัวแปรที่ถูกประกาศด้วย ```var``` จะเป็น *function scoped* เมื่อตัวแปรถูกสร้างภายใน function โค้ดใน function นั้นสามารถเข้าถึงตัวแปรนั้นได้ และตัวแปร *function scoped* ที่ถูกสร้างใน function จะไม่สามารถถูกเข้าถึงจากภายนอก function ได้ 173 | 174 | แนะนำให้ลองจินตนาการดูว่าถ้าตัวแปรเป็นตัวแปร *X scoped* หมายความว่าตัวแปรนี้เป็น property ของ X เท่านั้น 175 | 176 | ```javascript 177 | function myFunction() { 178 | var myVar = "Nick"; 179 | console.log(myVar); // "Nick" - myVar จะสามารถเข้าถึงได้จากภายใน function 180 | } 181 | console.log(myVar); // จะเกิด ReferenceError เพราะ myVar ไม่สามารถเข้าถึงได้จากภายนอก function 182 | ``` 183 | 184 | ตัวอย่างเพิ่มเติมสำหรับเรื่อง scope ของตัวแปร 185 | 186 | ```javascript 187 | function myFunction() { 188 | var myVar = "Nick"; 189 | if (true) { 190 | var myVar = "John"; 191 | console.log(myVar); // "John" 192 | // จริงๆ แล้ว myvar เป็นตัวแปร function scoped เราแค่ได้ลบค่าตัวแปร myVar จาก "Nick" และเปลี่ยนเป็น "John" 193 | } 194 | console.log(myVar); // "John" - จะเห็นได้ว่าค่าได้ถูกเปลี่ยนไปแล้ว 195 | } 196 | console.log(myVar); // จะเกิด ReferenceError เพราะ myVar ไม่สามารถเข้าถึงได้จากภายนอก function 197 | ``` 198 | 199 | นอกจากนี้ตัวแปรที่ประกาสด้วย *var* จะถูกย้ายตอน execution ไปอยู่ด้านบนสุดของ scope และนี่คือสิ่งที่เราเรียกว่า [var hoisting](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting). 200 | 201 | โค้ดตัวอย่างกรณีนี้: 202 | 203 | ```js 204 | console.log(myVar) // undefined -- ไม่มีการเกิด error 205 | var myVar = 2; 206 | ``` 207 | 208 | เพราะว่าตามความเข้าใจเวลา Execution จะเป็นแบบนี้: 209 | 210 | ```js 211 | var myVar; 212 | console.log(myVar) // undefined -- ไม่มีการเกิด error 213 | myVar = 2; 214 | ``` 215 | 216 | ##### let 217 | 218 | ```var``` และ ```let ``` จะคล้ายกันแต่ตัวแปรที่ประกาศด้วย ```let``` จะ 219 | 220 | - เป็น *block scoped* 221 | - จะ**ไม่สามารถ**เข้าถึงก่อนที่มันจะถูก assign ค่าได้ 222 | - ไม่สามารถประกาศตัวแปรซ้ำใน scope เดียวกันได้ 223 | 224 | ลองดูตัวอย่างเรื่องผลกระทบ (side effect) ของ block-scoping จากตัวอย่างก่อนหน้า 225 | 226 | ```javascript 227 | function myFunction() { 228 | let myVar = "Nick"; 229 | if (true) { 230 | let myVar = "John"; 231 | console.log(myVar); // "John" 232 | // จริงๆ แล้ว myVar เป็น block scoped เราสามารถสร้างตัวแปร myVar ใหม่ได้ 233 | // ตัวแปรนี้จะไม่สามารถเข้าถึงได้จากภายนอก block นี้และเป็นอิสระจากกัน 234 | // กับตัวแปร myVar ตัวแรกที่เราสร้าง ! 235 | } 236 | console.log(myVar); // "Nick", จะเห็นตามที่อธิบายข้างต้นว่าภายใน block ไม่ส่งผลกระทบกับค่านี้ 237 | } 238 | console.log(myVar); // จะเกิด ReferenceError เพราะ myVar จะไม่สามารถเข้าถึงได้จากภายนอก function 239 | ``` 240 | 241 | ตอนนี้น่าจะเข้าใจเหตุผลแล้วว่าทำไมตัวแปรที่ประกาศโดยใช้ *let* (และ *const*) ไม่สามารถเข้าถึงได้ก่อนจะถูก assign ค่า 242 | 243 | ```js 244 | console.log(myVar) // เกิด ReferenceError ! 245 | let myVar = 2; 246 | ``` 247 | 248 | สิ่งนี้จะแตกต่างกับตัวแปรที่ประกาศโดยใช้ *var* ถ้าเราพยายามที่จะอ่านหรือเขียนตัวแปรที่ประกาศโดย *let* หรือ *const* ก่อนที่ทำการ assign ค่าจะเกิด Error ทันที ปรากฏการณ์นี้มักถูกเรียกว่า [*Temporal dead zone*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_Dead_Zone_and_errors_with_let) หรือ *TDZ*. 249 | 250 | > **หมายเหตุ:** ในทางเทคนิคแล้ว การประกาศตัวแปร *let* กับ *const* เป็น hoisted เหมือนกัน, แต่ไม่ใช่กับการ assign ค่า ดังนั้นเมื่อมันไม่สามารถใช้งานได้ก่อน assign ค่าได้ทำให้ดูเหมือนว่าไม่มี hoisting แต่จริงๆ แล้วมันมี อ่าน[คำอธิบายเพิ่มเติมแบบละเอียดที่นี่](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified) ถ้าคุณต้องการ 251 | 252 | เพิ่มเติม คุณไม่สามารถประกาศตัวแปรที่ประกาศด้วย *let* ซ้ำได้: 253 | 254 | ```js 255 | let myVar = 2; 256 | let myVar = 3; // เกิด SyntaxError 257 | ``` 258 | 259 | ##### const 260 | 261 | การประกาศตัวแปรโดยใช้ ```const``` จะเหมือนกับ *let* แต่ต่างตรงที่พวกมันจะไม่สามารถ assign ค่าซ้ำได้ 262 | 263 | สรุปสั้นๆ สำหรับตัวแปรที่ประกาศแบบ *const*: 264 | 265 | - เป็น *block scoped* 266 | - ไม่สามารถเข้าถึงได้ก่อนถูก assign ค่า 267 | - ไม่สามารถประกาศซ้ำได้ใน scope เดียวกัน 268 | - ไม่สามารถ assign ซ้ำได้ 269 | 270 | ```js 271 | const myVar = "Nick"; 272 | myVar = "John" // เกิด error เพราะไม่สามารถ assign ค่าซ้ำได้ 273 | ``` 274 | 275 | ```js 276 | const myVar = "Nick"; 277 | const myVar = "John" // เกิด error เพราะไม่สามารถประกาศตัวแปรซ้ำได้ 278 | ``` 279 | 280 | มีบางจุดที่ต้องระวัง: ตัวแปร ```const``` ไม่ใช่ [**immutable**](#mutation_def) ! อธิบายเพิ่มเติมคือตัวแปรที่ประกาศโดย ```const``` ที่เก็บค่าเป็น *object* และ *array* ค่าข้างใน**สามารถ**เปลี่ยนแปลงได้ 281 | 282 | สำหรับ objects: 283 | ```js 284 | const person = { 285 | name: 'Nick' 286 | }; 287 | person.name = 'John' // สามารถทำได้ ! ตัวแปร person ไม่ได้ถูก assign ใหม่ แต่ถูกแปลงค่าข้างใน 288 | console.log(person.name) // "John" 289 | person = "Sandra" // เกิด error เพราะว่าไม่สามารถ assign ค่าซ้ำได้สำหรับตัวแปรที่ประกาศด้วย const 290 | ``` 291 | 292 | สำหรับ arrays: 293 | ```js 294 | const person = []; 295 | person.push('John'); // สามารถทำได้ ! ตัวแปร person ไม่ได้ถูก assign ใหม่ แต่ถูกแปลงค่าข้างใน 296 | console.log(person[0]) // "John" 297 | person = ["Nick"] // เกิด error เพราะว่าไม่สามารถ assign ค่าซ้ำได้สำหรับตัวแปรที่ประกาศด้วย const 298 | ``` 299 | 300 | #### ข้อมูลเพิ่มเติมจากภายนอก 301 | 302 | - [How let and const are scoped in JavaScript - WesBos](http://wesbos.com/javascript-scoping/) 303 | - [Temporal Dead Zone (TDZ) Demystified](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified) 304 | 305 | ### Arrow ฟังก์ชั่น 306 | 307 | ใน JavaScript อัปเดต ES6 มี *arrow ฟังก์ชั่น* ซึ่งเป็นการประกาศและใช้ฟังก์ชั่นในรูปแบบใหม่ และมีความสามารถที่มาเพิ่มเติมดังนี้ 308 | 309 | - เขียนสั้นกระชับมากขึ้น 310 | - *this* อ้างอิงถึงภายนอกรอบๆ 311 | - การ return ค่าทันทีได้แบบตรงไปตรงมา 312 | 313 | #### ตัวอย่างโค้ด 314 | 315 | - การ return ที่สั้นกระชัดและตรงไปตรงมามากขึ้น 316 | 317 | ```js 318 | function double(x) { return x * 2; } // วิธีปกติ 319 | console.log(double(2)) // 4 320 | ``` 321 | 322 | ```js 323 | const double = x => x * 2; // ฟังก์ชั่นเดียวกันที่เปลี่ยนมาเขียนโดยใช้ arrow ฟังก์ชั่นและ return ทันที 324 | console.log(double(2)) // 4 325 | ``` 326 | 327 | - การอ้างอิงเมื่อใช้ *this* 328 | 329 | ใน arrow ฟังก์ชั่น *this* จะอ้างอิงถึงค่าของ *this* ที่อยู่บริบทที่ครอบอยู่ พูดง่ายๆ คือเมื่อใช้ arrow ฟังก์ชั่นคุณไม่จำเป็นต้องทำทริค "that = this" ก่อนเรียกฟังก์ชั่นอื่นภายในฟังก์ชั่นอีกทีอีกต่อไป 330 | 331 | ```js 332 | function myFunc() { 333 | this.myVar = 0; 334 | setTimeout(() => { 335 | this.myVar++; 336 | console.log(this.myVar) // 1 337 | }, 0); 338 | } 339 | ``` 340 | 341 | #### อธิบายรายละเอียด 342 | 343 | ##### การย่อสั้น 344 | 345 | Arrow ฟังก์ชั่นจะสั้นกระชับกว่าฟังก์ชั่นแบบปกติทั่วไป ลองมาดูวิธีการเขียนแบบที่สามารถเขียนได้ดังนี้: 346 | 347 | - Implicit VS Explicit return 348 | 349 | **Explicit return** หมายถึง function ที่ต้องมีการใช้คีย์เวิร์ด *return* ข้างในตัวมัน 350 | 351 | ```js 352 | function double(x) { 353 | return x * 2; // ฟังก์ชั่นนี้เป็น explicitly returns x * 2, ดังนั้นต้องมีคีย์เวิร์ด *return* 354 | } 355 | ``` 356 | 357 | โดยวิธีเขียนฟังก์ชั่นแบบปกติแล้ว การ return จะเป็น explicit จะเสมอ แต่สำหรับ arrow ฟังก์ชั่นคุณสามารถทำ *implicit return* ซึ่งหมายความว่าคุณไม่จำเป็นต้องใช้คีย์เวิร์ด *return* เพื่อ return ค่าได้เลย 358 | 359 | ในการเขียน implicit return จะทำให้สามารถเขียนโค้ดได้เหลือเพียง 1 บรรทัด 360 | 361 | ```js 362 | const double = (x) => { 363 | return x * 2; // Explicit return ตรงนี้ 364 | } 365 | ``` 366 | 367 | เมื่อมันทำแค่ return ค่าตรงนั้นทันที เราสามารถเขียนแบบ implicit return ได้ข้างล่าง 368 | 369 | ```js 370 | const double = (x) => x * 2; 371 | ``` 372 | 373 | และจากข้างบน ที่เราต้องทำมีแค่ **เอาปีกกา** กับคีย์เวิร์ด **return** ออก แบบนี้เลยเรียกว่า *implicit* return เพราะว่าไม่จำเป็นต้องใช้คีย์เวิร์ด return แต่ฟังก์ชั่นก็ยัง return ```x * 2``` อยู่ 374 | 375 | > **หมายเหตุ:** ถ้าฟังก์ชั่นของคุณไม่ได้ต้องการการ return ค่า (พร้อมกับ *side effects*) จะใช้แบบไหนก็ไม่ต่างกัน 376 | 377 | นอกจากนี้ ถ้าคุณใช้ implicit return ค่าเป็น *object* คุณ**ต้องใช้วงเล็บครอบ** เพื่อป้องกันการสับสนกับ block scope ตามข้างล่าง 378 | 379 | ```js 380 | const getPerson = () => ({ name: "Nick", age: 24 }) 381 | console.log(getPerson()) // { name: "Nick", age: 24 } -- object implicitly return ค่าโดยใช้ arrow function 382 | ``` 383 | 384 | - มี argument แค่ตัวเดียว 385 | 386 | ถ้า function รับ parameter เพียงแค่ตัวเดียว สามารถเอาวงเล็บออกได้ ถ้าเราปรับปรุงจากโค้ด *double* ก่อนหน้าจะได้ดังนี้ 387 | 388 | ```js 389 | const double = (x) => x * 2; // arrow ฟังก์ชั่นนี้รับเพียง 1 parameter 390 | ``` 391 | 392 | วงเล็บสามารถเอาออกได้ดังนี้: 393 | 394 | ```js 395 | const double = x => x * 2; // arrow ฟังก์ชั่นนี้รับเพียง 1 parameter 396 | ``` 397 | 398 | - ไม่มี argument 399 | 400 | เมื่อไม่ต้องการ argument ใดๆ เลยใน arrow function คุณจำเป็นต้องใส่วงเล็บ ไม่อย่างนั้นมันจะไม่ใช่ syntax ที่ถูกต้อง 401 | 402 | ```js 403 | () => { // ถ้ามีวงเล็บ ทุกอย่างจะปกติ 404 | const x = 2; 405 | return x; 406 | } 407 | ``` 408 | 409 | ```js 410 | => { // ถ้าไม่มีวงเล็บ จะใช้งานไม่ได้! 411 | const x = 2; 412 | return x; 413 | } 414 | ``` 415 | 416 | ##### การอ้างอิงของ *this* 417 | 418 | เพื่อที่จะเข้าใจเรื่อง this กับ arrow ฟังก์ชั่นคุณต้องเข้าใจก่อนว่า [this](#this_def) ทำงานยังไงใน JavaScript 419 | 420 | ใน arrow function *this* มีค่าเท่าที่ค่าของ *this* ที่เป็นบริบทที่ครอบมันอยู่ นั่หมายถึงว่า arrow ฟังก์ชั่นไม่สร้าง *this* ขึ้นมาใหม่ แต่ว่ามันเอาค่าที่อยู่รอบนอกของมันมาใช้แทน 421 | 422 | ถ้าไม่ใช่ arrow ฟังก์ชั่นถ้าคุณต้องการใช้งานตัวแปร *this* ของฟังก์ชั่นเมื่ออยู่ภายใน function อีกที เพื่ออ้างอิงถึง *this* ภายนอก คุณจะต้องใช้ *that = this* หรือว่า *self = this* เป็นทริคเพื่ออ้างอิงถึง 423 | 424 | ยกตัวอย่างเช่น เมื่อใช้ฟังก์ชั่น setTimeout ภายใน myFunc: 425 | 426 | ```js 427 | function myFunc() { 428 | this.myVar = 0; 429 | var that = this; // ทริค that = this 430 | setTimeout( 431 | function() { // *this* ใหม่ถูกสร้างภายใน function scope นี้ 432 | that.myVar++; 433 | console.log(that.myVar) // 1 434 | 435 | console.log(this.myVar) // undefined -- เพราะว่าไปหาที่ฟังก์ชั่นที่อยู่นี้ ไม่ใช่ myFunc 436 | }, 437 | 0 438 | ); 439 | } 440 | ``` 441 | 442 | แต่เมื่อใช้ arrow ฟังก์ชั่น *this* จะอ้างอิงถึงที่อยู่รอบๆ ตัวมัน: 443 | 444 | ```js 445 | function myFunc() { 446 | this.myVar = 0; 447 | setTimeout( 448 | () => { // this มาจากรอบๆ นั่นหมายถึง myFunc ในกรณีนี้ 449 | this.myVar++; 450 | console.log(this.myVar) // 1 451 | }, 452 | 0 453 | ); 454 | } 455 | ``` 456 | 457 | #### หล่งข้อมูลที่มีประโยชน์ 458 | 459 | - [Arrow functions introduction - WesBos](http://wesbos.com/arrow-functions/) 460 | - [JavaScript arrow function - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 461 | - [Arrow function and lexical *this*](https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4) 462 | 463 | ### ค่า Default Parameter ของฟังก์ชั่น 464 | 465 | ตั้งแต่อัปเดตของ ES2015 JavaScript คุณสามารถตั้งค่า default ให้กับ parameter ของฟังก์ชั่นได้แล้วโดยใช้ syntax ตามข้างล่างนี้: 466 | 467 | ```js 468 | function myFunc(x = 10) { 469 | return x; 470 | } 471 | console.log(myFunc()) // 10 -- ไม่ได้มีค่าโยนเข้าไปใน parameter ดังนั้นค่า x จะถูก assign เป็นค่า default ของ myFunc นั่นก็คือ 10 472 | console.log(myFunc(5)) // 5 -- มีค่าโยนเข้าไปใน paramter ดังนั้นค่า x จะมีค่าเท่ากับ 5 ใน myFunc 473 | 474 | console.log(myFunc(undefined)) // 10 -- โยนค่าเป็น undefined ไป ดังนั้นจะนำค่า default มาใช้ซึ่งก็คือ 10 475 | console.log(myFunc(null)) // null -- โยนค่า null เข้าไปจึงได้ค่า x เป็น null, ดูรายละเอียดเพิ่มเติมข้างล่าง 476 | ``` 477 | 478 | ค่า default paramter จะถูกเรียกใช้ในสองกรณีนี้เท่านั้น: 479 | 480 | - ไม่โย parameter มาให้ 481 | - รับค่า parameter เป็น *undefined* 482 | 483 | หรือในอีกความหมายนึงคือ ถ้าคุณส่งค่าเป็น *null* จะไม่มีการใช้งาน default parameter 484 | 485 | > **หมายเหตุ:** ค่า Default สามารถใช้ร่วมกับ destructured parameters ได้ (อันนี้จะอยู่ในเนื้อห้าถัดไป) 486 | 487 | #### ข้อมูลเพิ่มเติมจากภายนอก 488 | 489 | - [Default parameter value - ES6 Features](http://es6-features.org/#DefaultParameterValues) 490 | - [Default parameters - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) 491 | 492 | ### Destructuring objects และ arrays 493 | 494 | *Destructuring* เป็นวิธีในการสร้างตัวแปรใหม่จากการแยกค่าบางค่าจากข้างในของ objects หรือ arrays ซึ่งทำให้สะดวกมากยิ่งขึ้น 495 | 496 | ยกตัวอย่างสำหรับการใช้งานเช่น *destructuring* สามารถใช้ในการแยกส่วน parameters ของฟังก์ชั่น หรือว่า *this.props* ที่มักเจอประจำในโปรเจ็คที่เป็น React 497 | 498 | #### อธิบายตัวอย่างโค้ด 499 | 500 | - Object 501 | 502 | ลองพิจารณา object ข้างล่างเป็นตัวอย่าง: 503 | 504 | ```js 505 | const person = { 506 | firstName: "Nick", 507 | lastName: "Anderson", 508 | age: 35, 509 | sex: "M" 510 | } 511 | ``` 512 | 513 | เมื่อไม่ใช้ destructuring: 514 | 515 | ```js 516 | const first = person.firstName; 517 | const age = person.age; 518 | const city = person.city || "Paris"; 519 | ``` 520 | 521 | เมื่อใช้ destructuring สามารถเขียนในบรรทัดเดียวได้แบบนี้: 522 | 523 | ```js 524 | const { firstName: first, age, city = "Paris" } = person; // แบบนี้ ! 525 | 526 | console.log(age) // 35 -- ตัวแปร age ถูกสร้างขึ้นมาใหม่และมีค่าเท่ากับ person.age 527 | console.log(first) // "Nick" -- ตัวแปร first ถูกสร้างขึ้นมาใหม่และมีค่าเท่ากับ person.firstName 528 | console.log(firstName) // ReferenceError -- person.firstName มีอยู่จริง แต่ตัวแปรที่ถูกสร้างขึ้นใหม่ถูกตั้งชื่อว่า first 529 | console.log(city) // "Paris" -- ตัวแปร city ถูกสร้างขึ้นใหม่ และ person.city มีค่าเป็น undefined เพราะฉะนั้น city จะมีค่าเท่ากับ default ที่ระบุไว้คือ "Paris" 530 | ``` 531 | 532 | **หมายเหตุ :** ใน ```const { age } = person;```, ปีกกาหลังคีย์เวิร์ด *const* มีได้หมายถึงเป็นการประกาศ object หรือว่า block แต่มันหมายถึงเป็น *destructuring* syntax 533 | 534 | - Parameters ของฟังก์ชั่น 535 | 536 | *Destructuring* จะใช้บ่อยในการ destructure objects parameters ในฟังก์ชั่น 537 | 538 | ถ้าไม่มี destructuring 539 | 540 | ```js 541 | function joinFirstLastName(person) { 542 | const firstName = person.firstName; 543 | const lastName = person.lastName; 544 | return firstName + '-' + lastName; 545 | } 546 | 547 | joinFirstLastName(person); // "Nick-Anderson" 548 | ``` 549 | 550 | *person* สามารถทำ destructuring เพื่อให้สั้นกระชับขึ้นได้ดังนี้: 551 | 552 | ```js 553 | function joinFirstLastName({ firstName, lastName }) { // สร้างตัวแปร firstName กับ lastName โดยการ destructuring ตัวแปร 554 | return firstName + '-' + lastName; 555 | } 556 | 557 | joinFirstLastName(person); // "Nick-Anderson" 558 | ``` 559 | 560 | Destructuring ยังถูกใช้บ่อยควบคู่กับ [arrow ฟังก์ชั่น](#arrow_func_concept): 561 | 562 | ```js 563 | const joinFirstLastName = ({ firstName, lastName }) => firstName + '-' + lastName; 564 | 565 | joinFirstLastName(person); // "Nick-Anderson" 566 | ``` 567 | 568 | - Array 569 | 570 | จาก array ข้างล่างต่อไปนี้: 571 | 572 | ```js 573 | const myArray = ["a", "b", "c"]; 574 | ``` 575 | 576 | เมื่อไม่มี destructuring 577 | 578 | ```js 579 | const x = myArray[0]; 580 | const y = myArray[1]; 581 | ``` 582 | 583 | เมื่อมี destructuring 584 | 585 | ```js 586 | const [x, y] = myArray; // แบบนี้ ! 587 | 588 | console.log(x) // "a" 589 | console.log(y) // "b" 590 | ``` 591 | 592 | #### แหล่งข้อมูลที่มีประโยชน์ 593 | 594 | - [ES6 Features - Destructuring Assignment](http://es6-features.org/#ArrayMatching) 595 | - [Destructuring Objects - WesBos](http://wesbos.com/destructuring-objects/) 596 | - [ExploringJS - Destructuring](http://exploringjs.com/es6/ch_destructuring.html) 597 | 598 | ### Array methods - map / filter / reduce 599 | 600 | *Map*, *filter* และ *reduce* เป็น method ของ array ที่มาจาก programming paradigm ที่ชื่อว่า [*functional programming*](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-functional-programming-7f218c68b3a0). 601 | 602 | สรุปแบบสั้นๆ: 603 | 604 | - **Array.prototype.map()** นำ array มาวนลูป ทำอะไรบางอย่างกับค่าของมันแต่ละค่าแล้ว return กลับกลายเป็น array ที่แปลงค่าแล้ว. 605 | - **Array.prototype.filter()** นำ array มาวนลูป เลิอกว่าค่าไหนเก็บไว้และค่าไหนทิ้ง และ return กลับไปเป็น array ที่ถูกเลือกแล้ว 606 | - **Array.prototype.reduce()** นำ array มาวนลูปและประกอบกันแต่ละค่าเหลือเพียงค่าเดียวเพื่อ return กลับไป 607 | 608 | แนะนำให้ใช้พวกนี้มากที่สุดเท่าที่จะทำได้ตามทฤษฐีของ functional programming เพราะว่ามันสั้นกระชับและสวยกว่า 609 | 610 | เมื่อมี 3 methods นี้ คุณสามารถละการใช้ *for* และ *forEach* ลูปในสถานการณ์ส่วนใหญ่ได้ เมื่อคุณจะใช้ *for* ลูป ลองเปลี่ยนมาใช้ *map*, *filter* และ *reduce* แทน แรกๆ อาจจะติดเพราะว่าต้องเปลี่ยนวิธีในการคิดแต่หลังจากเข้าใจและใช้ไปซักระยะจะสามารถใช้งานได้อย่างง่ายดาย 611 | 612 | #### ตัวอย่างโค้ด 613 | 614 | ```js 615 | const numbers = [0, 1, 2, 3, 4, 5, 6]; 616 | const doubledNumbers = numbers.map(n => n * 2); // [0, 2, 4, 6, 8, 10, 12] 617 | const evenNumbers = numbers.filter(n => n % 2 === 0); // [0, 2, 4, 6] 618 | const sum = numbers.reduce((prev, next) => prev + next, 0); // 21 619 | ``` 620 | 621 | คำนวณผลรวมเกรดทั้งหมดของนักเรียนโดยใช้ map, filter และ reduce: 622 | 623 | ```js 624 | const students = [ 625 | { name: "Nick", grade: 10 }, 626 | { name: "John", grade: 15 }, 627 | { name: "Julia", grade: 19 }, 628 | { name: "Nathalie", grade: 9 }, 629 | ]; 630 | 631 | const aboveTenSum = students 632 | .map(student => student.grade) // เรา map ข้อมูลนักเรียนให้กลายเป็น array ที่มีแต่เกรด 633 | .filter(grade => grade >= 10) // เรา filter เกรดให้เก็บไว้เฉพาะที่มีค่ามากกว่าเท่ากับ 10 634 | .reduce((prev, next) => prev + next, 0); // เรารวมผลรวมของทุกเกรดทีละคน 635 | 636 | console.log(aboveTenSum) // 44 -- 10 (Nick) + 15 (John) + 19 (Julia), Nathalie ได้เกรดต่ำกว่า 10 จะถูกข้ามไป 637 | ``` 638 | 639 | #### อธิบาย 640 | 641 | ลองดู array ของตัวเลขต่อไปนี้ที่จะใช้ในตัวอย่าง: 642 | 643 | 644 | ```js 645 | const numbers = [0, 1, 2, 3, 4, 5, 6]; 646 | ``` 647 | 648 | ##### Array.prototype.map() 649 | 650 | ```js 651 | const doubledNumbers = numbers.map(function(n) { 652 | return n * 2; 653 | }); 654 | console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12] 655 | ``` 656 | 657 | เกิดอะไรขึ้นในข้างบนบ้าง? เราใช้ .map กับ array ที่เก็บ *ตัวเลข* โดย map จะทำการวนรอบ (iterate) ทุกๆ ค่าที่อยู่ภายใน array และส่งค่าต่อเข้าไปในฟังก์ชั่นของเรา เป้าหมายของฟังก์ชั่นนี้คือต้องการสร้างค่าใหม่จากค่าเดิมที่ส่งเข้ามาและส่งกลับไป 658 | 659 | ถ้ากระจายฟังก์ชั่นให้อ่านง่ายขึ้นจะเป็นตามภาพดังนี้: 660 | 661 | ```js 662 | const doubleN = function(n) { return n * 2; }; 663 | const doubledNumbers = numbers.map(doubleN); 664 | console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12] 665 | ``` 666 | 667 | ```numbers.map(doubleN)``` produces ```[doubleN(0), doubleN(1), doubleN(2), doubleN(3), doubleN(4), doubleN(5), doubleN(6)]``` จะเท่ากับ ```[0, 2, 4, 6, 8, 10, 12]```. 668 | 669 | > **หมายเหตุ:** ถ้าคุณไม่ต้องการ return array ใหม่ แต่ต้องการทำลูปที่สร้าง side effects การใช้ for / forEach ลูปน่าจะเหมาะมากกว่าใช้ map 670 | 671 | ##### Array.prototype.filter() 672 | 673 | ```js 674 | const evenNumbers = numbers.filter(function(n) { 675 | return n % 2 === 0; // ถ้า n เป็นเลขคู่จะเป็น true, และ false ถ้า n ไม่ใช่ 676 | }); 677 | console.log(evenNumbers); // [0, 2, 4, 6] 678 | ``` 679 | 680 | เราใช้ .filter กับ array ที่เก็บ *ตัวเลข* ชุดเดิม, filter จะทำการวนรอบ (iterate) ทุกค่าใน array และส่งเข้าไปในฟังก์ชั่นของเรา เป้าหมายของฟังก์ชั่นนี้คือการ return เป็น boolean ว่าค่านั้นๆ จะต้องการเก็บเอาไว้หรือทิ้ง ผลสุดท้ายของ filter จะ return array ที่เหลือแต่ค่าที่เลือกเก็บเท่านั้น 681 | 682 | ##### Array.prototype.reduce() 683 | 684 | reduce method จะทำหน้าที่ *reduce* ทุกค่าใน array โดยมันจะวนรอบ (iterate) ค่าทั้งหมดให้กลายเหลือเป็นค่าเดียว โดยวิธีการประกอบค่าต่างๆ ยังไงอยู่ที่เรากำหนดในฟังก์ชั่น 685 | 686 | ```js 687 | const sum = numbers.reduce( 688 | function(acc, n) { 689 | return acc + n; 690 | }, 691 | 0 // ตัวสะสมค่าตัวแปรสำหรับการวนลูปรอบแรก 692 | ); 693 | 694 | console.log(sum) //21 695 | ``` 696 | 697 | เหมือนกับ method ของ .map และ .filter เพียงแต่ว่า .reduce จะนำค่าที่ได้จากรอบก่อนหน้ามาส่งต่อเป็น parameter แรกของฟังก์ชั่น 698 | 699 | มาดูความแตกต่าง: 700 | 701 | - .reduce รับ parameter สองตัว 702 | 703 | parameter แรกจะเป็นฟังก์ชั่นสำหรับการวนรอบ (itelation) 704 | 705 | parameter ที่สองจะเป็นค่าตัวแปรสะสม (ในที่นี้หมายถึง *acc*) สำหรับการวนรอบรอบแรก (คำข้อถัดไปจะมีอธิบายว่าทำไมต้องมี) 706 | 707 | - Parameters ของฟังก์ชั่น 708 | 709 | ฟังก์ชั่นที่ส่งเข้าไปใน parameter แรกของ .reduce จะได้รับ parameter ทั้งหมด 2 ตัว โดยตัวแรก (ในที่นี้คือ *acc*) คือตัวแปรสะสม ในขณะที่ตัวแปรที่สอง (*n*) คือค่าปัจจุบันของรอบลูปนั้นๆ 710 | 711 | ตัวแปรสะสมจะเท่ากับค่าที่ return ในฟังก์ชั่นของการวนรอบ (iteration) ก่อนหน้า ในการวนรอบแรก *acc* จะมีค่าเท่ากับค่าที่คุณส่งเป็น parameter ที่สองให้กับ .reduce นั่นเลยจำเป็นต้องโยนค่า 0 เข้าไปเป็น parameter ที่สองของ reduce นั่นเอง 712 | 713 | ###### ในการวนรอบรอบแรก 714 | 715 | ```acc = 0``` เพราะเราส่งค่า 0 เป็น parameter ที่สองให้กับ reduce 716 | 717 | ```n = 0``` ค่าแรกของ array ที่เก็บ *ตัวเลข* 718 | 719 | ฟังก์ชั่นจะ returns *acc* + *n* --> 0 + 0 --> 0 720 | 721 | ###### ในการวนรอบรอบที่สอง 722 | 723 | ```acc = 0``` เพราะว่ามันคือค่าที่มาจากการ return ของฟังก์ชั่นในการวนรอบก่อนหน้า 724 | 725 | ```n = 1``` ค่าที่สองของ array ที่เก็บ *ตัวเลข* 726 | 727 | ฟังก์ชั่นจะ returns *acc* + *n* --> 0 + 1 --> 1 728 | 729 | ###### ในการวนรอบรอบที่สาม 730 | 731 | ```acc = 1``` เพราะว่ามันคือค่าที่มาจากการ return ของฟังก์ชั่นในการวนรอบก่อนหน้า 732 | 733 | ```n = 2``` ค่าที่สามของ array ที่เก็บ *ตัวเลข* 734 | 735 | ฟังก์ชั่นจะ returns *acc* + *n* --> 1 + 2 --> 3 736 | 737 | ###### ในการวนรอบรอบที่สี่ 738 | 739 | ```acc = 3``` เพราะว่ามันคือค่าที่มาจากการ return ของฟังก์ชั่นในการวนรอบก่อนหน้า 740 | 741 | ```n = 3``` ค่าที่สี่ของ array ที่เก็บ *ตัวเลข* 742 | 743 | ฟังก์ชั่นจะ returns *acc* + *n* --> 3 + 3 --> 6 744 | 745 | ###### [...] ในการวนรอบสุดท้าย 746 | 747 | ```acc = 15``` เพราะว่ามันคือค่าที่มาจากการ return ของฟังก์ชั่นในการวนรอบก่อนหน้า 748 | 749 | ```n = 6``` ค่าสุดท้ายของ array ที่เก็บ *ตัวเลข* 750 | 751 | ฟังก์ชั่นจะ returns *acc* + *n* --> 15 + 6 --> 21 752 | 753 | ในการวนรอบสุดท้าย **.reduce** จะคืนค่า 21 กลับไป 754 | 755 | #### ข้อมูลเพิ่มเติมภายนอก 756 | 757 | - [Understanding map / filter / reduce in JS](https://hackernoon.com/understanding-map-filter-and-reduce-in-javascript-5df1c7eee464) 758 | 759 | ### Spread operator "..." 760 | 761 | spread operator ```...``` เป็นของใหม่ใน ES2015 และใช้สำหรับแผ่ขยายค่าของสิ่งที่สามารถนำมาวนรอบได้ (อย่างเช่น array) แตกออกเป็นชิ้นๆ และนำไปใส่ที่อื่น 762 | 763 | #### โค้ดตัวอย่าง 764 | 765 | ```js 766 | const arr1 = ["a", "b", "c"]; 767 | const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"] 768 | ``` 769 | 770 | ```js 771 | function myFunc(x, y, ...params) { 772 | console.log(x); 773 | console.log(y); 774 | console.log(params) 775 | } 776 | 777 | myFunc("a", "b", "c", "d", "e", "f") 778 | // "a" 779 | // "b" 780 | // ["c", "d", "e", "f"] 781 | ``` 782 | 783 | ```js 784 | const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; 785 | console.log(x); // 1 786 | console.log(y); // 2 787 | console.log(z); // { a: 3, b: 4 } 788 | 789 | const n = { x, y, ...z }; 790 | console.log(n); // { x: 1, y: 2, a: 3, b: 4 } 791 | ``` 792 | 793 | #### อธิบาย 794 | 795 | ##### ใช้กับสิ่งที่สามารถวนลูปได้ (iterables) (แบบ arrays) 796 | 797 | ถ้าเรามี array สองตัวดังนี้: 798 | 799 | ```js 800 | const arr1 = ["a", "b", "c"]; 801 | const arr2 = [arr1, "d", "e", "f"]; // [["a", "b", "c"], "d", "e", "f"] 802 | ``` 803 | 804 | ใน *arr2* ค่าแรกจะเป็น array เพราะว่า *arr1* ถูกนำเข้าไปใส่ใน *arr2* แต่ถ้าเราต้องให้ *arr2* เป็น array ของตัวอักษรเพียงอย่างเดียว สิ่งที่เราต้องทำคือเราสามารถ *spread* ค่าต่างๆ ของ *arr1* เข้าไปสู่ *arr2* 805 | 806 | เมื่อใช้ spread operator 807 | 808 | ```js 809 | const arr1 = ["a", "b", "c"]; 810 | const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"] 811 | ``` 812 | 813 | ##### Rest parameter ของฟังก์ชั่น 814 | 815 | ใน parameter ของฟังก์ชั่นเราสามารถใช้ rest operator สำหรับรวม parameters เป็น array ที่เราสามารถนำไปวนลูปได้ ถ้าเทียบก็เหมือนการห่อ **arguments** ที่รับมาเป็น object เอาไว้ 816 | 817 | ```js 818 | function myFunc() { 819 | for (var i = 0; i < arguments.length; i++) { 820 | console.log(arguments[i]); 821 | } 822 | } 823 | 824 | myFunc("Nick", "Anderson", 10, 12, 6); 825 | // "Nick" 826 | // "Anderson" 827 | // 10 828 | // 12 829 | // 6 830 | ``` 831 | 832 | แต่ถ้าเราต้องการให้ function นี้สร้าง student ใหม่พร้อมกับ grade ทั้งหมดของนักเรียนและค่าเฉลี่ยเกรด จะง่ายกว่าถ้าเราแยกให้สอง parameter แรกเป็นตัวแปรแยก และที่เหลือจากนั้นเป็น grade ที่เราสามารถนำไปวนรอบ (itelate) ได้มันน่าจะดีกว่ารึเปล่า? 833 | 834 | และ rest operator ทำให้เราสามารถทำแบบนั้นได้! 835 | 836 | ```js 837 | function createStudent(firstName, lastName, ...grades) { 838 | // firstName = "Nick" 839 | // lastName = "Anderson" 840 | // [10, 12, 6] -- "..." นำ paramters ที่รับเข้ามาที่เหลือสร้างเป็นตัวแปร array ชื่อ "grades" 841 | 842 | const avgGrade = grades.reduce((acc, curr) => acc + curr, 0) / grades.length; // คำนวณค่าเฉลี่ยของ grades 843 | 844 | return { 845 | firstName: firstName, 846 | lastName: lastName, 847 | grades: grades, 848 | avgGrade: avgGrade 849 | } 850 | } 851 | 852 | const student = createStudent("Nick", "Anderson", 10, 12, 6); 853 | console.log(student); 854 | // { 855 | // firstName: "Nick", 856 | // lastName: "Anderson", 857 | // grades: [10, 12, 6], 858 | // avgGrade: 9,33 859 | // } 860 | ``` 861 | 862 | > **หมายเหตุ:** ฟังก์ชั่น createStudent ยังไม่ดีเท่าไหร่เพราะว่าเรายังไม่ได้เช็ก grades.length ในกรณีที่ไม่มีค่าอยู่จริงหรือไม่ใช่ 0 แต่มันง่ายกว่าถ้าเขียนแบบนี้เพื่อทำความเข้าใจ เพราะฉะนั้นในโค้ดตัวอย่างเลยจะไม่มีการจัดการกับกรณีนี้ 863 | 864 | ##### Object properties spreading 865 | 866 | ก่อนจะเข้าเรื่องนี้ แนะนำให้อ่านคำอธิบายก่อนหน้าสำหรับ rest operator ที่ทำการห่อ parameter กลายเป็น object ที่สามารถทำการวนลูปได้ (itelable) 867 | 868 | ```js 869 | const myObj = { x: 1, y: 2, a: 3, b: 4 }; 870 | const { x, y, ...z } = myObj; // object destructuring ตรงนี้ 871 | console.log(x); // 1 872 | console.log(y); // 2 873 | console.log(z); // { a: 3, b: 4 } 874 | 875 | // z จะได้ค่าที่เหลือของ object ที่ถูก destructured แล้ว: myObj object เมื่อลบค่า x และ y properties ที่ถูก destructured ออกไปแล้ว 876 | 877 | const n = { x, y, ...z }; 878 | console.log(n); // { x: 1, y: 2, a: 3, b: 4 } 879 | 880 | // เมื่อนำ z object มา spread กลับเข้าไปให้กับตัวแปร n จะได้ค่าดังเดิม 881 | ``` 882 | 883 | #### ข้อมูลเพิ่มเติมจากภายนอก 884 | 885 | - [TC39 - Object rest/spread](https://github.com/tc39/proposal-object-rest-spread) 886 | - [Spread operator introduction - WesBos](https://github.com/wesbos/es6-articles/blob/master/28%20-%20Spread%20Operator%20Introduction.md) 887 | - [JavaScript & the spread operator](https://codeburst.io/javascript-the-spread-operator-a867a71668ca) 888 | - [6 Great uses of the spread operator](https://davidwalsh.name/spread-operator) 889 | 890 | ### Object property shorthand 891 | 892 | เมื่อ assign ค่าใช้ตัวแปรเป็น object property ถ้าชื่อตัวแปรมีค่าเท่ากับชื่อ property คุณสามารถทำแบบนี้ได้: 893 | 894 | ```js 895 | const x = 10; 896 | const myObj = { x }; 897 | console.log(myObj.x) // 10 898 | ``` 899 | 900 | #### อธิบาย 901 | 902 | ปกติแล้ว (pre-ES2015) เมื่อคุณประกาศ *object literal* ใหม่ และต้องการใช้ค่าของตัวแปรไปเป็นค่าให้กับ object properties นั้นปกติแล้วต้องเขียนแบบนี้: 903 | 904 | ```js 905 | const x = 10; 906 | const y = 20; 907 | 908 | const myObj = { 909 | x: x, // assign ค่าของตัวแปร x ให้เป็นค่าของ myObj.x 910 | y: y // assign ค่าของตัวแปร y ให้เป็นค่าของ myObj.y 911 | }; 912 | 913 | console.log(myObj.x) // 10 914 | console.log(myObj.y) // 20 915 | ``` 916 | 917 | จะเห็นว่ามันซ้ำซ้อนกันเพราะว่าชื่อ properties ของ myObj เป็นชื่อเดียวกับตัวแปรที่ต้องการ assign ให้กับ properties เหล่านั้น 918 | 919 | เมื่อเขียนด้วย ES2015 เมื่อชื่อตัวแปรเป็นชื่อเดียวกับชื่อ property สามารถเขียนให้สั้นลงเป็นแบบนี้ได้: 920 | 921 | ```js 922 | const x = 10; 923 | const y = 20; 924 | 925 | const myObj = { 926 | x, 927 | y 928 | }; 929 | 930 | console.log(myObj.x) // 10 931 | console.log(myObj.y) // 20 932 | ``` 933 | 934 | #### ข้อมูลเพิ่มเติมจากภายนอก 935 | 936 | - [Property shorthand - ES6 Features](http://es6-features.org/#PropertyShorthand) 937 | 938 | ### Promises 939 | 940 | Promise เป็น object ที่สามารถ return ค่าแบบ synchronous จากฟังก์ชั่นที่เป็น asynchronous ได้ ([ref](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261#3cd0)). 941 | 942 | Promise สามารถใช้เพื่อแก้ปัญหาของ [callback hell](http://callbackhell.com/) และมันมักนำไปใช้ในโค้ด Javascript โปรเจ็คใหม่ๆ ที่ทันสมัย 943 | 944 | #### ตัวอย่างโค้ด 945 | 946 | ```js 947 | const fetchingPosts = new Promise((res, rej) => { 948 | $.get("/posts") 949 | .done(posts => res(posts)) 950 | .fail(err => rej(err)); 951 | }); 952 | 953 | fetchingPosts 954 | .then(posts => console.log(posts)) 955 | .catch(err => console.log(err)); 956 | ``` 957 | 958 | #### อธิบาย 959 | 960 | เมื่อคุณสร้าง *Ajax request* จะได้รับ response ที่ไม่ synchronous เพราะว่ามันต้องใช้เวลาเพื่อจะได้ผลลัพธ์ที่กลับมา และมันอาจจะไม่ได้ค่าที่ต้องการกลับมาถ้า request ที่ส่งไปไม่สามารถใช้งานได้ด้วยเหตุบางประการ (404) 961 | 962 | เพื่อรับมือกับสถานการณ์แบบนั้น ES2015 ได้มี *promises* ซึ่ง promise มีทั้งหมด 3 states ด้วยกัน: 963 | 964 | - Pending 965 | - Fulfilled 966 | - Rejected 967 | 968 | หรือพูดอีกอย่างว่าเราต้องการใช้ promise ไปการจัดการกับพวก Ajax request เพื่อดึงข้อมูลจาก X 969 | 970 | ##### การสร้าง promise 971 | 972 | ก่อนที่เราจะใช้ promise เราจะใช้ jQuery สำหรับการทำ Ajax request ไปหา X 973 | 974 | ```js 975 | const xFetcherPromise = new Promise( // สร้าง promise โดยใช้คีย์เวิร์ด "new" และเก็บลงตัวแปร 976 | function(resolve, reject) { // constructor ของ promise รับ parameter เป็นฟังก์ชั่นที่มี parameter ชื่อ resolve และ reject ด้วยตัวมันเอง 977 | $.get("X") // ส่ง Ajax request 978 | .done(function(X) { // เมื่อ request เสร็จแล้ว ... 979 | resolve(X); // ... resolve promise ด้วยค่า X กลับไปเป็น parameter 980 | }) 981 | .fail(function(error) { // ถ้า request เกิดผิดพลาด... 982 | reject(error); // ... reject promise แล้วส่ง error กลับไปเป็น parameter 983 | }); 984 | } 985 | ) 986 | ``` 987 | 988 | จากที่เห็นในตัวอย่างข้างบน Promise object จะรับ *executor* ฟังก์ชั่นที่รับ parameter สองตัวคือ **resolve** และ **reject** ซึ่งสองตัวนี้ถ้ามีการเรียกใช้จะเป็นการย้าย state ของ promise จาก *pending* ไปสู่ *fulfilled* หรือ *rejected* 989 | 990 | promise จะอยู่ใน pending state หลังสร้าง instance และฟังก์ชั่น *executor* ของมันจะทำงานทันที และเมื่อมีการเรียกใช้ฟังก์ชั่น *resolve* หรือ *reject* ภายใน *executor* ฟังก์ชั่นตัว promise จะมีสิ่งที่เรียกว่า handlers คอยจัดการกับสิ่งที่เกิดขึ้น 991 | 992 | ##### การใช้งาน Promise handlers 993 | 994 | เพื่อที่จะรับผลลัพธ์ของ promise (หรือว่า error) เราจะสามารถควบคุมด้วย handlers โดยทำได้ดังนี้: 995 | 996 | ```js 997 | xFetcherPromise 998 | .then(function(X) { 999 | console.log(X); 1000 | }) 1001 | .catch(function(err) { 1002 | console.log(err) 1003 | }) 1004 | ``` 1005 | 1006 | ถ้า promise ทำงานสมบูรณ์และเรียกใช้ *resolve* โดยจะส่งค่าที่ได้ไปเป็น parameter ในฟังก์ชั่นใน ```.then``` 1007 | 1008 | ถ้าเกิดข้อผิดพลาด *reject* จะถูกเรียกแล้วส่งค่าไปเป็น parameter ในฟังก์ชั่นใน ```.catch``` 1009 | 1010 | > **หมายเหตุ :** ถ้า promise เข้าไปสู่ state fulfilled หรือ rejected โดยที่มีการใช้ handler แล้ว handler จะถูกเรียกใช้ ทำให้ไม่เกิด race condition ระหว่างรอ asynchronous เสร็จสมบูรณ์กับ handlers [(Ref: MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Description) 1011 | 1012 | #### ข้อมูลเพิ่มเติมจากภายนอก 1013 | 1014 | - [JavaScript Promises for dummies - Jecelyn Yeen](https://scotch.io/tutorials/javascript-promises-for-dummies) 1015 | - [JavaScript Promise API - David Walsh](https://davidwalsh.name/promises) 1016 | - [Using promises - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) 1017 | - [What is a promise - Eric Elliott](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261) 1018 | - [JavaScript Promises: an Introduction - Jake Archibald](https://developers.google.com/web/fundamentals/getting-started/primers/promises) 1019 | - [Promise documentation - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 1020 | 1021 | ### Template literals 1022 | 1023 | Template literals เป็น [*expression interpolation*](https://en.wikipedia.org/wiki/String_interpolation) สำหรับ strings ที่สามารถทำได้ตั้งแต่บรรทัดเดียวถึงหลายบรรทัด 1024 | 1025 | หรือในอีกกรณีนึงก็คือ มันเป็น syntax ของ string ที่สามารถใช้ JavaScript expesssions ข้างในได้อย่างสะดวกมากขึ้น (ยกตัวอย่างเช่นแทรกตัวแปรภายใน string) 1026 | 1027 | #### ตัวอย่างโค้ด 1028 | 1029 | ```js 1030 | const name = "Nick"; 1031 | `Hello ${name}, the following expression is equal to four : ${2+2}`; 1032 | 1033 | // Hello Nick, the following expression is equal to four: 4 1034 | ``` 1035 | 1036 | #### ข้อมูลเพิ่มเติมจากภายนอก 1037 | 1038 | - [String interpolation - ES6 Features](http://es6-features.org/#StringInterpolation) 1039 | - [ES6 Template Strings - Addy Osmani](https://developers.google.com/web/updates/2015/01/ES6-Template-Strings) 1040 | 1041 | ### Tagged template literals 1042 | 1043 | Template tags คือ *ฟังก์ชั่นที่สามารถกลายเป็นให้กับ [template literal](#template-literals) ได้*. ถ้าฟังก์ชั่นถูกเรียกใช้แบบนี้ parameter แรกจะเป็น array ของ *strings* ที่แสดงระหว่างตัวแปรของ template's interpolated และ parameter อื่นที่ตามมาคือค่า interpolated ดังนั้นถ้าใช้ spread operator `...` ก็จะสามารถรวบรวม parameter ที่เหลือทั้งหมดได้ [(Ref: MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals). 1044 | 1045 | > **หมายเหตุ :** Library ชื่อดังอย่าง [styled-components](https://www.styled-components.com/) ใช้ฟีเจอร์นี้อย่างเต็มรูปแบบ 1046 | 1047 | ข้างล่างคือตัวอย่างของการนำไปใช้งาน 1048 | ```js 1049 | function highlight(strings, ...values) { 1050 | const interpolation = strings.reduce((prev, current) => { 1051 | return prev + current + (values.length ? "" + values.shift() + "" : ""); 1052 | }, ""); 1053 | 1054 | return interpolation; 1055 | } 1056 | 1057 | const condiment = "jam"; 1058 | const meal = "toast"; 1059 | 1060 | highlight`I like ${condiment} on ${meal}.`; 1061 | // "I like jam on toast." 1062 | ``` 1063 | 1064 | ตัวอย่างที่น่าสนใจเพิ่มเติม: 1065 | ```js 1066 | function comma(strings, ...values) { 1067 | return strings.reduce((prev, next) => { 1068 | let value = values.shift() || []; 1069 | value = value.join(", "); 1070 | return prev + next + value; 1071 | }, ""); 1072 | } 1073 | 1074 | const snacks = ['apples', 'bananas', 'cherries']; 1075 | comma`I like ${snacks} to snack on.`; 1076 | // "I like apples, bananas, cherries to snack on." 1077 | ``` 1078 | 1079 | #### ข้อมูลเพิ่มเติมจากภายนอก 1080 | - [Wes Bos on Tagged Template Literals](http://wesbos.com/tagged-template-literals/) 1081 | - [Library of common template tags](https://github.com/declandewet/common-tags) 1082 | 1083 | ### Imports / Exports 1084 | 1085 | ES6 modules สามารถเข้าถึงตัวแปรต่างๆ ของ modules ที่ทำการแยก export ค่าต่างๆ ได้ด้วยการ imports 1086 | 1087 | แนะนำให้ลองอ่านข้อมูลเพิ่มเติมใน MDN resources เกี่ยวกับเรื่อง import/export (ดูข้อมูลเพิ่มเติมภายนอกข้างล่าง) จะดีเป็นอย่างมาก เนื้อหาข้างในค่อนข้างอธิบายได้ตรงไปตรงมาและสมบูรณ์แบบ 1088 | 1089 | #### อธิบายตัวอย่างโค้ด 1090 | 1091 | ##### Named exports 1092 | 1093 | Named exports ใช้สำหรับการ export หลายๆ ค่าของ module 1094 | 1095 | > **หมายเหตุ :** สามารถ export แบบ name-export [first-class citizens](https://en.wikipedia.org/wiki/First-class_citizen) ได้เฉพาะกับของที่มีชื่อเท่านั้น. 1096 | 1097 | ```js 1098 | // mathConstants.js 1099 | export const pi = 3.14; 1100 | export const exp = 2.7; 1101 | export const alpha = 0.35; 1102 | 1103 | // ------------- 1104 | 1105 | // myFile.js 1106 | import { pi, exp } from './mathConstants.js'; // Named import -- คล้ายๆ กับ syntax destructuring 1107 | console.log(pi) // 3.14 1108 | console.log(exp) // 2.7 1109 | 1110 | // ------------- 1111 | 1112 | // mySecondFile.js 1113 | import * as constants from './mathConstants.js'; // ทำค่าทั้งหมดที่มีการ exports มาเป็นตัวแปร 1114 | console.log(constants.pi) // 3.14 1115 | console.log(constants.exp) // 2.7 1116 | ``` 1117 | 1118 | named imports จะคล้ายกับ *destructuring* แต่ว่าจริงๆ แล้วมีความต่างและไม่เหมือนกัน มันไม่รองรับการใช้ default value หรือว่า *deep* destructuring 1119 | 1120 | นอกจากนี้คุณสามารถทำ alias ได้อยู่ เพียงแต่ว่า syntax จะแตกต่างกับ destructuring เป็นรูปแบบตามข้างล่าง 1121 | 1122 | ```js 1123 | import { foo as bar } from 'myFile.js'; // foo ถูก import เข้ามาและเอาไปเป็นค่าตัวแปรใหม่ที่ชื่อว่า bar 1124 | ``` 1125 | 1126 | ##### Default import / export 1127 | 1128 | สำหรับ default export เราจะสามารถ export default ได้แค่ตัวเดียวต่อ module และค่า default ที่ export สามารถเป็นได้ทั้ง function, class, object หรืออะไรก็ตาม ค่านี้เป็นเหมือนค่า "หลัก" ในการ export และมันจะง่ายต่อการ import เวลานำไปใช้ [Ref: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#Description) 1129 | 1130 | ```js 1131 | // coolNumber.js 1132 | const ultimateNumber = 42; 1133 | export default ultimateNumber; 1134 | 1135 | // ------------ 1136 | 1137 | // myFile.js 1138 | import number from './coolNumber.js'; 1139 | // Default export จะอิสระในการตั้งชื่อในหน้าที่ import เข้าไปใช้งาน อย่างข้างบนจะถูกเก็บลงตัวแปรชื่อ number ซึ่งเราสามารถตั้งเองได้ 1140 | console.log(number) // 42 1141 | ``` 1142 | 1143 | Export ฟังก์ชั่น: 1144 | 1145 | ```js 1146 | // sum.js 1147 | export default function sum(x, y) { 1148 | return x + y; 1149 | } 1150 | // ------------- 1151 | 1152 | // myFile.js 1153 | import sum from './sum.js'; 1154 | const result = sum(1, 2); 1155 | console.log(result) // 3 1156 | ``` 1157 | 1158 | #### ข้อมูลเพิ่มเติมจากภายนอก 1159 | 1160 | - [ES6 Modules in bulletpoints](https://ponyfoo.com/articles/es6#modules) 1161 | - [Export - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) 1162 | - [Import - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 1163 | - [Understanding ES6 Modules](https://www.sitepoint.com/understanding-es6-modules/) 1164 | - [Destructuring special case - import statements](https://ponyfoo.com/articles/es6-destructuring-in-depth#special-case-import-statements) 1165 | - [Misunderstanding ES6 Modules - Kent C. Dodds](https://medium.com/@kentcdodds/misunderstanding-es6-modules-upgrading-babel-tears-and-a-solution-ad2d5ab93ce0) 1166 | - [Modules in JavaScript](http://exploringjs.com/es6/ch_modules.html#sec_modules-in-javascript) 1167 | 1168 | ### JavaScript *this* 1169 | 1170 | *this* operator จะแตกต่างกับาภาษาอื่นๆ และโดยกรณีส่วนมากเป็นกำหนดเองว่า this จะทำงานในบริบทไหนในฟังก์ชั่น ([Ref: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)). 1171 | 1172 | เนื้อหานี้จะมีรายละเอียดที่ค่อนข้างอ่อนไหวและยากอยู่พอสมควร แนะนำให้ลองอ่านข้อมูลภายนอกเพิ่มเติมข้างล่าง ดังนั้นในที่นี้จะเขียนโดยอิงจากความเข้าใจส่วนตัวจาก[บทความที่เขียนโดย Yehuda Katz](http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/) อีกที 1173 | 1174 | ```js 1175 | function myFunc() { 1176 | ... 1177 | } 1178 | 1179 | // ข้างล่างจะแสดงให้เห็นค่าของ *this* ใน myFunc 1180 | 1181 | myFunc.call("myString", "hello") // "myString" -- ค่า parameter แรกใน .call จะถูกกำหนดเป็น *this* 1182 | 1183 | // ในโหมด non-strict-mode 1184 | myFunc("hello") // window -- myFunc() เป็น syntax เหมือนกับเรียก myFunc.call(window, "hello") 1185 | 1186 | // ในโหมด strict-mode 1187 | myFunc("hello") // undefined -- myFunc() เป็น syntax เหมือนกับเรียก myFunc.call(undefined, "hello") 1188 | ``` 1189 | 1190 | ```js 1191 | var person = { 1192 | myFunc: function() { ... } 1193 | } 1194 | 1195 | person.myFunc.call(person, "test") // person Object -- ค่า parameter แรกใน .call จะถูกกำหนดเป็น *this* 1196 | person.myFunc("test") // person Object -- person.myFunc() เป็น syntax เหมือนกับเรียก person.myFunc.call(person, "test") 1197 | 1198 | var myBoundFunc = person.myFunc.bind("hello") // สร้างฟังก์ชั่นใหม่ที่ผูก "hello" เป็นค่าของ *this* 1199 | person.myFunc("test") // person Object -- bind method ไม่มีผลใดๆ กับ method ดั้งเดิม 1200 | myBoundFunc("test") // "hello" -- myBoundFunc เป็น person.myFunc ที่มี "hello" เป็น *this* 1201 | ``` 1202 | 1203 | #### ข้อมูลเพิ่มเติมจากภายนอก 1204 | 1205 | - [Understanding JavaScript Function Invocation and "this" - Yehuda Katz](http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/) 1206 | - [JavaScript this - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) 1207 | 1208 | ### Class 1209 | 1210 | JavaScript เป็นภาษาที่เป็น [prototype-based](https://en.wikipedia.org/wiki/Prototype-based_programming) (เมื่อเปรียบเทียบกับ Java ที่เป็นภาษาที่เป็น [class-based](https://en.wikipedia.org/wiki/Class-based_programming)). ES6 ได้ทำให้ JavaScript เสมือนมี class ขึ้นมา แต่จริงๆแล้วเกิดจากการสืบทอดจาก prototype-based และ **ไม่ใช่** class-based ([ref](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)). 1211 | 1212 | คำว่า *class* จะไม่เหมือนกับ class ในภาษาอื่นๆ เป็นไปได้ควรทำความเข้าใจกับ class ใน JavaScript ก่อน assume ว่ามันจะเหมือนภาษาอื่น 1213 | 1214 | บทความนี้ไม่ได้สอนเรื่องภาษาตั้งแต่เริ่มต้น เราเชื่อคุณรู้อยู่แล้วว่า prototypes คืออะไรและทำหน้าที่อะไร แต่ถ้ายังไม่เข้าใจเรื่องพวกนี้เรามี link บางส่วนที่จะช่วยอธิบายเพื่อให้เรื่องเหล่านี้ได้ดีมากขึ้น 1215 | 1216 | - [Understanding Prototypes in JS - Yehuda Katz](http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/) 1217 | - [A plain English guide to JS prototypes - Sebastian Porto](http://sporto.github.io/blog/2013/02/22/a-plain-english-guide-to-javascript-prototypes/) 1218 | - [Inheritance and the prototype chain - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) 1219 | 1220 | #### ตัวอย่าง 1221 | 1222 | ก่อนจะมี ES6 ต้องเขียนแบบ prototype syntax: 1223 | 1224 | ```js 1225 | var Person = function(name, age) { 1226 | this.name = name; 1227 | this.age = age; 1228 | } 1229 | Person.prototype.stringSentence = function() { 1230 | return "Hello, my name is " + this.name + " and I'm " + this.age; 1231 | } 1232 | ``` 1233 | 1234 | เมื่อใช้ Syntax แบบ ES6: 1235 | 1236 | ```js 1237 | class Person { 1238 | constructor(name, age) { 1239 | this.name = name; 1240 | this.age = age; 1241 | } 1242 | 1243 | stringSentence() { 1244 | return "Hello, my name is " + this.name + " and I'm " + this.age; 1245 | } 1246 | } 1247 | 1248 | const myPerson = new Person("Manu", 23); 1249 | console.log(myPerson.age) // 23 1250 | console.log(myPerson.stringSentence()) // "Hello, my name is Manu and I'm 23 1251 | ``` 1252 | 1253 | #### ข้อมูลเพิ่มเติมจากภายนอก 1254 | 1255 | สำหรับทำความเข้าใจ prototype: 1256 | 1257 | - [Understanding Prototypes in JS - Yehuda Katz](http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/) 1258 | - [A plain English guide to JS prototypes - Sebastian Porto](http://sporto.github.io/blog/2013/02/22/a-plain-english-guide-to-javascript-prototypes/) 1259 | - [Inheritance and the prototype chain - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) 1260 | 1261 | สำหรับทำความเข้าใจ classes: 1262 | 1263 | - [ES6 Classes in Depth - Nicolas Bevacqua](https://ponyfoo.com/articles/es6-classes-in-depth) 1264 | - [ES6 Features - Classes](http://es6-features.org/#ClassDefinition) 1265 | - [JavaScript Classes - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) 1266 | 1267 | ### Async Await 1268 | 1269 | เพิ่มเติมจาก [Promises](#promises) จะมี syntax รูปแบบใหม่ให้สำหรับจัดการกับ asynchronous ชื่อว่า *async / await* 1270 | 1271 | จุดประสงค์ของ async/await function คือทำให้สามารถจัดการกับการใช้ promise synchronous ได้ง่ายขึ้น และทำพฤติกรรมคล้ายๆ กับ Promises ถ้าให้เปรียบเทียบคือถ้า Promises มีโครงสร้างคล้ายคลึงกับ callback สำหรับ async/await จะมีโครงสร้างคล้าย generators ผสมกับ promises นั่นเอง ซึ่ง async function จะ return เป็น Promise *เสมอ* ([Ref: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)) 1272 | 1273 | > **หมายเหตุ :** ต้องเข้าใจก่อนว่า promise คืออะไร และทำงานยังไง ก่อนที่จะพยายามทำความเข้าใจ async / await 1274 | 1275 | > **หมายเหตุ 2:** [*await* จะต้องใช้ใน *async* function](https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9#f3f0), นั่นหมายความว่าคุณไม่สามารถใช้ await ใน top level ของโค้ดได้ ถ้ามันไม่ได้อยู่ใน async function. 1276 | 1277 | #### ตัวอย่างโค้ด 1278 | 1279 | ```js 1280 | async function getGithubUser(username) { // คีย์เวิร์ด async ทำให้สามารถใช้ await ภายในฟังก์ชั่นได้ นั่นหมายถึงฟังก์ชั่นนี้ return เป็น promise 1281 | const response = await fetch(`https://api.github.com/users/${username}`); // Execution จะหยุดที่ตรงนี้จนกว่า promise จะ return หลังจาก fetch แล้ว resolved 1282 | return response.json(); 1283 | } 1284 | 1285 | getGithubUser('mbeaudru') 1286 | .then(user => console.log(user)) // log ข้อมูล response ของ user - ไม่สามารถใช้ syntax await ได้เพราะโค้ดไม่ได้เป็น async function 1287 | .catch(err => console.log(err)); // ถ้า error มีการ thrown ใน async function จะสามารถ catch ได้ที่นี่ 1288 | ``` 1289 | 1290 | #### อธิบายตัวอย่างโค้ด 1291 | 1292 | *Async / Await* สร้างมาจาก promise แต่ว่าออกแบบมาให้เขียนในรูปแบบที่ง่ายกว่า 1293 | 1294 | *async* operator จะเปลี่ยนฟังก์ชั่นเป็น asynchronous และจะ return เป็น *Promise* คุณสามารถใช้ *await* operator ใน *async* function เพื่อหยุดการ execution ที่บรรทัดนั้นๆ จนกว่า Promise จะ return ค่ากลับมาโดย resolves หรือ rejects 1295 | 1296 | ```js 1297 | async function myFunc() { 1298 | // เราสามารถใช้ await operator เพราะฟังก์ชั่นนี้กำหนดเป็น async 1299 | return "hello world"; 1300 | } 1301 | 1302 | myFunc().then(msg => console.log(msg)) // "hello world" -- ค่าที่คืนจากฟังก์ชั่น myFunc จะถูกเปลี่ยนไปเป็น promise เพราะว่ามีการใช้ async operator 1303 | ``` 1304 | 1305 | เมื่อถึงบรรทัด *return* ของ async function ตัว Promise ที่ได้รับค่าแล้วจะ return กลับไป ถ้าเกิด error และมีการ thrown ข้างใน async function ตัว promise state จะถูกเปลี่ยนเป็น *rejected* และถ้ากรณีไม่มีค่า return จาก async function ตัว promise จะยังคง return และ resolve แบบไม่มีค่ากลับไปเมื่อ async function ทำงานเสร็จสมบูรณ์ 1306 | 1307 | *await* operator จะใช้สำหรับรอ *Promise* จนกว่าจะได้รับค่า และสามารถใช้ได้เฉพาะในฟังก์ชั่นที่เป็น *async* เท่านั้น เมื่อโค้ด execution มาถึงจะหยุดจนกว่า promise จะได้รับการเติมเต็มข้อมูล (fulfilled) 1308 | 1309 | > **หมายเหตุ :** *fetch* เป็นฟังก์ชั่นที่ return เป็น Promise ที่สามารถทำ AJAX request ได้ 1310 | 1311 | มาดูกันว่าเราสามารถใช้ fetch ข้อมูล github user ด้วย promises ยังไงได้บ้าง: 1312 | 1313 | ```js 1314 | function getGithubUser(username) { 1315 | return fetch(`https://api.github.com/users/${username}`).then(response => response.json()); 1316 | } 1317 | 1318 | getGithubUser('mbeaudru') 1319 | .then(user => console.log(user)) 1320 | .catch(err => console.log(err)); 1321 | ``` 1322 | 1323 | และอันนี้คือถ้ากรณีใช้ *async / await* จะเป็นยังไง: 1324 | 1325 | ```js 1326 | async function getGithubUser(username) { // ใช้งาน promise + await ร่วมกัน 1327 | const response = await fetch(`https://api.github.com/users/${username}`); // Execution จะหยุดที่นี่จนกว่าข้อมูลใน Promise จะได้รับการเติมเต็ม (fulfilled) 1328 | return response.json(); 1329 | } 1330 | 1331 | getGithubUser('mbeaudru') 1332 | .then(user => console.log(user)) 1333 | .catch(err => console.log(err)); 1334 | ``` 1335 | 1336 | *async / await* syntax ทำให้เราสามารถเขียน promise แบบ chain ได้สะดวกสบายมากขึ้น 1337 | 1338 | ยกตัวอย่างเช่น ถ้าเราต้องการเอาค่า token ก่อนเพื่อที่จะนำข้านี้ไป fetch ข้อมูล blog post ที่อยู่ใน database เพื่อเอาข้อมูลคนเขียน post เราจะสามารถให้มันทำงานตามลำดับได้: 1339 | 1340 | > **หมายเหตุ :** *await* ต้องครอบด้วยวงเล็บเพื่อรอให้เรียก resolve และได้รับค่าก่อน ถึงจะนำไปใช้ต่อได้ถ้าเขียนในบรรทัดเดียวกันหรือเขียนแบบ chain 1341 | 1342 | ```js 1343 | async function fetchPostById(postId) { 1344 | const token = (await fetch('token_url')).json().token; 1345 | const post = (await fetch(`/posts/${postId}?token=${token}`)).json(); 1346 | const author = (await fetch(`/users/${post.authorId}`)).json(); 1347 | 1348 | post.author = author; 1349 | return post; 1350 | } 1351 | 1352 | fetchPostById('gzIrzeo64') 1353 | .then(post => console.log(post)) 1354 | .catch(err => console.log(err)); 1355 | ``` 1356 | 1357 | ##### การจัดการกับ Error 1358 | 1359 | นอกจากเพิ่ม *try / catch* blocks ครอบการใช้ *await* เพื่อดัก uncaught exceptions แล้ว - ไม่ว่าอะไรก็ตามที่ thrown ภายใน *async* function หรือว่ามันโดนยกเลิกระหว่าง *await* – จะ reject ตัว promise กลับไปใน *async* function ดังนั้นถ้าใช้ `throw` ใน async function จะเหมือนกับ return ตัว Promise ที่ rejects นั่นเอง [(Ref: PonyFoo)](https://ponyfoo.com/articles/understanding-javascript-async-await#error-handling). 1360 | 1361 | > **หมายเหตุ :** Promises เป็นแบบเดียวกัน! 1362 | 1363 | เมื่อใช้ Promises เราจะสามารถจัดการกับ error chain ได้แบบนี้: 1364 | 1365 | ```js 1366 | function getUser() { // Promise อันนี้จะโดน rejected! 1367 | return new Promise((res, rej) => rej("User not found !")); 1368 | } 1369 | 1370 | function getAvatarByUsername(userId) { 1371 | return getUser(userId).then(user => user.avatar); 1372 | } 1373 | 1374 | function getUserAvatar(username) { 1375 | return getAvatarByUsername(username).then(avatar => ({ username, avatar })); 1376 | } 1377 | 1378 | getUserAvatar('mbeaudru') 1379 | .then(res => console.log(res)) 1380 | .catch(err => console.log(err)); // "User not found !" 1381 | ``` 1382 | 1383 | กรณีถ้าใช้ *async / await*: 1384 | 1385 | ```js 1386 | async function getUser() { // Promise ที่จะ return กลับไปจะ rejected! 1387 | throw "User not found !"; 1388 | } 1389 | 1390 | async function getAvatarByUsername(userId) => { 1391 | const user = await getUser(userId); 1392 | return user.avatar; 1393 | } 1394 | 1395 | async function getUserAvatar(username) { 1396 | var avatar = await getAvatarByUsername(username); 1397 | return { username, avatar }; 1398 | } 1399 | 1400 | getUserAvatar('mbeaudru') 1401 | .then(res => console.log(res)) 1402 | .catch(err => console.log(err)); // "User not found !" 1403 | ``` 1404 | 1405 | #### ข้อมูลเพิ่มเติมจากภายนอก 1406 | 1407 | - [Async/Await - JavaScript.Info](https://javascript.info/async-await) 1408 | - [ES7 Async/Await](http://rossboucher.com/await/#/) 1409 | - [6 Reasons Why JavaScript’s Async/Await Blows Promises Away](https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9) 1410 | - [JavaScript awaits](https://dev.to/kayis/javascript-awaits) 1411 | - [Using Async Await in Express with Node 8](https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016) 1412 | - [Async Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) 1413 | - [Await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) 1414 | - [Using async / await in express with node 8](https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016) 1415 | 1416 | ### Truthy / Falsy 1417 | 1418 | ใน JavaScript ค่า truthy หรือ falsy เป็นค่าที่สามารถถูก casted ไปเป็น boolean เมื่ออยู่ในบริบทที่ต้องกระประเมินค่าเป็น boolean ยกตัวอย่างบริบทของ boolean เมื่อถูกนำไปใช้ใน ```if```: 1419 | 1420 | ค่าทั้งหมดสามารถถูก casted ไปเป็น ```true``` ยกเว้นแต่ว่ามันจะเป็นค่าเหล่านี้: 1421 | 1422 | - false 1423 | - 0 1424 | - "" (empty string) 1425 | - null 1426 | - undefined 1427 | - NaN 1428 | 1429 | นี่คือตัวอย่างของ *บริบทของ boolean*: 1430 | 1431 | - การประเมินค่าเมื่ออยู่ใน ```if``` condition 1432 | 1433 | ```js 1434 | if (myVar) {} 1435 | ``` 1436 | 1437 | ```myVar``` สามารถเป็นอะไรก็ได้ [first-class citizen](https://en.wikipedia.org/wiki/First-class_citizen) (ตัวแปร, ฟังก์ชั่น, boolean) แต่มันจะถูก casted ไปเป็น boolean เพราะว่ามันกำลังถูกประเมินอยู่ในบริบทของ boolean 1438 | 1439 | - การใช้ **NOT** ```!``` operator 1440 | 1441 | operator นี้จะ return false ถ้า operand สามารถถูก convert ไปเป็น true ได้ 1442 | 1443 | ```js 1444 | !0 // true -- 0 เป็น falsy ดังนั้นมันจะ returns true 1445 | !!0 // false -- 0 เป็น falsy ดังนั้น !0 จะ returns true และดังนั้น !(!0) จะ returns false 1446 | !!"" // false -- string ว่างเป็น falsy ดังนั้น NOT (NOT false) จะเท่ากับ false 1447 | ``` 1448 | 1449 | - เมื่อใช้ *Boolean* object constructor 1450 | 1451 | ```js 1452 | new Boolean(0) // false 1453 | new Boolean(1) // true 1454 | ``` 1455 | 1456 | - ในกรณีใช้ ternary evaluation 1457 | 1458 | ```js 1459 | myVar ? "truthy" : "falsy" 1460 | ``` 1461 | 1462 | myVar ถูกประเมินให้อยู่ในบริบทของ boolean 1463 | 1464 | ### Static Methods 1465 | 1466 | #### อธิบายสั้นๆ 1467 | 1468 | คีย์เวิร์ด `static` สามารถใช้ใน class เพื่อประกาศเป็น static method ได้ ตัว static method เป็นฟังก์ชั่นใน class ที่เป็นของ class object และจะไม่ใช่ของตัว instance ของ class นั้นๆ 1469 | 1470 | #### ตัวอย่างโค้ด 1471 | 1472 | ```js 1473 | class Repo{ 1474 | static getName() { 1475 | return "Repo name is modern-js-cheatsheet" 1476 | } 1477 | } 1478 | 1479 | //หมายเหตุ เราไม่ได้มีการสร้าง instance จาก Repo class 1480 | console.log(Repo.getName()) //Repo name คือ modern-js-cheatsheet 1481 | 1482 | let r = new Repo(); 1483 | console.log(r.getName()) //Uncaught TypeError: repo.getName is not a function 1484 | ``` 1485 | 1486 | #### อธิบายรายละเอียด 1487 | 1488 | Static method สามารถเรียก static method ตัวอื่นโดยใช้คีย์เวิร์ด `this` ได้ซึ่งจะไม่สามารถทำได้สำหรับ non-static methods และ non-static methods จะไม่สามารถเข้าถึง static method อื่นโดยใช้คีย์เวิร์ด `this` ได้ 1489 | 1490 | ##### เรียก static methods อื่นจาก static method 1491 | 1492 | เพื่อจะเรียก static method จาก static method อื่น ใช้คีย์เวิร์ด `this` แบบนี้ได้: 1493 | 1494 | ```js 1495 | class Repo{ 1496 | static getName() { 1497 | return "Repo name is modern-js-cheatsheet" 1498 | } 1499 | 1500 | static modifyName(){ 1501 | return this.getName() + '-added-this' 1502 | } 1503 | } 1504 | 1505 | console.log(Repo.modifyName()) //Repo name คือ modern-js-cheatsheet-added-this 1506 | ``` 1507 | 1508 | ##### เรียก static methods อื่นจาก non-static method 1509 | 1510 | Non-static methods สามารถเรียก static methods ได้ใน 2 ทาง; 1511 | 1. ###### ใช้ชื่อ class. 1512 | 1513 | เพื่อที่จะใช้ static method จาก non-static method เราใช้ชื่อ class และเรียก static method เหมือนเป็น property ตัวอย่างเช่น `ClassName.StaticMethodName` 1514 | 1515 | ```js 1516 | class Repo{ 1517 | static getName() { 1518 | return "Repo name is modern-js-cheatsheet" 1519 | } 1520 | 1521 | useName(){ 1522 | return Repo.getName() + ' and it contains some really important stuff' 1523 | } 1524 | } 1525 | 1526 | // เราต้องสร้าง instance จาก class ขึ้นมาก่อนเพื่อใช้เป็น non-static methods 1527 | let r = new Repo() 1528 | console.log(r.useName()) //Repo name คือ modern-js-cheatsheet and it contains some really important stuff 1529 | ``` 1530 | 1531 | 2. ###### ใช้ constructor 1532 | 1533 | Static methods สามารถเรียกเหมือนเป็น properties ได้ใน constructor object 1534 | 1535 | ```js 1536 | class Repo{ 1537 | static getName() { 1538 | return "Repo name is modern-js-cheatsheet" 1539 | } 1540 | 1541 | useName(){ 1542 | //เรียก static method เหมือนเป็น property ของ constructor 1543 | return this.constructor.getName() + ' and it contains some really important stuff' 1544 | } 1545 | } 1546 | 1547 | // เราต้องสร้าง instance จาก class ขึ้นมาก่อนเพื่อใช้เป็น non-static methods 1548 | let r = new Repo() 1549 | console.log(r.useName()) //Repo name คือ modern-js-cheatsheet and it contains some really important stuff 1550 | ``` 1551 | 1552 | #### ข้อมูลเพิ่มเติมจากภายนอก 1553 | - [static keyword- MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) 1554 | - [Static Methods- Javascript.info](https://javascript.info/class#static-methods) 1555 | - [Static Members in ES6- OdeToCode](http://odetocode.com/blogs/scott/archive/2015/02/02/static-members-in-es6.aspx) 1556 | 1557 | ## คำศัพธ์ 1558 | 1559 | ### Scope 1560 | 1561 | บริบทที่ค่าหรือ expression นั้น "มีตัวตน" หรือว่าสามารถอ้างอิงถึงได้ ถ้าตัวแปรหรือ expression ไม่ได้เป็น "อยู่ใน scope นั้นๆ" จะไม่สามารถใช้ได้ 1562 | 1563 | แหล่งอ้างอิง: [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Scope) 1564 | 1565 | ### Variable mutation 1566 | 1567 | หมายถึงตัวแปรที่ถูกการแปลงค่า (mutated) จากค่าตั้งต้นและถูกเปลี่ยนในภายหลัง 1568 | 1569 | ```js 1570 | var myArray = []; 1571 | myArray.push("firstEl") // myArray กำลังถูกแปลงค่า (mutated) 1572 | ``` 1573 | 1574 | ตัวแปรจะถูกเรียกว่า *immutable* ถ้ามันไม่สามารถแปลงค่า (mutated) ได้ 1575 | 1576 | [อ่านบทความ MDN Mutable](https://developer.mozilla.org/en-US/docs/Glossary/Mutable) สำหรับข้อมูลเพิ่มเติม 1577 | -------------------------------------------------------------------------------- /translations/ja-JP.md: -------------------------------------------------------------------------------- 1 | # モダン JavaScript チートシート 2 | 3 | ![モダン JavaScript チートシート](https://i.imgur.com/aexPxMb.png) 4 | 画像クレジット: [Ahmad Awais ⚡️](https://github.com/ahmadawais) 5 | 6 | ## イントロダクション 7 | 8 | ### 動機 9 | 10 | このドキュメントはモダンなプロジェクトでよく見られる JavaScript のチートシートと最新のサンプルコードです。 11 | 12 | このガイドは読者に JavaScript をゼロから教えるものではありません。 13 | 基礎知識は持っていて、モダンなコードベースに慣れる(例えば React を学ぶ)のに苦労している開発者を助けるためのものです。 14 | 説明の中で JavaScript の諸概念が使われています。 15 | 16 | また、議論の余地のあるポイントについてときどき個人的な tips を載せますが、その際はあくまでも個人的なおすすめであることを述べるように気をつけます。 17 | 18 | > **メモ:** ここで紹介されている概念のほとんどは JavaScript 言語のアップデート( ES2015 、しばしば ES6 と呼ばれるもの)によるものです。そのアップデートで追加された新しい機能の説明は[こちらのページ](http://es6-features.org)で見つけることができます。このページはとてもわかりやすく書かれています。 19 | 20 | ### 補完的なリソース 21 | 22 | 概念がうまく理解できない場合は、以下のリソースで答えを探すことをおすすめします: 23 | 24 | - [MDN (Mozilla Developer Network)](https://developer.mozilla.org/en-US/search?q=) 25 | - [You don't know JS (book)](https://github.com/getify/You-Dont-Know-JS) 26 | - [ES6 Features with examples](http://es6-features.org) 27 | - [WesBos blog (ES6)](http://wesbos.com/category/es6/) 28 | - [Javascript Basics for Beginners](https://www.udacity.com/course/javascript-basics--ud804) - Udacity の無料コース 29 | - [Reddit (JavaScript)](https://www.reddit.com/r/javascript/) 30 | - [Google](https://www.google.com/) (ブログとリソースを見つけるために) 31 | - [StackOverflow](https://stackoverflow.com/questions/tagged/javascript) 32 | 33 | ## 目次 34 | 35 | - [モダン JavaScript チートシート](#modern-javascript-cheatsheet) 36 | * [イントロダクション](#introduction) 37 | + [動機](#motivation) 38 | + [補完的なリソース](#complementary-resources) 39 | * [目次](#table-of-contents) 40 | * [各種概念](#notions) 41 | + [変数宣言: `var` / `const` / `let`](#variable-declaration-var-const-let) 42 | - [短い説明](#short-explanation) 43 | - [サンプルコード](#sample-code) 44 | - [詳細な説明](#detailed-explanation) 45 | - [外部のリソース](#external-resource) 46 | + [アロー関数](#-arrow-function) 47 | - [サンプルコード](#sample-code-1) 48 | - [詳細な説明](#detailed-explanation-1) 49 | * [簡潔さ](#concision) 50 | * [*`this`* 参照](#this-reference) 51 | - [有用なリソース](#useful-resources) 52 | + [関数の引数のデフォルト値](#function-default-parameter-value) 53 | - [外部のリソース](#external-resource-1) 54 | + [オブジェクトや配列の分割](#destructuring-objects-and-arrays) 55 | - [サンプルコード付きの説明](#explanation-with-sample-code) 56 | - [有用なリソース](#useful-resources-1) 57 | + [配列のメソッド - `map` / `filter` / `reduce`](#array-methods---map--filter--reduce) 58 | - [サンプルコード](#sample-code-2) 59 | - [説明](#explanation) 60 | * [`Array.prototype.map()`](#arrayprototypemap) 61 | * [`Array.prototype.filter()`](#arrayprototypefilter) 62 | * [`Array.prototype.reduce()`](#arrayprototypereduce) 63 | - [外部のリソース](#external-resource-2) 64 | + [スプレッド演算子「 `...` 」](#spread-operator-) 65 | - [サンプルコード](#sample-code-3) 66 | - [説明](#explanation-1) 67 | * [イテラブル(配列など)の中での使用](#in-iterables-like-arrays) 68 | * [関数のレスト引数](#function-rest-parameter) 69 | * [オブジェクトプロパティのスプレッディング](#object-properties-spreading) 70 | - [外部のリソース](#external-resources) 71 | + [オブジェクトプロパティの省略形](#object-property-shorthand) 72 | - [説明](#explanation-2) 73 | - [外部のリソース](#external-resources-1) 74 | + [プロミス](#promises) 75 | - [サンプルコード](#sample-code-4) 76 | - [説明](#explanation-3) 77 | * [プロミスの作成](#create-the-promise) 78 | * [プロミスハンドラーの使用](#promise-handlers-usage) 79 | - [外部のリソース](#external-resources-2) 80 | + [テンプレートリテラル](#template-literals) 81 | - [サンプルコード](#sample-code-5) 82 | - [外部のリソース](#external-resources-3) 83 | + [タグ付きテンプレートリテラル](#tagged-template-literals) 84 | - [外部のリソース](#external-resources-4) 85 | + [インポート / エクスポート](#imports--exports) 86 | - [サンプルコード付きの説明](#explanation-with-sample-code-1) 87 | * [名前付きエクスポート](#named-exports) 88 | * [デフォルトインポート / エクスポート](#default-import--export) 89 | - [外部のリソース](#external-resources-5) 90 | + [JavaScript の *`this`*](#-javascript-this) 91 | - [外部のリソース](#external-resources-6) 92 | + [クラス](#class) 93 | - [サンプルコード](#samples) 94 | - [外部のリソース](#external-resources-7) 95 | + [`extends` キーワードと `super` キーワード](#extends-and-super-keywords) 96 | - [サンプルコード](#sample-code-6) 97 | - [外部のリソース](#external-resources-8) 98 | + [Async Await](#async-await) 99 | - [サンプルコード](#sample-code-7) 100 | - [サンプルコード付きの説明](#explanation-with-sample-code-2) 101 | - [エラーハンドリング](#error-handling) 102 | - [外部のリソース](#external-resources-9) 103 | + [True になるもの / False になるもの](#truthy--falsy) 104 | - [外部のリソース](#external-resources-10) 105 | + [アナモルフィズム / カタモルフィズム](#anamorphisms-and-catamorphisms) 106 | - [アナモルフィズム](#anamorphisms) 107 | - [カタモルフィズム](#catamorphisms) 108 | - [外部のリソース](#external-resources-11) 109 | + [ジェネレーター](#generators) 110 | - [外部のリソース](#external-resources-12) 111 | + [スタティックメソッド](#static-methods) 112 | - [短い説明](#short-explanation-1) 113 | - [サンプルコード](#sample-code-8) 114 | - [詳細な説明](#detailed-explanation-2) 115 | * [スタティックメソッドから別のスタティックメソッドを呼ぶ](#calling-other-static-methods-from-a-static-method) 116 | * [スタティックでないメソッドからスタティックメソッドを呼ぶ](#calling-static-methods-from-non-static-methods) 117 | - [外部のリソース](#external-resources-13) 118 | * [用語集](#glossary) 119 | + [スコープ](#-scope) 120 | + [変数ミューテーション](#-variable-mutation) 121 | 122 | ## 各種概念 123 | 124 | ### 変数宣言: `var` / `const` / `let` 125 | 126 | JavaScript には、変数の宣言に利用できるキーワードが 3 つあり、それぞれ違いがあります。 127 | その 3 つのキーワードとは、 ```var``` と ```let``` と ```const``` です。 128 | 129 | #### 短い説明 130 | 131 | ```const``` キーワードで宣言された変数は再代入ができません。 132 | ```let``` と ```var``` は再代入ができます。 133 | 134 | 基本的に、変数はいつも ```const``` で宣言するようにして、変数を *変更* したり(訳注: 原文では「 mutate 」です)後から再代入したりする必要がある場合に ```let``` を使うことをおすすめします。 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |
スコープ再代入可能ミュータブルテンポラルデッドゾーン
`const`ブロック×
`let`ブロック
`var`関数×
166 | 167 | #### サンプルコード 168 | 169 | ```javascript 170 | const person = "Nick"; 171 | person = "John" // Will raise an error, person can't be reassigned 172 | ``` 173 | 174 | ```javascript 175 | let person = "Nick"; 176 | person = "John"; 177 | console.log(person) // "John", reassignment is allowed with let 178 | ``` 179 | 180 | #### 詳細な説明 181 | 182 | 変数の [*スコープ*](#scope_def) とは、ざっくり言うと、「コードの中でその変数が利用できる範囲」のことです。 183 | 184 | ##### `var` 185 | 186 | ```var``` で宣言された変数のスコープは *関数スコープ* になります。 187 | これは、ある変数が関数の中で作成されているとき、その関数の中のすべてのパーツがその変数にアクセスできることを意味します。 188 | 加えて、ある関数の中で作成された *関数スコープ* の変数はその関数の外側ではアクセスすることができません。 189 | 190 | *X スコープ* 変数ということばの意味は、その変数が X のプロパティであるような感じでイメージすることをおすすめします。 191 | 192 | ```javascript 193 | function myFunction() { 194 | var myVar = "Nick"; 195 | console.log(myVar); // "Nick" - myVar is accessible inside the function 196 | } 197 | console.log(myVar); // Throws a ReferenceError, myVar is not accessible outside the function. 198 | ``` 199 | 200 | 変数のスコープに引き続き着目し、もう少しわかりづらい例を見てみましょう: 201 | 202 | ```javascript 203 | function myFunction() { 204 | var myVar = "Nick"; 205 | if (true) { 206 | var myVar = "John"; 207 | console.log(myVar); // "John" 208 | // actually, myVar being function scoped, we just erased the previous myVar value "Nick" for "John" 209 | } 210 | console.log(myVar); // "John" - see how the instructions in the if block affected this value 211 | } 212 | console.log(myVar); // Throws a ReferenceError, myVar is not accessible outside the function. 213 | ``` 214 | 215 | 加えて、 *`var`* で宣言された変数は実行時にスコープの一番上に移動されます。 216 | この処理のことを [`var` ホイスティング](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting) と呼びます。 217 | 218 | 次のコードは 219 | 220 | ```js 221 | console.log(myVar) // undefined -- no error raised 222 | var myVar = 2; 223 | ``` 224 | 225 | 実行時に次のように解釈されます。 226 | 227 | ```js 228 | var myVar; 229 | console.log(myVar) // undefined -- no error raised 230 | myVar = 2; 231 | ``` 232 | 233 | ##### `let` 234 | 235 | ```var``` と ```let``` は大体同じですが、 ```let``` で宣言された変数には次の特徴があります。 236 | 237 | - *ブロックスコープ* である 238 | - 代入の前にアクセスすることが *できない* 239 | - 同じスコープの中で再宣言できない 240 | 241 | 上の例を使ってブロックスコープのインパクトを見てみましょう: 242 | 243 | ```javascript 244 | function myFunction() { 245 | let myVar = "Nick"; 246 | if (true) { 247 | let myVar = "John"; 248 | console.log(myVar); // "John" 249 | // actually, myVar being block scoped, we just created a new variable myVar. 250 | // this variable is not accessible outside this block and totally independent 251 | // from the first myVar created ! 252 | } 253 | console.log(myVar); // "Nick", see how the instructions in the if block DID NOT affect this value 254 | } 255 | console.log(myVar); // Throws a ReferenceError, myVar is not accessible outside the function. 256 | ``` 257 | 258 | ここで、 *`let`* (と *`const`* )で宣言された変数に関して、代入前にアクセスできないというのは次のコードに示すとおりの意味です: 259 | 260 | ```js 261 | console.log(myVar) // raises a ReferenceError ! 262 | let myVar = 2; 263 | ``` 264 | 265 | *`var`* とは対照的に、 *`let`* や *`const`* の変数では代入前に読み込みあるいは書き込みをしようとするとエラーが上がります。 266 | この現象はしばしば [*テンポラルデッドゾーン*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_Dead_Zone_and_errors_with_let) あるいは *TDZ* と呼ばれます。 267 | 268 | > **メモ:** 技術的には、 *`let`* と *`const`* の変数宣言もホイストされています。 269 | これらは代入前に使用されないように作られているため、直感的にはホスティングが起こっていないように感じられますが、実際には起こっています。 270 | 詳しく知りたい場合は[こちらの非常に詳しい説明](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified)をご覧ください。 271 | 272 | 加えて、 *`let`* 変数は再宣言することができません: 273 | 274 | ```js 275 | let myVar = 2; 276 | let myVar = 3; // Raises a SyntaxError 277 | ``` 278 | 279 | ##### `const` 280 | 281 | ```const``` で宣言された変数は *`let`* 変数のように振る舞いますが、再代入ができません。 282 | 283 | まとめると、 *`const`* 変数には次の特徴があります: 284 | 285 | - *ブロックスコープ* である 286 | - 代入前にアクセスすることができない 287 | - 同じスコープの中で再宣言できない 288 | - 再代入できない 289 | 290 | ```js 291 | const myVar = "Nick"; 292 | myVar = "John" // raises an error, reassignment is not allowed 293 | ``` 294 | 295 | ```js 296 | const myVar = "Nick"; 297 | const myVar = "John" // raises an error, re-declaration is not allowed 298 | ``` 299 | 300 | しかし、微妙なポイントがあります: ```const``` 変数は [**イミュータブル**](#mutation_def) ではありません! 301 | 具体的に言うと、 ```const``` で宣言された *オブジェクト* と *配列* は変更することが **できます** 。 302 | 303 | オブジェクトの場合: 304 | 305 | ```js 306 | const person = { 307 | name: 'Nick' 308 | }; 309 | person.name = 'John' // this will work ! person variable is not completely reassigned, but mutated 310 | console.log(person.name) // "John" 311 | person = "Sandra" // raises an error, because reassignment is not allowed with const declared variables 312 | ``` 313 | 314 | 配列の場合: 315 | 316 | ```js 317 | const person = []; 318 | person.push('John'); // this will work ! person variable is not completely reassigned, but mutated 319 | console.log(person[0]) // "John" 320 | person = ["Nick"] // raises an error, because reassignment is not allowed with const declared variables 321 | ``` 322 | 323 | #### 外部のリソース 324 | 325 | - [How let and const are scoped in JavaScript - WesBos](http://wesbos.com/javascript-scoping/) 326 | - [Temporal Dead Zone (TDZ) Demystified](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified) 327 | 328 | ### アロー関数 329 | 330 | ES6 JavaScript のアップデートで *アロー関数* が導入されました。 331 | これは、変数を宣言し利用するもうひとつの方法です。 332 | アロー関数がもたらすメリットは次のとおりです: 333 | 334 | - より簡潔である 335 | - *`this`* が周辺のスコープからピックアップされる 336 | - 暗黙的な `return` 337 | 338 | #### サンプルコード 339 | 340 | - 簡潔さと暗黙的な `return` 341 | 342 | ```js 343 | function double(x) { return x * 2; } // Traditional way 344 | console.log(double(2)) // 4 345 | ``` 346 | 347 | ```js 348 | const double = x => x * 2; // Same function written as an arrow function with implicit return 349 | console.log(double(2)) // 4 350 | ``` 351 | 352 | - *`this`* 参照 353 | 354 | アロー関数の中では、 *`this`* はその周りにある実行コンテキストの *`this`* と同じものになります。 355 | アロー関数を使えば、基本的に、関数の中で関数を呼び出す前に `that = this` とするトリックを使う必要がありません。 356 | 357 | ```js 358 | function myFunc() { 359 | this.myVar = 0; 360 | setTimeout(() => { 361 | this.myVar++; 362 | console.log(this.myVar) // 1 363 | }, 0); 364 | } 365 | ``` 366 | 367 | #### 詳細な説明 368 | 369 | ##### 簡潔さ 370 | 371 | アロー関数は、従来の関数よりも多くの点において簡潔です。 372 | すべての起こりうるケースを見てみましょう: 373 | 374 | - 暗黙的な `return` vs. 明示的な `return` 375 | 376 | **明示的な `return`** は、 *`return`* キーワードがボディの中で使われている関数のことです。 377 | 378 | ```js 379 | function double(x) { 380 | return x * 2; // this function explicitly returns x * 2, *return* keyword is used 381 | } 382 | ``` 383 | 384 | 関数を書く従来の方法では、 `return` は常に明示的に書く形になります。 385 | しかし、アロー関数では、 *暗黙的な `return`* を使うことができます。 386 | これは、値を返すのに *`return`* キーワードを使う必要がないという意味です。 387 | 388 | ```js 389 | const double = (x) => { 390 | return x * 2; // Explicit return here 391 | } 392 | ``` 393 | 394 | この関数は何かを返すだけのものなので( *`return`* キーワードの前に他の処理が無いので)、暗黙的な `return` を使うことができます。 395 | 396 | ```js 397 | const double = (x) => x * 2; // Correct, returns x*2 398 | ``` 399 | 400 | こうするためには、 **かっこと `return` キーワードを削除** しさえすれば OK です。 401 | そのため、これは *暗黙的な* `return` と呼ばれています。 402 | *`return`* キーワードは書かれていませんが、この関数は実際には ```x * 2``` を返します。 403 | 404 | > **メモ:** もしある関数が値を返さなければ、それは暗黙的な `return` も明示的な `return` も行っていません。 405 | 406 | 加えて、 *オブジェクト* を暗黙的に返したい場合は、ブロックのかっことの衝突を防ぐために **その周りにかっこを付ける必要があります**: 407 | 408 | ```js 409 | const getPerson = () => ({ name: "Nick", age: 24 }) 410 | console.log(getPerson()) // { name: "Nick", age: 24 } -- object implicitly returned by arrow function 411 | ``` 412 | 413 | - 引数が 1 つだけの場合 414 | 415 | 引数を 1 つだけ受け取る関数の場合は、引数の周りのかっこを省略することができます 416 | 上の *`double`* のコードをもう一度取り上げましょう: 417 | 418 | ```js 419 | const double = (x) => x * 2; // this arrow function only takes one parameter 420 | ``` 421 | 422 | 引数のかっこは省略できます: 423 | 424 | ```js 425 | const double = x => x * 2; // this arrow function only takes one parameter 426 | ``` 427 | 428 | - 引数が無い場合 429 | 430 | アロー関数が引数を取らない場合、かっこを付ける必要があります。 431 | かっこをつけないと正しいシンタックスではなくなります。 432 | 433 | ```js 434 | () => { // parentheses are provided, everything is fine 435 | const x = 2; 436 | return x; 437 | } 438 | ``` 439 | 440 | ```js 441 | => { // No parentheses, this won't work! 442 | const x = 2; 443 | return x; 444 | } 445 | ``` 446 | 447 | ##### *`this`* 参照 448 | 449 | アロー関数といっしょに導入されたこの微妙なポイントを理解するためには、 JavaScript における [`this`](#this_def) の振る舞いを知っておく必要があります。 450 | 451 | アロー関数では、 *`this`* はひとつ外側にある実行コンテキストの *`this`* の値に等しくなります。 452 | これはつまり、アロー関数は新しい *`this`* を作成せず代わりに周囲の環境から *`this`* を取得します。 453 | 454 | アロー関数がなかった頃は、関数内の関数で *`this`* の値にアクセスしたい場合は、 *`that = this`* または *`self = this`* のトリックを使う必要がありました。 455 | 456 | 例えば、 `setTimeout` 関数を `myFunc` の中で利用する場合は次のようにする必要がありました: 457 | 458 | ```js 459 | function myFunc() { 460 | this.myVar = 0; 461 | var that = this; // that = this trick 462 | setTimeout( 463 | function() { // A new *this* is created in this function scope 464 | that.myVar++; 465 | console.log(that.myVar) // 1 466 | 467 | console.log(this.myVar) // undefined -- see function declaration above 468 | }, 469 | 0 470 | ); 471 | } 472 | ``` 473 | 474 | アロー関数があれば、 *`this`* はその外部の環境から取得されます: 475 | 476 | ```js 477 | function myFunc() { 478 | this.myVar = 0; 479 | setTimeout( 480 | () => { // this taken from surrounding, meaning myFunc here 481 | this.myVar++; 482 | console.log(this.myVar) // 1 483 | }, 484 | 0 485 | ); 486 | } 487 | ``` 488 | 489 | #### 有用なリソース 490 | 491 | - [Arrow functions introduction - WesBos](http://wesbos.com/arrow-functions/) 492 | - [JavaScript arrow function - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 493 | - [Arrow function and lexical *this*](https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4) 494 | 495 | ### 関数の引数のデフォルト値 496 | 497 | ES2015 の JavaScript アップデート以降、関数の引数のデフォルト値を次のシンタックスでセットできます: 498 | 499 | ```js 500 | function myFunc(x = 10) { 501 | return x; 502 | } 503 | console.log(myFunc()) // 10 -- no value is provided so x default value 10 is assigned to x in myFunc 504 | console.log(myFunc(5)) // 5 -- a value is provided so x is equal to 5 in myFunc 505 | 506 | console.log(myFunc(undefined)) // 10 -- undefined value is provided so default value is assigned to x 507 | console.log(myFunc(null)) // null -- a value (null) is provided, see below for more details 508 | ``` 509 | 510 | デフォルト引数は次の 2 つの状況でのみ適用されます: 511 | 512 | - 引数が渡されなかった 513 | - *`undefined`* 引数が渡された 514 | 515 | 言い換えると、 *`null`* を渡した場合はデフォルト引数は **適用されません** 。 516 | 517 | > **メモ:** デフォルト値の代入は、分割引数(訳注: 原文では「 destructured parameters 」です)でも使用することができます(例は、次の概念をご覧ください) 518 | 519 | #### 外部のリソース 520 | 521 | - [Default parameter value - ES6 Features](http://es6-features.org/#DefaultParameterValues) 522 | - [Default parameters - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) 523 | 524 | ### オブジェクトや配列の分割 525 | 526 | *分割* (訳注: 原文では「 destructuring 」です)はオブジェクトまたは配列に格納されたデータから値を抽出して新しい変数を作成する便利な方法です。 527 | 528 | いくつかユースケースをあげるなら、 *分割* は関数の引数を分割するために使用することもできますし、 React プロジェクトの *`this.props`* で使用することもできます。 529 | 530 | #### サンプルコード付きの説明 531 | 532 | - オブジェクト 533 | 534 | 以下すべてのサンプルに対して次のオブジェクトについて考えてみましょう: 535 | 536 | ```js 537 | const person = { 538 | firstName: "Nick", 539 | lastName: "Anderson", 540 | age: 35, 541 | sex: "M" 542 | } 543 | ``` 544 | 545 | 分割がない場合、次のようになります。 546 | 547 | ```js 548 | const first = person.firstName; 549 | const age = person.age; 550 | const city = person.city || "Paris"; 551 | ``` 552 | 553 | 分割がある場合は、ワンラインで書くことができます: 554 | 555 | ```js 556 | const { firstName: first, age, city = "Paris" } = person; // That's it ! 557 | 558 | console.log(age) // 35 -- A new variable age is created and is equal to person.age 559 | console.log(first) // "Nick" -- A new variable first is created and is equal to person.firstName 560 | console.log(firstName) // ReferenceError -- person.firstName exists BUT the new variable created is named first 561 | console.log(city) // "Paris" -- A new variable city is created and since person.city is undefined, city is equal to the default value provided "Paris". 562 | ``` 563 | 564 | **メモ:** ```const { age } = person;``` において、 *`const`* キーワードの後のかっこは、オブジェクトやブロックの宣言として使用されるのではなく、 *分割* のシンタックスとなります。 565 | 566 | - 関数の引数 567 | 568 | *分割* は関数のオブジェクト引数を分割するためにしばしば使用されます。 569 | 570 | 分割を使わない場合、次のようになります。 571 | 572 | ```js 573 | function joinFirstLastName(person) { 574 | const firstName = person.firstName; 575 | const lastName = person.lastName; 576 | return firstName + '-' + lastName; 577 | } 578 | 579 | joinFirstLastName(person); // "Nick-Anderson" 580 | ``` 581 | 582 | オブジェクト引数 *`person`* を分割すれば、より簡潔な関数を書くことができます: 583 | 584 | ```js 585 | function joinFirstLastName({ firstName, lastName }) { // we create firstName and lastName variables by destructuring person parameter 586 | return firstName + '-' + lastName; 587 | } 588 | 589 | joinFirstLastName(person); // "Nick-Anderson" 590 | ``` 591 | 592 | 分割は[アロー関数](#arrow_func_concept)といっしょに使うとぐっといい感じになります: 593 | 594 | ```js 595 | const joinFirstLastName = ({ firstName, lastName }) => firstName + '-' + lastName; 596 | 597 | joinFirstLastName(person); // "Nick-Anderson" 598 | ``` 599 | 600 | - 配列 601 | 602 | 次の配列について考えてみましょう: 603 | 604 | ```js 605 | const myArray = ["a", "b", "c"]; 606 | ``` 607 | 608 | 分割を使わない場合、次のようになります。 609 | 610 | ```js 611 | const x = myArray[0]; 612 | const y = myArray[1]; 613 | ``` 614 | 615 | 分割を使うと、次のように書けます。 616 | 617 | ```js 618 | const [x, y] = myArray; // That's it ! 619 | 620 | console.log(x) // "a" 621 | console.log(y) // "b" 622 | ``` 623 | 624 | #### 有用なリソース 625 | 626 | - [ES6 Features - Destructuring Assignment](http://es6-features.org/#ArrayMatching) 627 | - [Destructuring Objects - WesBos](http://wesbos.com/destructuring-objects/) 628 | - [ExploringJS - Destructuring](http://exploringjs.com/es6/ch_destructuring.html) 629 | 630 | ### 配列のメソッド - `map` / `filter` / `reduce` 631 | 632 | *`map`* 、 *`filter`* 、 *`reduce`* は [*関数プログラミング*](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-functional-programming-7f218c68b3a0) という名前のプログラミングパラダイムに由来する配列のメソッドです。 633 | 634 | まとめ: 635 | 636 | - **Array.prototype.map()** 配列を受け取り、各要素に対して何かをして、変更された要素を持つ配列を返す。 637 | - **Array.prototype.filter()** 配列を受け取り、要素ごとに保持するかどうかを決めて、保持する要素のみからなる配列を返す。 638 | - **Array.prototype.reduce()** 配列を受け取り、要素を 1 つの値に集約する(その値が返される)。 639 | 640 | これらは組み合わせ可能で、簡潔でエレガントなので、関数プログラミングは原則に則ってできるかぎりこれらを使用することをおすすめします。 641 | 642 | これら 3 つのメソッドを使えば、ほとんどの状況で *`for`* ループと *`forEach`* ループを使用しなくてよくなります。 643 | *`for`* ループが使いたい場合は、 *`map`* と *`filter`* と *`reduce`* を組み合わせて処理を実現することを試みてください。 644 | 新しい考え方を身につける必要があるので最初は苦労するかもしれませんが、一度馴れればものごとはよりかんたんになります。 645 | 646 | #### サンプルコード 647 | 648 | ```js 649 | const numbers = [0, 1, 2, 3, 4, 5, 6]; 650 | const doubledNumbers = numbers.map(n => n * 2); // [0, 2, 4, 6, 8, 10, 12] 651 | const evenNumbers = numbers.filter(n => n % 2 === 0); // [0, 2, 4, 6] 652 | const sum = numbers.reduce((prev, next) => prev + next, 0); // 21 653 | ``` 654 | 655 | `map` 、 `filter` 、 `reduce` を使って、グレードが 10 以上の学生のグレードの合計値を計算してみます: 656 | 657 | ```js 658 | const students = [ 659 | { name: "Nick", grade: 10 }, 660 | { name: "John", grade: 15 }, 661 | { name: "Julia", grade: 19 }, 662 | { name: "Nathalie", grade: 9 }, 663 | ]; 664 | 665 | const aboveTenSum = students 666 | .map(student => student.grade) // we map the students array to an array of their grades 667 | .filter(grade => grade >= 10) // we filter the grades array to keep those 10 or above 668 | .reduce((prev, next) => prev + next, 0); // we sum all the grades 10 or above one by one 669 | 670 | console.log(aboveTenSum) // 44 -- 10 (Nick) + 15 (John) + 19 (Julia), Nathalie below 10 is ignored 671 | ``` 672 | 673 | #### 説明 674 | 675 | 例として、数値からなる次の配列について考えてみましょう: 676 | 677 | ```js 678 | const numbers = [0, 1, 2, 3, 4, 5, 6]; 679 | ``` 680 | 681 | ##### `Array.prototype.map()` 682 | 683 | ```js 684 | const doubledNumbers = numbers.map(function(n) { 685 | return n * 2; 686 | }); 687 | console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12] 688 | ``` 689 | 690 | ここでは何が起こっているのでしょうか。 691 | `.map` を配列 *`numbers`* に対して使用しています。 692 | `map` は渡された関数を配列の各要素に適用します。 693 | この関数のゴールは、 `map` が利用できるように、渡された値から新しい値を生成して返すことです。 694 | 695 | わかりやすくするために、今回は特別にこの関数を抽出しましょう: 696 | 697 | ```js 698 | const doubleN = function(n) { return n * 2; }; 699 | const doubledNumbers = numbers.map(doubleN); 700 | console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12] 701 | ``` 702 | 703 | ```numbers.map(doubleN)``` は ```[doubleN(0), doubleN(1), doubleN(2), doubleN(3), doubleN(4), doubleN(5), doubleN(6)]``` を生成します。 704 | そしてこれは ```[0, 2, 4, 6, 8, 10, 12]``` になります。 705 | 706 | > **メモ:** 新しい配列を返すのではなく副作用のあるループを回したいだけの場合は、 `map` の代わりの `for` / `forEach` ループを使いたくなるかもしれません。 707 | 708 | ##### `Array.prototype.filter()` 709 | 710 | ```js 711 | const evenNumbers = numbers.filter(function(n) { 712 | return n % 2 === 0; // true if "n" is par, false if "n" isn't 713 | }); 714 | console.log(evenNumbers); // [0, 2, 4, 6] 715 | ``` 716 | 717 | 配列 *`numbers`* に対して `.filter()` を使用しています。 718 | `filter` は指定された関数に配列の各要素を渡します。 719 | この関数のゴールは、現在の値を保持すべきかどうかを決める真偽値を返すことです。 720 | `filter` は保持すべき値のみを格納した配列を返します。 721 | 722 | ##### `Array.prototype.reduce()` 723 | 724 | `reduce` メソッドのゴールは、配列のすべての要素を走査してひとつの値に *縮減* することです。 725 | どのように集約するかのロジックはあなた次第です。 726 | 727 | ```js 728 | const sum = numbers.reduce( 729 | function(acc, n) { 730 | return acc + n; 731 | }, 732 | 0 // accumulator variable value at first iteration step 733 | ); 734 | 735 | console.log(sum) //21 736 | ``` 737 | 738 | `.map` メソッドと `.filter` メソッドのように、 `.reduce` は配列に対して適用されるもので、第 1 引数に関数を受け取ります。 739 | 740 | ただしこの場合は違いがあります: 741 | 742 | - `.reduce` は 2 つの引数を受け取ります 743 | 744 | 第 1 引数は、ループの各ステップで呼び出される関数です。 745 | 746 | 第 2 引数は、ループの最初のステップにおける蓄積用の変数(訳注: 原文では「 accumulator variable 」です)の値です(これを理解するには次のポイントを読んでください)。 747 | 748 | - 関数引数 749 | 750 | `.reduce` の第 1 引数として渡す関数は、 2 つの引数を受け取ります。 751 | 1 つめの引数(ここでは *`acc`* )は、蓄積用の変数で、 2 つめの引数( *`n`* )は現在の要素です。 752 | 753 | 蓄積用の変数は、 **ひとつ前の** ループのステップにおける関数の戻り値と等しいものです。 754 | ループの最初のステップでは *`acc`* は `.reduce` の第 2 引数に渡された値に等しくなります。 755 | 756 | ###### ループの最初のステップ 757 | 758 | `reduce` の第 2 引数に `0` を渡しているので、 ```acc = 0``` となります。 759 | 760 | *`numbers`* 配列の最初の要素を取るので、 ```n = 0``` となります。 761 | 762 | 関数は *`acc + n`* を返すので、これは `0 + 0` で `0` になります。 763 | 764 | ###### ループの第 2 ステップ 765 | 766 | ひとつ前のループのステップで関数が返した値が使われるので、 ```acc = 0``` となります。 767 | 768 | *`numbers`* 配列の 2 番目の要素を取るので、 ```n = 1``` となります。 769 | 770 | 関数は *`acc + n`* を返すので、これは `0 + 1` で `1` になります。 771 | 772 | ###### ループの第 3 ステップ 773 | 774 | ひとつ前のループのステップで関数が返した値が使われるので、 ```acc = 1``` となります。 775 | 776 | *`numbers`* 配列の 2 番目の要素を取るので、 ```n = 2``` となります。 777 | 778 | 関数は *`acc + n`* を返すので、これは `1 + 2` で `3` になります。 779 | 780 | ###### ループの第 4 ステップ 781 | 782 | ひとつ前のループのステップで関数が返した値が使われるので、 ```acc = 3``` となります。 783 | 784 | *`numbers`* 配列の 2 番目の要素を取るので、 ```n = 3``` となります。 785 | 786 | 関数は *`acc + n`* を返すので、これは `3 + 3` で `6` になります。 787 | 788 | ###### ループの最後のステップ 789 | 790 | ひとつ前のループのステップで関数が返した値が使われるので、 ```acc = 15``` となります。 791 | 792 | *`numbers`* 配列の最後の要素を取るので、 ```n = 6``` となります。 793 | 794 | 関数は *`acc + n`* を返すので、これは `15 + 6` で `21` になります。 795 | 796 | これがループの最後のステップなので、 **`.reduce`** は `21` を返します。 797 | 798 | #### 外部のリソース 799 | 800 | - [Understanding map / filter / reduce in JS](https://hackernoon.com/understanding-map-filter-and-reduce-in-javascript-5df1c7eee464) 801 | 802 | ### スプレッド演算子「 `...` 」 803 | 804 | スプレッド演算子(訳注: 原文では「 spread operator 」です)は ES2015 で導入されました。 805 | イテラブルなオブジェクト(訳注: 原文では「 iterable 」です)(配列など)の要素を、複数の要素が来るべきところに展開するために使用されます。 806 | 807 | #### サンプルコード 808 | 809 | ```js 810 | const arr1 = ["a", "b", "c"]; 811 | const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"] 812 | ``` 813 | 814 | ```js 815 | function myFunc(x, y, ...params) { 816 | console.log(x); 817 | console.log(y); 818 | console.log(params) 819 | } 820 | 821 | myFunc("a", "b", "c", "d", "e", "f") 822 | // "a" 823 | // "b" 824 | // ["c", "d", "e", "f"] 825 | ``` 826 | 827 | ```js 828 | const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; 829 | console.log(x); // 1 830 | console.log(y); // 2 831 | console.log(z); // { a: 3, b: 4 } 832 | 833 | const n = { x, y, ...z }; 834 | console.log(n); // { x: 1, y: 2, a: 3, b: 4 } 835 | ``` 836 | 837 | #### 説明 838 | 839 | ##### イテラブル(配列など)の中での使用 840 | 841 | 次の 2 つの配列があるものとします: 842 | 843 | ```js 844 | const arr1 = ["a", "b", "c"]; 845 | const arr2 = [arr1, "d", "e", "f"]; // [["a", "b", "c"], "d", "e", "f"] 846 | ``` 847 | 848 | *`arr1`* がそのまま *`arr2`* にインジェクトされたので、 *`arr2`* の第 1 要素は配列です。 849 | しかし、実際は *`arr2`* をすべて文字からなる配列にしたいものとしましょう。 850 | そうしたい場合は、 *`arr1`* の要素を *スプレッド* して *`arr2`* に入れることができます。 851 | 852 | スプレッド演算子を使うと、次のように書けます。 853 | 854 | ```js 855 | const arr1 = ["a", "b", "c"]; 856 | const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"] 857 | ``` 858 | 859 | ##### 関数のレスト引数 860 | 861 | 関数の引数において、レスト演算子(訳注: 原文では「 rest operator 」です)を使って引数をまとめて、ループ可能なひとつの配列に入れ込むことができます。 862 | すでに、関数に渡されたすべての引数を格納した配列に等しい **`arguments`** オブジェクトが関数にはバインドされています。 863 | 864 | ```js 865 | function myFunc() { 866 | for (var i = 0; i < arguments.length; i++) { 867 | console.log(arguments[i]); 868 | } 869 | } 870 | 871 | myFunc("Nick", "Anderson", 10, 12, 6); 872 | // "Nick" 873 | // "Anderson" 874 | // 10 875 | // 12 876 | // 6 877 | ``` 878 | 879 | しかし、仮に、この関数に、グレードと平均グレードを持つ新しい学生のオブジェクトを作成させたい場合を考えましょう。 880 | 最初の 2 つの引数を個別の 2 つの変数に抽出しておいて、すべてのグレードを格納したループ可能な配列が取得できるなら、便利ですよね。 881 | 882 | まさにこれがレスト演算子でできることなのです! 883 | 884 | ```js 885 | function createStudent(firstName, lastName, ...grades) { 886 | // firstName = "Nick" 887 | // lastName = "Anderson" 888 | // [10, 12, 6] -- "..." takes all other parameters passed and creates a "grades" array variable that contains them 889 | 890 | const avgGrade = grades.reduce((acc, curr) => acc + curr, 0) / grades.length; // computes average grade from grades 891 | 892 | return { 893 | firstName: firstName, 894 | lastName: lastName, 895 | grades: grades, 896 | avgGrade: avgGrade 897 | } 898 | } 899 | 900 | const student = createStudent("Nick", "Anderson", 10, 12, 6); 901 | console.log(student); 902 | // { 903 | // firstName: "Nick", 904 | // lastName: "Anderson", 905 | // grades: [10, 12, 6], 906 | // avgGrade: 9,33 907 | // } 908 | ``` 909 | 910 | > **メモ:** `grades.length` が存在するかどうか、あるいは `0` と異なるかどうかをチェックしていないので、 `createStudent` 関数はよくありません。 911 | しかし、この形だと読みやすいので、そのケースは処理していません。 912 | 913 | ##### オブジェクトプロパティのスプレッディング 914 | 915 | ここを読む場合は、イテラブルオブジェクトに対するレスト演算子と関数の引数に関するひとつ前の説明を先に読むことをおすすめします。 916 | 917 | ```js 918 | const myObj = { x: 1, y: 2, a: 3, b: 4 }; 919 | const { x, y, ...z } = myObj; // object destructuring here 920 | console.log(x); // 1 921 | console.log(y); // 2 922 | console.log(z); // { a: 3, b: 4 } 923 | 924 | // z is the rest of the object destructured: myObj object minus x and y properties destructured 925 | 926 | const n = { x, y, ...z }; 927 | console.log(n); // { x: 1, y: 2, a: 3, b: 4 } 928 | 929 | // Here z object properties are spread into n 930 | ``` 931 | 932 | #### 外部のリソース 933 | 934 | - [TC39 - Object rest/spread](https://github.com/tc39/proposal-object-rest-spread) 935 | - [Spread operator introduction - WesBos](https://github.com/wesbos/es6-articles/blob/master/28%20-%20Spread%20Operator%20Introduction.md) 936 | - [JavaScript & the spread operator](https://codeburst.io/javascript-the-spread-operator-a867a71668ca) 937 | - [6 Great uses of the spread operator](https://davidwalsh.name/spread-operator) 938 | 939 | ### オブジェクトプロパティの省略形 940 | 941 | 変数をオブジェクトのプロパティに代入するとき、変数名がプロパティ名と同じ場合は、次の形で書くことができます: 942 | 943 | ```js 944 | const x = 10; 945 | const myObj = { x }; 946 | console.log(myObj.x) // 10 947 | ``` 948 | 949 | #### 説明 950 | 951 | 新しい *オブジェクトリテラル* を宣言して、プロパティの値として変数を使用したい場合は、( ES2015 以前は)たいてい次のようなコードを書いたことでしょう: 952 | 953 | ```js 954 | const x = 10; 955 | const y = 20; 956 | 957 | const myObj = { 958 | x: x, // assigning x variable value to myObj.x 959 | y: y // assigning y variable value to myObj.y 960 | }; 961 | 962 | console.log(myObj.x) // 10 963 | console.log(myObj.y) // 20 964 | ``` 965 | 966 | ご覧のとおり、 `myObj` のプロパティ名はそこに値を割り当てたい変数の名前と同じなので、非常にくどい感じになります。 967 | 968 | ES2015 では、変数名がプロパティ名と同じ場合は、次の省略形を使うことができます: 969 | 970 | ```js 971 | const x = 10; 972 | const y = 20; 973 | 974 | const myObj = { 975 | x, 976 | y 977 | }; 978 | 979 | console.log(myObj.x) // 10 980 | console.log(myObj.y) // 20 981 | ``` 982 | 983 | #### 外部のリソース 984 | 985 | - [Property shorthand - ES6 Features](http://es6-features.org/#PropertyShorthand) 986 | 987 | ### プロミス 988 | 989 | プロミスとは、非同期的な関数(訳注: 原文では「 asynchronous function 」です)から同期的に(訳注: 原文では「 synchronously 」です)返すことができるオブジェクトです([参考](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261#3cd0)))。 990 | 991 | プロミスは[コールバック地獄](http://callbackhell.com/)(訳注: 原文では「 callback hell 」です)を避けるために使用することができます。 992 | モダンな JavaScript プロジェクトで出会うますます頻繁に使われるようになっています。 993 | 994 | #### サンプルコード 995 | 996 | ```js 997 | const fetchingPosts = new Promise((res, rej) => { 998 | $.get("/posts") 999 | .done(posts => res(posts)) 1000 | .fail(err => rej(err)); 1001 | }); 1002 | 1003 | fetchingPosts 1004 | .then(posts => console.log(posts)) 1005 | .catch(err => console.log(err)); 1006 | ``` 1007 | 1008 | #### 説明 1009 | 1010 | *Ajax リクエスト* を行う場合は、時間のかかるリソースが求められることもあるので、レスポンスは同期的ではありません。 1011 | リクエストされたリソースが何らかの理由で利用不可の場合( 404 の場合)、レスポンスが戻ってこないこともあります。 1012 | 1013 | このような状況を扱うために、 ES2015 は *プロミス* を提供しました。 1014 | プロミスは異なる 3 つの状態を持つことができます: 1015 | 1016 | - ペンディング(訳注: 原文では「 Pending 」です) 1017 | - フルフィルド(訳注: 原文では「 Fulfilled 」です) 1018 | - リジェクテッド(訳注: 原文では「 Rejected 」です) 1019 | 1020 | プロミスを使って、リソース X をフェッチするために Ajax リクエストを扱う場合を考えてみましょう。 1021 | 1022 | ##### プロミスの作成 1023 | 1024 | 最初にプロミスを作成します。 1025 | X への Ajax リクエストを行うために jQuery の `get` メソッドを使います。 1026 | 1027 | ```js 1028 | const xFetcherPromise = new Promise( // Create promise using "new" keyword and store it into a variable 1029 | function(resolve, reject) { // Promise constructor takes a function parameter which has resolve and reject parameters itself 1030 | $.get("X") // Launch the Ajax request 1031 | .done(function(X) { // Once the request is done... 1032 | resolve(X); // ... resolve the promise with the X value as parameter 1033 | }) 1034 | .fail(function(error) { // If the request has failed... 1035 | reject(error); // ... reject the promise with the error as parameter 1036 | }); 1037 | } 1038 | ) 1039 | ``` 1040 | 1041 | 上の例のとおり、プロミスオブジェクトは、 2 つの引数 **`resolve`** と **`reject`** を受け取る *エクセキューター* 関数(訳注: 原文では「 executor function 」です)を受け取ります。 1042 | これらの引数は、プロミスの *ペンディング* ステートを *フルフィルド* ステートと *リジェクテッド* ステートのそれぞれに移行させるときに呼び出されます。 1043 | 1044 | プロミスはインスタンスが作成された後はペンディングステートになっており、 *エクセキューター* 関数が即座に実行されます。 1045 | *エクセキューター* 関数の中で *`resolve`* か *`reject`* の一方が呼び出されたら、プロミスは関連づけられたハンドラーを呼び出します。 1046 | 1047 | ##### プロミスハンドラーの使用 1048 | 1049 | プロミスの結果(もしくはエラー)を取得するには、次のようにしてハンドラーをアタッチする必要があります: 1050 | 1051 | ```js 1052 | xFetcherPromise 1053 | .then(function(X) { 1054 | console.log(X); 1055 | }) 1056 | .catch(function(err) { 1057 | console.log(err) 1058 | }) 1059 | ``` 1060 | 1061 | プロミスが成功すれば、 *`resolve`* が実行され ```.then``` の引数として渡された関数が実行されます。 1062 | 1063 | プロミスが失敗すれば、 *`reject`* が実行され、 ```.catch``` の引数として渡された関数が実行されます。 1064 | 1065 | > **メモ:** もし、対応するハンドラーがアタッチされたときにプロミスがすでにフルフィルドやリジェクテッドの状態になっていれば、そのハンドラーは呼び出されます。非同期の操作の完了とアタッチされるハンドラの間で競合状態は発生しません。([参考: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Description)) 1066 | 1067 | #### 外部のリソース 1068 | 1069 | - [JavaScript Promises for dummies - Jecelyn Yeen](https://scotch.io/tutorials/javascript-promises-for-dummies) 1070 | - [JavaScript Promise API - David Walsh](https://davidwalsh.name/promises) 1071 | - [Using promises - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) 1072 | - [What is a promise - Eric Elliott](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261) 1073 | - [JavaScript Promises: an Introduction - Jake Archibald](https://developers.google.com/web/fundamentals/getting-started/primers/promises) 1074 | - [Promise documentation - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 1075 | 1076 | ### テンプレートリテラル 1077 | 1078 | テンプレートリテラルとは、単一行・複数行の文字列に対する [*式補完*](https://en.wikipedia.org/wiki/String_interpolation) (訳注: 原文は「 expression interpolation 」です)です。 1079 | 1080 | 言い換えると、テンプレートリテラルは、 JavaScript の式(例えば変数)をその中で使える新しい文字列シンタックスです。 1081 | 1082 | #### サンプルコード 1083 | 1084 | ```js 1085 | const name = "Nick"; 1086 | `Hello ${name}, the following expression is equal to four : ${2+2}`; 1087 | 1088 | // Hello Nick, the following expression is equal to four: 4 1089 | ``` 1090 | 1091 | #### 外部のリソース 1092 | 1093 | - [String interpolation - ES6 Features](http://es6-features.org/#StringInterpolation) 1094 | - [ES6 Template Strings - Addy Osmani](https://developers.google.com/web/updates/2015/01/ES6-Template-Strings) 1095 | 1096 | ### タグ付きテンプレートリテラル 1097 | 1098 | テンプレートタグは *[テンプレートリテラル](#template-literals)にプリフィックスされる関数* です。 1099 | 関数がこの形で呼び出されたとき、第 1 引数はテンプレートの補完を行う変数と変数の間に現れる *文字列* からなる配列で、その後の引数は補完に使われる値です。 1100 | これをすべて捉えるにはスプレッド演算子を使用します。 1101 | ([参考: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals)) 1102 | 1103 | > **メモ:** [styled-components](https://www.styled-components.com/) という有名なライブラリはこの機能を多用しています。 1104 | 1105 | 次のコードは、タグ付きテンプレートリテラルがどのように動作するかを示す遊びのサンプルです。 1106 | 1107 | ```js 1108 | function highlight(strings, ...values) { 1109 | const interpolation = strings.reduce((prev, current) => { 1110 | return prev + current + (values.length ? "" + values.shift() + "" : ""); 1111 | }, ""); 1112 | 1113 | return interpolation; 1114 | } 1115 | 1116 | const condiment = "jam"; 1117 | const meal = "toast"; 1118 | 1119 | highlight`I like ${condiment} on ${meal}.`; 1120 | // "I like jam on toast." 1121 | ``` 1122 | 1123 | 次はもっとおもしろいサンプルです: 1124 | 1125 | ```js 1126 | function comma(strings, ...values) { 1127 | return strings.reduce((prev, next) => { 1128 | let value = values.shift() || []; 1129 | value = value.join(", "); 1130 | return prev + next + value; 1131 | }, ""); 1132 | } 1133 | 1134 | const snacks = ['apples', 'bananas', 'cherries']; 1135 | comma`I like ${snacks} to snack on.`; 1136 | // "I like apples, bananas, cherries to snack on." 1137 | ``` 1138 | 1139 | #### 外部のリソース 1140 | 1141 | - [Wes Bos on Tagged Template Literals](http://wesbos.com/tagged-template-literals/) 1142 | - [Library of common template tags](https://github.com/declandewet/common-tags) 1143 | 1144 | ### インポート / エクスポート 1145 | 1146 | ES6 モジュールは、他のモジュールが明示的にエクスポートした変数や関数にモジュールがアクセスするために使用するための方法です。 1147 | 1148 | インポート / エクスポートに関する MDN のリソース(下の外部のリソースのところをご覧ください)を見ることを強くおすすめします。 1149 | そのリソースはわかりやすいと同時に、ポイントが網羅されています。 1150 | 1151 | #### サンプルコード付きの説明 1152 | 1153 | ##### 名前付きエクスポート 1154 | 1155 | 名前付きエクスポート(訳注: 原文では「 named exports 」です)は、モジュールから複数の値をエクスポートするための方法です。 1156 | 1157 | > **メモ:** 名前付きエクスポートは、名前の付いた[第一級オブジェクト](https://en.wikipedia.org/wiki/First-class_citizen)に対してのみ可能です。 1158 | 1159 | ```js 1160 | // mathConstants.js 1161 | export const pi = 3.14; 1162 | export const exp = 2.7; 1163 | export const alpha = 0.35; 1164 | 1165 | // ------------- 1166 | 1167 | // myFile.js 1168 | import { pi, exp } from './mathConstants.js'; // Named import -- destructuring-like syntax 1169 | console.log(pi) // 3.14 1170 | console.log(exp) // 2.7 1171 | 1172 | // ------------- 1173 | 1174 | // mySecondFile.js 1175 | import * as constants from './mathConstants.js'; // Inject all exported values into constants variable 1176 | console.log(constants.pi) // 3.14 1177 | console.log(constants.exp) // 2.7 1178 | ``` 1179 | 1180 | 名前付きインポートは *分割* に似ていますが、シンタックスが異なり、分割と同じものではありません。 1181 | 名前付きインポートは、デフォルト値や *深い* (訳注: 原文では「 deep 」です)分割をサポートしていません。 1182 | 1183 | 加えて、エイリアスを使うことができますが、そのシンタックスは分割のものとは異なります: 1184 | 1185 | ```js 1186 | import { foo as bar } from 'myFile.js'; // foo is imported and injected into a new bar variable 1187 | ``` 1188 | 1189 | ##### デフォルトインポート / エクスポート 1190 | 1191 | デフォルトエクスポートに関しては、モジュールごとにひとつだけデフォルトエクスポートがあります。 1192 | デフォルトエクスポートは、関数、クラス、オブジェクトその他どんなものにもすることができます。 1193 | デフォルトエクスポートはインポートするときの最もシンプルな形なので、その値は「メイン」のエクスポート値とみなされます。 1194 | ([参考: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#Description)) 1195 | 1196 | ```js 1197 | // coolNumber.js 1198 | const ultimateNumber = 42; 1199 | export default ultimateNumber; 1200 | 1201 | // ------------ 1202 | 1203 | // myFile.js 1204 | import number from './coolNumber.js'; 1205 | // Default export, independently from its name, is automatically injected into number variable; 1206 | console.log(number) // 42 1207 | ``` 1208 | 1209 | 関数のエクスポート: 1210 | 1211 | ```js 1212 | // sum.js 1213 | export default function sum(x, y) { 1214 | return x + y; 1215 | } 1216 | // ------------- 1217 | 1218 | // myFile.js 1219 | import sum from './sum.js'; 1220 | const result = sum(1, 2); 1221 | console.log(result) // 3 1222 | ``` 1223 | 1224 | #### 外部のリソース 1225 | 1226 | - [ES6 Modules in bulletpoints](https://ponyfoo.com/articles/es6#modules) 1227 | - [Export - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) 1228 | - [Import - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 1229 | - [Understanding ES6 Modules](https://www.sitepoint.com/understanding-es6-modules/) 1230 | - [Destructuring special case - import statements](https://ponyfoo.com/articles/es6-destructuring-in-depth#special-case-import-statements) 1231 | - [Misunderstanding ES6 Modules - Kent C. Dodds](https://medium.com/@kentcdodds/misunderstanding-es6-modules-upgrading-babel-tears-and-a-solution-ad2d5ab93ce0) 1232 | - [Modules in JavaScript](http://exploringjs.com/es6/ch_modules.html#sec_modules-in-javascript) 1233 | 1234 | ### JavaScript の *`this`* 1235 | 1236 | *`this`* 演算子の振る舞いは他の言語とは異なり、ほとんどの場合、関数の呼び出し方によって決まります。 1237 | ([参考: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)) 1238 | 1239 | この概念にはややこしいポイントがたくさんあり非常に難しいので、以下にあげる外部のリソースを深く読むことを強くおすすめします。 1240 | そのため、ここでは *`this`* の中身が何になるかを理解するための個人的なアイデアを紹介することにします。 1241 | この考え方は [Yehuda Katz が書いた記事](http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/) からのものです。 1242 | 1243 | ```js 1244 | function myFunc() { 1245 | ... 1246 | } 1247 | 1248 | // After each statement, you find the value of *this* in myFunc 1249 | 1250 | myFunc.call("myString", "hello") // "myString" -- first .call parameter value is injected into *this* 1251 | 1252 | // In non-strict-mode 1253 | myFunc("hello") // window -- myFunc() is syntax sugar for myFunc.call(window, "hello") 1254 | 1255 | // In strict-mode 1256 | myFunc("hello") // undefined -- myFunc() is syntax sugar for myFunc.call(undefined, "hello") 1257 | ``` 1258 | 1259 | ```js 1260 | var person = { 1261 | myFunc: function() { ... } 1262 | } 1263 | 1264 | person.myFunc.call(person, "test") // person Object -- first call parameter is injected into *this* 1265 | person.myFunc("test") // person Object -- person.myFunc() is syntax sugar for person.myFunc.call(person, "test") 1266 | 1267 | var myBoundFunc = person.myFunc.bind("hello") // Creates a new function in which we inject "hello" in *this* value 1268 | person.myFunc("test") // person Object -- The bind method has no effect on the original method 1269 | myBoundFunc("test") // "hello" -- myBoundFunc is person.myFunc with "hello" bound to *this* 1270 | ``` 1271 | 1272 | #### 外部のリソース 1273 | 1274 | - [Understanding JavaScript Function Invocation and "this" - Yehuda Katz](http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/) 1275 | - [JavaScript this - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) 1276 | 1277 | ### クラス 1278 | 1279 | JavaScript は [プロトタイプベース](https://en.wikipedia.org/wiki/Prototype-based_programming) の言語です(例えば、 Java は [クラスベース](https://en.wikipedia.org/wiki/Class-based_programming) の言語です)。 1280 | ES6 は、プロトタイプベースの継承に対するシンタックスシュガー(訳注: 原文では「 syntactic sugar 」です)としての JavaScript のクラスを導入しました。 1281 | これは新しいクラスベースの継承モデルでは **ありません** 。 1282 | 1283 | もしあなたが他の言語のクラスに馴れていれば、 *クラス* ということばは混乱を招く可能性があります。 1284 | もし他の言語のクラスに馴れているなら、 JavaScript のクラスも同じようなものだと考えたりはせず、まったく別物の概念だと考えましょう。 1285 | 1286 | このドキュメントは JavaScript についてゼロから教えようとするものではないため、読者は、プロトタイプが何であって、どのように振る舞うかを知っているものとします。 1287 | もしプロトタイプについてよく知らなければ、次のサンプルコードの下に記載された外部のリソースをご覧ください。 1288 | 1289 | #### サンプルコード 1290 | 1291 | ES6 以前のプロトタイプ構文: 1292 | 1293 | ```js 1294 | var Person = function(name, age) { 1295 | this.name = name; 1296 | this.age = age; 1297 | } 1298 | Person.prototype.stringSentence = function() { 1299 | return "Hello, my name is " + this.name + " and I'm " + this.age; 1300 | } 1301 | ``` 1302 | 1303 | ES6 のクラス構文: 1304 | 1305 | ```js 1306 | class Person { 1307 | constructor(name, age) { 1308 | this.name = name; 1309 | this.age = age; 1310 | } 1311 | 1312 | stringSentence() { 1313 | return "Hello, my name is " + this.name + " and I'm " + this.age; 1314 | } 1315 | } 1316 | 1317 | const myPerson = new Person("Manu", 23); 1318 | console.log(myPerson.age) // 23 1319 | console.log(myPerson.stringSentence()) // "Hello, my name is Manu and I'm 23 1320 | ``` 1321 | 1322 | #### 外部のリソース 1323 | 1324 | プロトタイプの理解のためのリソース: 1325 | 1326 | - [Understanding Prototypes in JS - Yehuda Katz](http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/) 1327 | - [A plain English guide to JS prototypes - Sebastian Porto](http://sporto.github.io/blog/2013/02/22/a-plain-english-guide-to-javascript-prototypes/) 1328 | - [Inheritance and the prototype chain - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) 1329 | 1330 | クラスの理解のためのリソース: 1331 | 1332 | - [ES6 Classes in Depth - Nicolas Bevacqua](https://ponyfoo.com/articles/es6-classes-in-depth) 1333 | - [ES6 Features - Classes](http://es6-features.org/#ClassDefinition) 1334 | - [JavaScript Classes - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) 1335 | 1336 | ### `extends` キーワードと `super` キーワード 1337 | 1338 | `extends` キーワードは、他のクラスの子どもとなるクラスを作成するために、クラス宣言あるいはクラス式に使われます([参考: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends))。 1339 | サブクラスはスーパークラスのすべてのプロパティを継承し、追加で新しいプロパティを追加したり継承したプロパティを変更したりすることができます。 1340 | 1341 | `super` キーワードはオブジェクトの親の関数を呼び出すために使用します。 1342 | これにはコンストラクターも含まれます。 1343 | 1344 | - コンストラクターの中では、 `super` キーワードは `this` キーワードが使われる前に使わなくてはなりません。 1345 | - `super()` を呼び出すと親クラスのコンストラクターが呼ばれます。クラスのコンストラクターに引数を渡したいときは、 `super(arguments)` という形で呼び出します。 1346 | - 親クラスが `X` というメソッドを持つ場合は(スタティックメソッドでもよい)、子クラスの中で `super.X()` とすればそれを呼び出すことができます。 1347 | 1348 | #### サンプルコード 1349 | 1350 | ```js 1351 | class Polygon { 1352 | constructor(height, width) { 1353 | this.name = 'Polygon'; 1354 | this.height = height; 1355 | this.width = width; 1356 | } 1357 | 1358 | getHelloPhrase() { 1359 | return `Hi, I am a ${this.name}`; 1360 | } 1361 | } 1362 | 1363 | class Square extends Polygon { 1364 | constructor(length) { 1365 | // Here, it calls the parent class' constructor with lengths 1366 | // provided for the Polygon's width and height 1367 | super(length, length); 1368 | // Note: In derived classes, super() must be called before you 1369 | // can use 'this'. Leaving this out will cause a reference error. 1370 | this.name = 'Square'; 1371 | this.length = length; 1372 | } 1373 | 1374 | getCustomHelloPhrase() { 1375 | const polygonPhrase = super.getHelloPhrase(); // accessing parent method with super.X() syntax 1376 | return `${polygonPhrase} with a length of ${this.length}`; 1377 | } 1378 | 1379 | get area() { 1380 | return this.height * this.width; 1381 | } 1382 | } 1383 | 1384 | const mySquare = new Square(10); 1385 | console.log(mySquare.area) // 100 1386 | console.log(mySquare.getHelloPhrase()) // 'Hi, I am a Square' -- Square inherits from Polygon and has access to its methods 1387 | console.log(mySquare.getCustomHelloPhrase()) // 'Hi, I am a Square with a length of 10' 1388 | ``` 1389 | 1390 | **メモ:** `Square` クラスの中で `super()` を呼ぶ前に `this` を使おうとすると、 `ReferenceError` が発生します: 1391 | 1392 | ```js 1393 | class Square extends Polygon { 1394 | constructor(length) { 1395 | this.height; // ReferenceError, super needs to be called first! 1396 | 1397 | // Here, it calls the parent class' constructor with lengths 1398 | // provided for the Polygon's width and height 1399 | super(length, length); 1400 | 1401 | // Note: In derived classes, super() must be called before you 1402 | // can use 'this'. Leaving this out will cause a reference error. 1403 | this.name = 'Square'; 1404 | } 1405 | } 1406 | ``` 1407 | 1408 | #### 外部のリソース 1409 | 1410 | - [Extends - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends) 1411 | - [Super operator - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super) 1412 | - [Inheritance - MDN](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance) 1413 | 1414 | ### Async Await 1415 | 1416 | [プロミス](#promises) に加えて、 *`async`* / *`await`* という非同期なコードを扱うための新しい構文を見ることがあるかもしれません。 1417 | 1418 | `async` / `await` 関数の目的は、プロミスを同期的に利用する振る舞いをシンプルにすることです。 1419 | そして、プロミスのグループを扱うことです。 1420 | プロミスが構造化されたコールバックに似ているのと同じように、 `async` / `await` もジェネレーターとプロミスの組み合わせに似ています。 1421 | `async` 関数は *常に* プロミスを返します。 1422 | ([参考: MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)) 1423 | 1424 | > **メモ:** `async` / `await` はプロミスをベースにしているため、 `async` / `await` を理解しようとする前に、プロミスがどんなもので、どのように動作するのかを理解する必要があります。 1425 | 1426 | > **メモ 2:** [*`await`* は *`async`* 関数の中で使わなくてはなりません](https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9#f3f0) 。これは、 `await` はコードのトップレベルでは使えない(コードのトップレベルは `async` 関数の中に入っていないので)、という意味です。 1427 | 1428 | #### サンプルコード 1429 | 1430 | ```js 1431 | async function getGithubUser(username) { // async keyword allows usage of await in the function and means function returns a promise 1432 | const response = await fetch(`https://api.github.com/users/${username}`); // Execution is paused here until the Promise returned by fetch is resolved 1433 | return response.json(); 1434 | } 1435 | 1436 | getGithubUser('mbeaudru') 1437 | .then(user => console.log(user)) // logging user response - cannot use await syntax since this code isn't in async function 1438 | .catch(err => console.log(err)); // if an error is thrown in our async function, we will catch it here 1439 | ``` 1440 | 1441 | #### サンプルコード付きの説明 1442 | 1443 | *`async`* / *`await`* はプロミスの上に構築されていますが、これらを使うとより命令型のスタイルでコードを書くことができます。 1444 | 1445 | *`async`* 演算子は関数に非同期のマークを付けて、常に *プロミス* を返す形にします。 1446 | *`async`* 関数の中で *`await`* 演算子を使うことで、その式の戻り値のプロミスがリゾルブかリジェクトのどちらかをするまで(原文では「 until the returned Promise from the expression either resolves or rejects 」です)その行で処理を一時停止することができます。 1447 | 1448 | ```js 1449 | async function myFunc() { 1450 | // we can use await operator because this function is async 1451 | return "hello world"; 1452 | } 1453 | 1454 | myFunc().then(msg => console.log(msg)) // "hello world" -- myFunc's return value is turned into a promise because of async operator 1455 | ``` 1456 | 1457 | `async` 関数の *`return`* 文に到達したときに、プロミスは戻り値を持ってフルフィルされます(訳注: 原文では「 is fullfilled with the value returned 」)。 1458 | `async` 関数の中でエラーが発生したら、プロミスのステートは *リジェクテッド* に変わります。 1459 | `async` 関数に戻り値が無い場合も、 `async` 関数の実行が完了したときにはプロミスが返され、値を持たずにリゾルブが発生します。 1460 | 1461 | *`await`* 演算子は *プロミス* がフルフィルドになるのを待つために使用するものであり、 *`async`* 関数のボディの中でのみ使用することができます。 1462 | 処理が *`await`* のところに来たら、プロミスがフルフィルされるまで処理が一時停止されます。 1463 | 1464 | > **メモ:** *`fetch`* は Ajax リクエストができるプロミスを返す関数です。 1465 | 1466 | まず、プロミスを使うと GitHub ユーザー情報の取得がどのようにできたかを見てみましょう: 1467 | 1468 | ```js 1469 | function getGithubUser(username) { 1470 | return fetch(`https://api.github.com/users/${username}`).then(response => response.json()); 1471 | } 1472 | 1473 | getGithubUser('mbeaudru') 1474 | .then(user => console.log(user)) 1475 | .catch(err => console.log(err)); 1476 | ``` 1477 | 1478 | *`async`* / *`await`* で同じことをすると次のようになります: 1479 | 1480 | ```js 1481 | async function getGithubUser(username) { // promise + await keyword usage allowed 1482 | const response = await fetch(`https://api.github.com/users/${username}`); // Execution stops here until fetch promise is fulfilled 1483 | return response.json(); 1484 | } 1485 | 1486 | getGithubUser('mbeaudru') 1487 | .then(user => console.log(user)) 1488 | .catch(err => console.log(err)); 1489 | ``` 1490 | 1491 | *`async`* / *`await`* 構文は依存関係のある複数のプロミスをチェインするときに特に便利です。 1492 | 1493 | 例えば、データベースにあるブログ投稿と作成者情報をフェッチするためのトークンを取得する必要がある場合は次のようになります: 1494 | 1495 | > **メモ:** リゾルブされた値のメソッドやプロパティを同一行で利用する場合、 *`await`* 式はかっこで囲う必要があります。 1496 | 1497 | ```js 1498 | async function fetchPostById(postId) { 1499 | const token = (await fetch('token_url')).json().token; 1500 | const post = (await fetch(`/posts/${postId}?token=${token}`)).json(); 1501 | const author = (await fetch(`/users/${post.authorId}`)).json(); 1502 | 1503 | post.author = author; 1504 | return post; 1505 | } 1506 | 1507 | fetchPostById('gzIrzeo64') 1508 | .then(post => console.log(post)) 1509 | .catch(err => console.log(err)); 1510 | ``` 1511 | 1512 | ##### エラーハンドリング 1513 | 1514 | *`await`* 式の周りに *`try`* / *`catch`* のブロックを追加しておかないと、キャッチされない例外がーー *`async`* 関数のボディの中で投げられたかどうかにかかわらず、また、 *`await`* でサスペンドされていても ーー *`async`* 関数が返したプロミスをリジェクトします。 1515 | `async` 関数の中で `throw` 文を使うことは、リジェクトするプロミスを返すことと同じ意味です。 1516 | ([(参考: PonyFoo)](https://ponyfoo.com/articles/understanding-javascript-async-await#error-handling)) 1517 | 1518 | > **メモ:** プロミスは同じように振る舞います! 1519 | 1520 | プロミスを使った場合、エラーチェインの処理は次のようにしていました: 1521 | 1522 | ```js 1523 | function getUser() { // This promise will be rejected! 1524 | return new Promise((res, rej) => rej("User not found !")); 1525 | } 1526 | 1527 | function getAvatarByUsername(userId) { 1528 | return getUser(userId).then(user => user.avatar); 1529 | } 1530 | 1531 | function getUserAvatar(username) { 1532 | return getAvatarByUsername(username).then(avatar => ({ username, avatar })); 1533 | } 1534 | 1535 | getUserAvatar('mbeaudru') 1536 | .then(res => console.log(res)) 1537 | .catch(err => console.log(err)); // "User not found !" 1538 | ``` 1539 | 1540 | *`async`* / *`await`* で同じことをすると次のようになります: 1541 | 1542 | ```js 1543 | async function getUser() { // The returned promise will be rejected! 1544 | throw "User not found !"; 1545 | } 1546 | 1547 | async function getAvatarByUsername(userId) => { 1548 | const user = await getUser(userId); 1549 | return user.avatar; 1550 | } 1551 | 1552 | async function getUserAvatar(username) { 1553 | var avatar = await getAvatarByUsername(username); 1554 | return { username, avatar }; 1555 | } 1556 | 1557 | getUserAvatar('mbeaudru') 1558 | .then(res => console.log(res)) 1559 | .catch(err => console.log(err)); // "User not found !" 1560 | ``` 1561 | 1562 | #### 外部のリソース 1563 | 1564 | - [Async/Await - JavaScript.Info](https://javascript.info/async-await) 1565 | - [ES7 Async/Await](http://rossboucher.com/await/#/) 1566 | - [6 Reasons Why JavaScript’s Async/Await Blows Promises Away](https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9) 1567 | - [JavaScript awaits](https://dev.to/kayis/javascript-awaits) 1568 | - [Using Async Await in Express with Node 8](https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016) 1569 | - [Async Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) 1570 | - [Await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) 1571 | - [Using async / await in express with node 8](https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016) 1572 | 1573 | ### True になるもの / False になるもの 1574 | 1575 | JavaScript では、 `true` になる値と `false` になる値は、真偽判定のコンテキストで評価されたとき(訳注: 原文では「 when evaluated in a boolean context 」です)に真偽値にキャストされます。 1576 | 真偽判定のコンテキストの例は、 ```if``` 条件での判定です。 1577 | 1578 | 次の値に等しくない値はすべて ```true``` にキャストされます: 1579 | 1580 | - ```false``` 1581 | - ```0``` 1582 | - ```""``` (空文字列) 1583 | - ```null``` 1584 | - ```undefined``` 1585 | - ```NaN``` 1586 | 1587 | *真偽判定のコンテキスト* の例は次のとおりです: 1588 | 1589 | - ```if``` 条件評価 1590 | 1591 | ```js 1592 | if (myVar) {} 1593 | ``` 1594 | 1595 | ```myVar``` にはどんな[第一級市民](https://en.wikipedia.org/wiki/First-class_citizen)(変数、関数、真偽値)を置くこともできますが、真偽判定のコンテキストで評価されているため真偽値にキャストされます。 1596 | 1597 | - 論理 **否定** 演算子 ```!``` の後 1598 | 1599 | この演算子はそのオペランドが `true` に変換できる場合は `false` を返します。 1600 | 逆の場合は `true` を返します。 1601 | 1602 | ```js 1603 | !0 // true -- 0 is falsy so it returns true 1604 | !!0 // false -- 0 is falsy so !0 returns true so !(!0) returns false 1605 | !!"" // false -- empty string is falsy so NOT (NOT false) equals false 1606 | ``` 1607 | 1608 | - *真偽値* オブジェクトコンストラクターでの使用 1609 | 1610 | ```js 1611 | new Boolean(0) // false 1612 | new Boolean(1) // true 1613 | ``` 1614 | 1615 | - 三項演算子の評価部分(訳注: 原文では「 In a ternary evaluation 」です) 1616 | 1617 | ```js 1618 | myVar ? "truthy" : "falsy" 1619 | ``` 1620 | 1621 | `myVar` は真偽判定コンテキストで評価されます。 1622 | 1623 | 2 つの値の比較には注意が必要です。 1624 | 1625 | (最終的に真偽値にキャストされる)比較対象のオブジェクトの値は、真偽値にキャストされるのでは **なく** 、 [プリミティブへの変換仕様](http://javascript.info/object-toprimitive) に基いて、プリミティブ値に変換されます。 1626 | 内部的には、 `[] == true` のようにオブジェクトが真偽値と比較された場合、 `[].toString() == true` という比較が行われます。 1627 | 1628 | ```js 1629 | let a = [] == true // a is false since [].toString() give "" back. 1630 | let b = [1] == true // b is true since [1].toString() give "1" back. 1631 | let c = [2] == true // c is false since [2].toString() give "2" back. 1632 | ``` 1633 | 1634 | #### 外部のリソース 1635 | 1636 | - [Truthy (MDN)](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) 1637 | - [Falsy (MDN)](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) 1638 | - [Truthy and Falsy values in JS - Josh Clanton](http://adripofjavascript.com/blog/drips/truthy-and-falsy-values-in-javascript.html) 1639 | 1640 | ### アナモルフィズム / カタモルフィズム 1641 | 1642 | #### アナモルフィズム 1643 | 1644 | アナモルフィズム(訳注: 原文では「 anamorphisms 」です)とは、あるオブジェクトを、オブジェクト型を含むより複雑な構造体にマップする関数のことです。 1645 | それは、シンプルな構造体を複雑な構造体に *アンフォールド* する(訳注: 原文では「 unfolding 」です)処理です。 1646 | 整数を、整数のリストにアンフォールドする場合を考えてみましょう。 1647 | この場合、整数が最初のオブジェクトで、整数のリストがより複雑な構造体です。 1648 | 1649 | **サンプルコード** 1650 | 1651 | ```js 1652 | function downToOne(n) { 1653 | const list = []; 1654 | 1655 | for (let i = n; i > 0; --i) { 1656 | list.push(i); 1657 | } 1658 | 1659 | return list; 1660 | } 1661 | 1662 | downToOne(5) 1663 | //=> [ 5, 4, 3, 2, 1 ] 1664 | ``` 1665 | 1666 | #### カタモルフィズム 1667 | 1668 | カタモルフィズム(訳注: 原文では「 catamorphisms 」です)とは、アナモルフィズムの逆で、複雑な構造体であるオブジェクトをよりシンプルな構造体に *フォールド* する(訳注: 原文では「 fold 」です)ことです。 1669 | 次の `product` のサンプルを見てください。 1670 | `product` は整数のリストを受け取り単一の整数を返します。 1671 | 1672 | **サンプルコード** 1673 | 1674 | ```js 1675 | function product(list) { 1676 | let product = 1; 1677 | 1678 | for (const n of list) { 1679 | product = product * n; 1680 | } 1681 | 1682 | return product; 1683 | } 1684 | 1685 | product(downToOne(5)) // 120 1686 | ``` 1687 | 1688 | #### 外部のリソース 1689 | 1690 | * [Anamorphisms in JavaScript](http://raganwald.com/2016/11/30/anamorphisms-in-javascript.html) 1691 | * [Anamorphism](https://en.wikipedia.org/wiki/Anamorphism) 1692 | * [Catamorphism](https://en.wikipedia.org/wiki/Catamorphism) 1693 | 1694 | ### ジェネレーター 1695 | 1696 | `downToOne` 関数を書く別の方法として、ジェネレーターを使ったものがあります。 1697 | `Generator` オブジェクトを生成するには、 `function *` 宣言を使わなくてはいけません。 1698 | ジェネレーターは、一度脱出した後に、コンテキスト(変数バインディング)を保った形で再突入できる関数です(訳注: 原文では「 Generators are functions that can be exited and later re-entered with its context (variable bindings) saved across re-entrances 」です)。 1699 | 1700 | 例えば、上の `downToOne` 関数は次のように書き直すことができます: 1701 | 1702 | ```js 1703 | function * downToOne(n) { 1704 | for (let i = n; i > 0; --i) { 1705 | yield i; 1706 | } 1707 | } 1708 | 1709 | [...downToOne(5)] //[ 5, 4, 3, 2, 1 ] 1710 | ``` 1711 | 1712 | ジェネレーターはイテラブルオブジェクトを返します。 1713 | イテレーターの `next()` 関数が呼ばれると、最初の `yield` 式までが実行されます。 1714 | `yield` 式はイテレーターから返すべき値を指定します。 1715 | `yield*` を使えば、その処理が別のジェネレーター関数に委譲されます。 1716 | ジェネレーター式の中で `return` 式が呼ばれると、それはジェネレーターに完了済みという印を付けて、戻り値として返します。 1717 | さらに `next()` を呼び出しても新しい値が返されることはありません。 1718 | 1719 | **サンプルコード** 1720 | 1721 | ```js 1722 | // Yield Example 1723 | function * idMaker() { 1724 | var index = 0; 1725 | while (index < 2) { 1726 | yield index; 1727 | index = index + 1; 1728 | } 1729 | } 1730 | 1731 | var gen = idMaker(); 1732 | 1733 | gen.next().value; // 0 1734 | gen.next().value; // 1 1735 | gen.next().value; // undefined 1736 | ``` 1737 | 1738 | `yield*` 式を使えば、ジェネレーターの繰り返しの中で別のジェネレーターを呼び出すことができます。 1739 | 1740 | ```js 1741 | // Yield * Example 1742 | function * genB(i) { 1743 | yield i + 1; 1744 | yield i + 2; 1745 | yield i + 3; 1746 | } 1747 | 1748 | function * genA(i) { 1749 | yield i; 1750 | yield* genB(i); 1751 | yield i + 10; 1752 | } 1753 | 1754 | var gen = genA(10); 1755 | 1756 | gen.next().value; // 10 1757 | gen.next().value; // 11 1758 | gen.next().value; // 12 1759 | gen.next().value; // 13 1760 | gen.next().value; // 20 1761 | ``` 1762 | 1763 | ```js 1764 | // Generator Return Example 1765 | function* yieldAndReturn() { 1766 | yield "Y"; 1767 | return "R"; 1768 | yield "unreachable"; 1769 | } 1770 | 1771 | var gen = yieldAndReturn() 1772 | gen.next(); // { value: "Y", done: false } 1773 | gen.next(); // { value: "R", done: true } 1774 | gen.next(); // { value: undefined, done: true } 1775 | ``` 1776 | 1777 | #### 外部のリソース 1778 | 1779 | * [Mozilla MDN Web Docs, Iterators and Generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#Generators) 1780 | 1781 | ### スタティックメソッド 1782 | 1783 | #### 短い説明 1784 | 1785 | `static` キーワードは、スタティックメソッドを宣言するためにクラスの中で使用します。 1786 | スタティックメソッドはクラスの中の関数であり、クラスオブジェクトに所属します。 1787 | そのクラスのインスタンスからは利用することができません。 1788 | 1789 | #### サンプルコード 1790 | 1791 | ```js 1792 | class Repo{ 1793 | static getName() { 1794 | return "Repo name is modern-js-cheatsheet" 1795 | } 1796 | } 1797 | 1798 | //Note that we did not have to create an instance of the Repo class 1799 | console.log(Repo.getName()) //Repo name is modern-js-cheatsheet 1800 | 1801 | let r = new Repo(); 1802 | console.log(r.getName()) //Uncaught TypeError: repo.getName is not a function 1803 | ``` 1804 | 1805 | #### 詳細な説明 1806 | 1807 | スタティックメソッドは、 `this` キーワードを使えば別のスタティックメソッドの中から呼ぶことができます。 1808 | しかし、スタティックではないメソッド(訳注: 原文では「 non-static methods 」です)の場合はこれはできません。 1809 | スタティックでないメソッドが `this` キーワードを使ってスタティックメソッドに直接アクセスすることはできません。 1810 | 1811 | ##### スタティックメソッドから別のスタティックメソッドを呼ぶ 1812 | 1813 | スタティックメソッドを別のスタティックメソッドから呼び出すには、次のように `this` キーワードを使うことができます: 1814 | 1815 | ```js 1816 | class Repo{ 1817 | static getName() { 1818 | return "Repo name is modern-js-cheatsheet" 1819 | } 1820 | 1821 | static modifyName(){ 1822 | return this.getName() + '-added-this' 1823 | } 1824 | } 1825 | 1826 | console.log(Repo.modifyName()) //Repo name is modern-js-cheatsheet-added-this 1827 | ``` 1828 | 1829 | ##### スタティックでないメソッドからスタティックメソッドを呼ぶ 1830 | 1831 | スタティックでないメソッドからはスタティックメソッドを呼ぶ方法が 2 つあります: 1832 | 1833 | Non-static methods can call static methods in 2 ways; 1834 | 1835 | 1. クラス名を使う 1836 | 1837 | スタティックでないメソッドからスタティックメソッドにアクセスするには、クラス名を使って、プロパティのような形でスタティックメソッドを呼び出します。 1838 | 例: `ClassName.StaticMethodName` 1839 | 1840 | ```js 1841 | class Repo{ 1842 | static getName() { 1843 | return "Repo name is modern-js-cheatsheet" 1844 | } 1845 | 1846 | useName(){ 1847 | return Repo.getName() + ' and it contains some really important stuff' 1848 | } 1849 | } 1850 | 1851 | // we need to instantiate the class to use non-static methods 1852 | let r = new Repo() 1853 | console.log(r.useName()) //Repo name is modern-js-cheatsheet and it contains some really important stuff 1854 | ``` 1855 | 1856 | 2. コンストラクターを使う 1857 | 1858 | スタティックメソッドは `constructor` オブジェクトのプロパティとして呼ぶことができます。 1859 | 1860 | ```js 1861 | class Repo{ 1862 | static getName() { 1863 | return "Repo name is modern-js-cheatsheet" 1864 | } 1865 | 1866 | useName(){ 1867 | //Calls the static method as a property of the constructor 1868 | return this.constructor.getName() + ' and it contains some really important stuff' 1869 | } 1870 | } 1871 | 1872 | // we need to instantiate the class to use non-static methods 1873 | let r = new Repo() 1874 | console.log(r.useName()) //Repo name is modern-js-cheatsheet and it contains some really important stuff 1875 | ``` 1876 | 1877 | #### 外部のリソース 1878 | 1879 | - [static keyword- MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) 1880 | - [Static Methods- Javascript.info](https://javascript.info/class#static-methods) 1881 | - [Static Members in ES6- OdeToCode](http://odetocode.com/blogs/scott/archive/2015/02/02/static-members-in-es6.aspx) 1882 | 1883 | ## 用語集 1884 | 1885 | ### スコープ 1886 | 1887 | 値と式が「見え」て、参照可能な場合なコンテキストのこと。 1888 | 変数やその他の式が「カレントスコープ」に無い場合は、それらは利用することができません。 1889 | 1890 | ソース: [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Scope) 1891 | 1892 | ### 変数ミューテーション 1893 | 1894 | 初期値が後から変更されたとき、その変数はミューテートされた(訳注: 原文では「 have been mutated 」です)と言います。 1895 | 1896 | ```js 1897 | var myArray = []; 1898 | myArray.push("firstEl") // myArray is being mutated 1899 | ``` 1900 | 1901 | もしある変数がミューテートできなければ、その変数は *イミュータブル* である(訳注: 原文では「 immutable 」です)と言います。 1902 | 1903 | 詳しくは、 [MDN の Mutable の記事](https://developer.mozilla.org/en-US/docs/Glossary/Mutable) をご覧ください。 1904 | --------------------------------------------------------------------------------