# 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);
};
```