「建議收藏」送你一份精心總結的3萬字ES6實用指南(下)

因爲篇幅限制,因此將文章分紅了 2 篇,這是下篇,包含了從 ES2016ES2021 的全部知識。html

ES2016

Array.prototype.includes

判斷一個數組是否包含某個元素,以前通常是這麼作的:vue

if (arr.indexOf(el) >= 0) {}

// 或者
if (~arr.indexOf(el)) {}
複製代碼

而如今你能夠這麼作了:webpack

if (arr.includes(el)) {}
複製代碼

indexOf 會返回找到元素在數組中的索引位置,判斷的邏輯是是否嚴格相等,因此他在遇到 NaN 的時候不能正確返回索引,可是 includes 解決了這個問題:git

[1, NaN, 3].indexOf(NaN)   // -1
[1, NaN, 3].includes(NaN)  // true
複製代碼

求冪運算符(**)

x ** y 是求 xy 次冪,和 Math.pow(x, y) 功能一致:es6

// x ** y
let squared = 2 ** 2  // 2 * 2 = 4
let cubed = 2 ** 3    // 2 * 2 * 2 = 8
複製代碼

x **= y 表示求 xy 次冪,而且把結果賦值給 xgithub

// x **= y
let x = 2;
x **= 3  // x 最後等於 8
複製代碼

ES2017

Object.values()

返回一個由對象自身全部可遍歷屬性的屬性值組成的數組:web

const person = { name: '布蘭' };
Object.defineProperty(person, 'age', {
    value: 12,
    enumrable: false  // age 屬性將不可遍歷
})
console.log(Object.values(person))  // ['布蘭']

// 相似 str.split('') 效果
console.log(Object.values('abc'))  // ['a', 'b', 'c']
複製代碼

Object.entries()

返回一個由對象自身全部可遍歷屬性的鍵值對組成的數組:正則表達式

const person = { name: '布蘭', age: 12 }
console.log(Object.entries(person))  // [["name", "布蘭"], ["age", 12]]
複製代碼

利用這個方法能夠很好的將對象轉成正在的 Map 結構:vue-cli

const person = { name: '布蘭', age: 12 }
const map = new Map(Object.entries(person))
console.log(map)  // Map { name: '布蘭', age: 12 }
複製代碼

Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptor() 會返回指定對象某個自身屬性的的描述對象,而 Object.getOwnPropertyDescriptors() 則是返回指定對象自身全部屬性的描述對象:npm

const person = { name: '布蘭', age: 12 }

console.log(Object.getOwnPropertyDescriptor(person, 'name'))
// { configurable: true, enumerable: true, value: "布蘭", writable: true }

console.log(Object.getOwnPropertyDescriptors(person))
//{ 
// name: { configurable: true, enumerable: true, value: "布蘭", writable: true },
// age: {configurable: false, enumerable: false, value: 12, writable: false}
//}
複製代碼

配合 Object.create() 能夠實現淺克隆:

const shallowClone = (obj) => Object.create(
    Object.getPrototypeOf(obj),
    Object.getOwnPropertyDescriptors(obj)
)
複製代碼

String.prototype.padStart()

str.padStart(length [, padStr]) 會返回一個新字符串,該字符串將從 str 字符串的左側開始填充某個字符串 padStr(非必填,若是不是字符串則會轉成字符串, 傳入 undefined 和不傳這個參數效果一致)直到達到指定位數 length 爲止:

'abc'.padStart(5, 2)          // '22abc'
'abc'.padStart(5, undefined)  // ' abc'
'abc'.padStart(5, {})         // '[oabc'
'abc'.padStart(5)             // ' abc'
'abcde'.padStart(2, 'f')      // 'abcde'
複製代碼

String.prototype.padEnd()

規則和 padStart 相似,可是是從字符串右側開始填充:

'abc'.padEnd(5, 2)  // 'abc22'
複製代碼

函數參數尾逗號

容許函數在定義和調用的時候時候最後一個參數後加上逗號:

function init( param1, param2, ) { }

init('a', 'b',)
複製代碼

Async函數

  • 使用 async 能夠聲明一個 async 函數,結合 await 能夠用一種很簡介的方法寫成基於 Promise 的異步行爲,而不須要刻意的鏈式調用。await 表達式會暫停整個 async 函數的執行進程並出讓其控制權,只有當其等待的基於 Promise 的異步操做被兌現或被拒絕以後纔會恢復進程。async 函數有以下幾種定義形式:

    // 函數聲明
    async function foo() {}
    
    // 函數表達式
    let foo = async function() {}
    
    // 箭頭函數
    let foo = async () => {}
    
    // 對象方法
    lef obj = {
        async foo() {}
    }
    
    // 類方法
    class Dog {
        async bark() {}
    }
    複製代碼
  • async 函數必定會返回一個 Promise 對象,因此它可使用 then 添加處理函數。若是一個 async 函數的返回值看起來不是Promise,那麼它將會被隱式地包裝在一個 Promise 中:

    async function foo() {
        return 'a'
    }
    foo().then(res => {
        console.log(res)  // 'a'
    })
    複製代碼
  • 內部若是發生錯誤,或者顯示拋出錯誤,那麼 async 函數會返回一個 rejected 狀態的 Promsie

    async function foo() {
        throw new Error('error')
    }
    foo().catch(err => {
        console.log(err)  // Error: error
    })
    複製代碼
  • 返回的 Promise 對象必須等到內部全部 await 命令 Promise 對象執行完纔會發生狀態改變,除非遇到 return 語句或拋出錯誤;任何一個 await 命令返回的 Promise 對象變 爲rejected 狀態,整個 Async 函數都會中斷後續執行:

    async function fn() {
        let a = await Promise.resolve('success')
        console.log('a_' + a)
        let b = await Promise.reject('fail')
        console.log('b_' + b)  // 不會執行
    }
    fn().then(res => {
        console.log(res)  // 不會執行
    }, err => {
        console.log(err)
    })
    // 'a_success'
    // 'fail'
    複製代碼
  • 因此爲了保證 async 裏的異步操做都能完成,咱們須要將他們放到 try...catch() 塊裏或者在 await 返回的 Promise 後跟一個 catch 處理函數:

    async function fn() {
        try {
            let a = await Promise.reject('a fail')
            console.log('a_' + a)  // 不會執行
        } catch (e) {
            console.log(e)  // 'a fail'
        }
        let b = await Promise.reject('b fail')
            .catch(e => {
                console.log(e)  // 'b fail'
            })
        console.log('b_' + b)  // 'bundefined'
    }
    fn().then(res => {
        console.log(res)  // undefined
    }, err => {
        console.log(err)  // 不會執行
    })
    複製代碼
  • 若是 async 函數裏的多個異步操做之間沒有依賴關係,建議將他們寫到一塊兒減小執行時間:

    // 寫法一
    let [foo, bar] = await Promise.all([getFoo(), getBar()])
    
    // 寫法二
    let fooPromise = getFoo()
    let barPromise = getBar()
    let foo = await fooPromise
    let bar = await barPromise
    複製代碼
  • await 命令只能用在 async 函數之中,若是用在普通函數,就會報錯。

共享內存和Atomics對象

ES2018

Promise.prototype.finally()

Promise.prototype.finally() 用於給 Promise 對象添加 onFinally 函數,這個函數主要是作一些清理的工做,只有狀態變化的時候纔會執行該 onFinally 函數。

function onFinally() {
    console.log(888)  // 並不會執行 
}
new Promise((resolve, reject) => {
    
}).finally(onFinally)
複製代碼

finally() 會生成一個 Promise 新實例,finally 通常會原樣後傳父 Promise,不管父級實例是什麼狀態:

let p1 = new Promise(() => {})
let p2 = p1.finally(() => {})
setTimeout(console.log, 0, p2)  // Promise {<pending>}

let p3 = new Promise((resolve, reject) => {
    resolve(3)
})
let p4 = p3.finally(() => {})
setTimeout(console.log, 0, p3)  // Promise {<fulfilled>: 3}
複製代碼

上面說的是通常,可是也有特殊狀況,好比 finally 裏返回了一個非 fulfilledPromise 或者拋出了異常的時候,則會返回對應狀態的新實例:

let p1 = new Promise((resolve, reject) => {
    resolve(3)
})
let p2 = p1.finally(() => new Promise(() => {}))
setTimeout(console.log, 0, p2)  // Promise {<pending>}

let p3 = p1.finally(() => Promise.reject(6))
setTimeout(console.log, 0, p3)  // Promise {<rejected>: 6}

let p4 = p1.finally(() => {
    throw new Error('error')
})
setTimeout(console.log, 0, p4)  // Promise {<rejected>: Error: error}
複製代碼

參考:深刻理解Promise

異步迭代器

想要了解異步迭代器最好的方式就是和同步迭代器進行對比。咱們知道可迭代數據的內部都是有一個 Symbol.iterator 屬性,它是一個函數,執行後會返回一個迭代器對象,這個迭代器對象有一個 next() 方法能夠對數據進行迭代,next() 執行後會返回一個對象,包含了當前迭代值 value 和 標識是否完成迭代的 done 屬性:

let iterator = [1, 2][Symbol.iterator]()
iterator.next()  // { value: 1, done: false }
iterator.next()  // { value: 2, done: false }
iterator.next()  // { value: undefinde, done: true }
複製代碼

上面這裏的 next() 執行的是同步操做,因此這個是同步迭代器,可是若是 next() 裏須要執行異步操做,那就須要異步迭代了,可異步迭代數據的內部有一個 Symbol.asyncIterator 屬性,基於此咱們來實現一個異步迭代器:

class Emitter {
    constructor(iterable) {
        this.data = iterable
    }
    [Symbol.asyncIterator]() {
        let length = this.data.length,
            index = 0;
        
        return {
            next:() => {
                const done = index >= length
                const value = !done ? this.data[index++] : undefined
                return new Promise((resolve, reject) => {
                    resolve({value, done})
                })
            }
        }
    }
}
複製代碼

異步迭代器的 next() 會進行異步的操做,一般是返回一個 Promise,因此須要對應的處理函數去處理結果:

let emitter = new Emitter([1, 2, 3])
let asyncIterator = emitter[Symbol.asyncIterator]()
asyncIterator.next().then(res => {
    console.log(res)  // { value: 1, done: false }
})
asyncIterator.next().then(res => {
    console.log(res)  // { value: 2, done: false }
})
asyncIterator.next().then(res => {
    console.log(res)  // { value: 3, done: false }
})
複製代碼

另外也可使用 for await...of 來迭代異步可迭代數據:

let asyncIterable = new Emitter([1, 2, 3])
async function asyncCount() {
    for await (const x of asyncIterable ) {
        console.log(x)
    }
}
asyncCount()
// 1 2 3
複製代碼

另外還能夠經過異步生成器來建立異步迭代器:

class Emitter {
    constructor(iterable) {
        this.data = iterable
    }
    async *[Symbol.asyncIterator]() {
        let length = this.data.length,
            index = 0;
            
        while (index < length) {
            yield this.data[index++]
        }
    }
}
async function asyncCount() {
    let emitter = new Emitter([1, 2, 3])
    const asyncIterable = emitter[Symbol.asyncIterator]()
    for await (const x of asyncIterable ) {
        console.log(x)
    }
}
asyncCount()
// 1 2 3
複製代碼

參考:

s修飾符(dotAll模式)

正則表達式新增了一個 s 修飾符,使得 . 能夠匹配任意單個字符:

/foo.bar/.test('foo\nbar')   // false
/foo.bar/s.test('foo\nbar')  // true
複製代碼

上面這又被稱爲 dotAll 模式,表示點(dot)表明一切字符。因此,正則表達式還引入了一個dotAll屬性,返回一個布爾值,表示該正則表達式是否處在dotAll模式:

/foo.bar/s.dotAll  // true
複製代碼

具名組匹配

正則表達式可使用捕獲組來匹配字符串,可是想要獲取某個組的結果只能經過對應的索引來獲取:

let re = /(\d{4})-(\d{2})-(\d{2})/
let result = re.exec('2015-01-02')
// result[0] === '2015-01-02'
// result[1] === '2015'
// result[2] === '01'
// result[3] === '02'
複製代碼

而如今咱們能夠經過給捕獲組 (?<name>...) 加上名字 name ,經過名字來獲取對應組的結果:

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
let result = re.exec('2015-01-02')
// result.groups.year === '2015'
// result.groups.month === '01'
// result.groups.day === '02'
複製代碼

配合解構賦值能夠寫出很是精簡的代碼:

let {groups: {year, month, day}} = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/.exec('2015-01-02')
console.log(year, month, day)  // 2015 01 02
複製代碼

具名組也能夠經過傳遞給 String.prototype.replace 的替換值中進行引用。若是該值爲字符串,則可使用 $<name> 獲取到對應組的結果:

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
let result = '2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// result === '02/01/2015'
複製代碼

參考:proposal-regexp-named-groups

後行斷言

後行斷言: (?<=y)xx 只有在 y 後面才能匹配:

/(?<=\$)\d+/.exec('I have $100.') // ['100'] 複製代碼

後行否認斷言: (?<!y)xx 只有不在 y 後面才能匹配:

/(?<!\$)\d+/.exec('I have $100.') // ['00'] 複製代碼

Unicode屬性轉義

容許正則表達式匹配符合 Unicode 某種屬性的全部字符,\p{...} 是匹配包含,\P{...} 是匹配不包含的字符,且必須搭配 /u 修飾符纔會生效:

/\p{Emoji}+/u.exec('😁😭笑死我了🤣😂不行了') // ['😁😭'] /\P{Emoji}+/u.exec('😁😭笑死我了🤣😂不行了') // ['笑死我了'] 複製代碼

這裏能夠查詢到更多的 Unicode 的屬性 Full_Properties

對象擴展運算符

對象的擴展運算符能夠用到解構賦值上,且只能應用到最後一個變量上:

let {x, ...y} = {x: 1, a: 2, b: 3}
console.log(y)  // {a: 2, b: 3}
複製代碼

對象擴展運算符不能解構原型上的屬性:

let obj = { x: 1 }
obj.__proto__ = { y: 2 }
let {...a} = obj
console.log(a.y)  // undefined
複製代碼

應用一:能夠實現淺拷貝,可是不會拷貝原始屬性:

let person = Object.create({ name: '布蘭' })
person.age = 12

// 淺拷貝寫法一
let { ...pClone1 } = person
console.log(pClone1)  // { age: 12 }
console.log(pClone1.name)  // undefined

// 淺拷貝寫法二
let pClone2 = {...person}
console.log(pClone2)  // { age: 12 }
console.log(pClone2.name)  // undefined
複製代碼

應用二:合併兩個對象:

let ab = {...a, ...b}

// 等同於
let ab = Object.assign({}, a, b);
複製代碼

應用三:重寫對象屬性

let aWithOverrides = { ...a, x: 1, y: 2 };
複製代碼

應用四:給新對象設置默認值

let aWithDefaults = { x: 1, y: 2, ...a };
複製代碼

應用五:利用擴展運算符的解構賦值能夠擴展函數參數:

function baseFunction({ a, b }) {}
function wrapperFunction({ x, y, ...restConfig }) {
    // 使用 x 和 y 參數進行操做
    // 其他參數傳給原始函數
    return baseFunction(restConfig)
}
複製代碼

參考:

放鬆對標籤模板裏字符串轉義的限制

ECMAScript 6 入門

ES2019

容許省略catch裏的參數

異常被捕獲的時候若是不須要作操做,甚至能夠省略 catch(err) 裏的參數和圓括號:

try {

} catch {
    
}
複製代碼

JSON.stringify()變更

UTF-8 標準規定,0xD8000xDFFF 之間的碼點,不能單獨使用,必須配對使用。 因此 JSON.stringify() 對單個碼點進行操做,若是碼點符合 UTF-8 標準,則會返回對應的字符,不然會返回對應的碼點:

JSON.stringify('\u{1f600}')  // ""😀""
JSON.stringify('\u{D834}')  // ""\ud834""
複製代碼

Symbol.prototype.description

Symbol 實例新增了一個描述屬性 description

let symbol = Symbol('foo')
symbol.description  // 'foo'
複製代碼

Function.prototype.toString()

函數的 toString() 會原樣輸出函數定義時候的樣子,不會省略註釋和空格。

Object.fromEntries()

Object.fromEntries() 方法是 Object.entries() 的逆操做,用於將一個鍵值對數組轉爲對象:

let person = { name: '布蘭', age: 12 }
let keyValueArr = Object.entries(person)   // [['name', '布蘭'], ['age', 12]]
let obj = Object.fromEntries(arr)  // { name: '布蘭', age: 12 }
複製代碼

經常使用可迭代數據結構之間的裝換:

let person = { name: '布蘭', age: 12 }

// 對象 -> 鍵值對數組
let keyValueArr = Object.entries(person)  // [['name', '布蘭'], ['age', 12]]

// 鍵值對數組 -> Map
let map = new Map(keyValueArr)  // Map {"name": "布蘭", "age": 12}

// Map -> 鍵值對數組
let arr = Array.from(map)  // [['name', '布蘭'], ['age', 12]] 

// 鍵值對數組 -> 對象
let obj = Array.from(arr).reduce((acc, [ key, val ]) => Object.assign(acc, { [key]: val }), {})  // { name: '布蘭', age: 12 }
複製代碼

參考:Object.fromEntries

字符串可直接輸入行分隔符和段分隔符

JavaScript 規定有 5 個字符,不能在字符串裏面直接使用,只能使用轉義形式。

  • U+005C:反斜槓(reverse solidus)
  • U+000D:回車(carriage return)
  • U+2028:行分隔符(line separator)
  • U+2029:段分隔符(paragraph separator)
  • U+000A:換行符(line feed)

可是因爲 JSON 容許字符串裏可使用 U+2028U+2029,因此使得 JSON.parse() 去解析字符串的時候可能會報錯,因此 ES2019 容許模板字符串裏能夠直接這兩個字符:

JSON.parse('"\u2028"')  // ""
JSON.parse('"\u2029"')  // ""
JSON.parse('"\u005C"')  // SyntaxError
複製代碼

String.prototype.trimStart

消除字符串頭部空格,返回一個新字符串;瀏覽器還額外增長了它的別名函數 trimLeft()

let str = ' hello world '
let newStr = str.trimStart()
console.log(newStr, newStr === str) 
// 'hello world ' false
複製代碼

String.prototype.trimEnd

消除字符串尾部空格,返回一個新字符串;瀏覽器還額外增長了它的別名函數 trimRight()

let str = ' hello world '
let newStr = str.trimEnd()
console.log(newStr, newStr === str) 
// ' hello world' false
複製代碼

Array.prototype.flat()

arr.flat(depth) 按照 depth (不傳值的話默認是 1)深度拍平一個數組,而且將結果以新數組形式返回:

// depth 默認是 1
const arr1 = [1, 2, [3, 4]]
console.log(arr1.flat())  // [1, 2, 3, 4]

// 使用 Infinity,可展開任意深度的嵌套數組;自動跳過空數組;
const arr2 = [1, , [2, [3, [4]]]]
console.log(arr2.flat(Infinity))
// [1, 2, 3, 4]
複製代碼

reduce 實現拍平一層數組:

const arr = [1, 2, [3, 4]]

// 方法一
let newStr = arr.reduce((acc, cur) => acc.concat(cur), [])

// 方法二
const flattened = arr => [].concat(...arr)
flattened(arr)
複製代碼

參考:flat

Array.prototype.flatMap()

flatMap(callback) 使用映射函數 callback 映射每一個元素,callback 每次的返回值組成一個數組,而且將這個數組執行相似 arr.flat(1) 的操做進行拍平一層後最後返回結果:

const arr1 = [1, 2, 3, 4]

arr1.flatMap(x => [x * 2])
// 將 [[2], [4], [6], [8]] 數組拍平一層獲得最終結果:[2, 4, 6, 8]
複製代碼

參考:flatMap

ES2020

String.prototype.matchAll()

String.prototype.matchAll() 方法,能夠一次性取出全部匹配。不過,它返回的是一個 RegExpStringIterator 迭代器同是也是一個可迭代的數據結構,因此能夠經過 for...of 進行迭代:

let str = 'test1test2'
let regexp = /t(e)(st(\d?))/g
let iterable = str.matchAll(regexp)
for (const x of iterable) {
    console.log(x)
}
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test1', groups: undefined]
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', groups: undefined]
複製代碼

注意當使用 matchAll(regexp) 的時候,正則表達式必須加上 /g 修飾符。

也能夠將這個可迭代數據轉成數組形式:

// 方法一
[...str.matchAll(regexp)]

// 方法二
Array.from(str.matchAll(regexp))
複製代碼

動態import()

標準用法的 import 導入的模塊是靜態的,會使全部被導入的模塊,在加載時就被編譯(沒法作到按需編譯,下降首頁加載速度)。有些場景中,你可能但願根據條件導入模塊或者按需導入模塊,這時你可使用動態導入代替靜態導入。

好比按需加載一個模塊能夠這樣:

if (xxx) {
    import('./module.js')
}
複製代碼

import() 是異步導入的,結果會返回一個 Promise

import('/module.js')
.then((module) => {
    // Do something with the module.
})
複製代碼

動態 import() 的應用場景挺多的,好比 Vue 中的路由懶加載就是使用的動態導入組件。另外因爲動態性不便於靜態分析工具和 tree-shaking 工做,因此不能濫用。

BigInt

BigInt 是一種內置對象,它提供了一種方法來表示大於 2 53 2^{53} - 1 的整數。這本來是 Javascript 中能夠用 Number 表示的最大數字。BigInt 能夠表示任意大的整數。

爲了區分 Number,定義一個 BigInt 須要在整數後面加上一個 n,或者用函數直接定義:

const num1 = 10n
const num2 = BigInt(20)
複製代碼

NumberBigInt 之間能進行比較,但他們之間是寬鬆相等;且因爲他們表示的是不一樣類型的數字,因此不能直接進行四則運算:

10n == 10         // true
10n === 10        // false
10n > 8           // true
10 + Number(10n)  // 20
10 + 10n          // TypeError
複製代碼

Promise.allSettled

Promise.allSettled(iterable) 當全部的實例都已經 settled,即狀態變化過了,那麼將返回一個新實例,該新實例的內部值是由全部實例的值和狀態組合成的數組,數組的每項是由每一個實例的狀態和內部值組成的對象。

function init(){
    return 3
}
let p1 = Promise.allSettled([
    new Promise((resolve, reject) => {
        resolve(9)
    }).then(res => {}),
    new Promise((resolve, reject) => {
        reject(6)
    }),
    init()
])
let p2 = p1.then(res => {
    console.log(res)
}, err => {
    console.log(err)
})
// [
// {status: "fulfilled", value: undefined}, 
// {status: "rejected", reason: 6}, 
// {status: "fulfilled", value: 3}
// ]
複製代碼

只要全部實例中包含一個 pending 狀態的實例,那麼 Promise.allSettled() 的結果爲返回一個這樣 Promise {<pending>} 的實例。

globalThis

在之前,從不一樣的 JavaScript 環境中獲取全局對象須要不一樣的語句。在 Web 中,能夠經過 windowself 或者 frames 取到全局對象,可是在 Web Workers 中,只有 self 能夠。在 Node.js 中,它們都沒法獲取,必須使用 global

而如今只須要使用 globalThis 便可獲取到頂層對象,而不用擔憂環境問題。

// 在瀏覽器中
globalThis === window  // true
複製代碼

import.meta

import.meta 是一個給 JavaScript 模塊暴露特定上下文的元數據屬性的對象。它包含了這個模塊的信息,好比說這個模塊的 URLimport.meta 必須在一個模塊裏使用:

// 沒有聲明 type="module",就使用 import.meta 會報錯
<script type="module" src="./js/module.js"></script>

// 在module.js裏
console.log(import.meta)  
// {url: "http://localhost/3ag/js/module.js"}
複製代碼

若是須要在配置了 Webpack 的項目,好比 Vue 裏使用 import.meta 須要加一個包且配置一下參數,不然項目編譯階段會報錯。

包配置詳情參考:@open-wc/webpack-import-meta-loader

好比我用的是 4.x 版本的 vue-cli,那我須要在 vue.config.js 裏配置:

module.exports = {
    chainWebpack: config => {
        config.module
            .rule('js')
            .test(/\.js$/)
            .use('@open-wc/webpack-import-meta-loader')
                .loader('@open-wc/webpack-import-meta-loader')
                .end()
    }
}
複製代碼

可選鏈操做符(?.)

一般咱們獲取一個深層對象的屬性會須要寫不少判斷或者使用邏輯與 && 操做符,由於對象的某個屬性若是爲 null 或者 undefined 就有可能報錯:

let obj = {
    first: {
        second: '布蘭'
    }
}

// 寫法一
let name1 = ''
if (obj) {
    if (obj.first) {
        name1 = obj.first.second
    }
}

// 寫法二
let name2 = obj && obj.first && obj.first.second
複製代碼

?. 操做符容許讀取位於鏈接對象鏈深處的屬性的值,而沒必要明確驗證鏈中的每一個引用是否有效。若是某個屬性爲 null 或者 undefined 則結果直接爲 undefined。有了可選鏈操做符就可使得表達式更加簡明瞭,對於上面例子用可選鏈操做符能夠這麼寫:

let name3 = obj?.first?.second
複製代碼

空值合併操做符(??)

對於邏輯或 || 運算符,當對運算符左側的操做數進行裝換爲 Boolean 值的時候,若是爲 true,則取左邊的操做數爲結果,不然取右邊的操做數爲結果:

let name = '' || '布蘭'
console.log(name)  // '布蘭'
複製代碼

咱們都知道 ''0nullundefinedfalseNaN 等轉成 Boolean 值的時候都是 false,因此都會取右邊的操做數。這個時候若是要給變量設置默認值,若是遇到自己值就多是 ''0 的狀況那就會出錯了,會被錯誤的設置爲默認值了。

?? 操做符就是爲了解決這個問題而出現的,x ?? y 只有左側的操做數爲 nullundefined 的時候才取右側操做數,不然取左側操做數:

let num = 0 ?? 1
console.log(num)  // 0
複製代碼

ES2021

以下這幾個提案已經肯定了會在 2021 年發佈,因此把他們歸到 ES2021 中。

String.prototype.replaceAll

以前須要替換一個字符串裏的所有匹配字符能夠這樣作:

const queryString = 'q=query+string+parameters'

// 方法一
const withSpaces1 = queryString.replace(/\+/g, ' ')

// 方法二
const withSpaces2 = queryString.split('+').join(' ')
複製代碼

而如今只須要這麼作:

const withSpace3 = queryString.replaceAll('+', ' ')
複製代碼

replaceAll 的第一個參數能夠是字符串也能夠是正則表達式,當是正則表達式的時候,必須加上全局修飾符 /g,不然報錯。

參考:string-replaceall

Promise.any()

Promsie.any()Promise.all() 同樣接受一個可迭代的對象,而後依據不一樣的入參會返回不一樣的新實例:

  • 傳一個空的可迭代對象或者可迭代對象全部 Promise 都是 rejected 狀態的,則會拋出一個 AggregateError 類型的錯誤,同時返回一個 rejected 狀態的新實例:

    let p1 = Promise.any([])
    let p2.catch(err => {})
    setTimeout(console.log, 0, p1)
    // Promise {<rejected>: AggregateError: All promises were rejected}
    複製代碼
  • 只要可迭代對象裏包含任何一個 fulfilled 狀態的 Promise,則會返回第一個 fulfilled 的實例,而且以它的值做爲新實例的值:

    let p = Promise.any([
        1,
        Promise.reject(2),
        new Promise((resolve, reject) => {}),
        Promise.resolve(3),
    ])
    setTimeout(console.log, 0, p)
    // Promise {<fulfilled>: 1}
    複製代碼
  • 其餘狀況下,都會返回一個 pending 狀態的實例:

    let p = Promise.any([
        Promise.reject(2),
        Promise.reject(3),
        new Promise((resolve, reject) => {}),
    ])
    setTimeout(console.log, 0, p)
    // Promise {<pending>: undefined}
    複製代碼

WeakRef

咱們知道一個普通的引用(默認是強引用)會將與之對應的對象保存在內存中。只有當該對象沒有任何的強引用時,JavaScript 引擎 GC 纔會銷燬該對象而且回收該對象所佔的內存空間。

WeakRef 對象容許你保留對另外一個對象的弱引用,而不會阻止被弱引用的對象被 GC 回收。WeakRef 的實例方法 deref() 能夠返回當前實例的 WeakRef 對象所綁定的 target 對象,若是該 target 對象已被 GC 回收則返回 undefined

let person = { name: '布蘭', age: 12 }
let wr = new WeakRef(person)
console.log(wr.deref())  
// { name: '布蘭', age: 12 }
複製代碼

正確使用 WeakRef 對象須要仔細的考慮,最好儘可能避免使用。這裏面有諸多緣由,好比:GC 在一個 JavaScript 引擎中的行爲有可能在另外一個 JavaScript 引擎中的行爲截然不同,或者甚至在同一類引擎,不一樣版本中 GC 的行爲都有可能有較大的差距。GC 目前仍是 JavaScript 引擎實現者不斷改進和改進解決方案的一個難題。

參考:

邏輯賦值符

邏輯賦值符包含 3 個:

  • x &&= y:邏輯與賦值符,至關於 x && (x = y)
  • x ||= y:邏輯或賦值符,至關於 x || (x = y)
  • x ??= y:邏輯空賦值符,至關於 x ?? (x = y)

看以下示例,加深理解:

let x = 0
x &&= 1  // x: 0
x ||= 1  // x: 1
x ??= 2  // x: 1

let y = 1
y &&= 0     // y: 0
y ||= null  // y: null
y ??= 2     // y: 2
複製代碼

數值分隔符(_)

對於下面一串數字,你一眼看上去不肯定它究竟是多少吧?

const num = 1000000000
複製代碼

那如今呢?是否是能夠很清楚的看出來它是 10 億:

const num = 1_000_000_000
複製代碼

數值分隔符(_)的做用就是爲了讓數值的可讀性更強。除了能用於十進制,還能夠用於二級制,十六進制甚至是 BigInt 類型:

let binarary = 0b1010_0001_1000_0101
let hex = 0xA0_B0_C0
let budget = 1_000_000_000_000n
複製代碼

使用時必須注意 _ 的兩邊必需要有類型的數值,不然會報錯,如下這些都是無效的寫法:

let num = 10_
let binarary = 0b1011_
let hex = 0x_0A0B
let budget = 1_n
複製代碼

參考文章

相關文章
相關標籤/搜索