那些必會用到的ES6精粹
摘要:ES6是基礎…
- 原文: ofollow,noindex">那些必會用到的ES6精粹
- 連結: https://segmentfault.com/a/1190000016460779
- 作者: BiaoChenXuYing
Fundebug經授權轉載,版權歸原作者所有。
前言
最新的 ECMAScript 都已經到釋出到 2018 版了。
我們應該有的態度是: Stay hungry ! Stay young !
從接觸 vue 到工作中用到 vue 將近 2 年了,在開發 vue 專案中用到了很多 es6 的 api ,es6 給我的開發帶來了很大便利。
本文只總結小汪在工作和麵試中經常遇到的 ES6 及之後的新 api 。
有空就得多總結,一邊總結,一邊重溫學習!!!
1 let 和 const
let 的作用域與 const 命令相同:只在宣告所在的塊級作用域內有效。且不存在變數提升 。
1.1 let
let 所宣告的變數,可以改變。
let a = 123 a = 456 // 正確,可以改變 let b = [123] b = [456] // 正確,可以改變
1.2 const
const 宣告一個只讀的常量。一旦宣告,常量的值就不能改變。
簡單型別的資料(數值、字串、布林值),不可以變動
const a = 123 a = 456 // 報錯,不可改變 const b = [123] b = [456] // 報錯,不可以重新賦值,不可改變
複合型別的資料(主要是物件和陣列),可以這樣子變動
const a = [123] a.push(456) // 成功 const b = {} b.name = 'demo'// 成功
1.3 不存在變數提升
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
所以 for迴圈的計數器,就很合適使用 let 命令。
let a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
1.4 推薦
對於 數值、字串、布林值 經常會變的,用 let 宣告。
物件、陣列和函式用 const 來宣告。
// 如經常用到的匯出 函式 export const funA = function(){ // .... }
2 解構(Destructuring)
2.1 陣列
一次性宣告多個變數:
let [a, b, c] = [1, 2, 3]; console.log(a) // 1 console.log(b) // 2 console.log(c) // 3
結合擴充套件運算子:
let [head, ...tail] = [1, 2, 3, 4]; console.log(head) // 1 console.log(tail) // [2, 3, 4]
解構賦值允許指定預設值:
let [foo = true] = []; foo // true let [x, y = 'b'] = ['a']; // x='a', y='b'
2.2 物件
解構不僅可以用於陣列,還可以用於物件。
let { a, b } = { a: "aaa", b: "bbb" }; a // "aaa" b // "bbb"
陣列中,變數的取值由它 排列的位置 決定;而物件中,變數必須與 屬性 同名,才能取到正確的值。
物件的解構也可以指定預設值。
let {x = 3} = {}; x // 3 let {x, y = 5} = {x: 1}; x // 1 y // 5
2.3 字串
字串也可以解構賦值。這是因為此時,字串被轉換成了一個類似陣列的物件。
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o"
2.4 用途
1. 交換變數的值
let x = 1; let y = 2; [x, y] = [y, x];
2. 從函式返回多個值
// 返回一個數組 function example() { let [a, b, c] = [1, 2, 3] return[a, b, c] } let [a, b, c] = example(); // 返回一個物件 function example() { return { foo: 1, bar: 2 }; } let { foo, bar } = example();
3. 函式引數的預設值
function funA (a = 1, b = 2){ return a + b; } funA(3) // 5 因為 a 是 3, b 是 2 funA(3,3) // 6 因為 a 是 3, b 是 3
4. 輸入模組的指定方法
載入模組時,往往需要指定輸入哪些方法。解構賦值使得輸入語句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
在 utils.js 中:
export const function A (){ console.log('A') } export const function B (){ console.log('B') } export const function C (){ console.log('C') }
在 元件中引用時:
import { A, B, C } from "./utils.js" //呼叫 A() // 輸出 A
3. 模板字串(template string)
模板字串(template string)用反引號(`)標識。
3.1 純字串
所有模板字串的空格和換行,都是被保留的.
console.log(`輸出值為 N, 換行`) // "輸出值為 N 換行"
3.2 字串中加變數
模板字串中嵌入變數,需要將變數名寫在 ${ } 之中
let x = 1; let y = 2; console.log(`輸出值為:${x}`) // "輸出值為:1" console.log(`輸出值為:${x + y}`) // "輸出值為:3"
3.3 模板字串之中還能呼叫函式。
function fn() { return "Hello World"; } console.log(`輸出值為:${fn()}`) // "輸出值為:Hello World"
4. 字串函式擴充套件
- includes():返回布林值,表示是否找到了引數字串。
- startsWith():返回布林值,表示引數字串是否在原字串的頭部。
- endsWith():返回布林值,表示引數字串是否在原字串的尾部。
let s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true
這三個方法都支援第二個引數,表示開始搜尋的位置。
let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false
5. 數值擴充套件
5.1 指數運算子
ES2016 新增了一個指數運算子(**)。
2 ** 2 // 4 2 ** 3 // 8
這個運算子的一個特點是右結合,而不是常見的左結合。多個指數運算子連用時,是從最右邊開始計算的。
// 相當於 2 ** (3 ** 2) 2 ** 3 ** 2 // 512
上面程式碼中,首先計算的是第二個指數運算子,而不是第一個。
指數運算子可以與等號結合,形成一個新的賦值運算子(**=)。
let a = 1.5; a **= 2; // 等同於 a = a * a; let b = 4; b **= 3; // 等同於 b = b * b * b;
6. 函式的擴充套件
除了在解構中說到的函式引數的預設值,還有不少經常會用到的方法。
6. 1 rest 引數
ES6 引入 rest 引數(形式為…變數名),用於獲取函式的多餘引數,這樣就不需要使用 arguments 物件了。rest 引數搭配的變數是一個數組,該變數將多餘的引數放入陣列中。
function add(...values) { let sum = 0; for (let val of values) { sum += val; } return sum; } add(2, 5, 3) // 10
上面程式碼的 add 函式是一個求和函式,利用 rest 引數,可以向該函式傳入任意數目的引數。
注意,rest 引數之後不能再有其他引數(即只能是最後一個引數),否則會報錯。
// 報錯 function f(a, ...b, c) { // ... }
6.2 箭頭函式
ES6 允許使用“箭頭”(=>)定義函式。
const f = v => v; console.log('輸出值:', f(3)) // 輸出值: 3 // 等同於 const f = function (v) { return v; };
如果箭頭函式不需要引數或需要多個引數,就使用一個圓括號代表引數部分。
// 等同於 const f = function () { return 5 }; const sum = (num1, num2) => num1 + num2; // 等同於 const sum = function(num1, num2) { return num1 + num2; };
如果箭頭函式的程式碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用 return 語句返回。
const sum = (num1, num2) => { return num1 + num2; }
箭頭函式的一個用處是簡化回撥函式。
const square = n => n * n; // 正常函式寫法 [1,2,3].map(function (x) { return x * x; }); // 箭頭函式寫法 [1,2,3].map(x => x * x);
注意: 函式體內的 this 物件,就是定義時所在的物件,而不是使用時所在的物件。
this 物件的指向是可變的,但是在箭頭函式中,它是固定的。
function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } let id = 21; foo.call({ id: 42 }); // id: 42
上面程式碼中,setTimeout 的引數是一個箭頭函式,這個箭頭函式的定義生效是在 foo 函式生成時,而它的真正執行要等到 100 毫秒後。如果是普通函式,執行時 this 應該指向全域性物件window,這時應該輸出 21。但是,箭頭函式導致 this 總是指向函式定義生效時所在的物件(本例是{ id: 42}),所以輸出的是 42。
7. 陣列的擴充套件
擴充套件運算子(spread)是三個點(…)。它好比 rest 引數的逆運算,將一個數組轉為用逗號分隔的引數序列。
7.1 數組合並的新寫法。
const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = ['d', 'e']; // ES5 的合併陣列 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6 的合併陣列 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
7.2 函式呼叫。
function add(x, y) { return x + y; } const numbers = [4, 4]; add(...numbers) // 8
7.3 複製陣列的簡便寫法。
const a1 = [1, 2]; // 寫法一 const a2 = [...a1]; a2[0] = 2; a1 // [1, 2] // 寫法二 const [...a2] = a1; a2[0] = 2; a1 // [1, 2]
上面的兩種寫法,a2 都是 a1 的克隆,且不會修改原來的陣列。
7.4 將字串轉為真正的陣列。
[...'hello'] // [ "h", "e", "l", "l", "o" ]
7.5 陣列例項的 entries(),keys() 和 values()
用 for…of 迴圈進行遍歷,唯一的區別是 keys() 是對鍵名的遍歷、values() 是對鍵值的遍歷,entries() 是對鍵值對的遍歷。
for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
7.6 includes()
Array.prototype.includes 方法返回一個布林值,表示某個陣列是否包含給定的值,與字串的 includes 方法類似。ES2016 引入了該方法。
[1, 2, 3].includes(2)// true [1, 2, 3].includes(4)// false [1, 2, NaN].includes(NaN) // true
該方法的第二個引數表示搜尋的起始位置,預設為 0。如果第二個引數為負數,則表示倒數的位置,如果這時它大於陣列長度(比如第二個引數為 -4,但陣列長度為 3 ),則會重置為從 0 開始。
[1, 2, 3].includes(3, 3);// false [1, 2, 3].includes(3, -1); // true
8. 物件的擴充套件
8.1 屬性和方法 的簡潔表示法
let birth = '2000/01/01'; const Person = { name: '張三', //等同於birth: birth birth, // 等同於hello: function ()... hello() { console.log('我的名字是', this.name); } };
8.2 Object.assign()
Object.assign方法用於物件的合併,將源物件(source)的所有可列舉屬性,複製到目標物件(target)。
const target = { a: 1 }; const source1 = { b: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
Object.assign方法的第一個引數是目標物件,後面的引數都是源物件。
注意,如果目標物件與源物件有同名屬性,或多個源物件有同名屬性,則後面的屬性會覆蓋前面的屬性。
const target = { a: 1, b: 1 }; const source1 = { b: 2, c: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
Object.assign 方法實行的是淺拷貝,而不是深拷貝。
const obj1 = {a: {b: 1}}; const obj2 = Object.assign({}, obj1); obj1.a.b = 2; obj2.a.b // 2
上面程式碼中,源物件 obj1 的 a 屬性的值是一個物件,Object.assign 拷貝得到的是這個物件的引用。這個物件的任何變化,都會反映到目標物件上面。
9. Set
ES6 提供了新的資料結構 Set。它類似於陣列,但是成員的值都是唯一的,沒有重複的值。
Set 本身是一個建構函式,用來生成 Set 資料結構。
// 基本用法 const s = new Set(); [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)); for (let i of s) { console.log(i); } // 2 3 5 4 // 去除陣列的重複成員 const array = [1, 1, 2, 3, 4, 4] [...new Set(array)] // [1, 2, 3, 4]
10. Promise 物件
Promise 是非同步程式設計的一種解決方案。
Promise 物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。
Promise 物件的狀態改變,只有兩種可能:從 pending 變為 fulfilled 和從 pending 變為
rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)
const someAsyncThing = function(flag) { return new Promise(function(resolve, reject) { if(flag){ resolve('ok'); }else{ reject('error') } }); }; someAsyncThing(true).then((data)=> { console.log('data:',data); // 輸出 'ok' }).catch((error)=>{ console.log('error:', error); // 不執行 }) someAsyncThing(false).then((data)=> { console.log('data:',data); // 不執行 }).catch((error)=>{ console.log('error:', error); // 輸出 'error' })
上面程式碼中,someAsyncThing 函式成功返回 ‘OK’, 失敗返回 ‘error’, 只有失敗時才會被 catch 捕捉到。
最簡單實現:
// 發起非同步請求 fetch('/api/todos') .then(res => res.json()) .then(data => ({ data })) .catch(err => ({ err }));
來看一道有意思的面試題:
setTimeout(function() { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(3); }).then(function() { console.log(4); }); console.log(5);
這道題應該考察 JavaScript 的執行機制的。
- 首先先碰到一個 setTimeout,於是會先設定一個定時,在定時結束後將傳遞這個函式放到任務佇列裡面,因此開始肯定不會輸出 1 。
- 然後是一個 Promise,裡面的函式是直接執行的,因此應該直接輸出 2 3 。
- 然後,Promise 的 then 應當會放到當前 tick 的最後,但是還是在當前 tick 中。因此,應當先輸出 5,然後再輸出 4 。
- 最後在到下一個 tick,就是 1 。
答案: “2 3 5 4 1”
11. async 函式
ES2017 標準引入了 async 函式,使得非同步操作變得更加方便。
async 函式的使用方式,直接在普通函式前面加上 async,表示這是一個非同步函式,在要非同步執行的語句前面加上 await,表示後面的表示式需要等待。async 是 Generator 的語法糖
async 函式內部 return 語句返回的值,會成為 then 方法回撥函式的引數。
async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world"
上面程式碼中,函式 f 內部 return 命令返回的值,會被 then 方法回撥函式接收到。
async 函式內部丟擲錯誤,會導致返回的 Promise 物件變為 reject 狀態。丟擲的錯誤物件會被 catch 方法回撥函式接收到。
async function f() { throw new Error('出錯了'); } f().then( result => console.log(result), error => console.log(error) ) // Error: 出錯了
11.3. async 函式返回的 Promise 物件,必須等到內部所有 await 命令後面的 Promise 物件執行完,才會發生狀態改變,除非遇到 return 語句或者丟擲錯誤。也就是說,只有 async 函式內部的非同步操作執行完,才會執行 then 方法指定的回撥函式。
下面是一個例子:
async function getTitle(url) { let response = await fetch(url); let html = await response.text(); return html.match(/<title>([\s\S]+)<\/title>/i)[1]; } getTitle('https://tc39.github.io/ecma262/').then(console.log('完成')) // "ECMAScript 2017 Language Specification"
上面程式碼中,函式 getTitle 內部有三個操作:抓取網頁、取出文字、匹配頁面標題。只有這三個操作全部完成,才會執行 then 方法裡面的 console.log。
在 vue 中,我們可能要先獲取 token ,之後再用 token 來請求使用者資料什麼的,可以這樣子用:
methods:{ getToken() { return new Promise((resolve, reject) => { this.$http.post('/token') .then(res => { if (res.data.code === 200) { resolve(res.data.data) } else { reject() } }) .catch(error => { console.error(error); }); }) }, getUserInfo(token) { return new Promise((resolve, reject) => { this.$http.post('/userInfo',{ token: token }) .then(res => { if (res.data.code === 200) { resolve(res.data.data) } else { reject() } }) .catch(error => { console.error(error); }); }) }, async initData() { let token = await this.getToken() this.userInfo = this.getUserInfo(token) }, }
12. import 和 export
import 匯入模組、export 匯出模組
// example2.js// 匯出預設, 有且只有一個預設 export default const example2 = { name : 'my name', age : 'my age', getName= function(){return 'my name' } } //全部匯入 // 名字可以修改 import people from './example2.js' -------------------我是一條華麗的分界線--------------------------- // example1.js // 部分匯出 export let name= 'my name' export let age= 'my age' export let getName= function(){ return 'my name'} // 匯入部分 // 名字必須和 定義的名字一樣。 import{name, age} from './example1.js' //有一種特殊情況,即允許你將整個模組當作單一物件進行匯入 //該模組的所有匯出都會作為物件的屬性存在 import * as example from "./example1.js" console.log(example.name) console.log(example.age) console.log(example.getName()) -------------------我是一條華麗的分界線--------------------------- // example3.js// 有匯出預設, 有且只有一個預設,// 又有部分匯出 export default const example3 = { birthday : '2018 09 20' } export let name= 'my name' export let age= 'my age' export let getName= function(){ return 'my name'} // 匯入預設與部分 import example3, {name, age} from './example1.js'
總結
- 當用 export default people 匯出時,就用 import people 匯入(不帶大括號)
- 一個檔案裡,有且只能有一個 export default。但可以有多個 export。
- 當用 export name 時,就用 import { name }匯入(記得帶上大括號)
- 當一個檔案裡,既有一個 export default people, 又有多個 export name 或者 export age 時,匯入就用 import people, { name, age }
- 當一個檔案裡出現 n 多個 export 匯出很多模組,匯入時除了一個一個匯入,也可以用 import * as example
13. Class
對於 Class ,小汪用在 react 中較多。
13.1基本用法:
//定義類 class FunSum { constructor(x, y) { this.x = x; this.y = y; } sum() { console.log( this.x +this.y') } } // 使用的時候,也是直接對類使用new命令,跟建構函式的用法完全一致。 let f = new FunSum(10, 20); f.sum() // 30
13.2 繼承
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 呼叫父類的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 呼叫父類的toString() } }
上面程式碼中,constructor 方法和 toString 方法之中,都出現了super關鍵字,它在這裡表示父類的建構函式,用來新建父類的 this 物件。
子類必須在 constructor 方法中呼叫 super 方法,否則新建例項時會報錯。這是因為子類自己的 this 物件,必須先通過父類的建構函式完成塑造,得到與父類同樣的例項屬性和方法,然後再對其進行加工,加上子類自己的例項屬性和方法。 如果不呼叫 super 方法,子類就得不到 this 物件。
class Point { /* ... */ } class ColorPoint extends Point { constructor() { } } let cp = new ColorPoint(); // ReferenceError
上面程式碼中,ColorPoint 繼承了父類 Point,但是它的建構函式沒有呼叫 super 方法,導致新建例項時報錯。
最後
總結和寫部落格的過程就是學習的過程,是一個享受的過程 !!!
好了,面試和工作中用到 ES6 精粹幾乎都在這了。
如果你覺得該文章對你有幫助,歡迎到我的 github star 一下,謝謝。
github 地址文章很多內容參考了: ECMAScript 6 標準入門
如果你是 JavaScript 語言的初學者,建議先看 《JavaScript 語言入門教程》
你以為本文就這麼結束了 ? 精彩在後面 !!!
對 全棧開發 有興趣的朋友可以掃下方二維碼關注我的公眾號
我會不定期更新有價值的內容。
微信公眾號:BiaoChenXuYing
分享 前端開發、後端開發 等相關的技術文章,熱點資源,全棧程式員的成長之路。
關注公眾號並回復 福利 便免費送你視訊資源,絕對乾貨。
福利詳情請點選: 免費資源分享–Python、Java、Linux、Go、node、vue、react、javaScript
關於Fundebug
Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了6億+錯誤事件,得到了Google、360、金山軟體等眾多知名使用者的認可。歡迎免費試用!