###### tags: `📆2022年` `💻編程/🖼前端` `🗂Overview`
# ECMAScirpt6
==以下只記錄ES5和ES6差異較大且常用的語法、概念,主要用於學習往後前端框架的預備知識。==
- European Computer Manufacturers Association(ECMA,歐洲計算機制造聯合會),是評估、開發、認可電信和計算機標準的組織
- ECMA是標準,JavaScript是實現。參考:[[🏖JavaScript_00_Overview#🏖簡介]]
- 目的是讓所有前端腳本都實現ECMA,但目前只有JavaScript實現ECMA標準,因此ECMAScript ≈ JavaScript
- ECMAScript,ECMAScript 2015,簡稱ECMA或ES(ES6)
- 高級瀏覽器支持ES6
- 低級濟覽器主要支持ES5(ES3.1)
- ECMA官網:[https://www.ecma-international.org/]()
- [ES6兼容表](https://kangax.github.io/compat-table/es6/)
## 🏖變量聲明
- 使用`var`聲明變量的缺點:
1. `var`可重複聲明
2. `var`無法限制修改
3. `var`無塊級作用域(如:`{}`、`if`、`for`…),只有在`function()`中有作用域
### let
- `let`不可重複聲明
```javascript
let test01 = 10
// Uncaught SyntaxError: Identifier 'test01' has already been declared
let test01 = 20
```
- `let`有作用域
```javascript
// Uncaught ReferenceError: test01 is not defined
{
let test01 = 10
}
console.log(test01)
```
- `let`不存在變量提升,變量必須先使用後聲明
```javascript
// Uncaught ReferenceError: Cannot access 'test01' before initialization
console.log(test01)
let TESE_01 = 10
```
### const
`const`用於聲明一個只讀的常量,像Java中的`final`,並且一旦聲明必須初始化
- 不可重複聲明
- 有作用域
- 不存在變量提升,變量必須先使用後聲明
- `const`聲明的變量為只讀的常量;如果聲明的是複合的常量,複合常量的值可以修改,但無法再賦值其他複合常量
```javascript
// Uncaught TypeError: Assignment to constant variable.
const TESE_01 = 10
TESE_01 = 20
// 如果聲明的是複合的常量,複合常量的值可以修改,但無法再賦值其他複合常量
// 因為const聲明的變量只是不能修改棧內存的地址,但可以修改引用地址中的內容
const TEST_02 = {name: "mickey", id: "123"}
TEST_02.id = 233
console.log(TEST_02)
```
- 一旦聲明必須初始化
```javascript
// Uncaught SyntaxError: Missing initializer in const declaration
const test01
```
## 🏖=> 箭頭函數
### 基本用法
將`function`簡化為`=>`,類似於java中的Lambda表達式,參考:[[💡Java8_01_Lambda#💡基礎語法]]
```javascript
// ES6前寫法
const FUN_01 = function(num){
return num * num
}
console.log(FUN_01(10))
// ES6簡化1,方法中有多行代碼
const FUN_02 = (num) => {
return num * num
}
console.log(FUN_02(20))
// ES6簡化2,方法中僅一行代碼,可省略{}和return
// 如果只有一個參數,可省略()
const FUN_03 = num => num * num
console.log(FUN_03(30))
const FUN_04 = (num1, num2) => num1 * num2
console.log(FUN_04(10, 20))
// ES6簡化3,方法無輸入參數
const FUN_05 = () => 10
console.log(FUN_05())
// 直接返回複合常量,可以用()包起,避免歧義造成語法錯誤
const FUN_06 = id => ({name: 'mickey', id: id})
console.log(FUN_06('233'))
```
### this指向問題
- 無法用`=>`作為構造器方法
```javascript
// ES6前,function構造器方法
function constructer01() {}
const OBJ_01 = new constructer01()
console.log(OBJ_01)
// ES6,無法用=>作為構造器方法
// Uncaught TypeError: constructer02 is not a constructor
constructer02 = () =>{}
const OBJ_02 = new constructer02()
console.log(OBJ_02)
```
- `=>`建立的函數,不是調用對象本身,可參考:[[🏖JavaScript_05_函數#🏖this]]
```javascript
// function函數中,this代表調用對象本身
const OBJ_03 = {
fun: function(){
console.log(this)
},
}
OBJ_03.fun(); // {fun: ƒ}
// 沒有調用者默認為window
const OBJ_04 = function(){
console.log(this)
}
// 相當於window.OBJ_04()
OBJ_04(); // Window {window: Window, self: Window, document: document, name: '', location: Location, …}
// =>函數中,this代表window對象,和調用對象無關
const OBJ_05 = {
fun: () => {
console.log(this)
},
}
OBJ_05.fun(); // Window {window: Window, self: Window, document: document, name: '', location: Location, …}
```
- 箭頭函數的`this`為定義時所在的對象,默認使用父級的`this`,也就是說箭頭函數的`this`是繼承來的,沒有自己的`this`
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box{
width: 100px;
height: 100px;
background-color: aqua;
}
#box.bgcolor{
background-color: cadetblue;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
// 箭頭函數的this為定義時所在的對象
const myBox = document.getElementById('box')
myBox.onclick = function(){
setTimeout(() => {
console.log(this)
this.className = 'bgcolor'
}, 3000);
}
</script>
</body>
</html>
```
## 🏖數組新增的高級方法
- ES5數組常用方法,可參考:[[🏖JavaScript_08_常用對象#🏖Array 數組]]
- 使用方法類似於Java 8中的StreamAPI,可參考:[[💡Java8_02_StreamAPI]]
- 以下只列出常用方法
```javascript
let array01 = [5, 20, 30, 60, 13, 50, 6, 120, 8]
// filter,過濾器,過濾返回false的參數
let array02 = array01.filter(num => num >= 10)
console.log(array02)
// map,映射,返回處理後的參數
let array03 = array02.map(num => num * 0.5)
console.log(array03)
// reduce,匯總,將流中元素反複結合,得到一個值
let total = array03.reduce(
(preNum, nowNum) => preNum + nowNum,
0 // 初始值
)
console.log(total)
// 合并使用
let newTotal = array01
.filter(num => num >= 10)
.map(num => num * 0.5)
.reduce(
(preNum, nowNum) => preNum + nowNum,
0 // 初始值
)
console.log(newTotal)
```
## 🏖新增Set、Map數據結構
- ES6中新增Set和Map兩種數據結構,相關用法可參考:[ES6 Map 与 Set - 菜鳥教程](https://www.runoob.com/w3cnote/es6-map-set.html)
- Set為不可重覆的List,和Java的概念不一樣,可參考[Set - MDN Web Docs - Mozilla](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Set)
- Map為鍵值對,其中鍵不可重覆,概念類似於Java中的Map
- `size`屬性,Map總成員數
- `set(key, value)`,添加鍵值對
- `get(key)`,取得鍵值對
- `has(key)`,Map中是否包含指定鍵
- `delete(key)`,刪除鍵值對
- `clear()`,清空所有鍵值對
- `keys()`,返回所有鍵名遍歷器
- `values()`,返回所有鍵的值遍歷器
- `entries()`,返回Map中所有成員遍歷器
- `forEach()`,遍歷Map所有成員
## 🏖字符串新增功能
- 新方法
- `startWith(str)`,字符串是否以str開頭
- `endWith(str)`,字符串是否以str結尾
- 模版字符串,Template String
```javascript
// 模版字符串
let value = 'Hello My World' // 變量聲明必須在模版字符串前面
let templateStr01 = `
<h1>Hello World</h1>
<b>支持換行<br>
<p>支持變量:${value}</p>
`
console.log(templateStr01)
```
## 🏖解構賦值
解構賦值可以方便數組或物件中的資料,可參考:[解構賦值 - mozilla.org](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
- 數組中成員數左右必須一致
```javascript
let list01 = ['a', 'b', 'c']
// 原始方式取得陣列中的值
// let str01 = list01[0]
// let str02 = list01[1]
// let str03 = list01[2]
// ES6新方式
let [str01, str02, str03] = list01
console.log(str01)
console.log(str02)
console.log(str03)
```
- 物件的屬性名和變量名必須一致
```javascript
let obj01 = {
name: 'Mickey',
id: 123,
gender: 'male',
}
// 原始方式取得物件中的值
// let name = obj01.name
// let id = obj01.id
// let gender = obj01.gender
// ES6新方式
let {name, id, gender} = obj01
console.log(name)
console.log(id)
console.log(gender)
```
- 聲明時必須賦值
```javascript
let list01 = ['a', 'b', 'c']
// 會報錯
// let [str01, str02, str03]
// [str01, str02, str03] = list01
```
### ... 三點運算符
詳細用法可參考:[Spread Operator & Rest Operator(展開與其餘運算符)](https://ithelp.ithome.com.tw/articles/10185496)
- 展開運算符,用於展開數組
```javascript
// 展開運算符
let arr01 = [11, 12, 13]
let arr02 = [21, 22, 23]
console.log([...arr01, 7, 8, 9, ...arr02])
```
- 其餘運算符
```javascript
// 後面多余的參數都會放入數組中
function test02(a, b, ...c){
console.log(c) // (5) [53, 54, 55, 65, 57]
}
test02(51, 52, 53, 54, 55, 65, 57)
```
## 🏖Class類的概念
更接近於面向對象的用法
- `constructor`,表示構造方法
- `this`,表示實例對象
- `extends`,表示繼承
- `super`,表示父類構造函數,用於建立父類`this`對象
```javascript
// ES5前,類的使用方式
function Person01(name, age, gender){
this.name = name
this.age = age
this.gender = gender
this.printName = function(){
console.log(this.name)
}
}
let person01 = new Person01('Mickey', '123', 'male')
person01.printName()
// ES6的方式
class Person02{
// 構造方法,有參
constructor(name, age, gender){
this.name = name
this.age = age
this.gender = gender
}
// 構造方法,無參
// constructor(){
// this.name = 'unknow'
// this.age = 'unknow'
// this.gender = 'unknow'
// }
// 類方法
printName(){
console.log(this.name)
}
}
// 類繼承
class Employee extends Person02{
constructor(name, age, gender, department){
super(name, age, gender)
this.department = department
}
printDepartment(){
console.log(this.department)
}
}
let person02 = new Person02('Mickey', '123', 'female')
person02.printName()
let person03 = new Employee('Jack', '223', 'male', 'PG')
person03.printName()
person03.printDepartment()
```
## 🏖JSON新應用
- 屬性名和值名稱一樣可以省略
```javascript
// 屬性名和值名稱一樣可以省略
let name = 'Mickey'
let mickeyAge = 123
let obj01 = {name: name, age: mickeyAge, gender: 'male'}
let obj02 = {
name,
age: mickeyAge, // 屬性名和值的變量名稱不一樣就不能省洛
gender: 'male',
printName(){ // 定義函數,可省略function關鍵字
console.log(name)
},
}
obj02.printName()
```
- `JSON.stringify()`,串行化
```javascript
let obj03 = {name: 'jack', age: 223, gender: 'male'}
// 串行化
let jsonStr = JSON.stringify(obj03)
console.log(jsonStr) // {"name":"jack","age":223,"gender":"male"}
```
- `JSON.parse()`,反串行化
```javascript
// 反串行化
let obj04 = JSON.parse(jsonStr)
console.log(obj04.name)
```
## 🏖Module模塊化編程
- 模塊化優點
1. 避免變量、函數重名導致的錯誤
2. 避免引入時的層層依賴
3. 可提升執行效率
### export
`export`,用於規定模塊對外接口
1. 一次指定所有的對外接口
```javascript
// T037_Moduel01.js
let name = 'Mickey'
let id = 123
function printName(){
console.log(name)
}
function add(a, b){
console.log(a + b)
}
// 指定暴露的變量、函數
export {name, id, printName, add}
```
2. 在變量、函數前指定此接口是否對外
```javascript
// T037_Moduel02.js
// 指定暴露的變量、函數
export let name = 'Jack'
let id = 223
export function printName(){
console.log(name)
}
export function printId(){
console.log(id)
}
export class Employee{
constructor(){}
doSomething(){
console.log("do something...")
}
}
```
3. `export default`,由導入方決定函數名,但一個模塊只能使用一次
```javascript
// T037_Moduel03.js
let name = 'Marry'
let id = 323
// 由導入方決定函數名,但一個模塊只能有一個export default
export default function(args){
console.log(args)
}
```
### import
`import`,用於引入其他模塊的功能
1. 使用模塊定義的變量名、函數名
```html
<!-- 在html引入模塊要用type="module" -->
<script type="module">
// 使用模塊定義的變量名、函數名
import {name, id, printName} from './T037_Moduel01.js'
console.log(name)
printName()
</script>
```
2. 如果引入重名的變量或函數,`as`指定別名
```html
<script type="module">
// 如果引入重名的變量或函數,指定別名
import {name as jackName, printName as printJack, printId, Employee} from './T037_Moduel02.js'
console.log(jackName) // 使用別名調用變量
printJack() // 使用別名調用函數
let employee = new Employee()
employee.doSomething()
</script>
```
3. 導入`export default`,指定名稱
```html
<script type="module">
// 導入export default,指定名稱
import printArgs from './T037_Moduel03.js'
printArgs([1, 2, 3, 4])
</script>
```
4. 將模塊所有內容導入
```html
<script type="module">
// 將模塊所有內容導入
import * as test from './T037_Moduel01.js'
console.log(test.name)
test.printName()
</script>
```
## 🏖Promise
- Promise主要用於異步計算,它可以將異步操作隊列化,並按期望的順序執行
### ES6前異步用法
ES6前處理多層異步請求,很容易陷入回調地獄,導致程式碼層次過多
```javascript
// ES5,回調地獄,之一
$.get('url01', data1 => {
// do something...
$.get('url02', data2 => {
// do something...
$.get('url03', data3 =>{
// do something...
})
})
})
// ES5,回調地獄,之二
setTimeout(() => {
console.log('first step')
setTimeout(() => {
console.log('second step')
setTimeout(() => {
console.log('third step')
}, 1000)
}, 1000)
}, 1000)
```
### ES6異步用法
ES6 Promise可以解決程式碼層次過多的問題
```javascript
// ES6 Promise用法
const promise01 = new Promise((reslove, reject) => {
console.log('first step')
// // 執行.then第一個函數,狀態為成功
// reslove('success')
// // 執行.then第二個函數,狀態為拒絕
// reject('error')
// 執行catch函數,表示出現異常
throw 'test exception'
})
.then(
reslove => { // 成功時調用
console.log('reslove message : ' + reslove)
return 'sucess = first' // 返回做為下一個then的參數
},
reject => { // 拒絕時調用
console.log('reject message : ' + reject)
return 'error = first'
}
).catch(err =>{ // 異常時的處理
console.log("have some error : " + error)
})
// Promise可作為變量做後續處理
promise01.then(reslove =>{
console.log('second step')
console.log('reslove message : ' + reslove)
},
reject => {
console.log('second step')
console.log('reject message : ' + reject)
}
)
// 無限定狀態都會調用,並按順序執行
promise01.then(function(){
console.log('third step')
}).then(function(){
console.log('fourth step')
})
```
### ES6處理并發
```javascript
// 并發請求
Promise.all([
new Promise((resolve, reject) =>{
// do something
resolve('first request')
}),
new Promise((resolve, reject) =>{
// do something
resolve('second request')
}),
]).then( // 包含所有并發請求的信息
resolve => {
console.log(resolve) // ['first request', 'second request']
}
)
```