# ES6
## ES module
以前javascript是沒有模塊體系的,後來js社區提出了三種模塊化方案,主要是CommonJS,AMD,CMD三大規範。比如nodejs的模塊系統require、webpack等都是以CommonJS規範來實現的;requirejs是基於AMD規範來實現的,seajs是基於CMD規範實現的。
但這些都不是最好的方式,於是es6提出了官方的es-module模塊化方案,成為瀏覽器和服務器通用的模塊解決方案。
es-module主要由兩個命令構成:export和import,即導出和導入。
使用es-module時,可以說一個js文件就是一個模塊,文件內的變數、函式、類需要export,然後才能在另一個模塊中使用import來引用。
### 普通導入導出
```javascript
// test1.js
// 导出 - export一个count变量
export const count = 100;
// test2.js
// 导入 - import text1.js 的count
import { count } from './test1';
```
### 默認引入導出
```javascript
// test1.js
// 默认导出一个count变量
const count = 100;
export default count; // 使用默认导出时,不能使用变量声明表达式
// 导入时是默认的变量名`default`,你可以定义一个新的名字,或使用`default as`重命名
// test2.js
import count from './test1';
或
import anyname from './test1'; // anyname可以是任意变量名
// 等同于
import { default as count } from './test1';
或
import { default as anyname } from './test1';
```
>[color=#61dafb]使用默認導出,在其導入時其變數名可自定義
### 多種引入導出
```javascript
// test1.js
export const Component = {};
export default = {};
// test2.js
import React,{ Component } from './test1'; // React是默认导出的{},默认导出的变量是可以自定义的
```
### 使用 * as
首先,在validate.js定義兩個驗證函式
```javascript
// validate.js
// 验证手机号
export function checkphone(phone) {
...
return phone;
}
// 验证email
export function checkemail(email) {
...
return email;
}
// test.js
import { checkphone,checkemail } from './validate';
console.log(checkphone);
console.log(checkemail);
// 使用* as整體介紹
// test.js
import * as validates from './validate';
console.log(validates.checkphone);
console.log(validates.checkemail);
```
:::warning
`* as`實際上是將導入模塊的所有值存放在一個 object 中,然後可以通過這個 object 去訪問它們
:::
### 引入時重命名、引入普通樣式
```javascript
// test1.js
export let count = 100;
// test2.js 导入时将count重命名为num
import { count as num } from './test1'
// 引入普通樣式
import './style.css';
import styles from './style.css'; // css-module
```
### 注意事項
1. 普通導出(export)時,可以導出物件,聲明表達式,但不能導出已聲明的變數,或者無變數聲明的表達式
>無變數聲明,某些匿名函式function(){...},class {...}等等
:::danger
總之:export 命令規定的是對外的接口,必須與模塊內部的變數建立一一對應關係。因為使用 import 命令的時候,用戶需要知道所要加載的變數名或函式名
:::
```javascript
const count = 100;
export count; // 错误❌
export 100; // 错误❌
export function(){}; // 错误❌
export class {}; // 错误❌
export const count = 100; // 正确✔️
export function check(){}; // 正确✔️
export class Component {}; // 正确✔️
export { count }; // 正确✔️
```
2. 默認導出(export default)時,不能導出聲明表達式,class除外
這是因為 export default 命令其實只是輸出一個叫做 default 的變數,所以它後面不能跟變數聲明語句。
```javascript
export default const count = 100; // 错误❌
const count = 100;
export default count; // 正确✔️
export default { count }; // 正确✔️
export default [ count ] // 正确✔️
export default function check(){}; // 正确✔️
export default class Component {}; // 正确✔️
```
3. import導入的變數是唯讀的
```javascript
import { a } from './xxx.js'
a = {}; // 错误 Syntax Error : 'a' is read-only;
a.foo = 'hello'; // 合法操作
```
## 解構賦值
ES6 允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構賦值(Destructuring)。
### 物件解構賦值
物件屬性的結構是key:value,解構賦值時,變數名需要和屬性名 key 一致,否則會是 undefined。
```javascript
let user = { name: "ben", age: 20, gender:"男" };
// 解构赋值
const { name, age } = user; // 变量名对应的是对象属性的key
console.log(name); // ben
console.log(age); // 20
// 传统写法
let user = { name: "ben", age: 20, gender:"男" };
const name = user.name;
const age = user.age;
```
#### 解構時重命名
物件屬性是 key:value 結構,因此在解構時,解構出來的變數名默認是該鍵名key,但我們可以重新命名它。
```javascript
let user = { name: "ben", age: 20, gender:"男" };
// 解构赋值
const { name: username , age: userage } = user;
console.log(name); // 报错
console.log(age); // 报错
console.log(username); // ben 解构时已将变量名改为username
console.log(userage); // 20 解构时已将变量名改为userage
```
#### 解構時設置預設值
```javascript
// 没有name的user
let user = { age: 20, gender:"男" };
// 正常解构赋值
const { name } = user;
console.log(name); // undefined
// 设置默认值
const { name="ben" } = user;
console.log(name); // ben 值不存在时使用默认值
```
### 複製的解構賦值
#### 基本使用
陣列的解構賦值和物件不同,陣列解構賦值對應的是鍵(key),而陣列對應的是索引(Index)
```javascript
let [first, ,last] = [1, 2, 3];
console.log(first); // 1
console.log(last); // 3
```
#### 設置預設值
```javascript
let [ first, , ,last=10 ] = [1, 2, 3];
console.log(first); // 1
console.log(last); // 10 // 该陣列第四位不存在,因此使用默认值10
```
#### 設置多個變數
當要設置多個變數時,可使用變數解構賦值來簡化程式碼
```javascript
// 传统写法
let name = "ben";
let age = 23;
let gender = "男";
// 解构赋值写法
let [ name, age, gender ] = [ "ben", 23, "男" ];
```
### 字串的解構賦值
```javascript
const [a, b, c, d, e] = 'hello';
console.log(a); // h
console.log(b); // e
console.log(e); // o
```
### 函式的解構賦值
#### 參數是陣列
```javascript
// 原來
function add(args){
return args[0] + args[1];
}
// 解構賦值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
```
#### 參數是物件
```javascript
// 原來
function add(args){
return args.x + args.y;
}
// 解構賦值
function add({ x, y }){
return x + y;
}
add({ x:1, y:2 }); // 3
```
#### 使用默認值
```javascript
// 解構賦值
function add({ x=0, y=1 }){
return x + y;
}
add({ x:1, y:2 }); // 3
add({ y:2 }); // 2
add(); // 1
```
## 物件擴展
### 物件屬性簡寫
#### 基本使用
```javascript
// 解構賦值
let name = "ben";
let age = 23;
let gender = "男";
// 赋值给user变量,使用简写的形式
const user = { name, age, gender };
// 实际上等同于:
const user = {
name: name,
age: age,
gender: gender
}
```
#### 擴展運算子
```javascript
// 賦值給user變數,使用簡寫的形式
const base = { name:"ben", age:23 };
// 使用`...`
const user = { ...base, gender:"男" }
console.log(user); // { name:"ben", age:23, gender:"男" }
```
### Object.keys()
物件屬性是 key:value 結構,而Object.keys()方法主要用於把物件屬性的鍵 key 轉成一個陣列。
```javascript
const user = { name:"ben", age:23, gender:"男" };
const userkey = Object.keys(user);
console.log(userkey); // [ "name", "age", "gender" ]
```
### Object.values()
和 Object.keys() 不同,Object.values 方法主要用於把物件屬性的值 value 轉成一個陣列。
> Object.values 是 ES7 中新增的方法
```javascript
const user = { name:"ben", age:23, gender:"男" };
const uservalue = Object.keys(user);
console.log(uservalue); // [ "ben", 23, "男" ]
```
### Object.assign()
這是一個物件合併的方法。從右往左合併,當遇到相同屬性時會直接覆蓋。
:::danger
這是淺層複製
:::
```javascript
const base = { name:"ben", age:23 }; // 基本信息
const expand = { age: 20, gender:"男", birthday:"1月1日" }; // 額外信息
// 物件合併
const user = object.assign(base,expand)
console.log(user); // { name:"ben", age: 20, gender:"男", birthday:"1月1日" }
```
### Object.entries()
這是一個物件屬性迭代的方法,最終產生一個二維陣列。二維陣列中的每個屬性對應物件屬性的 key 和 value。
```javascript
const user = { name:"ben", age:23};
// 物件屬性迭代
console.log(Object.entries(user)); // [ ["name","ben"],["age",23] ]
```
### Object.is
Object.is() 用於判斷兩個值是否相等,與運算符 === 等效。
```javascript
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
```
如果要判斷兩個物件是否相等,建議使用 [lodash](https://lodash.com/) 的 isEqual 方法
```javascript
import _ from 'lodash';
let object = { 'a': 1 };
let other = { 'a': 1 };
_.isEqual(object, other);
// => true
object === other;
// => false
```
## 擴展 (展開)
拓展運算符...是在物件或陣列中展開屬性的一種語法。遵循從右往左,相同則覆蓋的原則。
### 物件中使用
```javascript
const base = { name:"ben", age:23 };
// 通過拓展運算符'...'將base屬性在user中展開,從而合併物件。
const user = { age:20, gender: "男", ...base };
console.log(user); // { age:23, gender: "男", name:"ben"};
```
### React 中使用
```javascript
class UserComponent extends Component {
render(){
const user = { name:"ben", age:23, gender: "男" };
return <div>
<Header />
<UserPage {...user} />
{/* 等同於 */}
<UserPage name="ben" age="23" gender="男" />
<Footer />
</div>
}
}
```
### 陣列中使用
```javascript
const base = [ 1, 2, 3 ];
const arr = [ 0, ...base ]; // 數組合併,和concat有相同的功效
console.log(arr); // [ 0, 1, 2, 3 ]
```
### new Set 和 ... 實現陣列去重複
ES6 提供了新的數據結構 Set。它類似於陣列,但是成員的值都是唯一的,沒有重複的值。
```javascript
const arr = [ 1,1,2,2,3,4,5,5,5 ];
const noRepeatArr = [ ...new Set(arr) ];
console.log(noRepeatArr); // [1,2,3,4,5]
```
### 字串中使用
```javascript
const str = "hello";
const arr = [ 0, ...str ];
console.log(arr); // [ 0, "h","e","l","l","o" ]
```
## includes
includes 是 ES7 中新增的一個方法,用於在陣列或字串中判斷是否包含某個元素,如果包含就返回 true,否則就返回 false。
### 陣列中使用
```javascript
const arr = [ 1, 2, 3 ];
console.log(arr.includes(2)); // true
console.log(arr.includes(9)); // false
// 傳統寫法
console.log(arr.indexOf(2)!==-1); // true
console.log(arr.indexOf(9)!==-1); // false
```
### 字串中使用
```javascript
const str = "Hello world!";
console.log(str.includes("e")); // true
console.log(str.includes("z")); // false
// 傳統寫法
console.log(str.indexOf("e")!==-1); // true
console.log(str.indexOf("z")!==-1); // false
```
## class 類別
ES6 中的 class 是 ES5 原型寫法的一個語法糖,擁有很好的開發體驗
### 基本使用
```javascript
// 定義一個空的類
class Point {
}
// 如果class內沒有定義constructor,則默認class的constructor為空函式。
class Point {
constructor() {}
}
```
生成實例,傳遞參數
```javascript
// es6寫法
class Point {
// 構造函式
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
// 訪問
const a = new Point(1, 2);
// 傳統es5寫法
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
// 訪問
var p = new Point(1, 2);
```
### 定義 class 類別屬性
#### 在 constructor 內定義
但這樣有點麻煩,因為都必須寫在 constructor 內。
```javascript
// es6寫法
class Point {
constructor() {
this.state={
name: null
}
this.getname = this.getname.bind(this);
}
getname(){
// do
}
}
```
#### 使用 class properties proposal 定義類屬性
[class properties proposal](https://github.com/tc39/proposal-class-fields) 支持省略 constructor,在類內直接定義類別的屬性,大大簡化了類別的操作。
>class properties proposal 是 ES7 新提案, 需要[@babel/plugin-proposal-class-properties插件](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties)的支持。
```javascript
class Point {
// 直接定義類屬性
state = {
name: null
}
// 不再需要在constructor內綁定
getname(){
// do
}
}
```
#### class properties proposal 在 React 中的應用
```javascript
class App extends Component {
// 直接定義類屬性state
state = {
name: "ben",
age: 23,
gender: "男"
}
render(){
const { name, age, gender } = this.state;
return <div>
<p> 當前登錄用戶: { name },{ age }歲,{ gender } </p>
</div>
}
}
```
### 使用 static 定義靜態方法和靜態屬性
使用static定義類的靜態方法,不需要實例化就能訪問。
```javascript
class Point {
// static定義靜態方法
static getname(name){
return name;
}
// static定義靜態屬性
static gender = "man";
checkphone(){
// do something
}
}
// 訪問
const P = Point.getname("ben"); // ben
const gender = Point.gender; // => man
```
### 在 class 類別內定義函式
```javascript
class Point {
getname(){
return 1;
}
// 或使用箭頭函式
getname = () =>{
return 1;
}
}
```
### class 類別的繼承
#### 基本使用
class 的繼承需要使用 extends 關鍵字 ,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。
```javascript
class A {}
class B extends A {
constructor() {
super();
// do
}
}
```
#### super()
:::warning
子類要繼承父類,還需要在子類的構造函式內調用 super 方法,否則會報錯。
super實際上是調用父類的構造函式,但它的this指向的是子類。
:::
:::danger
super 只能用在子類的構造函式中,並且需要執行 super() 之後才能訪問 this
:::
```javascript
class A {}
class B extends A {
constructor() {
console.log(this); // 報錯
super(); // 繼承父類A的屬性和方法
console.log(this); // 正確
}
}
```
#### 使用 class properties proposal 直接繼承
使用 class properties proposal 的好處是可以省略constructor的繁瑣綁定,可以直接定義且自動繼承。
```javascript
class A {
state={
age:23
}
}
class B extends A {
state = {
userage: this.state.age // age已被繼承,因此可直接訪問。this指向的是B。
}
}
// 訪問
const P = new B();
console.log(P.state.userage); // 23
```
### Decorator 類別修飾器
修飾器是 ES7 新增的一個特性,主要用於修改 class 類別的行為。[proposal-decorators](https://github.com/tc39/proposal-decorators)
使用情況特殊,但以不建議使用
詳細教學參考 2019 react 入门到高级课程 - ES6 - 022 堂
:::warning
修飾器只能用於類別和類別的方法,不能用於函式。
:::
老師建議:
:::warning
隨著 react hook 的推出,開發者將更偏向於函式組件,而類組件將會被更少地使用,而修飾器主要應用於類,因此往後修飾器的使用也會減少。
因此不建議大量地使用修飾器,或者可以**不使用它**。
:::
## Promise
Promise 是異步編程的一種解決方案。
Promise是一個原生構造函式,通過 new 可生成 promise 實例。它接受一個函式作為參數,並且這個函式有 resolve 和 reject 兩個參數。
Promise 有三種狀態,分別是:
- pending: 等待,未完成
- resolved: 成功
- rejected: 失敗
:::warning
詳細教學參考 2019 react 入门到高级课程 - ES6 - 023 堂
教得很細,可複習
:::
### then
then()是 promise 的一個實例方法,它接受兩個回調函式作為參數,第一個回調是當 pending=>resolved 時調用,表示成功, 第二個回調是當 pending=>rejected 時調用,表示失敗。
:::warning
then 方法執行完會返回一個新的 promise 實例,因此它還可以接下一個 then,下一個 then...,可採用鏈式寫法。
:::
```javascript
const promise = new Promise((resolve, reject)=>{
// ...做一些事情
if (true){
resolve(1);
} else {
reject(2);
}
})
// 第一個then,處理完後返回一個新的promise實例
.then(
// resolve回調函式
value=>{
console.log(value); // 1
return 11;
},
// reject回調函式
error=>{
console.log(error); // 2
return getJSON(post.commentURL); // 返回一個promise實例給下一個then
}
)
// 第二個then,處理上一個then返回的結果
.then(
// resolve回調函式
value=>{
console.log(value); // 11
},
// 上一個then如果返回promise實例,並且是reject狀態,則執行這個回調
error=>{
console.log(error); // 22
}
)
// 第三個then
...
...
以此類推
```
:::warning
總之,error 回調總是 rejected 的時候才會被調用,如果上一個 then 沒有返回 promise 實例,或者返回的 promise 實例沒有 rejected,則不會調用。
:::
### catch
```javascript
getJSON('/posts.json')
.then(posts=>{
// ...
})
.then(value=>{
// ...
})
.catch((error)=>{
// 異常捕獲
// 處理 getJSON 和 前一個回調函式運行時發生的錯誤
console.log('發生錯誤!', error);
});
```
### finally
```javascript
fetch('/posts.json') // 注意: 請求本地json文件時,這裡的路徑是相對於index.html的
.then(value=>{
// ...
})
.then(value=>{
// ...
})
.catch(error=>{
// 異常捕獲
// 處理 getJSON 和 前一個回調函式運行時發生的錯誤
console.log('發生錯誤!', error);
})
.finally(()=>{
// 最後執行
// 不管成功失敗都會執行
})
```
### 範例:使用 promise 封裝 ajax
原生 ajax 並不支持 promise,但我們可以用 promise 封裝它。
```javascript
const getJSON =( url )=>{
return new Promise(function(resolve, reject) {
const XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();
XHR.onreadystatechange =()=>{
if (XHR.readyState == 4) {
if (XHR.status == 200) {
try {
const response = JSON.parse(XHR.responseText);
resolve(response);
}
catch (e) {
reject(e);
}
}
else {
reject(new Error(XHR.statusText));
}
}
}
})}
getJSON('./post.json')
.then(data=>{
// 拿到數據後,做一些處理
if(data.code===200){
console.log(data);
// alert("請求成功");
}else{
alert(`請求失敗:${data.msg}`);
}
})
.catch(error=>{
// 異常捕獲
// 處理 getJSON 和 前一個回調函式運行時發生的錯誤
console.log('發生錯誤!', error);
})
.finally(()=>{
console.log('請求處理結束');
// 最後執行
// 不管成功失敗都會執行
})
```
### fetch
使用 fetch 請求本地 json 文件,fetch 請求默認會返回一個promise。
```javascript
fetch('./post.json') // 注意: 請求本地json文件時,這裡的路徑是相對於index.html的
.then(response=>{
// 解析json數據
return response.json();
})
.then(data=>{
// 拿到數據後,做一些處理
if(data.code===200){
console.log(data);
// alert("請求成功");
}else{
alert(`請求失敗:${data.msg}`);
}
})
.catch(error=>{
// 異常捕獲
// 處理 getJSON 和 前一個回調函式運行時發生的錯誤
console.log('發生錯誤!', error);
})
.finally(()=>{
console.log('請求處理結束');
// 最後執行
// 不管成功失敗都會執行
})
```
### Promise.all([p1, p2, p3...]) 方法
Promise.all 方法用於將多個 Promise 實例,包裝成一個全新的 Promise 實例,也就是包裝實例。而傳入的 Promise 實例可以稱之為內部實例。
:::info
只有內部實例全部處於 fulfilled 狀態時,這個包裝實例才會處於 fulfilled 狀態。並且內部實例的返回值會組成一個陣列返回給包裝實例。
:::
:::info
當內部實例中有一個被 rejected 時,包裝實例的狀態也會變成 rejected,並且這第一個被 rejected 的內部實例的返回值會傳遞給包裝實例
:::
```javascript
// 生成一個Promise物件的數組
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// 當所有內部實例fulfilled時執行
}).catch(function(reason){
// 異常捕獲
});
```
:::warning
注意:如果內部實例自定義了 catch,則該內部實例拋出錯誤時不會再走包裝實例的 catch,而是走自定義的 catch。
:::
## Generator
和 promise 一樣,generator 也是一種非同步處理方案。
但 generator 做得更徹底,它能實現以同步的方式來處理非同步,過去處理非同步可能需要十幾行程式碼,而它只需要一行程式碼。
詳細教學參考 2019 react 入门到高级课程 - ES6 - 024 堂
## async、await
ES2017(es8) 標準引入了 async 函式,使得非同步操作變得更加方便。
async / await 對應的是 generator 的function* / yield,它是 generator 的語法糖,但比 generator 更友好,async 內置執行器,靈活簡單。
### 基本定義
async 函式的定義很簡單,只需要在函式聲明前加 async 關鍵詞即可。async 函式內可使用 await 關鍵詞來處理非同步。
```javascript
async function fn() {
const result = await promise ; // await後面通常需要接promise,比如fetch、axios請求等
}
// 調用
fn();
```
>當函式執行的時候,一旦遇到 await 就會先返回,等到非同步操作完成,再接著執行函式體內後面的語句。
>
### async 函式多種寫法
```javascript
// 1,函式聲明
async function fn(){
// Todo
}
// 2,函式表達式
const foo = async function (){
// Todo
};
// 3,物件的方法
let obj = {
name:"ben",
age: 23,
async fn() {
// Todo
}
};
obj.fn();
// 4,Class類的方法
class Post {
async getPost(id) {
const post = await fetch("/api/post",{ postId: id }).then(res=>res.json());
// ...
}
}
const Posts = new Post();
Posts.getPost('id');
// 5,箭頭函式
const fn = async () => {
// ...
};
```
### 基本用法
```javascript
async function getPost() {
try {
const post = await fetch('./post.json').then(res=>res.json());
// 或
const postres = await fetch('./post.json')
const post = await postres.json();
console.log(post);
// 其他工作
...
}
catch(err){
// 捕獲異常
console.log(err);
}
}
// 調用
getPost();
```
### 返回 Promise
async 函式返回一個 Promise 物件。
>async 函式內部 return 語句返回的值,會成為 then 方法回調函式的參數。
如果 await 後面的異步操作出錯,那麼 async 函式返回的 Promise 物件就和被reject。
```javascript
async function getPost() {
try {
return await fetch('./post.json'); // 返回await結果
// 其他工作
}
catch(err){
// 異常捕獲
console.log(err);
}
}
// 調用
getPost()
.then(res=>{
return res.json();
})
.then(data=>{
console.log(data);
})
.catch(err=>{
console.log(err);
})
```
### 匿名執行 async
```javascript
(async () => {
const response = await fetch('./post.json').then(res=>res.json());
console.log(response);
})();
```