---
# System prepended metadata

title: ES6

---

# ES6
> - ECMAScript(ECMA) 是一種標準
> - JS 是實現此標準的其中一種語言


> - ECMA 每年六月發佈一次版本
>   - 201506 -> ES2015(ES6)
>   - 201606 -> ES2016
>   - ... 
> - ES6 指 ES2015 以後的版本

> 兼容性: https://kangax.github.io/compat-table/es6/ 
> - IE10, FF, Chrome, Mobile, NodeJS 




## 變量
> - var問題: 可以重複定義, 不能限制修改, 只有函數級作用域(沒有塊級作用域)

> - let: 不能重複定義, 變量(可以修改), 塊級作用域
> - const: 不能重複定義, 常量(限制修改), 塊級作用域
### let
> - let 是用來聲明變量的

> - 不可以重複聲明
```javascript=
var b;
var b;

let a;
let a; // Identifier 'a' has already been declared
```
> - 不會 hoisting
>   - 先聲明後使用

```javascript=
console.log(a);
var a = 10; // undefined
console.log(b);
let b = 10; // Cannot access 'b' before initialization
```

> - 在全局作用域下聲明, 不會給 window 添加屬性
```javascript=
let a = 1;
var b = 1;
console.log(window.a); // undefined
console.log(window.b); // 1
```

> - 在一個 {} 中 let 聲明的變量具有塊級作用域 ( 只有在 {} 裡面才能訪問到該變量 )
>   - var 不具備此特點
```javascript=
if (true) {
  var a = 100;
  let b = 100;
}

console.log(a); // 100
console.log(b); // b is not defined
```
```javascript=
// 防止循環變量變成全局變量
for (var i = 0; i < 3; i++) {}
console.log(i); // 3

for (let j = 0; j < 3; j++) {}
console.log(j); // j is not defined
```


> - 暫時性死區
>   - 在塊級作用域 let 聲明變量, 會讓該作用域綁定變量, 百毒不侵

```javascript=
var a = 10;
if (true) {
  console.log(a); // Cannot access 'a' before initialization
  let a = 10; // -> 在塊級 let 聲明後, 外面的變量就不能用了
}
```

> - 經典題
```javascript=
var arr = [];
for (var i = 0; i < 2; i++) {
  arr[i] = function () {
    console.log(i);
  }
}
arr[0](); // 2
arr[1](); // 2

// var 沒有塊級作用域的概念, 也就是說兩個函數輸出的 i 都是全局的
// 所以調用時, i 已經 = 2 而跳出循環, 故兩次輸出都是 2 
```
```javascript=
let arr = [];
for (let i = 0; i < 2; i++) {
  arr[i] = function () {
    console.log(i);
  }
}
arr[0](); // 0
arr[1](); // 1

// 因為for循環的產生了兩個塊級作用域,
    /*
    {
      let i = 0;
      arr[i] = function () {
        console.log(i);
      }
    }
    {
      let i = 1;
      arr[i] = function () {
        console.log(i);
      }
    }
    */
// 這兩個 i 是兩個變量, 他們在不同的作用域裡, 互不影響
// 函數調用時, 由於函數內沒有聲明 i 而往上一層作用域查找, 故為 0 跟 1
```

### const
> - 聲明常量(不能變)

> #### 聲明具有塊級作用域
```javascript=
if (true) {
  const a = 10;
  if (true) {
    const a = 20;
    console.log(a); // 20
  }
  console.log(a); // 10
}
console.log(a); // a is not defined
```

> #### 聲明時, 必須附值
```javascript=
const PI; // Missing initializer in const declaration
```
> #### 常量聲明不可更改
> - 效率高, 因為JS解析引擎不用一直監控值的變化
> - 簡單數據類型: 不可改值
```javascript=
const PI = 3.14;
PI = 100; // Assignment to constant variable.
```

>   - 複雜數據類型: 不可改址
```javascript=
const ARR = [100, 200];
ARR[0] = 1;
console.log(ARR); // (2) [1, 200]

ARR = ['a', 'b']; // Assignment to constant variable.
```

### 塊級作用域
> #### 一個 {} 就是一個塊級作用域
>   - `if(){}` `for(){}` `function(){}` `let obj = {}` `{}`

> - 對象的注意點 
```javascript=
// 對象
let a = {
	'a': 1,
	'b': 2,
}

// 對象
({'a':1, 'b':2})


// 一般的塊級作用域
{
  let a = 1;
  let b = 2;
}


// 對象的 { 不能放在行首, 否則就不是表示一個對象
{
	'a': 1, // Unexpected token ':'
	'b': 2,
} 
```
> - eval 注意點
```javascript=
eval('{"a":1, "b":2}'); // Unexpected token ':'
  // 因為 {"a":1, "b":2} 無法表達為一個對象, 造成格式錯誤
```
```javascript=
// 解決
console.log(eval('({"a":1, "b":2})')); // {a: 1, b: 2}
eval('var a = {"a":1, "b":2}');
console.log(a); // {a: 1, b: 2}
```

> #### 塊級作用域下的聲明
> - 在塊級作用域下聲明 var 跟 function  依舊是全局的
> - 在塊級作用域下聲明 let const 是私有的
```javascript=
{
  var a = 1;
  function b(){};
  let c = 2;
}
console.log(a); // 1
console.log(b); // ƒ b(){}
console.log(c); // c is not defined -> 外面訪問不到
```
> - if 跟 for 的問題
```javascript=
console.log(a,b); // undefined undefined
if (0) {
	console.log(1); // 沒有
	var a = 1;
	function b(){};
	let c = 2;
}
console.log(a,b); // undefined undefined
console.log(c); // c is not defined

// 你都沒有進去 if , 怎麼會有聲明 !!!
```
```javascript=
console.log(a,b); // undefined undefined
if (1) {
  console.log(a,b); // undefined ƒ b(){} -> 一進去先對 function 附值
  var a = 1;
  function b(){};
  let c = 2;
}
console.log(a,b); // 1 ƒ b(){}
console.log(c); // c is not defined

// 以前 function 跟 var 的運作模式:
// 1. hoisting 聲明
// 2. 一進到塊級作用域 function 馬上附值
// 3. 走過 var 的附值表達後才對 var 剛剛聲明的附值

// let 跟 const: 先聲明後使用
```
> - 綁定點擊
```htmlmixed=
<body>
  <ul id="tmp">
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
  <script src='test.js'></script>
</body>
```
```javascript=
// 以前的寫法

var lis = document.getElementById('tmp').getElementsByTagName('li');

for (var i = 0; i < lis.length; i++) {
  /* 出問題
  lis[i].onclick = function () {
    console.log(i); // 3
      // 1. 事件屬於異步, 點擊的時候 for 早就跑完了
      // 2. var 在塊級作用域下聲明還是全局的,
      //    每次循環都把上一次的 i 值給改了, 循環完為 3
  }
  */
  
  /* 解法一
  lis[i].index = i;
  lis[i].onclick = function () {
    console.log(this.index);
  } 
  */
  // 解法二
  (function (i){
    lis[i].onclick = function () {
      console.log(i);
    }
  })(i)
}
console.log(i);

/* 拆分每次循環
  var i = 3; // 0 -> 1 -> 2 -> 3
  {
    lis[0].index = 0;
    lis[0].onclick = function(){console.log(this.index);}
  }
  {
    lis[1].index = 1;
    lis[1].onclick = function(){console.log(this.index);}
  }
  {
    lis[2].index = 2;
    lis[2].onclick = function(){console.log(this.index);}
  }

  - 如果 console.log(i) -> 每個作用域裡都沒有 i 而往全局找 -> 3
  - 一般 function 會拆出來, 否則創建了三個重複的 function
*/
```
```javascript=
// let 寫法
let lis = document.getElementById('tmp').getElementsByTagName('li');

for (let i = 0; i < lis.length; i++) {
  lis[i].onclick = function () {
    console.log(i);
  }
}
console.log(i); // i is not defined

/* 每次 for 循環都會創建一個作用域, let 聲明會被綁在作用域裡
  {
    let i = 0;
    lis[0].onclick = function () {console.log(i)};
  }
  {
    let i = 1;
    lis[1].onclick = function () {console.log(i)};
  }
  {
    let i = 2;
    lis[2].onclick = function () {console.log(i)};
  }
  
  - console.log(i) 在對象本身就有了, 所以直接拿, 外面反而沒有~
*/
```


## 小結: var, let, const 區別
> - var
>   - 函數級作用域
>   - hoistiong
>   - 值可改
> - let
>   - 塊級作用域
>   - 沒有 hoistiong
>   - 值可改
> - const
>   - 塊級作用域
>   - 沒有 hoistiong
>   - 值不可改
>   - 聲明必須附值

## 解構
### 陣列解構
> - 左右結構需一樣
> - 定義與賦值同步完成
```javascript=

// 定義賦值 同步完成
let [a,b] = [1,2];
console.log(a,b);


/* 不同步
let [c,d]; // Missing initializer in destructuring declaration
[c,d] = [1,2];
*/
```

> #### 解構附值
```javascript=
let arr = [1,2,3];
// 以前提取方法
console.log(arr[0], arr[1], arr[2]); // 1 2 3

// ES6 允許直接讓變量直接從陣列中提取值
[a,b,c,d] = arr;
console.log(a,b,c,d); // 1 2 3 undefined

let [e,f,g] = [1,2,3];
console.log(e,f,g); // 1 2 3
```
> #### 設置默認值
>   - 當聲明的變量拿到 undefined 時, 才會執行默認值的表達式
```javascript=
// 設置默認值
let [x,y=10] = [1,2];
console.log(x,y); // 1 2

let [a, b=(function () {console.log('haha');return 10})()] = [1, 2];
console.log(a,b) // 1 2 -> 沒有執行 b 的默認值的表達式

let [i, j=(function () {console.log('haha');return 10})()] = [1];
console.log(i,j)
// haha -> 給 b undefined 時, 才會執行默認值的表達式
// 1 10
```
> #### 省略附值
```javascript=
let [,,x] = [1,2,3];
console.log(x); // 3
```
> #### 剩餘參數附值
```javascript=
let [x,...y] = [1,2,3];
console.log(x,y); // 1 [2, 3]
```

### 對象解構
> #### 對象解構附值
```javascript=
// a c 是屬性名, b d 是變量名
  // 將屬性對應的值賦予給變量 b d
let {a:b, c:d} = {a:1, b:2, c:3, d:4};
console.log(b,d); // 1 3
console.log(a,c); // a is not defined -> a c 不是變量, 是對象的屬性名


// 好記得方法: 取別名
  // 把 {a:1, c:3} 的變量名取成 b 跟 d
```
> #### 簡寫
> - 如果變量名跟屬性名相同, 可以縮寫
```javascript=
let {a,b} = {a:1, b:2}; // = {a:a, b:b}
console.log(a,b); // 1 2 -> 提取的是後面的變量名 a b
```
> #### 設置默認
> - 跟陣列邏輯相同, 給 undefined 才走默認值
```javascript=
let {x,y=10} = {x:1};
console.log(x,y); // 1 10
let {a,b=10} = {a:1, b:undefined};
console.log(a,b); // 1 10
```
> #### 混搭
```javascript=
let {x,y=10,z:[a,b,c]} = {x:1,z:[1,2,3]};

console.log(x,y); // 1 10
console.log(a,b,c); // 1 2 3
console.log(z); // z is not defined -> 屬性名
```

> #### 注意
```javascript=
let x,y;
[x,y] = [1,2]; // 這樣做沒有問題
console.log(x,y); // 1 2 


let a,b;
/*
{a,b} = {a:1, b:2}; // Unexpected token '='
                      // 這樣做就有問題了, 因為 {a,b} 沒有被視為一個對象!
*/
({a,b} = {a:1, b:2}); // 整個包起來就沒問題了
console.log(a,b); // 1 2
```

### 其他情況
> #### 右邊附值非陣列或對象
> - 會自動將值轉為對象
> - 注意
>   - 陣列附值只接受有 length 的偽陣列
>   - 對象在取值時, 必須要有對應的屬性, 否則 undefined
```javascript=
// 陣列
let [a,b] = '12';
console.log(a,b); // 1 2
// let [c] = 1; // 1 is not iterable
// console.log(c);


// 因為 
console.log(Object('12'));
  // String {"12"}
  //   0: "1"
  //   1: "2"
  //   length: 2 -> 有 length
  //   __proto__: String
  //   [[PrimitiveValue]]: "12"

console.log(Object(1));
  // Number {1} -> 沒有 length
  //   __proto__: Number
  //   [[PrimitiveValue]]: 1

// 對象
let {d:e,f:g} = '12';
console.log(e,g); // undefined undefined -> 都沒有對應的鍵值
let {h:i} = 1;
console.log(i); // undefined


let {__proto__:j} = '12';
let {__proto__:k} = 1;
console.log(j,k); // String{} Number{}
```

### 函數參數
```javascript=
// 陣列參數
function fn(a,b,...c) {
	console.log(a,b,c);
}
fn(1,2,3,4,5); // 1 2 [3, 4, 5]


// 對象參數
function fn2({a,b}) {
	console.log(a,b);
}
fn2({a:1, b:2});

/* 注意1. 
function fn3({a=1,b=2}){
	console.log(a,b);
}
fn3(); // Cannot read property 'a' of undefined
*/
  // 原因: 
  // - 因為 fn3() 沒有傳參
  // - 導致參數變成 {a=1, b=2} = undefined, 
  // - 而在 undefined 裡當然找不到 a b 屬性

  // 測試:
  // cosole.log(let {q=1} = undefined);
    // Cannot read property 'q' of undefined

function fn4({a=1, b=2}={}) {
	console.log(a,b);
}
function fn5({a,b}={a:1, b:2}) {
	console.log(a,b);
}
fn4(); // 1 2
fn5(); // 1 2


fn4({}); // 1 2
fn5({}); // undefined undefined
  // 注意2.
  // - 因為傳了一個對象實參進去後, 形參不走默認值了
  // - 變成 {a,b}={}
  // - 所以聲明了 a b 後, 沒有附值
```


## 箭頭函數
`(形參)=>{函數體}`

> - 用來簡化函數定義
> - 箭頭函數是匿名的, 所以都會附址給變量

> - 如果參數只有一個, () 可省
> - 如果函數體只有一句話, 且那句話是 return, {} 可省

```javascript=
// 一般函數定義
let fn = function (a) {
  return a
}

// 箭頭函數定義
let fn2 = (b) => {
  return b
}

// 如果函數體只有一句話, 且該句的執行解果剛好是要 return, 那麼 return 跟 {} 可省
// 如果只有一個參數, 那 () 也可省
let fn3 = c => c

console.log(fn(1), fn2(2), fn3(3)) // 1 2 3 
```

> #### this
> - 箭頭函數不綁定 this ( 箭頭函數沒有自己的 this )
> - 箭頭函數中的 this 是該函數的上級作用域下的 this
> - 即使用 bind 也不會改變箭頭函數的 this
>   但是應該沒有人會用箭頭函數又想用 bind 來改ㄅ..

> - 整理
>   - 普通函數的 this => 誰調用, this 指誰
>   - 箭頭函數的 this => 函數定在哪, this 就指誰
>   - bind, call, apply 的 this => 綁誰指誰
>   - 簡單一句, 箭頭函式的內外 this 保持一致
```javascript=
// 傳統函數
function fn() {
  console.log(this);
  return function () {
    console.log(this);
  }
}

obj = {
	fn: fn,
}

obj.fn()();
// {fn: ƒ}
  // obj.fn()調用後, 根據誰調用指誰的定律, this 指向 obj, 並返回一個函數
// Window
  // 返回一個函數後再調用, 此時前面沒有 . 表示是被省略 Window 調用的
```

```javascript=
function fn() {
  console.log(this);
  return () => { // 箭頭函數綁在這個fn() 函數裡, 
                 // 所以他的 this 視 fn() 的 this 而定
    console.log(this);
  }
}

let obj = {
  fn: fn, 
}

obj.fn()();
```
```javascript=
// 這個案例應該更清楚些
/*
document.onclick = ()=>{
  console.log(this); // 這個 this 寫在全局, 所以 this 指向 Window 
} 
*/

document.onclick = function () { 
  console.log(this); // 這個 fn 被 document 調用, 
                     // 所以 this 指向 #document
}
```


> #### 面試題
```javascript=
let obj = {
  fn: function () {
    console.log(this);
  },
  fn2: ()=> {
    console.log(this);
  },
}

obj.fn(); // {fn: ƒ, fn2: ƒ}
  // function 會產生作用域, 所以 this 指向的是函數調用者
obj.fn2(); // Window
  // 由於箭頭函數沒有自己的 this,
  // 又對象不會產生作用域
  // 所以fn2被定義在全局作用域
```

> #### 箭頭函數沒有 arguments
```javascript=
let fn = (...args) => {
  console.log(args);
  console.log(arguments);
}
fn();
  // []
  // arguments is not defined
```
> #### 箭頭函數不能成為構造函數
```javascript=
let F = function () {}
console.log(new F); // F {}
function F2(){}
console.log(new F2) // F2 {}
let F3 = ()=>{}
console.log(new F3) // F3 is not a constructor
```


### 剩餘參數
`...args`
> - `...` 接收剩餘參數
> - 剩餘參數後面為變量名, 一般都用 args
> - 剩餘參數以陣列的形式存取
 
> - 作用類似 python 的 *args

```javascript=
function fn(...args) {
  console.log(args);
}


let fn2 = (...apple) => {
  console.log(apple); // 變量隨便取也沒差, 只是別人要看的懂
}


fn(1,2,3); // (3) [1 2 3] -> 以陣列的方式存取沒人接收的參數
fn2(1,2,3); // (3) [1 2 3]
```
```javascript=
// 求和函數練一下
function fn(...args) {
  let sum = 0;
  args.forEach(function (v) { // 參數遍歷
    sum += v; // 參數加到變量中
  })
  return sum // 回傳變量值
}

// 簡化
let fn2 = (...args) => { // ...args 不能省
  let sum = 0;
  args.forEach(v => sum += v) // 一個參數一句話 -> 省
  return sum
}

console.log(fn(1,2,3)); // 6
console.log(fn2(1,2,3)); // 6
```
```javascript=
// 搭配解構
let arr = [1,2,3];
let [a, ...b] = arr;

console.log(a,b); // 1 [2, 3]
```

## 擴展運算符
### 將陣列或對象轉為以逗號分隔的參數序列 
> - 剛好與剩餘參數相反: 將逗號分隔的參數序列轉成陣列
> - 講白話就是把 \[] 拆掉, 然後你要幹嘛就幹嘛

```javascript=
let arr = [1,2,3];
console.log(...arr); // 1 2 3
  // ...arr = 1,2,3
  // console.log(1,2,3) -> 1 2 3 
```

> #### - 應用: 合併陣列
```javascript=
let arr = [1,2];
let arr2 = [3,4];
let arr3 = [...arr, ...arr2]; // ...arr = 1, 2; ..arr2 = 3, 4;
console.log(arr3); // (4) [1, 2, 3, 4]
```
```javascript=
// 以前寫法
let arr = [1,2];
let arr2 = [3,4];

// concat 返回一個新陣列, 不改變原陣列
console.log(arr.concat(arr2)); // (4) [1, 2, 3, 4]
console.log(arr, arr2); // (2) [1, 2] (2) [3, 4]


// ... 搭配 push, push 可以接收多個參數
// push 會改變原陣列, 返回陣列長度
console.log(arr.push(...arr2)); // 4 -> 長度
console.log(arr); // (4) [1, 2, 3, 4] -> 改變原陣列
```
> #### - 應用: 求陣列最大值
```javascript=
let arr = [11,24,66,4];

console.log(Math.max.apply(Math, arr)); // 66
console.log(Math.max(...arr)); // 66
```
### 將偽陣列轉換成陣列
```htmlmixed=
<body>
  <div></div>
  <div></div>
  <div></div>
  <script src='test.js'></script>
</body>
```
```javascript=
console.log([...document.getElementsByTagName('div')]); // [div, div, div]
console.log([...'123']); // ["1", "2", "3"]
function fn(){
  console.log(arguments)
  console.log(...arguments)
  console.log([...arguments])
}
fn(1,2,3);
  // Arguments [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  // 1 2 3 
    // ... 就是把 [] 給拆了, => console.log(1,2,3)
  // [1, 2, 3]
    // console.log([1,2,3])
```

## Array 方法

### 類方法
`console.dir(Array)`
### `Array.of()`
> #### 前情提要
> - 所有的類都是函數, 而 `Array()` 會將參數變成一個陣列來返回
```javascript=
console.log(Array('a','b','c')); // ["a", "b", "c"]
console.log(Array('c')); // ["c"]
console.log(Array(1,2,3)); // [1, 2, 3]
console.log(Array(3)); // [empty × 3] -> 問題

// 如果 Array 只有一個參數且為數字, 那會返回 [empty x 數字]
```
> #### `Array.of()`
> - 用法跟 `Array()` 一模ㄧ樣, 但解決了這個問題
```javascript=
console.log(Array.of(3)); // [3]
```
### 陣列的 empty 問題

> #### empty
> - 陣列的索引沒有任何值, 即空位
> - `null` , `undefined` 之類的都不是空位
> - 可以使用 in 來驗證有沒有值
```javascript=
let arr = [,undefined,null]
console.log(arr.length); // 3 -> 空位還是有算長度的
console.log(0 in arr); // false -> 索引 0 是沒有值的
console.log(1 in arr); // true -> [1] [2] 都有
console.log(2 in arr); // true
```
> #### Js 對 empty 處理
> - ES5 會把空位跳過
> - ES6 會把空位補成 undefined

```javascript=
let arr = [,undefined,null]

// ES5 會把空位跳過
arr.filter((v,i)=>{
  console.log(v,i);
    // undefined 1
    // null 2
      // 只循環兩次
})
for (let k in arr){
  console.log(k);
    // 1
    // 2
}


// ES6 會把空位補成 undefined
arr.find((v,i)=>{
  console.log(v,i);
    // undefined 0
    // undefined 1
    // null 2
})
for (let k of arr){
  console.log(k);
    // undefined
    // undefined
    // null
}
```
> #### 面試題:
> - 得到七個1 的陣列
```javascript=
let arr = Array(7).fill(1); // 產生七個空位陣列, 用 1 補滿
console.log(arr); // [1, 1, 1, 1, 1, 1, 1]
```



### `Array.from()`
`Array.from(arrayLike[, mapFn[, thisArg]])`

> - 將偽陣列轉為陣列的方法
>   `Array.from([array-like])` => `Array [xx,xx,xx]`
>   - `arrayLike` : 偽陣列
>   - `mapFn` : Array.prototype.map 方法
>     - 類似 map 的用法 : 執行callback, 對陣列的值做處理, 返回新陣列
>     - `map(callback(curValue[, index[, array]]){}[, thisArg])`
>       - 因為偽陣列不是陣列, 所以 callback 第三個參數我測試是 undefined
>   - `thisArg` : 執行方法時, 改變 this 指向

```javascript=
let fakeArr = {
  '0': 1,
  '1': 2,
  'length': 2, // 偽陣列必須要有這個, 否則轉出來是空陣列
}

let obj = {};
let arr = Array.from(fakeArr, v=>v*2);
let arr2 = Array.from(fakeArr, function (v, i, arr) {
  console.log(v, i, arr, this);
  // 1 0 undefined {} 
  // 1 0 undefined {}
    // 注意1. 
      // arr 是沒東西的, 只有兩個實參進來
    // 注意2. 
      // 如果 function callback 改箭頭函數, 
      // 那第三個參數 (thisArg) 改 this 也沒用, 因為箭頭沒有 this
}, obj)


console.log(arr);
  // (2) [2, 4]
console.log(arr2);
  // (2) [undefined, undefined] -> 我沒有 return 東西
  
  
// 只要轉換後有 length 幾乎都可以
console.log(Array.from('123')); // ["1", "2", "3"]
console.log(Array.from(document.getElementById('tmp').getElementsByTagName('li')));
  // [li, li, li]
console.log(Array.from(1)); // [] -> 必須要有 length
```



### 原型方法
`console.dir(Array.prototype)`

### copyWithin
`arr.copyWithin(target[, start[, end]])`
> - 複製某些位置的值來替換掉某些位置的值

> - 第一個參數為要從第幾個開始替換
> - 第二個參數為從第幾個開始複製
> - 第三個參數為複製到第幾個 (不包括該參數的位置)

> - 會改變原陣列
```javascript=
let arr = [0,1,2,3,4,5,6,7,8,9]
arr.copyWithin(5,3,5);
console.log(arr); // [0, 1, 2, 3, 4, 3, 4, 7, 8, 9]
  // [0,1,2,3,4,5,6,7,8,9] 
  // param1 = 5 -> 第五個位置開始替換
  // param2,3 = 3,5 -> 替換成地 [3~5) 個參數, 意即 [3],[4]


arr.copyWithin(7, 3);
console.log(arr); // [0, 1, 2, 3, 4, 3, 4, 3, 4, 3]
  // [0, 1, 2, 3, 4, 3, 4, 7, 8, 9]
  // 第一個參數為 7 -> 第七個參數開始替換
  // 替換成 3, -> 第三個參數沒寫默認為最後, 意即 [3],[4]...[9]
  // 超過直接截掉
    // [0, 1, 2, 3, 4, 3, 4, 7, 8, 9]
    //                       3, 4, 3, 4, 7, 8, 9
    // [0, 1, 2, 3, 4, 3, 4, 3, 4, 3]
```

### Array.prototype.fill()
`arr.fill(value[, start[, end]])`
> - 以指定值來替換陣列的某些值

> - 第一個參數為指定值
> - 第二三參數為頭尾, 包前不包後, 不寫預設為 0 跟 尾

> - 改變原陣列
```javascript=
let arr = [0,1,2,3,4,5]
arr.fill('apple');
console.log(arr); 
  // ["apple", "apple", "apple", "apple", "apple", "apple"]
  // 沒寫二三參 -> 全改
arr.fill('cat', 2, 4);
console.log(arr); 
  // ["apple", "apple", "cat", "cat", "apple", "apple"]
  // 二三餐包前不包後 [2], [3]
```





### includes()
`Array.prototype.includes(valueToFind[, fromIndex])`
> - `fromIndex` 如果給負值, 那就會從 `length+(fromIndex)` 開始找

```javascript=
let arr = [1,3,5,7];
let res = arr.includes(3);
console.log(res); // true
res = arr.includes(10);
console.log(res); // false

res = arr.includes(3, 2);
console.log(res); // false
res = arr.includes(3, -3); // 4 + (-3) = 1
console.log(res); // true

// ES5: some()
res = arr.some(v => v == 3);
console.log(res); // true
res = arr.some(v => v == 10);
console.log(res); // false
```


> ### callback 系列
> - 每個 function 的 this 都指向 window
> - 除了 reduce 跟 reduceRight 以外, 都可以使用第二參數改變 this 指向


### Arror.prototype.map
`arr.map(function (value, item, arrayObj){})`
> - 創建一個新陣列, 調用 callback 後, return 的結果一個一個丟進==新陣列==
> - 進去幾個出來幾個
```javascript=
let arr = [1,2,3]; // 進去三個
let obj = {};

let arr2 = arr.map(function(v,i,a) {
  console.log(v,i,a, this)
    // 1 0 (3) [1, 2, 3] {} 
    // 2 1 (3) [1, 2, 3] {}
    // 3 2 (3) [1, 2, 3] {}
 
  return v * 2
}, obj)
console.log(arr2);  // (3) [2, 4, 6] //=> 出來三個
```


### filter()
`arr.filter(callback(element[, index[, array]])[, thisArg])`
> - 過濾陣列
> - 遍歷陣列, 每次遍歷如果返回 true 就留下該值, false 就不留
> - 返回一個新陣列, 不改變原陣列

```javascript=
let arr = ['a',1,{a:1}];

arr2 = arr.filter(function (v,i,a) {
  console.log(v,i,a);
    // a 1 ["a", 1, {…}]
    // 1 1 ["a", 1, {…}]
    // {a: 1} 2 ["a", 1, {…}]
  return typeof v === 'number'
})
console.log(arr); // ["a", 1, {…}]
console.log(arr2); // [1]
```



### find()
`Array.prototype.find(callback(v,i,a)[, thisArg])`
> - 參數意義都ㄧ樣
> - 找出『第一個』符合條件的(返回 true), 沒有找到 `undefined`
> - 找到即返回該『==值==』, 結束循環
>   vs. `some()` : 返回 `boolean`

```javascript=
let arr = [
  {
    id: 1,
    name: 'GodJJ',
  },
  {
    id: 1,
    name: 'Bebe',
  },
]

let tmpArr1 = arr.find(v => v.id == 1);
let tmpArr2 = arr.find(v => v.id == 2);

console.log(tmpArr1); 
  // {id: 1, name: "GodJJ"} -> BeBe 沒返回 -> 找到一個即停止循環
console.log(tmpArr2); // undefined -> 沒找到返回 undefined
```



### findIndex
`Array.prototype.findIndex(callback[, thisArg])`
> - 返回 ==第一個== 符合條件的『==索引==』, 沒找到返回 -1

```javascript=
let arr = [1,3,5,7];
let index = arr.findIndex(v => v>=5);
let index2 = arr.findIndex(v => v>=10);
console.log(index); // 2
console.log(index2); // -1
```



### every()
`arr.every(callback(element[, index[, array]])[, thisArg])`
> - 遍歷陣列, 只要有一個是 false, 則立即返回 fasle
```javascript=
let tmp = [1,2,3].every(v=> typeof v === 'number');
console.log(tmp); // true
tmp = [1,'3',2].some(v=> {
	console.log(v);
	return typeof v === 'number'
})
console.log(tmp); 
// 1 '3' 
// false
```

### some()
`arr.some(callback(element[, index[, array]])[, thisArg])`
> - 遍歷陣列, 只要有一個是 true, 則立即返回 true
```javascript=
let tmp = [1,'3',2].some(v=> {
	console.log(v);
	return typeof v === 'number'
})
console.log(tmp);
  // 1
  // true
```

### Array.prototype.reduce()
`arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])`
> - `accumulator` : 累積值
>   - 說白了就是一個過程
>   - 算術時, 22+24+84+99+... 通常都會兩個兩個加, 
>     - 46+84+99 => 130+99 => 219
>     - 而這個兩個兩個加的和, 就是這個參數代表的意義
> - `currentValue` : 當前值
> - `initialValue` : 初始值

> - 返回值為最終的和

> - `Array.prototype.reduceRight()` 一模一樣, 只是是從右邊向左操作
```javascript=
// 有初始值
let tmp = [1,2,3].reduce((prev, item, index, array)=> {
  console.log(prev, item, index, array);
  return prev + item
},10);
console.log(tmp);
  // 10 1 0 [1, 2, 3]
  // 11 2 1 [1, 2, 3]
  // 13 3 2 [1, 2, 3]
  // 16


// 沒有初始值 -> 少一次循環, 第一個值會被拿去當初始值
tmp = [1,2,3].reduce((prev, item, index, array)=> {
  console.log(prev, item, index, array);
  return prev + item
});
console.log(tmp);
  // 1 2 1 [1, 2, 3]
  // 3 3 2 [1, 2, 3]
  // 6
  
// 非數字類型也會做拼接
tmp = ['a','b','c'].reduce((prev, item, index, array)=> {
  console.log(prev, item, index, array);
  return prev + item
});
console.log(tmp);
  // a b 1 ["a", "b", "c"]
  // ab c 2 ["a", "b", "c"]
  // abc
```
> - 練習: 求一個平均值
```javascript=
let arr = [33,52,34,11];
let ave = arr.reduce((tmp, value, item)=>{
  if (item < arr.length-1) { // -1 是因為 tmp 將 [0] 拿來當底了
    return tmp + value
  } else {
    return (tmp+value) / arr.length
  }
})

console.log(ave); // 32.5
```


### Array.prototype.keys()
`arr.keys()`
> - 返回的是一個迭代器對象
> - 使用方法: 搭配 `for of` 迭代陣列

> - `for of` 為 ES6 的方法, 主要用來遍歷 Array
>   - vs. `for in` 遍歷 Object

```javascript=
let arr = ['a','b','c']
let tmp = arr.keys();
console.log(tmp, arr);
  // Array Iterator {} ["a", "b", "c"]
  // - 返回一個陣列迭代器對象
  // - 沒有對原陣列幹嘛


// for of 迭代陣列
  // 會迭代每個值
  // String 
for (let k of arr) {
  console.log(k, 'of');
    // a of
    // b of
    // c of
}
// for of 使用 keys
  // 迭代每個索引
  // 返回的索引是數字類型
for (let k of arr.keys()) {
  console.log(k, typeof k, 'of keys');
    // 0 "number" "of keys"
    // 1 "number" "of keys"
    // 2 "number" "of keys"
}


// for in 迭代陣列
  // 把陣列當成對象的方式來迭代
  // 返回的是對象形式的 k:v 的 k
  // 所以返回的是 string 的 鍵值
for (let k in arr) {
  console.log(k, typeof k, 'in');
    // 0 string in -> '0': 'a' 的 '0'
    // 1 string in
    // 2 string in
}

// for in 使用 keys() -> 沒有反應~~~
for (let k in arr.keys()) {
  console.log(k, typeof k, 'in keys');
}


// 小結: 
// - 迭代陣列可以使用 for of
// - 迭代對象可以使用 for in
```


### Array.prototype.entries()
`arr.entries()`
> -

```javascript=
let arr = ['a','b','c']
let tmp = arr.entries();
console.log(tmp); // Array Iterator {} -> 返回一個迭代器

// 迭代陣列返回陣列 [index, value]
for (let k of arr.entries()) {
  console.log(k);
    // [0, "a"]
    // [1, "b"]
    // [2, "c"]
}

// 常用解構來拿值
for (let [i,v] of arr.entries()) {
  console.log(i,v);
    // 0 "a"
    // 1 "b"
    // 2 "c"
}
```










## Template literals ( Template strings )
> - 使用 `` 來代替 '' 或 ""
> - 好處
>   - 可以解析變量 `${}`
>   - 可以多行
>   - 可以調用函數
```javascript=
let tempStr = `tmStr`;
console.log(tempStr); // tmStr

// --------解析變量----------------- //
let obj = {
  name: 'GodJJ',
  play: 'AD',
}
// 傳統做法: 使用 + 拼接
console.log(obj.name + '打' + obj.play); // GodJJ打AD

// 解析變量, 直接使用 ${}
console.log(`${obj.name}打${obj.play}`); // GodJJ打AD


// ----------換行--------------- //

// 傳統換行: 使用 \n 之類的空白字符
console.log(
  obj.name + '\n' +
  obj.age + '歲' + '\n' +
  '打' + obj.play
)

// Template strings 直接換
// 在寫一些 HTML 標籤就很方便
console.log(`${obj.name}
${obj.age}歲
打${obj.play}
`)

// ---------調用函數----------------- //
function fn() {
	return '打我啊笨蛋'
}

console.log(`你說: ${fn()}`); // 你說: 打我啊笨蛋
```


## string 方法
### includes
`str.includes(searchString[, position])`
> - 查找 str 有沒指定 str
> - `position` 如果傳非數字, 會轉成數字 `Number()`
> - 返回 boolean
```javascript=
console.log('abcd'.includes('a')); // true
console.log('abcd'.includes('a', 1)); // false
console.log('abcd'.includes('a', '1')); // false
console.log('abcd'.includes('a', null)); // true -> Number(null) -> 0
```

### startsWith() & endsWith()
`str.startsWith(searchString[, position])`
`str.endsWith(searchString[, length])`
> - 判斷是否為 xx 開頭 / 結尾
> - 返回 boolean

> - `startsWith` 第二個參數為第幾個位置, 亦即第幾個位置開始是否為 xx
> - `endsWith` 第二個參數為長度, 意即 match 的長度
```javascript=
let str = 'GaLaGAGA';
console.log(str.startsWith('GaLa')); // true
console.log(str.startsWith('LaGa')); // false
console.log(str.endsWith('GAGA')); // true
console.log(str.endsWith('LALA')); // false


console.log('abcd'.startsWith('b', 1)); // true
  // 第一位開始的 str ('bcd') 是否為 'b' 開頭
console.log('abcd'.startsWith('b', 2)); // false
console.log('abcd'.endsWith('cd', 3)); // false
  // 查找長度為 3
  // 意即 'abc' 的結尾是否為 'cd' -> false
console.log('abcd'.endsWith('cd', 4)); // true
```

### repeat()
`str.repeat(次數)`
> - 重複 str
> - 不可以是 負數 或 Infinity
> - 小數向下取整 `Math.floor?`
>   - (0~-1) 之間, 全部都會取整為 0 
```javascript=
console.log('a'.repeat(3)); // 'aaa'
// console.log('a'.repeat(-3)); // Invalid count value
console.log('a'.repeat(0.3)); // -> 取整數 0
console.log('a'.repeat(1.3)); // 'a'
console.log('a'.repeat(-0.3)); // -> 取整 0
```

### padStart & padEnd
`str.padStart(targetLength [, padString])`
`str.padEnd(targetLength [, padString])`

> - ES7 的方法
> - 補全字符串到指定長度, 如果長度超過, 返回原字符串
> - 第二參數為指定字符串, 如果沒給預設為 ' '
```javascript=
// 如果沒有給第二參數, 補全 ' ' 到長度 5
console.log('ab'.padStart(5)); // '   ab'
console.log('ab'.padEnd(5)); // 'ab   '

// 指定 'cd' 補到長度 5, 補滿即返回
console.log('ab'.padStart(5,'cd')); // 'cdcab'
console.log('ab'.padEnd(5,'cd')); // 'abcdc'


// 指定長度小於原字符串
console.log('abcde'.padStart(3)); // 'abcde'
console.log('abcde'.padEnd(3)); // 'abcde' 
```


## Function
### 參數
> #### 參數默認值
```javascript=
// - 以前防止沒傳參數的做法
function fn(x) {
  x = x || 3;
  console.log(x);
}

// 問題: 參數傳轉 Boolean 會變成 false 的值時, 還是會使用預設值
fn(0); // 3
fn(null); // 3
fn(undefined); // 3
fn(''); // 3
fn(NaN); // 3

// - 參數直接寫默認值
  // 只有傳 undefined ( 或沒傳 ) 會走默認值
function fn2(x=3) {
  console.log(x);
}
fn2(0); // 0
fn2(null); // null
fn2(undefined); // 3
fn2(''); // ''
fn2(NaN); // NaN
```
> - 一般有默認值得形參都會放後面
```javascript=
// 因為如果我要傳實參給後面的形參, 那這個參數用起來就很麻煩, 因為每次都要傳 undefined
function fn3(x=1, y) {
  console.log(x,y);
}
// fn3(,10); // Unexpected token ','
fn3(undefined, 10); // 1 10
```
> #### 剩餘參數
```javascript=
function fn4(...args) {
  console.log(arguments); // 偽陣列
  console.log(args); // 陣列
}
fn4(1,2,3);
  // Arguments [1, 2, 3, callee: (...), Symbol(Symbol.iterator): ƒ]
    // __proto__: Object 
  // [1, 2, 3]
    // __proto__: Array(0)
```
> - 參數默認會影響函數的 length 屬性
>   - length 紀錄形參個數
```javascript=
function fn(x,y) {}
console.log(fn.length) // 2

function fn2(x,y=1){}
console.log(fn2.length); // 1

function fn3(x, ...args){}
console.log(fn3.length); // 1 -> 剩餘參數也不計在函數長度裡
```
> - 剩餘參數必須在最後
```javascript=
let fn = (a,b,...args,c)=>{}
  // Rest parameter must be last formal parameter
fn(1,3,55,33,22,44);
```
> - 練習: 搭配拆解陣列來玩
```javascript=
let fn = (...args)=>{
  // args = [xx,xx,xx]
  // 再擴展運算把 [] 拆掉當成調用 fn2() 的參數
  fn2(...args);  // xx,xx,xx
}

let fn2 = (...args)=>{
  console.log(args);
}
fn(33,442,2,3);  // [33, 442, 2, 3]
```



> #### 參數解構附值
>   - 簡單寫一下, 詳細寫在解構附值的地方
```javascript=
// 參數陣列解構
function fn([x,y]) {
  console.log(x,y)
}
fn([1,2]) // 1 2

// 參數對象解構
function fn2({a:b, c:d}) {
  console.log(b,d);
}
fn2({a:1, c:2}); // 1 2
```
> #### 參數默認值作用域

```javascript=
var  a = 1;
function fn(x=a,y=x) {
  console.log(x,y);
  var a = 10;
}
fn(); // 1 1
fn(2); // 2 2

// 調用函數 fn() 創造一個私有作用域後
// - 一開始會在私有作用域聲明形參的變量並賦值
// - 此時會先從實參開始找有沒有
// - 找不到後再去全局找
// - 真的沒有就報錯 => not defined -> 注意是報找不到歐

// fn() 沒有參數, 表示傳了 undefined 進去後, 開始走默認值a, 
// 此時開始找 a 變量有沒有, 沒有! 所以找全局 a = 1, 故 x = a = 1;
// 接著聲明 y , y 也走默認值 x, 而此時私有變量 x 剛剛已經聲明可以用了
// 故 y = x = 1;

// fn(2); -> 有對第一個參數傳 2, x 沒有走默認值, 直接拿下 2 => x = 2
// y 走默認值 y = x = 2;
```

```javascript=
// !!! 我還沒搞懂為什麼 !!!
var  x = 1,y = 2;
function fn(x=x,y=y) {
  console.log(x,y);
  var x = 10, y = 20;
}
fn(); // Cannot access 'x' before initialization

// 換全局換名字就可以了
// 如果換名字就可以, 表示他是會往全局找的啊, 那為啥...


// let z = z // Cannot access 'z' before initialization 
// 從這個來看, 表示他應該會先處理右邊?
// 那參數 x=x 是否應該也是先處理右邊, 而 x 在全局就搜得到了, 所以應該是 x = x = 1
// 但是報錯了~~~~
```
```python
In [1]: a = 10
In [2]: def fn(a=a): 
   ...:     print(a) 
   ...:                               
In [3]: fn() # 10 -> python 正常
```



### 函數名
```javascript=
// - 各種創建函數的名稱
function fn() {}
console.log(fn.name); // 'fn'

let fn2 = function () {}
console.log(fn2.name); // 'fn2'


console.log((function () {}).name); // ''


let fn3 = fn.bind(null);
console.log(fn3.name); // 'bound fn'


let fn4 = new Function();
console.log(fn4.name); // 'anonymous'



// 複習:
  // new Function('形參', '函數體');
// 面試題:
  // 有一個字串: let str = '[1,2]';
  // 請使用 let arr = new Function() 的方法讓 arr 得到一個陣列
  
let str = '[1,2,3]';
let arr = (new Function('return' + str))();

console.log(arr); // [1,2,3]

  // 解答: 
  // (function (){
  //    return [1,2,3]
  //  })()
```

## Object

### 簡寫
```javascript=
let a = 10, b = 20;
let c = 'wow';

// - 對象裡面的 k:v 簡寫
  // 如果k = v, 那就可以寫一個就好了
let obj = {a,b}; 
  // {a:a, b:b}
console.log(obj); // {a: 10, b: 20}

// - 對象裡面的函數簡寫
let obj2 = {
  fn(){},
  // fn: fn(){}
}
console.log(obj2); // {fn: ƒ}


// - 使用變量的值當屬性名
let obj3 = {
  c:a, // c: 10 -> 直接寫, 會被拿去轉成字串當屬性名
  [c]:a, // wow: 10 -> 找到該變量, 拿值
  [c+'IntheWorld']:a // wowIntheWorld: 10 -> 拼接也沒問題~
}
console.log(obj3); // {c: 10, wow: 10, wowIntheWorld: 10}

  // 記法: obj[c]
```
### 函數
> #### 前情提要
> - `Object()` 本身會將參數做成一個對象返回
```javascript=
console.log(Object(1)); // Number {1} -> 對象
console.log(Object('1')); // String {"1"} -> 偽陣列
console.log(Object(true)); // Boolean {true} -> 對象
console.log(Object(null)); // {}
console.log(Object(undefined)); // {}
console.log(Object(NaN)); // Number {NaN}


console.log(Object([1])); // [1] -> 陣列
console.log(Object({a:1})); // {a: 1} -> 對象
console.log(Object(function(){})); // ƒ (){} -> 函數
```
> - `console.dir(Object)`
### `Object.is()`
> #### 前情提要
> - `===` 的問題
```javascript=
// - NaN不等於任何人
console.log(NaN === NaN); // false
// - -0 = 0
console.log(-0 === 0); // true
```
`Object.is(value1, value2);`
> - is 解決了這個問題
> - 判斷兩個值是否相同
```javascript=
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(-0, 0)) // false
```
### `Object.assign()`
`Object.assign(target, ...sources)`
> - 淺拷貝對象
> - 將後面參數拷貝一份給目標參數,
>   如果後面有多個參數, 依序拷貝
> - 屬性名重複, 後值蓋前值

> - 直接改變第一個參數對象
> - 返回第一個參數對象的址
```javascript=
let obj = {a:1};
let obj2 = {a:2, b:20};
let obj3 = {a:3, c:30};

let obj4 = Object.assign(obj, obj2, obj3);
console.log(obj, obj2, obj3); 
  // {a: 3, b: 20, c: 30} {a: 2, b: 20} {a: 3, c: 30}
  // 會直接改變第一個參數的對象值
console.log(obj4); // {a: 3, b: 20, c: 30} => 返回第一個參數的址
```

> - 深拷貝問題
```javascript
let a = {a:1, b: {c:2}}
let b = Object.assign({}, a);

b // {a: 1, b: {…}}
b===a // false
b.b === a.b // true => 淺拷貝

// MDN 作法
let c = JSON.parse(JSON.stringify(a))
c // {a: 1, b: {…}}
a === c // false
a.b === c.b // false => 深拷貝
```



### ES7 對象的擴展運算符
```javascript=
let obj = {a:1};
let obj2 = {a:2, b:20};
let obj3 = {a:3, c:30};

let obj4 = {...obj,...obj2,...obj3};
console.log(obj4); // {a: 3, b: 20, c: 30}
```

### `Object.getOwnPropertyDescriptor()`
`Object.getOwnPropertyDescriptor(obj, prop)`
> - 查詢指定對象的指定屬性的描述符
> - 屬性是由 name 跟 屬性描述符 (property descriptor) 組成

> - property descriptor 包含: 
>   - `value` : 該屬性值
>   - `writable` : 該屬性值可否更動
>   - `get` `set` -> 還沒看懂
>   - `configurable` : 該屬性描述可不可被改, 屬性可否被刪除
>   - `enumerable` : 該屬性會不會被遍歷
```javascript=
console.log(Object.getOwnPropertyDescriptor('123', 'length'));
/*
value: 3
writable: false -> 值不能被改, 所我下面試著改值看看
enumerable: false
configurable: false
*/
var str = '123';
console.log(str.length = 0); // 0 -> 我有改歐~
console.log(str); // '123' -> 沒變~~


let arr = [1,2,3];
console.log(Object.getOwnPropertyDescriptor(arr, 'length'));
/*
value: 3
writable: true -> 可以改
enumerable: false
configurable: false
*/
arr.length = 0;
console.log(arr); // [] -> 清掉了~~
```
### `keys()` `values()` `entries()`
`Object.keys(obj)`
`Object.values(obj)`
`Object.entries(obj)`
> - 返回對象所有==可遍歷==屬性的 鍵/ 值/ \[鍵,值] 組成的陣列
```javascript=
let arr = [1,2,3]; // 這是一個陣列對象
console.dir(arr);
  // 0: "a"
  // 1: "b"
  // 2: "c"
  // length: 3
  // __proto__: Array(0)

// 下面都指只有返回三組 
// 因為 'length' 是不能遍歷的
console.log(Object.keys(arr)); // ["0", "1", "2"]
console.log(Object.values(arr)); // ["a", "b", "c"]
console.log(Object.entries(arr)); // [["0", "a"], ["1", "b"], ["2", "c"]]


console.log(Object.getOwnPropertyDescriptor(arr, 'length'));
  // enumerable: false
```

### set & get 對象方法
```javascript=
// 測試
let obj = {
  test:'test',
  set name(v) {
    console.log(v, 'set');
    this.test = v; // 設置 name 屬性時會將值傳到這個形參
    return 'setReturn' // 感覺不到有什麼用~~
  }, get name(){
    console.log('get');
    return 'getReturn' // 如果沒有這個, 返回 undefined,
                       // 表示 obj.name = 'haha' 並沒有附值給 name 屬性
                       // 而是將值傳進 set 方法的形參而已
                       // 要將 name 屬性賦值, 必須依靠 get 的 return
  }
}



console.log(obj.name = 'haha');
  // haha set
  // haha
console.log(obj.test);
  // haha  

console.log(obj.name);
  // get
  // getReturn



// 整理邏輯
let obj = {
  _name: null,
  set name(v){
    // this.name = v //=> 死循環!!! 因為不斷的設置 name 屬性
    this._name = v
  },
  get name(){
    return this._name
  }
}

obj.name = 'haha'; // 將值傳到形參 v, 再將值賦給 _name 屬性
console.log(obj.name); // 調用時返回 _name 屬性
```

## Symbol
> - 基礎數據類型
> - 類似 String
### 創建
> - 使用 `Symbol()` 函數來創建
> - Symbol 可以丟一個參數來描述這個 Symbol 數據
```javascript=
let sym = Symbol();
let sym2 = Symbol('haha');
let sym3 = Symbol(1);
console.log(sym, sym2, sym3); // Symbol() Symbol(haha) Symbol(1)
console.log(typeof sym); // symbol
```
### 特色
> #### 每個 Symbol 數據都是不同的
> - 即使表面上看起來一樣
```javascript=
let str = 'a';
let str2 = 'a';
console.log(str == str); // true


let sym = Symbol('p');
let sym2 = Symbol('p');
console.log(sym == sym2); // false
```
> #### Symbol 無法進行運算
```javascript=
console.log(Symbol(1)+1);
  // Cannot convert a Symbol value to a number
  
// 拼接也是運算, 所以不行
console.log(Symbol(1)+'1'); 
  // Cannot convert a Symbol value to a string
```
> #### Symbol 無法轉成數字
```javascript=
Number(Symbol(1)); // Cannot convert a Symbol value to a number
```

> #### Symbol 可以轉成字符串
```javascript=
console.log(String(Symbol(1))); // 'Symbol(1)'
console.log(Symbol(1).toString()); // 'Symbol(1)'
```

> #### Symbol 可以轉成 Boolean
```javascript=
console.log(!Symbol(1)); // false

// 描述符對 Symbol(false) 整體的值好像沒有差別
// 轉 Boolean 就是 true
console.log(Boolean(Symbol(false))); // true
```
### 用法
> - 因為每個數據都不同, 所以常用於對象的屬性名上, 
>   避免被別人修改值, 又或者新增屬性名時, 跟別人一樣而改到別人的值
> - Symbol() 當成屬性名時, 是==匿名==且==無法被枚舉==的
>   - 訪問方法1. 創建時指向的變量
>   - 訪問方法2. `Object.getOwnPropertySymbols()`
```javascript=
let sym = Symbol();

let obj = {
	[sym]: 'test',
	'a': 1,
}

// 無法被枚舉~~
for (let k in obj) {
	console.log(k);
} // a
console.log(Object.getOwnPropertyNames(obj)); ['a']

// 訪問的方法
  // 1. 直接訪問創建時指向的變量
console.log(sym);
  // 2. Object.getOwnPropertySymbols() 方法
console.log(Object.getOwnPropertySymbols(obj));
```


### `Symbol.for()` 
> - 使用 `Symbol.for()` 創建時, 
>   會先查找有無一樣使用 `Symbol.for()` 創建且參數相同的
>   如果找到就返回該對象, 如果沒有就創建一個
```javascript=
let sym = Symbol.for('sym');
let sym2 = Symbol.for('sym');
let sym3 = Symbol('sym');
let sym4 = Symbol('sym');

console.log(sym === sym2); // true -> 兩個址是一樣的 !!!
console.log(sym3 == sym4); // false -> Symbol() 跟誰都不一樣
console.log(sym == sym3); // false
```

### `Symbol.keyFor()`
> - 查找使用 `Symbol.for()` 創建時的參數, 沒有就 undefined
```javascript=
let sym = Symbol.for('sym');
let sym2 = Symbol('sym2'); // 不是使用 for 創建
console.log(Symbol.keyFor(sym)); 'sym'
console.log(Symbol.keyFor(sym2)); undefined // undefined
```


## set
> - 類似陣列
> - 只有value, 沒有 key
> - 會去重複
### 生成
`new Set([iterable])`
> - 參數接受陣列或類陣列(只要有 iterable API 都行)
>   - 有 iterable API : 陣列, argumnets, 元素集合, Set, Map, String
```javascript=
let s = new Set();
console.log(s); // Set(0) {}
console.log(s.size); // 0

s = new Set([1,2,3,3]);
console.log(s); // Set(3) {1, 2, 3} -> 去掉重複的 3
console.log(s.size); // 3
```

### Set 轉 Array
> - 擴展運算符
```javascript=
let s = new Set([1,2,3,3]);
console.log(s); // Set(3) {1, 2, 3}
let arr = [...s]; // 擴展運算
console.log(arr); // (3) [1, 2, 3]
```

### Set 方法
> #### `add(v)` : 
> - 添加值, 返回 Set 實例本身
> - 參數一次只能加一個, 兩個以上後面都無視
> #### `delete(v)` : 
> - 刪除值, 返回 boolean, 表示有無刪除成功
> #### `has(v)` : 
> - 查詢值的有無, 返回 boolean 表示有無
> #### `clear()` : 
> - 清除所有值, 沒有返回值
```javascript=
var s = new Set();

// add()
var a = s.add('G').add('o').add('d').add('J').add('J');
console.log(s); // Set(4) {"G", "o", "d", "J"}
console.log(a); // Set(4) {"G", "o", "d", "J"}

// delete()
var d = s.delete('G');
var d2 = s.delete('A');
console.log(d, d2); // true false
console.log(s); // Set(3) {"o", "d", "J"}

// has()
var h = s.has('J');
var h2 = s.has('A');
console.log(h,h2); // true false

// clear()
var c = s.clear();
console.log(s); // Set(0) {}
console.log(c); // undefined
```


> #### `forEach(cbFn(v,i,a){}, this)`
> #### `keys()`
> #### `values()`
> #### `entries()`
> - 用法一模一樣
> - 差別在於 value 跟 index 都是當前的 value
>   因為 Set 只有 value 沒有 key

```javascript=
var s = new Set(['G', 'O', 'D']);
var obj = {};
s.forEach(function (v,i,s) {
  console.log(v,i,s, this);
  // G G Set(3) {"G", "O", "D"} {}
  // O O Set(3) {"G", "O", "D"} {}
  // D D Set(3) {"G", "O", "D"} {}
  
    // callcack 一樣三個參數, thisArg 一樣可以改 this 指向
}, obj)



// 只有 value 沒有 k ~~~
for (let k of s.keys()) {
	console.log(k);
    // G
    // O
    // D
}
console.log('----');
for (let v of s.values()) {
	console.log(v);
    // G
    // O
    // D
}
console.log('----');
for (let [v,k] of s.entries()) {
	console.log(v,k);
    // G G
    // O O
    // D D
}
console.log('----');
```
### 應用: 去重
```javascript=
let arr = [1,2,2,6];
function rmRp(arr) {
  // return [...new Set(arr)]
  return Array.from(new Set(arr))
}
console.log(rmRp(arr));
```

> - 其他去重法 https://github.com/YvetteLau/Step-By-Step/issues/31 

### 應用: 聯集, 交集, 差集
> - 聯集 : 合併去重
> - 交集 : A 裡包含 B 者
> - 差集 : 聯集 - 交集
```javascript=
let arr = [1,2,3,6];
let arr2 = [1,4,6,9];

function add(arr, arr2) {
  return [...new Set([...arr,...arr2])]
}
console.log(add(arr, arr2)); //  [1, 2, 3, 6, 4, 9]


function and(arr,arr2) {
  return arr.filter(v=>arr2.includes(v))
}
console.log(and(arr,arr2)); // [1, 6]


function not(arr,arr2) {
  return add(arr, arr2).filter(v=>!and(arr,arr2).includes(v))
}
console.log(not(arr, arr2)); // [2, 3, 4, 9]
```

```javascript=
// 物件去重
let json1=[
  {id:1,name:"aaa"},
  {id:2,name:"bbb"},
  {id:3,name:"ccc"},
] 


let json2=[
  {id:1,name:"aaa"},
  {id:2,name:"bbb"},
  {id:4,name:"ddd"},
]

let json = json1.concat(json2); 

let newJson = []; 

for(item1 of json){ 
  let flag = true;  // 標記是否重複, false 為重複
  // 循環新陣列
  for(item2 of newJson) { 
    // 判斷是否重複
    if(item1.id == item2.id) { 
      flag = false;
    }
  }
  // 沒有重複就推進去
  if (flag) {
    newJson.push(item1);
  }
}

console.log("newJson",newJson);

```



## Map
`new Map([[k1,v1],[k2,v2]])`
> - Map 參數是一個陣列, 陣列的值是包著 key 跟 value 的陣列
> - Map 對象保存 key, value
>   - vs. Object:
>     - 對象的 key 必須為 String 或 Symbol, 非字串也都會轉成字串
>       Map 任何類型都能成為 key
>     - 對象是無序的, Map 是有序的
>     - Map 通過 size 屬性來記錄鍵值組個數, 對象則無
```javascript=
let arr = [1,2];
let o = {a:1};
let obj = {
  'a': 1,
  b:2,
  undefined: 3,
  null: 4,
  [arr]: 5,
  [o]: 6,
  true: 7,
  1:8,
}
console.log(obj);


let m = new Map([[1,1], [null,2],[[1,2],3],[{},4],[undefined, 5]])
console.log(m);
```
<img src='https://i.imgur.com/L5oMrmp.png' style='height: 200px'/> <img src='https://i.imgur.com/ZPCxcZa.png' style='height: 200px'/>
> - 屬性名如果重複, 後面的值會把前面蓋掉, 該對象依樣
```javascript=
let m = new Map([[1,1],[1,2]]);
console.log(m); 
  // Map(1) {1 => 2}
  // [[Entries]]
    // 0: {1 => 2}
      // key: 1
      // value: 2
  // size: 1
  // __proto__: Map
```


### 方法
> #### `get` `set` `has` `delete` `clear`
> - `get(k)` 
>   - 獲取 value
> - `set(k,v)`
>   - 如果已經有 k, 修改值
>   - 如果沒有 k, 新增 
>   - 會直接改變原對象, 且返回原對象的址
> - `has(k)`
>   - 判斷 key 有無對應 value
>   - 返回 Boolean 
> - `delete(k)`
>   - 刪除對應的 k,v
>   - 返回 Boolean 表示刪除是否成功
> - `clear()` 
>   - 清空
>   - 沒有返回值
```javascript=
let o = {};
let a = [];

let m = new Map([[o,1], [a,2], [undefined, 3], [null, 4]]);
console.log(m.get(o)); // 1
console.log(m.get(a)); // 2
console.log(m.get(undefined)); // 3
console.log(m.get(null)); // 4


console.log(m.set(o,5)); 
  // Map(4) {{…} => 5, Array(0) => 2, undefined => 3, null => 4}
console.log(m.set(1,6));
  // Map(5) {{…} => 5, Array(0) => 2, undefined => 3, null => 4, 1 => 6}
console.log(m);
  // Map(5) {{…} => 5, Array(0) => 2, undefined => 3, null => 4, 1 => 6}


console.log(m.has(1)); // true
console.log(m.has(true)); // false


console.log(m.delete(1)); // true
console.log(m.delete(1)); // false
console.log(m); 
  // Map(4) {{…} => 5, Array(0) => 2, undefined => 3, null => 4}


console.log(m.clear()); // undefined
console.log(m); // Map(0) {}
```


> #### `forEach` `keys` `values` `entries`
> - 搭配 `for...of...` 使用
> - 每次循環都會返回一組 \[k,v]
```javascript=
let o = {};
let a = [];

let m = new Map([[o,1], [a,2], [undefined, 3], [null, 4]]);


m.forEach((v,i,a)=> {
  console.log(v,i,a);
    // 1 {} Map(4) {{…} => 1, Array(0) => 2, undefined => 3, null => 4}
    // 2 [] Map(4) {...}
    // 3 undefined Map(4) {...}
    // 4 null Map(4) {...}
})

console.log('------')
for (let k of m.keys()){
  console.log(k);
    // {}
    // []
    // undefined
    // null
}
console.log('------')
for (let v of m.values()){
  console.log(v);
    // 1
    // 2
    // 3
    // 4
}

console.log('------')
for (let [k,v] of m.entries()){
  console.log(k,v);
    // {} 1
    // [] 2 
    // undefined 3
    // null 4
}
```

### 面試題: 將一個 Array 變成 Map
> - Array 的 key 是索引
> - 拿索引當Map 的 k 即可
```javascript=
let arr = ['a','b','c'];
let map = new Map();
for (let [k,v] of arr.entries()) {
  map.set(k,v);
}
console.log(map);
  // 0: {0 => "a"}
  // 1: {1 => "b"}
  // 2: {2 => "c"}
```

## Proxy
`new Proxy(target, handler);`
> - 對目標對象攔截操作, 以此改變原本預設的操作
> - 參數
>   - `target` : 目標對象, (對象都行, 甚至 Proxy 也行)
>   - `handler` : 攔截後要做的事, 總共有十三種方法

### handler
> #### `get` `set` `has`
> - `get(target, propKey, receiver)` : 
>   - `target` : 目標對象
>   - `propKey` : 被指定操作的屬性名
>   - `receiver` : 當前 Proxy 實例, this
>   - 獲取對象屬性的值時, 觸發這個處理
> - `set(target, propKey, value, receiver)` : 
>   - `value` : 賦予的值
>   - 新增或修改屬性時, 觸發
> - `has(target, propKey)` : 
>   - 查詢是否有指定 k 時, 觸發
```javascript=
let obj = {a:1, b:2};

let proxy = new Proxy(obj, {
  // 以下以操作原本該有的操作為主
  get(t, p, r){
    console.log('get', arguments);
      // get Arguments(3) [{…}, "a", Proxy, ...]
      // get Arguments(3) [{…}, "b", Proxy, ...]
      // get Arguments(3) [{…}, "c", Proxy, ...]
    
    return t[p] // get return 什麼就是什麼, 沒有 return 就undefined
  }, 
  set(t,p,v,r){
    console.log('set', arguments);
      // set Arguments(4) [{…}, "a", 3, Proxy, ...]
      
    t[p] = v; // 把拿到的值賦給指定的對象的屬性名
    return 'set' // set 設return 好像沒什麼效果?
  }, 
  has(t,p){
    console.log('has', arguments);
      // has Arguments(2) [{…}, "a", ...]
      // has Arguments(2) [{…}, "__proto__", ...]
      // has Arguments(2) [{…}, "z", ...]
    
    if (p.startsWith('_')) { // 如果是私有變量名, 返回 false
      return false
    }
    return p in t // 如果有就返回 true
  } 
})

console.log(proxy.a); // 1
console.log(proxy.b); // 2
console.log(proxy.c); // undefined


console.log(proxy.a = 3); // 3
console.log(obj.a); // 3 
                      // obj 改了~
                      // 注意, 我直接對原對象操作時, 是不會觸發這些方法的


console.log('a' in proxy); // true
console.log('__proto__' in proxy); // false
console.log('z' in proxy); // false
```

> #### `apply`
> - `apply(target, object, args)`
>   - `object` : 指定 this 對象
>   - `args` : 傳進來的實參
>   - 執行的時候(函數直接執行, call, apply, ...) 觸發
```javascript=
function fn() {
  console.log(this);
}

let proxy = new Proxy(fn, {
  apply(target, object, args){
    console.log('apply', arguments);
      
    // 判斷有沒有指定改變 this 指向
    if (object) {
      // target.call(object, ...args); // 可以用現成的
      
      // 也可以自己寫, 
      // 最好加到原型上, 否則調用後才刪除這個對象, 
      // console.log(this) 的時候會看到
      object.__proto__.fn = target;
      object.fn();
      delete object.__proto__.fn
    } else {
      target(...args);
    }
  }
})

let obj = {};

proxy(1,2,3);
  // apply Arguments(3) [ƒ, undefined, Array(3), ...]
  // Window
proxy.call(obj, 4,5);
  // apply Arguments(3) [ƒ, {…}, Array(2), ...]
  // {}
proxy.apply(obj, [6,7]);
  // apply Arguments(3) [ƒ, {…}, Array(2), ...]
  // {}
```


## class
> - 以前構造函數
> - 這套是大神們所研究出來的 SOP, 而非官方的, 所以使用起來才那麼麻煩
```javascript=
// 父構造函數
function Father(x,y) {
  this.x = x;
  this.y = y;
}

// 添加共用方法要寫到原型上
Father.prototype.show = function () {
  console.log(this.x, this.y);
}

// 子構造函數繼承
function Son(x,y) {
	Father.call(this,x,y); // 使用 call 拿到 Father 實例的東西
}
Son.prototype = new Father(); // 使用 Father 的原型方法
Son.prototype.constructor = Son; // 別迷失了自己

let s = new Son(5,6);
s.show(); // 5 6
```
```javascript=
// - 函數: return 簡單數據類型
function fn() {
  this.a = 10;
  return 1 // return 簡單數據類型, 不會影響實例

}

// - fn 的原型
fn.prototype.test = function () {
  console.log(this.a);
}

// - 函數: return 複雜數據類型
function fn2() {
  this.a = 10;
  return {b:10,} // return 引用數據類型, 會影響實例對象
}


// - 構造函數
let fnn  = new fn();
console.log(fnn); // fn2 {a: 10}
// - 調用函數
console.log(fn2()); // 1

let fnn2 = new fn2();
console.log(fnn2); // {b: 10}


let fnn = new fn();
fnn.test(); // 10
console.dir(fnn.constructor); // ƒ fn()
```
- class 基本上替代了這麻煩的問題
```javascript=
class Cls {
  constructor() {
    this.a = 10;
    // return {b:10}
  }
  // 添加方法就直接寫
  test () {
    console.log(this.a);
  }
}


let cls = new Cls
console.log(cls.a); // 10
cls.test(); // 10



console.log(typeof Cls); // functin -> Class 依然是 function
Cls(); 
  // Class constructor Cls cannot be invoked without 'new' 
  // 只是不能直接調用
```

### class 的 name 問題
> - 當類被指向時, 從外面只能間接使用類
```javascript=
// - 沒有被指向的類
class Cls {
  constructor() {
    console.log(Cls.name);
  }
}
let c = new Cls(); // Cls 如果沒有對類指向時, 可以直接使用類名


// - 被一個變量指向的類
let cls2 = class Cls2{
  constructor() {
    console.log(cls2.name);
    console.log(Cls2.name);
  }
}

// let c3 = new Cls2(); 
  // Cls2 is not defined
  // 但如果對類指向時, 外面就無法直接找到類名了
  // 必須透過變量找到該類
let c2 = new cls2(); 
  // Cls
  // Cls
    // 但是類裡面還是可以直接使用類名
    // 且不論直接使用類還是使用變量來調用類名, 都是同一個類名, 
    // 我也不知道我在寫什麼, 
    // 反正就是指向同一個對象, 在類裡面兩個變量都能用, 類名都是原類名
console.log(cls2.name); // Cls2 //=> 在外面透過變量找名字, 一樣是類名而非變量名


// 構造函數也是
let fn = function Fn() {}

new Fn(); // Fn is not defined
```

### class 執行
> - 類雖然不能直接執行, 
>   但是類可以直接實例後執行
```javascript=

// 實例 new class{} 後會返回一個實例對象,
// 接著 () 執行對象, 相當於執行 constructor() 函數
// 寫在一起就變這樣:
new class{
  constructor(){
    console.log(...arguments);
  }
}('我','是','a','r','g'); // 我 是 a r g
```

### class 不會 hoisting
```json=
new Cls(); // Cannot access 'Cls' before initialization
class Cls{}
```

### class 靜態 (static) 方法
> - 類就相當於的原型
> - 所有寫在類裡的東西都會在實例的 \_\_proto__ 而被實例繼承
> - 如果想要寫在 Class 本身, 讓能讓 Class 本身調用,
>   那就要在方法前面加 `static`

> - 簡易圖:
>   <img src='https://i.imgur.com/WStNf4a.jpg' style='width: 300px'/>
```javascript=
class Cls{
  constructor(...args) {
    console.log(args, this);
  }
  fn() {
    console.log('dynamic', this);
  }
  static fn2() {
    console.log('static', this);
  }
}

let c = new Cls(1,2);
  // [1, 2] Cls {}
console.dir(c);
  // __proto__ 
    // constructor: class Cls
      // name: "Cls"
      // fn2: ƒ fn2()
    // fn: ƒ fn()
    // __proto__: Object
    
c.fn(); 
  // dynamic Cls {...}
               // __proto__: Object -> this 指向類實例
// c.fn2(); // c.fn2 is not a function -> 找不到!!


Cls.fn2(); 
  // static class Cls{類本身} 
    // this 指向類本身
    // 靜態方法類實例不能用!
    // 類似於 Array.of() 等方法
    
    
    
// 繼承的話當然也可以訪問的到, 而 Cls2 的實例類當然也訪問不到
class Cls2 extends Cls{}
Cls2.fn2();
```

### class 原型上的方法不可枚舉
```javascript=
// 構造函數的原型上所添加的東西是可以枚舉的
function Fn(){
  this.a = 100;
}
Fn.prototype.b = function () {};
Fn.prototype.c = 200;


let f = new Fn();
for (let k in f) {
  console.log(k); // a b c -> 是可枚舉
}
console.dir(f);
  // a: 100
  // __proto__
    // b: ƒ ()
    // c: 200
    // constructor: ƒ Fn()
    // __proto__: Object


// 類的原型上添加的方法是不能玫舉的
class Cls{
	constructor() {
		this.a = 100;
	}
	dFn(){}
}

let c = new Cls();
for (let k in c) {
	console.log(k); // a -> 只有 a
}

console.dir(c);
  // a: 100
  // __proto__:
    // constructor: class Cls
    // dFn: ƒ dFn()
    // __proto__: Object
    
console.log(Object.getOwnPropertyDescriptor(Cls.prototype, 'dFn')) 
  // enumerable: false
console.log(Object.getOwnPropertyDescriptor(c, 'a'))
  // enumerable: true
```
### 繼承

> - 使用 `extends` 表示繼承
> - 子類必須調用一次 `super()` 來拿到 this
>   - `super()` 將相當於用 `call()` 調用父類的 constructor
>     意即其實 super 只能將屬性拿到, 方法是靠 extends 拿到的
```javascript=
class A {
  constructor(x) {
    this.x = '老爸的x' + x;
    console.log(this);
  }
  AFn() {
    console.log(this);
    console.log(this.x);
  }
  static ASFn() {
		console.log(this);
	}
}

class B extends A {
  constructor(x) {
    // console.log(this);
      // 還沒 super調用之前, 不能使用 this, 否則會報錯 
    super(x);
      // 執行super 相當於執行了父類的 constructor, 但是 this 指向子類
      // 就類似於 A.constructor.call(B)
      // 執行完後該實例就有 A類實例後的屬性方法了
    this.x = '兒子的x' + x; //如果要修改就後來再寫就好了
  }
  AFn() { // 修改方法
		this.x = 'test';
		super.AFn(); // 如果重寫方法後想要再調用父類方法, 一樣使用 super
	}
	static ASFn() {
		console.log(12345);
		super.ASFn();
	}
}

let a = new A(3);
  // A {x: "老爸的x3"}
    // x: "老爸的x3"
    // __proto__:
      // constructor: class A
        // ASFn: ƒ ASFn()
        // name: "A"
      // AFn: ƒ AFn()
      // __proto__:
console.dir(a); // A
a.AFn(); 
  // A {x: "老爸的x3"}
  // 老爸的x3

console.log('-----------------');

let b = new B(3);
  // B  {x: "老爸的x3"}
    // x: "兒子的x3"
    // __proto__: A
console.dir(b); // B
b.AFn();
  // B {x: "兒子的x3"}
  // 兒子的x3
B.ASFn(); 
  // 12345
  // class B extends A {...}
```

> #### 問題: class 能繼承以前的寫法嗎? 
> - 可以, super 找得到函數都行
```javascript=
function Father(x,y) {
  this.x = x;
  this.y = y;
}
Father.prototype.show = function () {
  console.log(this.x, this.y);
}


class Son extends Father{
  constructor(x,y) {
    super(x,y);
  }
}

let s = new Son(5,6);
s.show(); // 5 6 -> 可以~
```



## Promise
`new Promise( function(resolve, reject) {...} /* executor */  );`
> - Promise 的參數為一個 executor 函數, 只要實例馬上執行
> - executor 函數有兩個形參( resolve 跟 reject ), 
>   這兩個形參也都是函數, 且為 callback
> - 當實例 Promise 時, 該實例的狀態會設為 pending, 
>   而 Promise 在執行完 executor 函數後會變成兩種狀態 ( fulfilled 或 rejected ) 其一
>   - 在 executor 函數調用 resolve 函數參數時, 會將 Promise 狀態轉成 fulfilled
>   - 在 executor 函數調用 reject 函數參數時, 會將 Promise 狀態轉成 rejected
>   - 如果在執行 executor 函數時, 產生錯誤, Promise 狀態會直接變成 rejected 
>     (這是MDN寫的, 可是我只要在 executor 函數掛掉, 整個就掛掉了...)


### Promise.prototype.then
`p.then(onFulfilled[, onRejected]);`
> - 這兩個參數都是 callback, 等 Promise 狀態改變, 才會調用對應的 callback

> - 當 Promise 的狀態為 Fulfilled 時, 調用第一個參數, 
> - 當 Promise 的狀態為 Rejected 時, 調用第二個參數, 
>   且如果沒有設定實參時, 預設也會有固定的錯誤訊息傳進來
> - 當 Promise 的狀態沒有改變 ( pending ) 時, then 兩個參數都不會調用

> - 但有些太明顯的錯誤會直接掛掉, 例如 const不賦值, double let,... 

> - Promise 執行順序:
>   executor函數 -> 同步對列 -> then中的回調
  
```javascript=
let p = new Promise((resolve, reject)=>{
  console.log(0);
  d;
  resolve();
  reject(e);
}); 
  /* 測試整理:
   * 1. 當執行 executor 時產生錯誤, 
   *    會直接將 Promise 狀態改為 rejected 而執行 onRejected
   * 2. 如果都沒有產生錯誤, Project 就會看你調用哪個參數來決定 Project 狀態
   * 3. 如果都沒有改變狀態, 就啥都不幹, 執行完同步任務就沒了
   * 4. 但如果錯誤太明顯, 例如 double let , 程序會直接掛掉, 不會管你
   * 
    0 
    4 
    2 
    ReferenceError: d is not defined
      at test.js:4
      at new Promise (<anonymous>)
      at test.js:2
    我還能執行
  */

p.then(()=>{
  console.log(1);
  console.log();
},(e)=>{
  console.log(2);
  console.log(e);
  console.log('我還能執行');
})
console.log(4);
```
### executor 裡放異步操作
> - 在 div 裡添加圖片
```htmlmixed=
<body>
  <div></div>
  <script src='test.js'></script>
</body>
```
```javascript=
let box = document.querySelector('div');
function loadImg(url) {
  return new Promise((resolve, reject)=> {
    console.log(0);
    // 1. 創建一個圖片對象
    let img = new Image(); 
    // 2. 把url參數傳進去
    img.src = url; 
    
    // 3. 註冊事件, 成功調用 resolve, 失敗調用 reject
    img.onload = ()=>{ 
      resolve(img);
    }    
    img.onerror = (e)=>{
      reject(e);
    }
  })
}

console.log(1);
loadImg('https://www.nationalgeographic.com/content/dam/animals/thumbs/rights-exempt/mammals/r/raccoon_thumb.ngsversion.1485815402351.adapt.1900.1.JPG').then((img)=>{
  box.appendChild(img)
  console.log(img);
},(e)=>{
  console.log(e);
})
console.log(2);

loadImg('hngsversion.1485815402351.adapt.1900.1.JPG').then((img)=>{
  box.appendChild(img)
  console.log(img);
},(e)=>{
  console.log(e);
})
console.log(3);



/* 最後的結果
 * 
1
0
2
0
3
Get net::ERR_FILE_NOT_FOUND
Event -> 這坨是調用 reject 時會傳進來的實參
  isTrusted: true
  type: "error"
  target: null
  currentTarget: null
  eventPhase: 0
  bubbles: false
  cancelable: false
  defaultPrevented: false
  composed: false
  timeStamp: 74.85000003362074
  srcElement: null
  returnValue: true
  cancelBubble: false
  path: [img]
  __proto__: Event

<img src='...'/>
 * 
 * 
 * 先執行了同步隊列的 1, 接著進去 executor log了一個 0 後註冊了兩個異步隊列
 * 接著執行同步對列2, 進去executor log了一個 0 後再註冊了兩個異步對列
 * 最後執行同步對列3
 * 
 * 後面分別觸發  executor 裡的註冊事件, 一次成功一次失敗
 * /
```

### Promise.prototype.catch
`p.catch(onRejected);`
> - 捕獲錯誤用的, 作用跟 `then()` 第二個參數 ( onRejected ) 一樣
> - 差別在於即使 executor 函數執行後狀態為 fulfilled 
>   而執行 `then` 參數函數而錯誤時, catch 會攔截而執行
>   但 `then` 不會報錯而又執行自己
> - 故常見寫法是 `then` 直接不寫第二參, 在後面接著設 catch 來攔截
>   `p.then(()=>).catch(()=>{})`
```javascript=
let p = new Promise(resolves=>{
	console.log(1);
	d
	resolves();
})

p.then(()=>{
  console.log(2);
  d;
}, (e)=>{
  console.log(5);
  console.log(e);
  d;
}).catch(e=>{
  console.log(3);
  console.log(e);
})
console.log(4);

/* 1. 執行 executor 函數時, 而改變狀態, 依照狀態執行對應的 then 參數函數
 *    但如果在執行 then 參數而報錯時, 程式就會掛掉
 * 2. 所以可以再多設置一個 catch 屬性
 *    當執行 then 而報錯時, catch 會攔截錯誤
 *    或者then 沒有設置第二參數時, onRejected 也會變成 catch 設定的參數

  1
  4
  5
  ReferenceError: d is not defined
    at test.js:4
    at new Promise (<anonymous>)
    at test.js:2
  3
  ReferenceError: d is not defined
    at test.js:14
    
*/
```

### Promise.all
`Promise.all([Promise1, Promise2, ...])`

> - Promise.all 也會產生一個帶有 Promise 狀態的對象,
> - 參數為 Promise 實例組成的陣列

> - 當所有參數的實例狀態==都==為 onFulfilled 時, 
>   `Promise.all()` 產生的對象狀態就會變成 onFulfilled, 而執行對應的 callback,
>   傳進來的實參為所有陣列監控的執行成功結果, 用陣列裝起來
> - 當有一個參數實例狀態為 onRejeted 時, 
>   Promise.all 對象狀態馬上會變為 onRejected 而執行 rejeted callback
>   不會再往下監控, 意即馬上返回錯誤訊息給 then, 後面也錯也不會被執行到
> - 當沒有參數實例為 onRejeted 但有參數實例的狀態還在 pending 時, 
>   Promise.all 對象狀態依舊會保持 pending

```javascript=
let p1 = new Promise((resolve, reject)=> {
  console.log('p1');
  // reject('p1 OnRejected');
  resolve('p1 OnFulfilled');
})

let p2 = new Promise((resolve, reject)=> {
  console.log('p2');
  // reject('p2 OnRejected');
  resolve('p2 OnFulfilled');
	
})
let p3 = new Promise((resolve, reject)=> {
  console.log('p3');
  // resolve('p3');
})

console.log(p1,p2,p3);

let pAll = Promise.all([p1,p2,p3]);
console.log(pAll);
pAll.then(res=>{
  // onFulfilled
  console.log(res); // 當成功時, 傳進來的是三個成功的結果, 用陣列裝
}).catch(e=>{
  // onRejected
  console.log(e);
})

/* 全部都沒錯誤訊息

Promise {<resolved>: "p1 OnFulfilled"} 
Promise {<resolved>: "p2 OnFulfilled"} 
Promise {<resolved>: "p3"}

=> pAll.then.catch // ["p1 OnFulfilled", "p2 OnFulfilled", "p3"]

*/

/* p1 跟 p2 狀態都為 OnRejected
Promise {<rejected>: "p1 OnRejected"} 
Promise {<rejected>: "p2 OnRejected"} 
Promise {<resolved>: "p3"}

=> pAll.then.catch // p1 OnRejected
   第一個就返回了~~~ 
*/ 

/* 三個沒有狀態為 rejected, 但還有 pending 時
 
Promise {<resolved>: "p1 OnFulfilled"} 
Promise {<resolved>: "p2 OnFulfilled"} 
Promise {<pending>}

=> 不會執行任何其他操作, 等 pending 開獎~~~
*/

```

### Promise.race
`Promise.race([p1,p2,p3])`
> - 用法跟 all 一樣, 
> - 差別在於 race 是只要有 Promise 實例改變, 直接 return
```javascript=
let p1 = new Promise((resolve, reject)=> {
  console.log('p1');
  // reject('p1 OnRejected');
  resolve('p1 OnFulfilled');
})

let p2 = new Promise((resolve, reject)=> {
  console.log('p2');
  reject('p2 OnRejected');
  resolve('p2 OnFulfilled');
	
})
let p3 = new Promise((resolve, reject)=> {
  console.log('p3');
  resolve('p3');
})

console.log(p1,p2,p3);


let pRace = Promise.race([p1,p2,p3]);
pRace.then(res=>{
  console.log(res);
}).catch(e=>{
  console.log(e);
})


console.log(pRace); // p1 OnFulfilled
```

## Generator
> - 讓函數暫停執行

> - 形式:
>   - function 跟 函數名間有 *
>   - 函數裡有 yeild
> - 執行 Generator 函數後不會執行 code, 而是返回一個 Generator 實例
> - Generator 實例裡的 `next()` 是用來執行 code 的
> - 每次 `next()` 都會執行到 yeild 結束, 立馬返回, 下次從 yeild 下一個動作開始
> - `next()` 有返回值, 紀錄 yeild 返回的東西與 function 是否執行完了

> - 優點, 可以決定什麼時候執行函數的哪些部分
>   例如在某個異步操作調用 next(), 當觸發異步時, 執行 next() 等
```javascript=
// function* fn(){ //=> 特色1. function 跟 函數名 之間有個 *,
                   //         在哪無所謂, 空格是給人看的
function *fn(){
  console.log(1);
  yield;           //=> 特色2. 函數體有個 yield 
  console.log(2);
  yield 'a';
}
let gen = fn();
console.log(gen); 
/*
 * 特色3. Generator 函數執行後不會執行裡面的 code
 *       而是返回一個 Generator 實例
 * 
fn {<suspended>}
  __proto__: Generator
  [[GeneratorLocation]]: test.js:4
  [[GeneratorStatus]]: "closed"
  [[GeneratorFunction]]: ƒ *fn()
  [[GeneratorReceiver]]: Window
*/
console.log('one'); // one
console.log(gen.next()); 
/* 
 * 特色4. Genertor 函數執行是調用實例裡的 next()
 * 特色5. 
 *   - 最重要的特色
 *   - Genertor 函數不會全部執行完, 而是執行到 yield; 表達式之前(包含表達式)
 *   - 然後返回一個對象, 包含 
 *     - value => yeild 後面的值, 沒有就返回 undefined
 *     - done => 函數執行完沒, 還沒就 false
 * 
   1
   {value: undefined, done: false}
 */
console.log('two'); // two
console.log(gen.next());
/*
   2
   {value: "a", done: false}
*/
console.log('three'); // three
console.log(gen.next()); // {value: undefined, done: true}
console.log(gen.next()); // {value: undefined, done: true}
```
### yield
> - 可以傳參數, 也可以有返回值

> #### 傳參
> <img src='https://i.imgur.com/9NEqTTK.jpg' style='width: 300px;'/>

> - 每次 next 執行, 只會執行到 yeild 結束就返回
> - 所以如果要傳參, 要塞在後面那個 next

```javascript=
function *fn(){
  console.log(1);
  let a = yield;
  console.log(2,a);
}
let gen = fn();
console.log(gen.next('haha'));
console.log(gen.next('wowo'));
console.log(gen.next());
console.log(gen.next());

/*
1
{value: undefined, done: false}
2 "wowo"                        //=> 從結果可以證明傳參要放在下次
{value: undefined, done: true}
{value: undefined, done: true}
{value: undefined, done: true}
*/
```
> #### 返回值
> - 如特色5 所述, next() 會有一個返回值, 其中的 value 就是 yeild 後面的東西
>   所以其實可以把 yeild 看成 return (執行完就把後面東西返回)
> - 另外一個返回值就是 return, next() 在最後一次如果有 return, 也會返回到 next.value 裡
```javascript=
function *fn(){
  console.log(1);
  let a = yield 'yeild';
  console.log(2,a);
  return 'return'
}
let gen = fn();
console.log(gen.next('haha'));
// 1
// {value: "yeild", done: false}
console.log(gen.next('wowo'));
// 2 "wowo"
// {value: "return", done: true}
```

### 其他
> #### 箭頭函數無法寫生成器
> - 要在 function 跟 變量名中間加 *, 箭頭函數沒有 function...



## async
> - async 是函數對象
> - 執行 async 函數會返回 Promise 對象
> - 意即在 async 函數內執行的成功失敗, 都會改變 Promise 的狀態
> - async 成功執行時, 函數裡返回的東西會傳到 onResolve callback 函數中

```javascript=
async function fn(){
  // b; // 執行有誤時, 返回的狀態就是 rejected
  return 'hhaa' // 成功執行的話, 返回的狀態就是 resolves
}

let a = fn(); // 執行 async 函數, 返回 Promise 對象
console.log(a);
  /* 執行有誤
    Promise {...}
      __proto__: Promise
      [[PromiseStatus]]: "rejected"
      [[PromiseValue]]: ReferenceError: b is not defined at fn
  */
  /* 正常執行
    Promise {<resolved>: "hhaa"} //=> 把 return 返回給 resolved
      __proto__: Promise
      [[PromiseStatus]]: "resolved"
      [[PromiseValue]]: "hhaa"
  */

a.then((resolve)=>{
  console.log(resolve)
}).catch((e)=>{
  console.log(e);
})

  /* 執行有誤
  ReferenceError: b is not defined
    at fn (test.js:4)
    at test.js:9
  */
  /* 正常執行
     hhaa
  */
```

### await

> - `await` 後面可以放
>   - `async()`
>   - `function()`
>   - `promise`
> - `await` 只能在 `async()` 裡面

> #### 先整理前面的想法
> - 調用 Promise 時, 會馬上執行他的參數函數(executor), 
>   接著返回一個 Promise 實例對象
> - 當調用該對象的 `then` 方法或 `catch` 等方法時, 
>   會依據 Promise 狀態執行不同參數函數
```javascript=
let p = new Promise((res, rej)=>{
  console.log(1);
  // rej(456);
  res(123);
  console.log(2);
})

console.log(p); 
  // Promise {<rejected>: 456}
  // 或 Promise {<resolved>: 123}

p.then(res=>{
  // Promise 狀態為 resolved 時, 走 then 一參
  console.log('p.thne', 3);
  console.log(res);
}).catch(e=>{
  // Promise 狀態為 rejected 時, then 有二參走二參, 
  // 沒有二參走 catch => 因為表示二參還是出錯, 不管哪個參數出錯, catch 都會接
  console.log('p.catch', 4);
  console.log(e);
})
```

> - await 
>   - 會直接拿到原本要傳給 resolve 或 rejected 的參數
>   - 並判斷 p 的狀態, 
>     - 如果是 resolve 就繼續執行, 
>     - 如果是 rejected 就直接執行 then 二參或 catch, 並將參數傳進去
>     
```javascript=
let p = new Promise((res, rej)=>{
	console.log(1);
  // b;
	// rej(456);
	res(123);
	console.log(2);
})

console.log(p); // 

async function fn(){
  console.log(5);
  let tmp = await p;
  console.log(6, tmp);
  return 'a'
} 



fn().then(res=>{
  console.log('fn()', 7);
  console.log(res);
}).catch(e=>{
  console.log('fn()', 8);
  console.log(e);
});



/* exeutor 正常執行 , Promise 狀態為 resolved 時
  1
  2
  Promise {<resolved>: 123}
  5 
  6 123 => 這裡沒有 undefined, 表示 p 先拿到參數後才執行 log(6, tmp)
        => 原本 p.then 是異步執行, 這裡不是, 否則 tmp 是 undefined 才對
  fn() 7
  a
*/

/* exeutor 正常執行 , Promise 狀態為 rejected 時
  1
  2
  Promise {<rejected>: 456}
  5     
     => 沒有執行 log(6, tmp), 
     => 也就是說當 await p 發現 p 是 rejected 狀態時, 直接跳二參或 catch
     => 並直接將參數傳到二參 或 catch
  fn() 8
  456
*/


/* exeutor 執行到報錯時, Promise 狀態當然直接 rejected 時
  1
  Promise {<rejected>: ReferenceError: d is not defined}
  5
  fn() 8
  ReferenceError: d is not defined
    at test.js:4
    at new Promise (<anonymous>)
    at test.js:2
*/
```

> - await 後面不是只能放 promise, 隨便一個值也能放,
>   只是經常放異步操作而已, 例如 promise, generator, 另一個 await 也行
```javascript=
async function fn(){
  console.log(1);
  let a = await 123; // 隨便放個值也能執行
  console.log(a);
}
fn();
  // 1
  // 123
```


### 小結
> - Promise, Generator, Async/Await 本質上還是異步(回調), 只是寫起來方便
> - 用同步的寫法寫異步



## esmodule 
> - nodeJS 用的, 暫略




## 其他
> ### `Promise`/ `Generator`/ `async/await`
> - 我看到有個老師用 JQ 介紹 Promise, Generator, Async/Await, 
>   現在我還不會 JQ , 先把他抄起來

> #### 同步與異步
> - 同步: 書寫簡單
> - 異步: 性能高
```javascript=
// 異步
$.ajax({
  url: '/banner_data',
  succes(banner) {
    $.ajax({
      url: '/user_data',
      succes(user) {
        $.ajax({
          url: '/item_data',
          succes(item) {
            // ....
          }, 
          error() {
            console.log('數據獲取失敗')
          }
        });
      }, 
      error() {
        console.log('數據獲取失敗')
      }
    });
  }, 
  error() {
    console.log('數據獲取失敗')
  }
});

// 同步
let data1 = $.ajax({url: '/banner_data',});
let data2 = $.ajax({url: '/user_data',});
let data3 = $.ajax({url: '/item_data',});
```
> #### Promise

> - `$ vi 1.txt 2.txt 3.txt` 假設有三個文件可以讀取
```javascript=
let p = new Promise(res, rej){
  $.ajax({
    url: '1.txt',
    succes(json) { // 加載成功, 調用 res()
      res(json)
    },
    error(err) { // 加載失敗, 調用 rej()
      rej(err)
    }
  })
}
p.then(json=>{
  console.log('succes');
}, err=>{
  console.log('error');
});
```
> ##### Promise.all()
```javascript=
let p = new Promise(res, rej){
  $.ajax({
    url: '1.txt',
    succes(json) { 
      res(json)
    },
    error(err) {
      rej(err)
    }
  })
}
let p2 = new Promise(res, rej){
  $.ajax({
    url: '2.txt',
    succes(json) { 
      res(json)
    },
    error(err) {
      rej(err)
    }
  })
}
let p3 = new Promise(res, rej){
  $.ajax({
    url: '3.txt',
    succes(json) {
      res(json)
    },
    error(err) {
      rej(err)
    }
  })
}

Promise.all([p,p2,p3]).then(arr=>{
  let [j1, j2, j3] = arr; // 把三個文檔資料拿到
  console.log('succes');
}, err=>{
  console.log('error');
});
```
> #### JQ.ajax.then()
```javascript=
// ajax 有返回值, 返回值為一個跟 promise 兼容的對象

let j = $.ajax({
  url: '3.txt',
  dataType: 'json',
  succes(json) {
    console.log('succes')
  },
  error() {
    console.log('error')
  }
})
console.log(j);
```
```javascript=
// 其中一個方法就是 then
$.ajax({
  url: '3.txt',
  dataType: 'json',
}).then(json=>{}, err=>{})
```

> #### 合體!
```javascript=
Promise.all([
  $.ajax({url: '1.txt', dataType: 'json',}),
  $.ajax({url: '2.txt', dataType: 'json',}),
  $.ajax({url: '3.txt', dataType: 'json',}),
]).then(arr=>{
  let [j1, j2, j3] = arr
  console.log('succes')
}, err=>{
  console.log(err)
})
```
> #### promise 問題
> - 異步操作帶有邏輯時, Promise 不好用
```javascript=
// 讀取到的資料是 vip 與 不是 vip 的操作結果不同
$.ajax('1.txt', function (user) {
  if (user.vip) {
    let data = $.ajax({url: '2.txt', dataType: 'json',});
    console.log(data);
  } else {
    let data = $.ajax({url: '3.txt', dataType: 'json',});
    console.log(data);
  }
})
```

> ### async/await
```javascript=
async  function fn () {
  try {
    let date1 = await $.ajax({url: '1.txt', dataType: 'json'});
    let date2 = await $.ajax({url: '22.txt', dataType: 'json'}); // 故意出錯
    let date3 = await $.ajax({url: '3.txt', dataType: 'json'});
    console.log('succes');
  } catch(e) {
    console.log('error');
    throw new Error(e); // 拋出錯誤訊息
  }
}
fn()
```

```javascript=
// 帶邏輯的異步

(async ()=>{
  let data1 = await $.ajax('user_info'); // 根據資料
  // 判斷
  if (user.vip) {
    let data = await $.ajax({url: '2.txt', dataType: 'json',});
    console.log(data);
  } esle {
    let data = await $.ajax({url: '3.txt', dataType: 'json',});
    console.log(data);
  }
}()
```

### 追求性能的迷思
> #### Q. 可以為了性能而盡量不用語法糖嗎？
> - 對前端來說, 0.001s 跟 0.000001s 的差別並不大
> - 對前端來說: 用戶體驗 >= 兼容性 > 可維護性(可讀性) > 性能(在可接受範圍下)
> - 性能又可分為 網絡性能(加載) 跟 執行性能
>   - 如果為了很小的執行性能來讓檔案變大, 導致加載變慢, 可能不值得

> - 編譯不會佔多少時間
> - async/await 是系統級的東西, 性能部會差
> - 都選擇了 JS 還關心性能?
```javascript=
// 寫法一
$.ajax({
  url: '1.txt',
  succes(banner) {
    // ... 套三層 略
  }, 
  error() {
    console.log('error')
  }
});

// 寫法二
(async  function () {
  try {
    let date1 = await $.ajax({url: '1.txt', dataType: 'json'});
    let date2 = await $.ajax({url: '22.txt', dataType: 'json'}); // 故意出錯
    let date3 = await $.ajax({url: '3.txt', dataType: 'json'});
    console.log('succes');
  } catch(e) {
    console.log('error');
    throw new Error(e); // 拋出錯誤訊息
  }
})()

// 為了讓程序更快選擇寫 寫法一 而讓程序變大, 對前端來說可能不值得
```


## [babel](https://babeljs.io/)
> - 主要是用來編譯的
> - 安裝 babel 可以用 npm 來裝
>   - npm (Node Package Manager), 就是字面上的意思
>   - 安裝 [nodeJS](https://nodejs.org/en/) 時，就會順帶裝上了
>   

### npm 安裝 babel
> - 首先先開一個文件夾來管理
> - 進到文件夾後 `$ npm init` 
>   - 這個動作會創建一個 `package.json` 檔案
>   - 他會問很多問題後建立一個 json 檔來管理
> - 接著安裝 babel `$ npm install --save-dev @babel/core @babel/cli`
>   - `--save-dev` 的意思就是把安裝什麼東西寫進  package.json 裡
>     - `-D` 為 `--save-dev` 的簡寫

```jsonld=
// 安裝前
{
  "name": "babel",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

// 安裝後
{
  "name": "babel",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.7.7", // 多了這兩個剛剛安裝的東西
    "@babel/core": "^7.7.7"
  }
}
```
> - 如此, 如果要在其他電腦工作, 或者 babel或其他工作用環境檔案不見, 
>   打 `$ npm i` 後, npm 就會去這個 json 查看 `'devDependencies'` 
>   來將原本使用的版本載下來
>   - `i` 為 `install` 的簡寫
```shell
$ rm -rf node_modules package-lock.json #=> 我把剛載下來東西給清了
$ tree #=> 只留 package.json
.
└── package.json 


$ npm i     #=> 安裝, 其他都沒打
$ ls        #=> 剛剛載的又載回來了
node_modules package-lock.json package.json
```
### 簡化運行腳本
> - 在 `package.json` 裡的 `"scripts"` 可以添加鍵值來簡化運行
> - 原本運行可能需要打一些參數
>   `$ node test.js --port=80 -s`
> - 當我寫進 `package.json` 的 `scripts` 後
>   `$ npm run start` 效果一樣, 省下打一堆參數的麻煩
```jsonld
{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node test.js --port=80 -s", // 寫
    "build": "babel src -d lib" // 官網寫的, 主要想寫 -d 的意義
                                // -d: 輸出的意思, 亦即
                                // babel 編譯文件夾 輸出到 輸出文件夾
  },
}
```

### 最後一件事
> - 必須創建一個 `.babelrc` 檔, 裡面寫上 `{"presets": ["@babel/preset-env"]}`
```shell
$ ls 
node_modules  package-lock.json	package.json 

$ vi .babelrc
```
```jsonld
{
  "presets": ["@babel/preset-env"] 
}
```
> - 並安裝 `@babel/preset-env`
>   `$ npm i @babel/preset-env -D`
> - preset 是預設的意思, 簡單說就是依照 `@babel/preset-env` 的配置執行
```shell
$ cat package.json
{ 
  // ...,
  "devDependencies": {
    "@babel/cli": "^7.7.7",
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7"
  }
}
```
> #### 開始編譯
```shell=
$ # 查看所有配置

$ ls -a
.  ..  .babelrc  js node_modules  package-lock.json  package.json


$ cat cat js/test.js  #=> 寫了一個 ES6 的 JS 檔在 JS 資料夾中
let [a,b] = [1,2];
const tmp = ()=>{
 console.log(a+b);
};
tmp();


$ cat package.json
{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "babel js -d tmp"
  },
  "devDependencies": {
    "@babel/cli": "^7.7.7",
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7"
  }
}

$ # 開始編譯
$ npm run build
Successfully compiled 1 file with Babel.


$ ls
js  node_modules  package-lock.json  package.json  tmp


$ cat tmp/test.js # 編譯輸出檔~~
"use strict";

var a = 1,
    b = 2;

var tmp = function tmp() {
  console.log(a + b);
};
```


















