ES6-ES12部分簡單知識點總結,希望對大家有用~

MomentYY發表於2022-03-24

ES6-ES12簡單知識點總結

1.ES6相關知識點

1.1.物件字面量的增強

ES6中對物件字面量的寫法進行了增強,主要包含以下三個方面的增強:

  • 屬性的簡寫:當給物件設定屬性時,如果希望變數名和屬性名一樣就可以直接寫該變數名;
  • 方法的簡寫:物件中的方法可直接寫成foo() {}的形式;
  • 計算屬性名:物件的屬性名可以動態傳入,將變數使用[]包裹即可;
const obj = {
  // 1.屬性簡寫
  name,
  age,
  // 2.方法簡寫
  foo() {
    console.log('foo')
  },
  // 3.計算屬性名
  [key]: 'value'
}

1.2.解構

為了方便從陣列或物件中獲取資料,ES6給我們提供瞭解構的方案,可分為陣列的解構和物件的解構。

  • 陣列的解構:注意陣列的解構是按元素順序來的。

    const names = ['curry', 'kobe', 'klay', 'james']
    
    // 1.基本的解構,解構出陣列所有的元素
    var [name1, name2, name3, name4] = names
    console.log(name1, name2, name3, name4) // curry kobe klay james
    
    // 2.解構部分元素,只解構後面兩個元素
    var [, , name3, name4] = names
    console.log(name3, name4) // klay james
    
    // 3.解構出第一個元素,後面的元素放到一個新陣列中
    var [name1, ...newNames] = names
    console.log(newNames) // [ 'kobe', 'klay', 'james' ]
    
    // 4.解構陣列的同時,給元素指定預設值,如果陣列中沒有該元素,就會使用預設值
    var [name1, name2, name3, name4, name5 = 'default'] = names
    console.log(name1, name2, name3, name4, name5) // curry kobe klay james default
    
  • 物件的解構:物件的解構是不按順序的,是根據key來賦值的。

    const obj = {
      name: 'curry',
      age: 30,
      team: '金州勇士'
    }
    
    // 1.基本的解構
    var { name, age, team } = obj
    console.log(name, age, team) // curry 30 金州勇士
    
    // 2.解構的同時重新命名
    var { name: newName, age: newAge, team: newTeam } = obj
    console.log(newName, newAge, newTeam) // curry 30 金州勇士
    
    // 3.解構的同時指定預設值,當物件中沒有該屬性時,就會取預設值
    var { height = '1.83' } = obj
    console.log(height) // 1.83
    
    // 4.同時重新命名和指定預設值
    var { height: newHeight = '1.83' } = obj
    console.log(newHeight) // 1.83
    
  • 解構的應用場景:一般開發中拿到一個變數,可以對齊進行解構使用,比如對函式的引數進行解構。

    function fn({ name, age, team }) {
      console.log(name, age, team)
    }
    fn(obj) // curry 30 金州勇士
    

1.3.let和const的使用

在ES5中,宣告變數都是使用var,從ES6開始新增了兩個宣告變數的關鍵字let和const。

  • let關鍵字:從直觀的角度來說,let和var是沒有太大的區別的,都是用於宣告一個變數。

    let message = 'hello world'
    
  • const關鍵字:const宣告的變數稱為常量,表示儲存的資料一旦被賦值就不能再被修改了,但是如果是引用型別的資料,還是可以通過引用找到對應的物件進行修改的。

    const obj = {
      name: 'curry',
      age: 30
    }
    const names = ['curry', 'kobe']
    
    obj.name = 'kobe'
    names.push('klay')
    
    console.log(obj) // { name: 'kobe', age: 30 }
    console.log(names) // [ 'curry', 'kobe', 'klay' ]
    
    // 注意:不能直接給引用型別重新賦值
    /*
      obj = {} // TypeError: Assignment to constant variable.
      names = [] // TypeError: Assignment to constant variable.
    */
    
  • 注意:與var不同的是,let和const是不允許重複宣告變數的。並且使用let和const宣告的變數是具有自己的塊級作用域的。var和let可以不設定初始值,const必須設定。

有關作用域提升的問題:

使用過var關鍵字的人都知道,var宣告的變數是會進行作用域提升的,如果使用let和const宣告的變數,是不允許在宣告之前對其進行訪問的,會直接報錯。

console.log(message) // undefined
var message = 'hello world'
console.log(message) // ReferenceError: Cannot access 'message' before initialization
let message = 'hello world'

為什麼var宣告的變數有作用域提升,而let和const沒有呢?

  • 作用域提升:在宣告變數的作用域中,如果這個變數可以在宣告之前被訪問,那麼就可以稱之為作用域提升。
  • 在JavaScript執行之前,會先對我們宣告的變數進行收集建立,普通變數的預設值都為undefined,只有等到執行賦值程式碼時,該變數才會被真正賦值,所以在宣告之前訪問就為undefined。
  • 那let和const不能訪問,是不是let和const宣告的變數沒有在程式碼執行前被收集建立呢?到底let和const有沒有作用域提升?
    • 其實let和const宣告的變數在程式碼執行前是有被收集建立的,只是不能訪問而已,所以就不能稱之為作用域提升,在ECMA262對let和const的描述中,這些變數會被建立在包含它們的詞法環境被例項化時,但是是不可以訪問它們的,直到詞法繫結被求值(也就是變數被賦值之後);
    • 在使用var宣告的變數,是會新增到window上的,而let和const宣告的變數是不會被新增到window上的,實際上是被新增到了變數環境中進行儲存;
    • 暫時性死區:在程式碼中,使用let、const宣告的變數,在宣告之前變數是不可以被訪問的,這種現象稱之為temporal dead zone(TDZ);

總結:在上面的程式碼中,其實message在程式碼執行前就已經被建立了,在用let進行修飾後,js底層會對其進行約束,以至於在宣告前不可以訪問。

1.4.模板字串

ES6允許使用字串模板來嵌入JS的變數或者表示式來進行拼接。

使用``來編寫字串,可以${expression}來嵌入動態內容,裡面可以寫一個表示式。

const name = 'curry'
const age = 30

console.log(`My name is ${name}\nMy age is ${age}`)

標籤模板字串:模板字串另外一種用法,可以用來呼叫函式,如果使用標籤模板字串,並且在呼叫的時候插入其他的變數。

  • 模板字串被拆分了;
  • 第一個元素是陣列,元素為被${}的字串組合;
  • 後面的元素是一個個${}中傳入的變數值;
function fn(x, y, z) {
  console.log(x, y, z)
}

const name = 'curry'
const age = 30

fn`sojfo${name}hghaooa${age}jgoajg` // [ 'sojfo', 'hghaooa', 'jgoajg' ] curry 30

應用:在react中編寫css就有這麼一個庫叫styled-components,其原理就是使用的標籤模組字串。

1.5.函式的預設引數

在ES6之前,我們編寫的函式引數是沒有預設值的,而ES6給我們提供了預設引數,如果傳入了該引數就使用傳入的值,沒有傳入就使用預設值。

  • 在ES6之前,如果想實現預設引數的效果,就必須編寫以下最終程式碼。

    function fn() {
      var m = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'aaa'
      var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'bbb'
      console.log(m, n)
    }
    
    fn() // aaa bbb
    fn(111, 222) // 111 222
    
  • 在ES6中,可以在宣告函式時給其引數預設值。

    • 一般預設值寫法:

      function fn(m = 'aaa', n = 'bbb') {
        console.log(m, n)
      }
      
    • 預設值搭配解構的使用:

      // 1.寫法一:物件引數+解構
      function fn1({ name, age } = { name: 'curry', age: 30 }) {
        console.log(name, age)
      }
      fn1() // curry 30
      
      // 2.寫法二:解構+預設值
      function fn2({ name = 'curry', age = 30 } = {}) {
        console.log(name, age)
      }
      fn2() // curry 30
      

注意:有預設值的引數一般會放到最後,在JS中函式都會有一個length屬性,length長度代表函式引數個數,但是有預設值的引數是不會被計算在內的。但是如果將有預設值的引數放在中間,那麼後面的引數也不會被計算在內的。

function fn1(x, y, z) {}
console.log(fn1.length) // 3

function fn2(x, y, z = 30) {}
console.log(fn2.length) // 2

function fn3(x, y = 30, z) {}
console.log(fn3.length) // 1

1.6.函式的剩餘引數

ES6中引用了剩餘引數,可以將不定數量的引數放入到一個陣列中,如果最後一個引數是以...為字首的,那麼它會將剩餘的引數放入到該引數中,並且作為一個陣列。

剩餘引數和arguments有什麼區別呢?

  • 剩餘引數只包含那些沒有對應形參的實參,而arguments物件包含了傳給函式的所有實參;
  • arguments物件不是一個真正的陣列,而rest是一個真正的陣列,可以進行陣列的所有操作;
  • arguments是早期的ECMAScript中為了方便去獲取所有的引數提供的一個資料結構,而剩餘引數是ES6中提供並且希望代替arguments的;
function foo(m, n, ...args) {
  console.log(m, n, args)
  console.log(arguments) // [Arguments] { '0': 10, '1': 20, '2': 30, '3': 40, '4': 50 }
}

foo(10, 20, 30, 40, 50) // 10 20 [ 30, 40, 50 ]

1.7.箭頭函式

相比於普通函式,箭頭函式有一下幾個特殊點:

  • 箭頭函式內部是不繫結this的,會去它上一層作用域中找;
  • 箭頭函式是沒有顯示原型的,所以不能作為建構函式,使用new操作符來呼叫;
  • 箭頭函式沒有自己的argments;
const fn = () => {
  console.log(this)
  // console.log(argments) // ReferenceError: argments is not defined
}

fn() // window物件
console.log(fn.prototype) // undefined

1.8.展開語法

展開語法(spread syntax)可以在函式呼叫或陣列構造是,將陣列表示或者字串在語法層面展開,還可以在構造字面量物件時,將物件表示式按鍵值對的方式展開。

展開語法的主要使用場景:在函式呼叫時陣列構造時使用:

const nums = [1, 2, 3]
const str = 'abc'

function fn(x, y, z) {
  console.log(x, y, z)
}

// 函式呼叫時
fn(...nums) // 1 2 3
fn(...str) // a b c

// 陣列構造時
const newNums1 = [...nums, 4, 5]
const newNums2 = [...nums, ...str]
console.log(newNums1) // [ 1, 2, 3, 4, 5 ]
console.log(newNums2) // [ 1, 2, 3, 'a', 'b', 'c' ]

1.9.數值的表示

ES6中規範了二進位制和八進位制的寫法。

const num1 = 188 // 十進位制
const num2 = 0b101 // 二進位制
const num3 = 0o567 // 八進位制
const num4 = 0x8cf // 十六進位制

// 列印轉成對應的十進位制數
console.log(num1, num2, num3, num4) // 188 5 375 2255

1.10.Symbol的使用

在ES6之前,物件屬性名都是字串形式,很容易造成屬性名衝突,Symbol是ES6中新增的一個基本資料型別,可用來生成一個獨一無二的值。

  • 作為屬性名:

    const s1 = Symbol()
    const s2 = Symbol()
    const s3 = Symbol()
    
    // 對比呼叫Symbol生成的值
    console.log(s1 === s2) // false
    
    // Symbol值作為物件key
    // 寫法一:定義物件字面量時使用
    const obj = {
      [s1]: 'aaaa'
    }
    // 寫法二:物件新增屬性
    obj[s2] = 'bbbb'
    // 寫法三:通過Object.defineProperty()
    Object.defineProperty(obj, s3, {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 'cccc'
    })
    
    console.log(obj) // { [Symbol()]: 'aaaa', [Symbol()]: 'bbbb', [Symbol()]: 'cccc' }
    
  • 作為屬性值:

    const s1 = Symbol()
    
    const obj = {
      name: 'curry',
      age: s1
    }
    
    console.log(obj) // { name: 'curry', age: Symbol() }
    
  • 注意:Symbol作為屬性名時,不能通過.方式來獲取,也就是obj.s1,只能通過obj[s1]的方式獲取。並且在通過Object.keys()獲取物件所有的key時,是獲取不到這些Symbol的,可以藉助Object.getOwnPropertySymbols()來獲取所有型別為Symbol的key。

    console.log(obj.s1) // undefined
    
    console.log(obj[s1]) // aaaa
    console.log(obj[s2]) // bbbb
    
    // 使用Object.keys獲取物件所有key並列印,發現陣列中是沒有Symbol的key
    console.log(Object.keys(obj)) // []
    
    // 使用Object.getOwnPropertySymbols(obj)獲取所有的Symbol的key
    console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol(), Symbol(), Symbol() ]
    

延伸:Symbol的作用就是給我們建立一個獨一無二的值,如果我們想建立一個相同的Symbol可以怎麼做呢?

  • 可以使用Symbol.for()來做到這一點;
  • 並且可以通過Symbol.keyFor()方法來獲取對應的key;
// 通過Symbol.for(描述)建立並傳入相同描述
const s1 = Symbol.for('aaaa')
const s2 = Symbol.for('aaaa')

console.log(s1 === s2) // true

// 拿到s1傳入的值
const key = Symbol.keyFor(s1)
console.log(key) // aaaa

// 將拿到的值又傳給s3
const s3 = Symbol.for(key)
console.log(s3 === s1) // true
console.log(s3 === s2) // true

1.11.Set和WeakSet的使用

在ES6之前,儲存資料的結構主要有兩種,分別是陣列和物件。在ES6中新增了另外兩種資料結構:Set、Map,以及它們另外形式的WeakSet、WeakMap。下面就先來看看Set和WeakSet。

(1)Set:用於儲存資料,類似於陣列,與陣列不同的是Set中的元素是不能重複的。

  • Set的基本使用:

    // 1.建立一個Set結構
    const set = new Set()
    
    // 2.往set中新增元素
    set.add(30)
    set.add(30) // 相同的基本資料型別只會保留一個
    set.add('abc')
    set.add(undefined)
    set.add({})
    set.add({}) // 物件可以重複的原因是,物件存放的是記憶體地址
    set.add(NaN)
    set.add(NaN) // 為什麼NaN也不能重複,因為在Set內部會視NaN為同一個東西
    
    console.log(set) // Set(6) { 30, 'abc', undefined, {}, {}, NaN }
    
  • Set常見的屬性:

    • size:返回Set中元素的個數;

      console.log(set.size) // 6
      
  • Set常見的方法:

    • add(value):新增一個元素,返回set物件本身;
    • delete(value):從set中刪除與value值相等的元素,返回boolean型別;
    • has(value):判斷set中是否存在value這個元素,返回boolean;
    • clear():清空set中所有的元素,無返回值;
    • forEach(callback,[thisArg]):遍歷set物件(Set的例項化物件是一個可迭代物件,所以也支援for...of遍歷);
    // add
    console.log(set.add(30)) // Set(1) { 30 }
    console.log(set.add(60)) // Set(2) { 30, 60 }
    console.log(set.add(90)) // Set(2) { 30, 60, 90 }
    // delete
    console.log(set.delete(40)) // 未找到40,返回false
    console.log(set.delete(30)) // true
    // has
    console.log(set.has(60)) // true
    console.log(set.has(30)) // false
    // forEach
    set.forEach(item => {
      console.log(item) // 60 90
    })
    // for...of
    for (const item of set) {
      console.log(item) // 60 90
    }
    // clear
    set.clear()
    console.log(set) // Set(0) {}
    

(2)WeakSet:也要求內部元素不能重複。

  • WeakSet與Set的區別:

    • 區別一:WeakSet中只能存放物件型別,不能存放基本資料型別;
    • 區別二:WeakSet對元素的引用是弱引用,如果沒有其他引用對該元素進行引用,是可以被GC回收的;
    • 區別三:WeakSet由於是弱引用的原因,所以是不能進行遍歷的,儲存到WeakSet中的物件是沒辦法通過遍歷獲取的;
    • 什麼是弱引用?:簡單理解就是對物件型別的引用是可以看成沒有的,但是卻可以訪問其物件內部的資料;
  • WeakSet常見方法:add(value)、delete(value)、has(value),沒有clear和forEach;

  • WeakSet的基本使用:

    const wSet = new WeakSet()
    // 只能存放物件型別
    // wSet.add(10) // TypeError: Invalid value used in weak set
    wSet.add({ name: 'curry', age: 30 })
    wSet.add([1, 2, 3])
    

1.12.Map和WeakMap的使用

下面來講講Map和WeakMap,主要用於儲存對映關係。

(1)Map:對於普通物件儲存對映關係,只能使用字串或者Symbol作為屬性名,如果普通物件使用物件來作為key的話,會自動將物件轉成字串,但是Map允許我們使用物件作為key。

const obj1 = { name: 'curry', age: 30 }

const obj = {
  [obj1]: 'aaaa'
}
// 普通物件使用物件作為key,物件會自動轉成字串[object Object]
console.log(obj) // { '[object Object]': 'aaaa' }
  • Map的基本使用:

    const obj1 = { name: 'curry', age: 30 }
    const obj2 = { name: 'kobe', age: 24 }
    
    // 方式一:建立map後,通過set新增屬性
    const map1 = new Map()
    map1.set(obj1, 'aaaa')
    map1.set(obj2, 'bbbb')
    
    // 方式二:建立map時,傳入一個陣列
    const map2 = new Map([
      [obj1, 'aaaa'],
      [obj2, 'bbbb']
    ])
    
    console.log(map1)
    console.log(map2)
    

  • Map常見的屬性:

    • size:返回Map中元素的個數;

      console.log(map1.size) // 2
      
  • Map常見的方法:與Set很相似,設定和獲取屬性不太一樣。

    • set(key, value):在Map中新增key和value,並且返回整個map物件;
    • get(key):根據key獲取map中的value;
    • has(key):判斷map中是否包含某一個key,返回boolean值;
    • delete(key):根據key刪除一個鍵值對,返回boolean值;
    • clear():清空所有的屬性;
    • forEach(callback, [thisArg]):遍歷map(也可使用for...of遍歷);
    const map = new Map()
    const obj = { name: 'curry', age: 30 }
    
    // set
    map.set(obj, 'aaaa')
    map.set('bbbb', 1234)
    map.set(11, 'cccc')
    // get
    console.log(map.get(obj)) // aaaa
    // delete
    console.log(map.delete(11)) // true
    console.log(map.delete(22)) // false
    // has
    console.log(map.has('bbbb')) // true
    console.log(map.has('abc')) // false
    // forEach
    map.forEach((value, key) => {
      console.log(key, value)
      /*
        { name: 'curry', age: 30 } aaaa
        bbbb 1234
      */
    })
    // clear
    map.clear()
    
    console.log(map) // Map(0) {}
    

    如果map使用for...of遍歷,看一下遍歷出來的值是什麼樣子的:

    // 這裡取出的item是一個個陣列,陣列第一個元素是key,第二個元素是value
    for (const item of map) {
      console.log(item)
    }
    
    // 所以可以在取值的時候對item進行解構
    for (const [key, value] of map) {
      console.log(key, value)
    }
    

(2)WeakMap:也是以鍵值對的形式存在。

  • WeakMap和Map的區別:與Set和WeakSet的區別類似;
    • 區別一:WeakMap的key只能使用物件,不接受其他型別作為key;
    • 區別二:WeakMap的key對物件的引用是弱引用,如果沒有其他引用指向這個物件,那麼GC可以回收該物件;
    • 區別三:WeakMap和WeakSet一樣也不能進行遍歷;
  • WeakMap常見的方法:set(key, value)、get(key)、has(key)、delete(key),沒有clear和forEach;

2.ES7相關知識點

2.1.陣列的includes方法

在ES7之前,判斷陣列中包含某一個元素,一般可以用indexOf判斷是否為-1,ES7給我們提供了includes方法來判斷陣列中是否包含指定元素,返回boolean型別;

arr.includes(valueToFind[, fromIndex])
  • valueToFind:需要查詢的元素;
  • fromIndex:從指定索引開始查詢,如果from為負值,就從array.length + fromIndex的位置開始查詢(預設值為0);
const names = ['curry', 'kobe', NaN]

console.log(names.includes('curry')) // true
console.log(names.includes('klay')) // false
console.log(names.includes('curry', 1)) // false

console.log(names.indexOf(NaN)) // -1
console.log(names.includes(NaN)) // true

注意:includes方法是可以判斷NaN是否存在的,因為includes的內部實現對NaN採用isNaN方法進行判斷。

2.2.指數運算子

在ES7之前,計算數字的指數需要通過Math.pow方法來完成,在ES7中,增加了**運算子,可以來計算指數。

計算2的3次方:

const res1 = Math.pow(2, 3)
const res2 = 2 ** 3

console.log(res1, res2) // 8 8

3.ES8相關知識點

3.1.獲取物件所有的value

我們知道可以使用Object.keys獲取一個物件的所有key,ES8給我們提供了Object.values來獲取所有的value值。

const obj = {
  name: 'curry',
  age: 30,
  team: '勇士'
}

// 傳入一個物件
console.log(Object.values(obj)) // [ 'curry', 30, '勇士' ]
// 傳入一個字串,達到split的效果
console.log(Object.values('abc')) // [ 'a', 'b', 'c' ]

3.2.Object的entries方法

通過Object.entries()可以獲取到一個二維陣列,陣列中會存放可列舉的鍵值對陣列,陣列元素分別為物件的key和value。

const obj = {
  name: 'curry',
  age: 30,
  team: '勇士'
}

// 傳入一個物件
console.log(Object.entries(obj)) // [ [ 'name', 'curry' ], [ 'age', 30 ], [ 'team', '勇士' ] ]

for (const [key, value] of Object.entries(obj)) {
  console.log(key, value)
  /*
    name curry
    age 30
    team 勇士
  */
}

// 傳入一個陣列,下標作為第一個元素
console.log(Object.entries(['curry', 'kobe', 'klay'])) // [ [ '0', 'curry' ], [ '1', 'kobe' ], [ '2', 'klay' ] ]

// 傳入一個字串,下標作為第一個元素
console.log(Object.entries('abc')) // [ [ '0', 'a' ], [ '1', 'b' ], [ '2', 'c' ] ]

3.3.字串的填充

如果需要對字串進行前後填充,來實現某種展示格式,ES8中提供了padStart和padEnd方法,分別可以對字串進行首位填充。

const str = 'ssssssssss'

// 如果指定填充的位數小於等於str的長度,就不會進行填充
// 指定的位數需大於str的長度,就會填充str長度減去指定位數
// 如下指定了15,最終填充的只有5位,因為str的長度為10
console.log(str.padStart(15, '*')) // *****ssssssssss
console.log(str.padEnd(15, '-')) // ssssssssss-----

應用場景:字串的填充可以用於對身份證、銀行卡進行位數隱藏。

const bankCard = '2034399002125581'
// 擷取銀行卡後四位陣列
const lastFourNum = bankCard.slice(-4)
// 將前面陣列全部填充為*
const finalBankCard = lastFourNum.padStart(bankCard.length, '*')
console.log(finalBankCard) // ************5581

3.4.Object的getOwnPropertyDescriptors方法

Object.getOwnPropertyDescriptors()方法用來獲取一個物件的所有自身屬性的描述符。

const obj = {}

Object.defineProperties(obj, {
  name: {
    configurable: false,
    writable: false,
    enumerable: false,
    value: 'curry'
  },
  age: {
    configurable: true,
    writable: true,
    enumerable: true,
    value: 30
  }
})

console.log(Object.getOwnPropertyDescriptors(obj))

4.ES9相關知識點

4.1.物件的展開語法

在前面講了,陣列是可以使用展開語法的,在ES9中,在構建物件字面量時,也可以使用展開語法了。

const obj1 = {
  name: 'curry',
  age: 30
}

const newObj1 = { ...obj1, team: '勇士' }
console.log(newObj1) // { name: 'curry', age: 30, team: '勇士' }

注意:物件的展開運算子只能實現淺拷貝,如果物件內部還包含引用型別的值,就會指向同一地址空間。

const obj = {
  name: 'curry',
  age: 30,
  friends: ['kobe', 'klay']
}

const newObj = { ...obj }
newObj.friends.push('james')
console.log(newObj) // { name: 'curry', age: 30, team: '勇士' }
console.log(obj) // { name: 'curry', age: 30, friends: [ 'kobe', 'klay', 'james' ] }

5.ES10相關知識點

5.1.flat和flatMap

(1)flat方法

按照一個可指定的深度遞迴遍歷陣列,並將所有的元素和遍歷到的子陣列合併為一個新陣列返回。(可用於陣列扁平化)

const nums = [1, 2, [3, 3], [4, [5, 5]]]

// 對陣列進行降維處理
console.log(nums.flat(1)) // [ 1, 2, 3, 3, 4, [ 5, 5 ] ]
console.log(nums.flat(2)) // [ 1, 2, 3, 3, 4, 5, 5 ]
// 如果不知道陣列巢狀了多少層,可直接指定Infinity
console.log(nums.flat(Infinity)) // [ 1, 2, 3, 3, 4, 5, 5 ]

(2)flatMap方法

首先使用對映函式對映每個元素,然後將結果壓縮成一個新陣列。

  • flatMap先進行map操作,然後做flat操作;
  • flatMap中的flat操作深度為1;
const arr = [[1, 2, 3, 4], ['a', 'b','c', 'd']]

// 陣列自動降一維
const newArr = arr.flatMap(item => {
  return item
})

console.log(newArr) // [ 1, 2, 3, 4, 'a', 'b', 'c', 'd' ]

5.2.Object的fromEntries方法

在前面講了可以通過Object.entries()將一個物件轉換成鍵值對陣列,而Object.fromEntries()可以將鍵值對列表轉換成物件。

const entries = [
  ['name', 'curry'],
  ['age', 30]
]

const obj = Object.fromEntries(entries)
console.log(obj) // { name: 'curry', age: 30 }

應用場景:解析url的query部分,將其轉成一個物件。

const url = 'http://127.0.0.1:8000/search?name=curry&age=30'

const queryStr = url.slice(url.indexOf('?') + 1, url.length)
console.log(queryStr) // name=curry&age=30

// URLSearchParams:可用於處理url的查詢字串
const queryParams = new URLSearchParams(queryStr)
console.log(queryParams) // URLSearchParams { 'name' => 'curry', 'age' => '30' }

const paramObj = Object.fromEntries(queryParams)
console.log(paramObj) // { name: 'curry', age: '30' }

5.3.去除字串首尾空格

去除一個字串首尾空格可以通過trim()方法,而trimStart和trimEnd方法分別可以單獨去除字串前或後的空格。

// 這裡以·代表空格
const str = '····ssss···'
console.log(str.trim()) // ssss
console.log(str.trimStart()) // ssss···  
console.log(str.trimEnd()) // ····ssss

5.4.Symbol的description

在前面講了Symbol型別,在建立一個Symbol型別的值時,還可以傳入指定的描述符。

const s1 = Symbol('aaaa')
const s2 = Symbol('bbbb')

console.log(s1.description) // aaaa
console.log(s2.description) // bbbb

6.ES11相關知識點

6.1.BigInt型別

在JavaScript中,如果數字過大,可能是不正確的,先看看JavaScript提供給我們的最大值和最小值是多少。

const maxNum = Number.MAX_SAFE_INTEGER
const minNum = Number.MIN_SAFE_INTEGER

console.log(maxNum) // 9007199254740991
console.log(minNum) // -9007199254740991

如果給最大值再加大數值,很明顯數值是不正確的:

console.log(maxNum + 1) // 9007199254740992
console.log(maxNum + 2) // 9007199254740992

所以,ES11引入了新的資料型別BigInt,可用於表示很大的整數:

  • BigInt的寫法需要在數值後面加上n
  • 只有數值同為BigInt型別才能進行運算(不能進行隱式轉換),如果需要運算的資料不是BigInt型別就要加上n轉成BigInt;
  • 也可使用BigInt()方法進行轉型別;
const bigInt = 9007199254740991n
console.log(bigInt + 2n) // 9007199254740993n
console.log(bigInt + bigInt) // 18014398509481982n

6.2.空值合併操作符(??)

如果||不能對空字串和0進行很好的判斷,就可以使用??

const test1 = ''
const test2 = 0
const test3 = null
const test4 = undefined

const res1 = test1 || 'default'
const res2 = test2 || 'default'
console.log(res1) // default
console.log(res2) // default

// ??認為空字串和0是真值的
const res3 = test1 ?? 'default'
const res4 = test2 ?? 'default'
console.log(res3) // ''
console.log(res4) // 0

// 只有當??前面的值為null或者undefined,才會使用後面的值
const res5 = test3 ?? 'default'
const res6 = test4 ?? 'default'
console.log(res5) // default
console.log(res6) // default

6.3.可選鏈

可選鏈(Optional Chaining)的主要作用就是讓程式碼在進行null和undefined判斷時更加清晰和簡潔,確保我們訪問物件屬性是安全的(因為從undefined裡面取值是會報錯的)。

const obj = {
  name: 'curry',
  friend: {
    name: 'kobe',
    age: 24
  }
}

// 先判斷obj中是否有friend屬性,然後再判斷friend中是否有name屬性
console.log(obj?.friend?.name) // kobe

// console.log(obj.teammate.name) // TypeError: Cannot read property 'name' of undefined
// obj中沒有teammate屬性,所以就直接返回undefined,不會再從undefined中取name了,不會報錯影響後面程式執行
console.log(obj?.teammate?.name) // undefined

6.4.globalThis

在ES11之前獲取JavaScript的全域性物件在不同的環境下的獲取方式不同:

  • 瀏覽器中:this、window獲取;
  • node中:global獲取;

ES11中,對我們獲取全域性物件進行統一規範:

console.log(globalThis)

瀏覽器中:

node中:

7.ES12相關知識點

7.1.FinalizationRegistry

FinalizationRegistry物件可以讓你在物件被垃圾回收時請求一個回撥。

  • FinalizationRegistry提供了這樣的一種方法:當一個在登錄檔中註冊的物件被回收時,請求在某個時間點上呼叫一個清理回撥;(清理回撥有時被稱為 finalizer )
  • FinalizationRegistry可例項化一個物件,可以藉助該物件註冊想要清理回撥的物件,傳入該物件和所含的值;
let obj1 = { name: 'curry', age: 30 }
let obj2 = { name: 'kobe', age: 24 }

// 例項化登錄檔
const register = new FinalizationRegistry(value => {
  console.log(`${value}物件被銷燬了`)
})

// 註冊obj1和obj2,並指定其值
register.register(obj1, 'obj1')
register.register(obj2, 'obj2')

// 將兩個物件銷燬
obj1 = null
obj2 = null

需要藉助瀏覽器的GC來進行測試,當兩個物件被真正回收了,就會呼叫清理回撥,列印對應內容:

7.2.WeakRef

在前面講了WeakSet和WeakMap中的元素對物件的引用都是弱引用,WeakRef就可以專門實現對一個物件進行弱引用,也就是說該物件被GC回收時,是不會看它是否有弱引用的,有沒有弱引用一樣被回收。

可以結合上面的FinalizationRegistry進行測試:

  • WeakRef建立的弱引用需通過deref()來訪問物件屬性;
let obj = { name: 'curry', age: 30 }

// 建立一個弱引用weakObj指向物件{ name: 'curry', age: 30 }
let weakObj = new WeakRef(obj)

// 拿到{ name: 'curry', age: 30 }物件
// console.log(weakObj.deref())
// console.log(weakObj.deref().name) // curry
// console.log(weakObj.deref().age) // 30

const register = new FinalizationRegistry(value => {
  console.log(`${value}物件被銷燬了`)
})
register.register(obj, 'obj')

// 去掉obj對物件{ name: 'curry', age: 30 }的引用
obj = null

// 等物件回收後再列印
setTimeout(() => {
  // 注意:當弱引用的物件被GC回收後,weakObj.deref()的值為undefined
  // 如果不想報錯,可以使用可選鏈或者邏輯與
  console.log(weakObj.deref()?.name)
  console.log(weakObj.deref() && weakObj.deref().name)
}, 10000)

7.3.邏輯賦值運算

ES12為我們提供了三種邏輯賦值運算:

  • ||=:邏輯或賦值運算;

    let msg = undefined
    msg ||= 'default'
    console.log(msg) // default
    /*
      等同於:
      msg = msg || 'default'
    */
    
  • &&=:邏輯與賦值運算;

    let obj = {
      name: 'curry',
      age: 30
    }
    // 在obj有值的情況下,將obj賦值為obj.name
    obj &&= obj.name
    console.log(obj) // curry
    /*
      等同於:
      obj = obj && obj.name
    */
    
  • ??=:邏輯空賦值運算;

    let msg = 0
    // 當msg為null或undefined情況下,將msg賦值為default,這裡為0,所以值還是0
    msg ??= 'default'
    console.log(msg) // 0
    /*
      等同於:
      msg = msg ?? 'default'
    */
    

注意:該新增特性可能過新,可能出現node版本不支援的情況,可以在瀏覽器中列印檢視。

7.4.數值分隔符

當數值過長時,ES12允許我們使用_進行連線,方便閱讀。

const num = 100_000_000
console.log(num) //100000000

7.5.字串替換

ES12提供了一個replaceAll方法,該返回一個新字串,新字串所有滿足 pattern 的部分都已被replacement 替換。pattern可以是一個字串或一個RegExpreplacement可以是一個字串或一個在每次匹配被呼叫的函式。

const newStr = str.replaceAll(regexp|substr, newSubstr|function)

將所有的a替換為b:

const str = 'ababababab'

// 字串
const newStr1 = str.replaceAll('a', 'b')
// 正則
const regex = /a/g
const newStr2 = str.replace(regex, 'b')

console.log(newStr1) // bbbbbbbbbb
console.log(newStr2) // bbbbbbbbbb

注意:該新增特性可能過新,可能出現node版本不支援的情況,可以在瀏覽器中列印檢視。

總結:

本篇文字只總結了ES6-ES12部分簡單知識點,還有一些其它ES6之後新增的特性,如Promise、迭代器、生成器、async、await等等,將會在後續的文章中進行整理。

相關文章