ES2015 也叫 ES6,區別只是一個是以釋出的年份來命名,一個是以版本號來命名
從那以後組織每年都會釋出一個新版本,根據這個規則,ES2016 === ES7... ES2020 === ES11
但通常我習慣將 ES2015 及其後續版本統稱為 ES2015+
變數宣告
ES2015 增加了兩個宣告變數識別符號的關鍵字,let
和 const
,兩者都支援塊級作用域,並且在宣告之前不能訪問
凡是不需要重新賦值的變數識別符號都可以使用 const
關鍵字來宣告
其餘需要重新賦值的變數就使用 let
關鍵字來宣告,像迴圈計數器之類的
{
const arr = ['a', 'b', 'c', 'd', 'e']
const length = arr.length
for (let i = 0; i < length; i++) {
//
}
}
// 塊之外無法訪問
物件字面量
物件字面量的簡寫形式以及計算屬性
const foo = 1
const obj = {
foo, // 屬性簡寫,等同於 foo: foo,
bar() {
// 方法簡寫
},
// 計算屬性
['na' + 'me']: 'by.Genesis',
__proto__: 原型
}
這些特性都可以簡化原本的程式碼
__proto__
用來 get/set 原型,不過並不推薦使用,應該使用 Object.getPrototypeOf(o)
和 Object.setPrototypeOf(o, proto)
箭頭函式
箭頭(=>)就是一種函式簡寫方式,同時提供一些有用的特性
// 當函式有且僅有一個引數的時候可以省略引數的圓括號
;[1, 2, 3].forEach(item => { console.log(item) })
// 當函式體內只有一條語句的時候可以省略函式體的花括號,同時隱式返回該條語句
const sum = (x, y) => x + y
// 如果隱式返回的是一個物件字面量,為了消除歧義,可以使用一對圓括號包裹物件字面量
const pos = (x, y) => ({ x: x + 1, y: y * 2 })
// 詞法 this
const obj = {
name: 'by.Genesis',
showName() {
setTimeout(() => {
console.log(this.name) // obj.showName() this === obj
}, 300)
}
}
// 立即執行箭頭函式表示式
;(() => {
alert(101)
})()
class
類(class)就是傳統的建構函式基於原型繼承的語法糖
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
update() {
// 方法
}
}
// 繼承
class Student extends Person {
constructor(name, age, grade) {
super(name, age)
this.grade = grade
}
update() {
// 呼叫父類的方法
super.update()
}
get foo() {
// getter
}
set foo() {
// setter
}
static baz() {
// 靜態方法通過 Student.baz() 呼叫
}
}
const s1 = new Student('by.Genesis', 20, 2)
同函式一樣,類也可以作為表示式賦值給一個變數,或者作為引數傳給函式,甚至從函式中返回
const Person = class {
//
}
無論是用類宣告還是表示式,都需要先定義,然後再使用,不會提升,不會提升
Symbol
符號(Symbol)是一種新的原始型別,其沒有字面量形式
符號可以分為3類,普通符號,全域性符號和眾所周知的符號(well-known Symbol)
// 建立 Symbol,不需要 new
// 傳入的引數作為該 Symbol 的描述符
const name = Symbol('name')
// 將 Symbol 用作物件的 key
// 只能使用可計算屬性名的方式
const o = {
[name]: 'by.Genesis'
}
// 通過 typeof 操作符判斷值型別
typeof Symbol('name') === 'symbol'
// 符號值是唯一的,就算在建立時傳入了相同的引數,得到的符號也不是同一個
Symbol('name') !== Symbol('name')
// 不過在全域性符號登錄檔中同一個 key 返回的是同一個符號
Symbol.for('name') === Symbol.for('name')
// 獲取符號描述符
Symbol('name').description === 'name'
物件的符號屬性無法通過傳統的方法遍歷出來,需要的時候可以使用 Object.getOwnPropertySymbols()
方法獲取引數物件中所有符號屬性組成的陣列
Object.getOwnPropertySymbols(o) // [Symbol(name)]
除此之外,還有一些 眾所周知的符號(well-known Symbol),這類符號的作用是暴露一些 JavaScript 內部操作
// 當陣列作為 concat 引數時,預設會被展開
const arr = [4, 5, 6]
;[1, 2, 3].concat(arr) // [1, 2, 3, 4, 5, 6]
// 可以修改此行為讓陣列引數不展開
arr[Symbol.isConcatSpreadable] = false
;[1, 2, 3].concat(arr) // [1, 2, 3, [4, 5, 6]]
// 也可以讓類陣列物件展開
;[1, 2, 3].concat({
[Symbol.isConcatSpreadable]: true,
length: 3,
0: 4,
1: 5,
2: 6
}) // [1, 2, 3, 4, 5, 6]
Promise
Promise
主要用來表示一個未來值
// 建立一個 promise
const p = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'by.Genesis')
})
// promise resolve 時執行
p.then(res => {
console.log(res) // 'by.Genesis'
})
// 總是會執行,無論 resolve 還是 reject
p.finally(() => console.log('finally'))
// 立即建立一個 fulfilled 的 promise
const p2 = Promise.resolve('101真狗')
// 立即建立一個 rejected 的 promise
const p3 = Promise.reject(404)
// promise reject 時執行
p3.catch(err => {
console.log(err) // 404
})
// 等待一組 promise 全部 resolve
// 一旦有一個為 rejected 則立即 reject
Promise.all([p2, p3])
// 獲取一組 promise 中最快的那一個,無論 resolve 還是 reject
Promise.race([p2, p3])
// 等待一組 promise 全部 settled,無論 resolve 還是 reject
Promise.allSettled([p2, p3])
// 獲取一組 promise 中最快 resolve 的那一個
// 只有當全部都為 rejected 的時候 reject
Promise.any([p2, p3])
迭代器和生成器
當一個物件擁有一個 next
方法,並且呼叫該方法時可以得到一個包含 done
和 value
兩個屬性的結果物件,那麼這就是一個迭代器(Iterator)
iterator = {
next() {
return {
done: false,
value: 10
}
}
}
其中 done
為 Boolean 型別,表示該迭代器是否已經迭代完畢
生成器(Generator)是一種特殊函式,宣告的時候在 function
關鍵字和函式名中間多了個星號(*)
生成器內通過 yield
關鍵字返回值
function *g() {
yield 1
yield 2
yield 3
}
// 呼叫生成器可以得到一個迭代器
iterator = g()
// 呼叫迭代器的 next 方法執行生成器內部程式碼並得到結果物件
iterator.next() // { value: 1, done: false }
當一個物件具有特殊的符號 [Symbol.iterator]
方法,並且該方法返回一個迭代器的時候,那麼這個物件就是一個可迭代物件(Iterable)
iterable = {
*[Symbol.iterator]() { // 這裡同時使用了物件方法簡寫,計算屬性以及生成器
yield 1
yield 2
yield 3
}
}
String,Array,Map,Set,NodeList 等等都是可迭代物件,迭代器自身也是可迭代物件,迭代器的 [Symbol.iterator]
方法返回自身
生成器可以通過 yield*
委託給其它可迭代物件
iterable = {
*[Symbol.iterator]() {
yield 1
yield* [2, 3]
}
}
可迭代物件可以使用 for of
語法遍歷
for (let v of iterable) {
console.log(v) // 1 2 3
}
非同步函式
非同步函式(Async function)在函式前面新增一個 async
關鍵字,其內部可以使用 await
關鍵字
await
表示式可以將其後面的 Promise resolve 的值提取出來
async function fn() {
const x = await Promise.resolve(101)
return x
}
// 執行非同步函式也返回一個 Promise
fn().then(res => console.log(res)) // 101
非同步函式就是生成器和 Promise 語法糖
非同步迭代
當一個迭代器的 next
方法返回一個 Promise,並且該 Promise resolve 後可以得到一個包含 done
和 value
兩個屬性的結果物件,那麼這個迭代器就是一個非同步迭代器(Async Iterator)
asyncIterator = {
next() {
return Promise.resolve({
done: false,
value: 10
})
}
}
將非同步函式和生成器結合到一起,就是非同步生成器(Async Generator),其內部可以同時使用 await
和 yield
關鍵字
async function *g() {
yield 1
const a = await new Promise(resolve => {
setTimeout(resolve, 3000, 2)
})
yield a
yield Promise.resolve(a + 1)
}
// 執行非同步生成器返回一個非同步迭代器
asyncIterator = g()
當一個物件具有特殊的符號 [Symbol.asyncIterator]
方法,並且該方法返回一個非同步迭代器的時候,那麼這個物件就是一個非同步可迭代物件(Async Iterable)
asyncIterable = {
async *[Symbol.asyncIterator]() {
yield 1
const a = await new Promise(resolve => {
setTimeout(resolve, 3000, 2)
})
yield a
yield Promise.resolve(a + 1)
}
}
非同步可迭代物件使用 for await of
語法遍歷
;(async () => {
for await (let v of asyncIterable) {
console.log(v) // 1 2 3
}
})()
await
應該放到非同步函式中
Map & Set
Map 是包含鍵值對(key-value)的有序集合,其中 key 可以是 任意型別 (是任意型別,包括引用型別甚至 DOM 元素都可以作為 Map 的 key)
// 建立一個 Map
const m = new Map([['a', 1], ['b', 2]])
// 新增值,如果已存在就是修改
m.set('c', 3)
// 獲取值
m.get('b') // 2
// 判斷值
m.has('b') // true
// 獲取長度
m.size // 3
// 刪除值
m.delete('b')
m.has('b') // false
m.size // 2
// 清空
m.clear()
m.size // 0
Set 就是一組不重複值的有序集合
// 建立一個 Set
const s = new Set([1, 2])
// 新增值
s.add(3)
s.add(1) // 該值已存在,集合保持不變
// 除了沒有獲取值的方法,剩下的和 Map 一致
Map 和 Set 都可以通過 forEach
方法遍歷其中的值
set.forEach(handler => handler())
可以把 Set 看作是 key 和 value 為同一個值的特殊 Map,也可以認為 Set 是隻有 key
Map 和 Set 遍歷順序和新增時的順序是一致的,因此都是有序集合
WeakSet & WeakMap
弱版本只能用來存放引用型別
WeakMap 只對其 key 有型別要求,而 value 可以是任意型別
弱版本不是可迭代物件,不能遍歷,也沒有 size 屬性,也不能用 clear 方法清空集合,只具備最基本的新增,刪除等方法
弱版本是弱引用,其優勢就是利於垃圾回收
解構
按照一定模式從物件或者可迭代物件中提取值
// 可迭代物件解構
// let 宣告對變數 a, b, c 都生效
let [a, b, c] = [1, 2]
a === 1
b === 2
c === undefined
// 交換值
;[a, b] = [b, a] // a = 2, b = 1
// 陣列可以解構任意可迭代物件,包括字串
// 解構時也可以跳過一些不需要的值
;[, , c] = '123' // c = '3'
// 物件屬性解構
{ x, y, z: { w } } = { x: 3, y: 4, z: { w: 5 } }
a === 3
b === 4
w === 5
預設值
在宣告函式引數或者解構的時候都可以指定一個預設值,當對應的值為 undefined
的時候,就會使用這個預設值
// 函式引數預設值
const sum = (x, y = 4) => x + y
sum(3) === 7
// 迭代器解構的預設值
const [a, b = 2] = [1]
a === 1
b === 2
// 物件解構的預設值
const { name = 'by.Genesis' } = { age: 18 }
name === 'by.Genesis'
// 函式引數和物件解構一起使用
const fn = ({ height = 18, width = 36 } = {}) => {}
fn() // height = 18, width = 36
fn({ height: 36 }) // height = 36, width = 36
fn({ height: 36, width: 18 }) // height = 36, width = 18
Spread & Rest
可迭代物件均可使用展開(Spread)運算子(...)展開為獨立的值,這些值可以作為函式的引數或放到陣列中
// 展開可迭代物件作為函式引數
Math.max(...[5, 20, 10]) === 20
// 展開可迭代物件到一個陣列中
const arr = [...new Set([1, 2, 2, 3])] // [1, 2, 3]
// 展開可迭代物件到一個陣列中
const newArr = [1, ...[2, 3], ...'45'] // [1, 2, 3, '4', '5']
而普通物件也可以展開其屬性,放到另一個物件中,這和 Object.assign
方法作用類似
// 展開物件屬性到另一個物件中
const o = {
a: 1,
...{
b: 2,
c: 3
}
} // o = { a: 1, b: 2, c: 3 }
const o2 = Object.assign({ a: 1 }, { b: 2, c: 3 })
和展開相反,多個值可以使用收集(Rest)運算子(...)打包成一個陣列,或者多個物件屬性打包成一個物件
// 函式剩餘引數打包成一個陣列
const fn = (x, ...y) => y.length
fn(2, 5, 7, 11) === 3 // x = 2, y = [5, 7, 11]
// 可迭代物件剩餘值打包成一個陣列
const [a, ...b] = new Set([2, 5, 7, 11])
a === 2
// b = [5, 7, 11]
// 物件剩餘屬性打包成一個物件
const { a, ...o } = {
a: 1,
b: 2,
c: 3
} // o = { b: 2, c: 3 }
收集運算子只能用於最後一個識別符號
模板字串
模板字串就是功能更強大的字串,它支援多行以及插值
在模板字串中插值使用 ${}
花括號裡面可以插入表示式,表示式甚至可以是另一個模板字串
const str = `
<ul>
${lists.map(item => {
return `<li>${item.user} is ${item.age} years old.</li>`
}).join('')}
</ul>
`
標籤模板
const username = 'by.Genesis'
const age = 18
const str = tag`${username} is ${age} years old.`
// tag 就是一個函式
// 第一個引數為字串按插值分割而成的陣列
// 後面的引數為插值表示式的值
// 可以自行處理字串邏輯
function tag(template, ...substitutions) {
console.log(template) // ['', ' is ', ' years old.']
console.log(substitutions) // ['by.Genesis', 18]
return substitutions[0] + template[1] + 'handsome'
}
str === 'by.Genesis is handsome'
代理和反射
代理(Proxy)就是為一個目標物件生成一個代理,當對這個代理物件執行一些操作的時候,就會觸發對應的攔截器,在攔截器中可以自行定義操作和返回的值,或者用反射(Reflect)執行元操作,每個代理方法都有對應的反射方法
const obj = {}
const proxy = new Proxy(obj, {
get(target, key) {
// 屬性取值
if (key === 'name') {
// 自定義返回值
return 'by.Genesis'
} else {
// 用反射還原操作
return Reflect.get(target, key)
}
},
set() { 屬性賦值 },
has() { in 操作符 },
deleteProperty() { 刪除屬性 },
getPrototypeOf() { 獲取原型 },
setPrototypeOf() { 設定原型 },
defineProperty() { Object.defineProperty },
getOwnPropertyDescriptor() { Object.getOwnPropertyDescriptor },
preventExtensions() { Object.preventExtensions },
isExtensible() { Object.isExtensible },
ownKeys() { Object.keys, Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.assign },
enumerable() { for in 迴圈 },
apply() { 函式普通呼叫 },
construct() { new 方式呼叫函式 }
})
proxy.name === 'by.Genesis'
obj.name === undefined
以上是這些攔截器以及對應的觸發條件
邏輯運算
Nullish coalescing Operator
JavaScript 裡面的假值(Falsy)有 null, undefined, 0, '', NaN, false
,除假值外都為真值(Truthy)
而空值(Nullish)只有 null
和 undefined
,當該運算子左側為空值時返回右側
null ?? 1 // 1
undefined ?? 1 // 1
0 ?? 1 // 0
0 || 1 // 1
Optional chaining
鏈式操作時,當中間某個值是 null
或者 undefined
就會報錯,而這個操作符可以讓鏈式操作更安全
const o = {}
o.p.q // Uncaught TypeError: Cannot read property 'q' of undefined
o.p?.q // undefined
邏輯賦值
// 邏輯或賦值
a ||= b // 當 a 為假值時賦值
a || a = b
// 邏輯與賦值
a &&= b // 當 a 為真值時賦值
a && a = b
// 邏輯空賦值
a ??= b // 當 a 為空值時賦值
a ?? a = b
模組
在 ES 模組(Modules) 問世之前,已經有各種定義模組的規範了,比如 AMD,CommonJS 等,ES 模組提供語言層面的支援
使用 export
關鍵字匯出模組,使用 import
關鍵字匯入模組
// 可以同時匯出多個具名模組
export const sum = (x, y) => x + y
export const name = 'by.Genesis'
// 匯入具名模組時名稱必須和匯出時一致
// 另外可以使用 as 關鍵字指定別名
import { sum, name as username } from './example.js'
// 也可以先宣告再匯出,匯出時也可以使用 as 關鍵字指定別名
// 指定別名後,匯入的時候就需要使用這個別名了
export { sum, name as username }
// 一個模組只允許有一個預設匯出
export default { name: 'by.Genesis' }
// 匯入預設模組可以任意命名
import o from './example.js'
// 同時匯入預設模組和具名模組
import o, { sum } from './example.js'
// 全部匯入並指定一個別名,所有模組都會成為這個別名的屬性
import * as m from './example.js'
m.default // 預設模組是 default 屬性
m.sum // 具名模組就是自己的名字
// 從另一個模組中匯入再匯出
export * from './another.js'
// 直接匯入,不指定任何命名
import './example.js'
// 甚至還可以不匯出任何東西,僅僅只是執行一些程式碼而已
數字
// 非無窮
Number.isFinite(101) // true
Number.isFinite(NaN) // false
// 安全整數
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) === true
// 二進位制數字 0b 開頭(binary),八進位制數字 0o 開頭(octonary)
// 前面是數字零,後面是字母,大小寫都可以,但是為了便於區別,建議使用小寫
0b1001 === 0o11
// 新增指數運算子(兩個乘號),主要是給指數運算一個正兒八經的運算子,而不是去呼叫方法
2 ** 3 === Math.pow(2, 3)
2 ** 3 === 2 * 2 * 2
// 數字中可以新增下劃線分割數字,增加數字的可讀性
123_4567_8889 === 12345678889
BigInt
大整數用來表示安全整數範圍之外的整數,數字字面量後面新增一個 n
const num = 9007199254740992n
typeof num === 'bigint'
字串方法
// 重複幾次
'xyz'.repeat(3) // 'xyzxyzxyz'
// 判斷開頭
'http://xyz.io/'.startsWith('http') === true
// 判斷結尾
'avator.jpg'.endsWith('.jpg') === true
// 判斷包含
'xyz'.includes('yz') === true
// 首尾填充,第一個引數為填充後長度,第二個引數為填充字串
'2'.padStart(2, '0') // '02'
// 字串已經達到長度則不填充
'12'.padStart(2, '0') // '12'
// 首尾去空白
' xyz '.trimStart() === 'xyz '
' xyz '.trimLeft() === 'xyz '
' xyz '.trimEnd() === ' xyz'
' xyz '.trimRight() === ' xyz'
// 字串全部替換
'xyx'.replaceAll('x', 'z') === 'zyz'
// replace 方法只會替換一次
'xyx'.replace('x', 'z') === 'zyx'
// 多次替換需要使用全域性正則
'xyx'.replace(/x/g, 'z') === 'zyz'
陣列方法
靜態方法
// 建立只有一個數字值的陣列
Array.of(3) // [3]
// 建構函式只會建立長度為傳入數字的稀疏陣列
new Array(3) // [empty × 3]
// 將類陣列或者可迭代物件轉換為陣列
Array.from($('.modal'))
Array.from({
length: 5
}).map((item, index) => index + 1) // [1, 2, 3, 4, 5]
例項方法
// 填充陣列,可以傳入一個起始索引
new Array(3).fill('x', 1) // [empty, 'x', 'x']
// 包含判斷,可以傳入一個起始索引
[NaN, 1, 2].includes(NaN) === true
[NaN, 1, 2].includes(NaN, 1) === false
// 陣列中查詢元素,返回找到的元素
[1, 2, NaN].find(item => item !== item) // NaN
// 查詢元素索引,返回找到元素的索引
['x', 'y', NaN].findIndex(item => item !== item) === 2
// 複製到指定位置
// 這是一個變異方法,直接在原陣列上進行修改
// Array#copyWithin(target, start, ?end)
[1, 2, 3, 4, 5, 6].copyWithin(3, 0, 3) // [1, 2, 3, 1, 2, 3]
// 扁平化
[1, [2, [3, [4]]]].flat(2) // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat(Infinity) // [1, 2, 3, 4]
// flatMap 相當於 map + flat(1)
// 會自動扁平化一層
// 這個方法可以讓返回的陣列變長,這是普通 map 無法合理辦到的
[1, 2, 3, 4].flatMap(x => [x, x * x]) // [1, 1, 2, 4, 3, 9, 4, 16]
其它方法
// 物件比較
Object.is(NaN, NaN) // true
Object.is(0, -0) // false
// Object.keys() 補充方法
Object.values({ x: 1, y: 2 }) // [1, 2]
Object.entries({ x: 1, y: 2 }) // [['x', 1], ['y', 2]]
Object.fromEntries([['x', 1], ['y', 2]]) // { x: 1, y: 2 }
// 獲取物件全部自身屬性描述
Object.getOwnPropertyDescriptors({ x: 1, y: 2 }) // { x: { value: 1, writable: true, enumerable: true, configurable: true }, y: { value: 2, ... } }