日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
你需要知道的ES2015—ES2023開(kāi)發(fā)技巧!

ECMAScript 是 JavaScript 的標(biāo)準(zhǔn)與規(guī)范,JavaScript 是 ECMAScript 標(biāo)準(zhǔn)的實(shí)現(xiàn)和擴(kuò)展。本文就來(lái)看看 ECMAScript 各版本有哪些實(shí)用技巧吧!

圖片

一、ES6 新特性(2015)

1. let和const

在ES6中,新增了let和const關(guān)鍵字,其中 let 主要用來(lái)聲明變量,而 const 通常用來(lái)聲明常量。let、const相對(duì)于var關(guān)鍵字有以下特點(diǎn):

特性

var

let

const

變量提升

?

×

×

全局變量

?

×

×

重復(fù)聲明

?

×

×

重新賦值

?

?

×

暫時(shí)性死區(qū)

×

?

?

塊作用域

×

?

?

只聲明不初始化

?

?

×

這里主要介紹其中的四點(diǎn):

(1)重新賦值

const 關(guān)鍵字聲明的變量是“不可修改”的。其實(shí),const 保證的并不是變量的值不能改動(dòng),而是變量指向的那個(gè)內(nèi)存地址不能改動(dòng)。對(duì)于基本類型的數(shù)據(jù)(數(shù)值、字符串、布爾值),其值就保存在變量指向的那個(gè)內(nèi)存地址,因此等同于常量。但對(duì)于引用類型的數(shù)據(jù)(主要是對(duì)象和數(shù)組),變量指向數(shù)據(jù)的內(nèi)存地址,保存的只是一個(gè)指針,const只能保證這個(gè)指針是不變的,至于它指向的數(shù)據(jù)結(jié)構(gòu)就不可控制了。

(2)塊級(jí)作用域

在引入let和const之前是不存在塊級(jí)作用域的說(shuō)法的,這也就導(dǎo)致了很多問(wèn)題,比如內(nèi)層變量會(huì)覆蓋外層的同名變量:

var a = 1;
if (true) {
  var a = 2;
}

console.log(a); // 輸出結(jié)果:2

循環(huán)變量會(huì)泄漏為全局變量:

var arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);  // 輸出結(jié)果:1  2  3
}

console.log(i); // 輸出結(jié)果:3

而通過(guò)let和const定義的變量存在塊級(jí)作用域,就不會(huì)產(chǎn)生上述問(wèn)題:

let a = 1;
if (true) {
  let a = 2;
}

console.log(a); // 輸出結(jié)果:1

const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);  // 輸出結(jié)果:1  2  3
}

console.log(i); // Uncaught ReferenceError: i is not defined

(3)變量提升

我們知道,在ES6之前是存在變量提升的,所謂的變量提升就是變量可以在聲明之前使用:

console.log(a); // 輸出結(jié)果:undefined
var a = 1;

變量提升的本質(zhì)是JavaScript引擎在執(zhí)行代碼之前會(huì)對(duì)代碼進(jìn)行編譯分析,這個(gè)階段會(huì)將檢測(cè)到的變量和函數(shù)聲明添加到 JavaScript 引擎中名為 Lexical Environment 的內(nèi)存中,并賦予一個(gè)初始化值 undefined。然后再進(jìn)入代碼執(zhí)行階段。所以在代碼執(zhí)行之前,JS 引擎就已經(jīng)知道聲明的變量和函數(shù)。

這種現(xiàn)象就不太符合我們的直覺(jué),所以在ES6中,let和const關(guān)鍵字限制了變量提升,let 定義的變量添加到 Lexical Environment 后不再進(jìn)行初始化為 undefined 操作,JS 引擎只會(huì)在執(zhí)行到詞法聲明和賦值時(shí)才進(jìn)行初始化。而在變量創(chuàng)建到真正初始化之間的時(shí)間跨度內(nèi),它們無(wú)法訪問(wèn)或使用,ES6 將其稱之為暫時(shí)性死區(qū):

// 暫時(shí)性死區(qū) 開(kāi)始
a = "hello";     //  Uncaught ReferenceError: Cannot access 'a' before initialization

let a;   
//  暫時(shí)性死區(qū) 結(jié)束
console.log(a);  // undefined

(4)重復(fù)聲明

在ES6之前,var關(guān)鍵字聲明的變量對(duì)于一個(gè)作用域內(nèi)變量的重復(fù)聲明是沒(méi)有限制的,甚至可以聲明與參數(shù)同名變量,以下兩個(gè)函數(shù)都不會(huì)報(bào)錯(cuò):

function funcA() {
  var a = 1;
  var a = 2;
}

function funcB(args) {
  var args = 1; 
}

而let修復(fù)了這種不嚴(yán)謹(jǐn)?shù)脑O(shè)計(jì):

function funcA() {
  let a = 1;
  let a = 2;  // Uncaught SyntaxError: Identifier 'a' has already been declared
}

function funcB(args) {
  let args = 1;  // Uncaught SyntaxError: Identifier 'args' has already been declared
}

現(xiàn)在我們項(xiàng)目中已經(jīng)完全放棄了var,而使用let來(lái)定義變量,使用const來(lái)定義常量。在ESlint開(kāi)啟了如下規(guī)則:

"no-var": 0;

2. 解構(gòu)賦值

ES6中還引入了解構(gòu)賦值的概念,解構(gòu)賦值遵循“模式匹配”,即只要等號(hào)兩邊的模式相等,左邊的變量就會(huì)被賦予對(duì)應(yīng)的值。不同類型數(shù)據(jù)的解構(gòu)方式不同,下面就分別來(lái)看看不同類型數(shù)據(jù)的解構(gòu)方式。

平時(shí)在開(kāi)發(fā)中,我主要會(huì)用到對(duì)象的解構(gòu)賦值,比如在React中解構(gòu)porps值等,使用解構(gòu)賦值來(lái)獲取父組件傳來(lái)的值;在React Hooks中的useState使用到了數(shù)組的解構(gòu)賦值;

(1)數(shù)組解構(gòu)

具有 Iterator 接口的數(shù)據(jù)結(jié)構(gòu),都可以采用數(shù)組形式的解構(gòu)賦值。

const [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo, bar, baz) // 輸出結(jié)果:1  2  3

這里,ES6實(shí)現(xiàn)了對(duì)數(shù)組的結(jié)構(gòu),并依次賦值變量foo、bar、baz。數(shù)組的解構(gòu)賦值按照位置將值與變量對(duì)應(yīng)。

數(shù)組還可以實(shí)現(xiàn)不完全解構(gòu),只解構(gòu)部分內(nèi)容:

const [x, y] = [1, 2, 3];   // 提取前兩個(gè)值
const [, y, z] = [1, 2, 3]  // 提取后兩個(gè)值
const [x, , z] = [1, 2, 3]  // 提取第一三個(gè)值

如果解構(gòu)時(shí)對(duì)應(yīng)的位置沒(méi)有值就會(huì)將變量賦值為undefined:

const [x, y, z] = [1, 2]; 
console.log(z)  // 輸出結(jié)果:undefined

數(shù)組解構(gòu)賦值可以使用rest操作符來(lái)捕獲剩余項(xiàng):

const [x, ...y] = [1, 2, 3];   
console.log(x);  // 輸出結(jié)果:1
console.log(y);  // 輸出結(jié)果:[2, 3]

在解構(gòu)時(shí)還支持使用默認(rèn)值,當(dāng)對(duì)應(yīng)的值為undefined時(shí)才會(huì)使用默認(rèn)值:

const [x, y, z = 3] = [1, 2]; 
console.log(z)  // 輸出結(jié)果:3

(2)對(duì)象解構(gòu)

對(duì)象的解構(gòu)賦值的本質(zhì)其實(shí)是先找到同名的屬性,在賦值給對(duì)應(yīng)的變量:

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
console.log(foo, bar); // 輸出結(jié)果:aaa  bbb

需要注意的是,在JavaScript中,對(duì)象的屬性是沒(méi)有順序的。所以,在解構(gòu)賦值時(shí),變量必須與屬性同名才能去取到值。

對(duì)象的解構(gòu)賦值也是支持默認(rèn)值的,當(dāng)定義的變量在對(duì)象中不存在時(shí),其默認(rèn)值才會(huì)生效:

let { foo, bar, baz = 'ccc'} = { foo: 'aaa', bar: 'bbb', baz: null };
console.log(foo, bar, baz); // 輸出結(jié)果:aaa  bbb  null

let { foo, bar, baz = 'ccc'} = { foo: 'aaa', bar: 'bbb' };
console.log(foo, bar, baz); // 輸出結(jié)果:aaa  bbb  ccc

可以看到,只有定義的變量是嚴(yán)格的===undefined時(shí),它的默認(rèn)值才會(huì)生效。

除此之外,我們還需要注意,不能給已聲明的變量進(jìn)行賦值,因?yàn)楫?dāng)缺少 let、const、var 關(guān)鍵詞時(shí),將會(huì)把 {baz} 理解為代碼塊從而導(dǎo)致語(yǔ)法錯(cuò)誤,所以下面代碼會(huì)報(bào)錯(cuò):

let baz;
{ baz } = { foo: 'aaa', bar: 'bbb', baz: 'ccc' };

可以使用括號(hào)包裹整個(gè)解構(gòu)賦值語(yǔ)句來(lái)解決上述問(wèn)題:

let baz;
({ baz } = { foo: 'aaa', bar: 'bbb', baz: 'ccc' });
console.log(baz)

在對(duì)象的解構(gòu)賦值中,可以將現(xiàn)有對(duì)象的方法賦值給某個(gè)變量,比如:

let { log, sin, cos } = Math;
log(12)  // 輸出結(jié)果:2.4849066497880004
sin(1)   // 輸出結(jié)果:0.8414709848078965
cos(1)   // 輸出結(jié)果:0.5403023058681398

(3)其他解構(gòu)賦值

剩下的幾種解構(gòu)賦值,目前我在項(xiàng)目中應(yīng)用的較少,來(lái)簡(jiǎn)單看一下。

  • 字符串解構(gòu)

字符串解構(gòu)規(guī)則:只要等號(hào)右邊的值不是對(duì)象或數(shù)組,就先將其轉(zhuǎn)為類數(shù)組對(duì)象,在進(jìn)行解構(gòu):

const [a, b, c, d, e] = 'hello';
console.log(a, b, c, d, e)  // 輸出結(jié)果:h e l l o

類數(shù)組對(duì)象有 length 屬性,因此可以給這個(gè)屬性進(jìn)行解構(gòu)賦值:

let {length} = 'hello';    // 輸出結(jié)果:5

由于字符串都是一個(gè)常量,所以我們通常是知道它的值是什么的,所以很少會(huì)使用變量的解構(gòu)賦值。

  • 數(shù)值和布爾值解構(gòu)賦值

對(duì)數(shù)值和布爾值進(jìn)行解構(gòu)時(shí),它們將會(huì)先被轉(zhuǎn)為對(duì)象,然后再應(yīng)用解構(gòu)語(yǔ)法:

let {toString: s} = 123;
s === Number.prototype.toString // 輸出結(jié)果:true

let {toString: s} = true;
s === Boolean.prototype.toString // 輸出結(jié)果:true

注意null和undefined不能轉(zhuǎn)換為對(duì)象,所以如果右邊是這兩個(gè)值,就會(huì)報(bào)錯(cuò)。

  • 函數(shù)參數(shù)解構(gòu)賦值

函數(shù)參數(shù)表面上是一個(gè)數(shù)組,在傳入?yún)?shù)的那一刻,就會(huì)被解構(gòu)為x和y。

function add([x, y]){
  return x + y;
}
add([1, 2]);   // 3

除此之外,我們還可以解構(gòu)函數(shù)的返回值:

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

3. 模板字符串

傳統(tǒng)的JavaScript語(yǔ)言中,輸出模板經(jīng)常使用的是字符串拼接的形式,這樣寫相當(dāng)繁瑣,在ES6中引入了模板字符串的概念來(lái)解決以上問(wèn)題。

模板字符串是增強(qiáng)版的字符串,用反引號(hào)``來(lái)標(biāo)識(shí),他可以用來(lái)定義單行字符串,也可以定義多行字符串,或者在字符串中嵌入變量。

// 字符串中嵌入變量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// 字符串中調(diào)用函數(shù)
` ${fn()}

在平時(shí)的開(kāi)發(fā)中,除了上面代碼中的應(yīng)用,很多地方會(huì)用到模板字符串,比如拼接一個(gè)DOM串,在Emotion/styled中定義DOM結(jié)構(gòu)等,都會(huì)用到模板字符串。不過(guò)在模板字符串中定義DOM元素就不會(huì)有代碼提示了。

在使用模板字符串時(shí),需要注意以下幾點(diǎn):

  • 如果在字符串中使用反引號(hào),需要使用\來(lái)轉(zhuǎn)義;
  • 如果在多行字符串中有空格和縮進(jìn),那么它們都會(huì)被保留在輸出中;
  • 模板字符串中嵌入變量,需要將變量名寫在${}之中;
  • 模板字符串中可以放任意的表達(dá)式,也可以進(jìn)行運(yùn)算,以及引用對(duì)象的屬性,甚至可以調(diào)用函數(shù);
  • 如果模板字符中的變量沒(méi)有聲明,會(huì)報(bào)錯(cuò)。

4. 函數(shù)默認(rèn)參數(shù)

在ES6之前,函數(shù)是不支持默認(rèn)參數(shù)的,ES6實(shí)現(xiàn)了對(duì)此的支持,并且只有不傳入?yún)?shù)時(shí)才會(huì)觸發(fā)默認(rèn)值:

function getPoint(x = 0, y = 0) {
  console.log(x, y);
}

getPoint(1, 2);   // 1  2
getPoint()        // 0  0 
getPoint(1)       // 1  0

當(dāng)使用函數(shù)默認(rèn)值時(shí),需要注意以下幾點(diǎn):

(1)函數(shù)length屬性值

函數(shù)length屬性通常用來(lái)表示函數(shù)參數(shù)的個(gè)數(shù),當(dāng)引入函數(shù)默認(rèn)值之后,length表示的就是第一個(gè)有默認(rèn)值參數(shù)之前的普通參數(shù)個(gè)數(shù):

const funcA = function(x, y) {};
console.log(funcA.length);  // 輸出結(jié)果:2 

const funcB = function(x, y = 1) {};
console.log(funcB.length);  // 輸出結(jié)果:1

const funcC = function(x = 1, y) {};
console.log(funcC.length);  // 輸出結(jié)果 0

(2)參數(shù)作用域

當(dāng)給函數(shù)的參數(shù)設(shè)置了默認(rèn)值之后,參數(shù)在被初始化時(shí)將形成一個(gè)獨(dú)立作用域,初始化完成后作用域消解:

let x = 1;

function func(x, y = x) {
  console.log(y);
}

func(2);

這里最終會(huì)打印出2。在函數(shù)調(diào)用時(shí),參數(shù) x, y 將形成一個(gè)獨(dú)立的作用域,所以參數(shù)中的y會(huì)等于第一個(gè)參數(shù)中的x,而不是上面定義的1。

5. 箭頭函數(shù)

ES6中引入了箭頭函數(shù),用來(lái)簡(jiǎn)化函數(shù)的定義:

const counter = (x, y) => x + y;

相對(duì)于普通函數(shù),箭頭函數(shù)有以下特點(diǎn):

(1)更加簡(jiǎn)潔

  • 如果沒(méi)有參數(shù),就直接寫一個(gè)空括號(hào)即可
  • 如果只有一個(gè)參數(shù),可以省去參數(shù)的括號(hào)
  • 如果有多個(gè)參數(shù),用逗號(hào)分割
  • 如果函數(shù)體的返回值只有一句,可以省略大括號(hào)
// 1. 不傳入?yún)?shù)
const funcA = () => console.log('funcA');
// 等價(jià)于
const funcA = function() {
  console.log('funcA');
} 

// 2. 傳入?yún)?shù)
const funcB = (x, y) => x + y;
// 等價(jià)于
const funcB = function(x, y) {
  return x + y;
} 

// 3. 單個(gè)參數(shù)的簡(jiǎn)化
const funcC = (x) => x;
// 對(duì)于單個(gè)參數(shù),可以去掉 (),簡(jiǎn)化為
const funcC = x => x;
// 等價(jià)于
const funcC = function(x) {
  return x;
}

// 4. 上述代碼函數(shù)體只有單條語(yǔ)句,如果有多條,需要使用 {}
const funcD = (x, y) => { console.log(x, y); return x + y; }
// 等價(jià)于
const funcD = function(x, y) {
  console.log(x, y);
  return x + y;
}

(2)不綁定 this

箭頭函數(shù)不會(huì)創(chuàng)建自己的this, 所以它沒(méi)有自己的this,它只會(huì)在自己作用域的上一層繼承this。所以箭頭函數(shù)中this的指向在它在定義時(shí)已經(jīng)確定了,之后不會(huì)改變。

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor

對(duì)象obj的方法b是使用箭頭函數(shù)定義的,這個(gè)函數(shù)中的this就永遠(yuǎn)指向它定義時(shí)所處的全局執(zhí)行環(huán)境中的this,即便這個(gè)函數(shù)是作為對(duì)象obj的方法調(diào)用,this依舊指向Window對(duì)象。需要注意,定義對(duì)象的大括號(hào){}是無(wú)法形成一個(gè)單獨(dú)的執(zhí)行環(huán)境的,它依舊是處于全局執(zhí)行環(huán)境中。

同樣,使用call()、apply()、bind()等方法也不能改變箭頭函數(shù)中this的指向:

var id = 'Global';
let fun1 = () => {
    console.log(this.id)
};
fun1();                     // 'Global'
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'

(3)不可作為構(gòu)造函數(shù)

構(gòu)造函數(shù) new 操作符的執(zhí)行步驟如下:

  1. 創(chuàng)建一個(gè)對(duì)象
  2. 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(也就是將對(duì)象的__proto__屬性指向構(gòu)造函數(shù)的prototype屬性)
  3. 指向構(gòu)造函數(shù)中的代碼,構(gòu)造函數(shù)中的this指向該對(duì)象(也就是為這個(gè)對(duì)象添加屬性和方法)
  4. 返回新的對(duì)象

實(shí)際上第二步就是將函數(shù)中的this指向該對(duì)象。但是由于箭頭函數(shù)時(shí)沒(méi)有自己的this的,且this指向外層的執(zhí)行環(huán)境,且不能改變指向,所以不能當(dāng)做構(gòu)造函數(shù)使用。

(4)不綁定 arguments

箭頭函數(shù)沒(méi)有自己的arguments對(duì)象。在箭頭函數(shù)中訪問(wèn)arguments實(shí)際上獲得的是它外層函數(shù)的arguments值。

6. 擴(kuò)展運(yùn)算符

擴(kuò)展運(yùn)算符:...  就像是rest參數(shù)的逆運(yùn)算,將一個(gè)數(shù)組轉(zhuǎn)為用逗號(hào)分隔的參數(shù)序列,對(duì)數(shù)組進(jìn)行解包。

spread 擴(kuò)展運(yùn)算符有以下用途:

(1)將數(shù)組轉(zhuǎn)化為用逗號(hào)分隔的參數(shù)序列:

function  test(a,b,c){
    console.log(a); // 1
    console.log(b); // 2
    console.log(c); // 3
}

var arr = [1, 2, 3];
test(...arr);

(2)將一個(gè)數(shù)組拼接到另一個(gè)數(shù)組:

var arr1 = [1, 2, 3,4];
var arr2 = [...arr1, 4, 5, 6];
console.log(arr2);  // [1, 2, 3, 4, 4, 5, 6]

(3)將字符串轉(zhuǎn)為逗號(hào)分隔的數(shù)組:

var str='JavaScript';
var arr= [...str];
console.log(arr); // ["J", "a", "v", "a", "S", "c", "r", "i", "p", "t"]

7. Symbol

ES6中引入了一個(gè)新的基本數(shù)據(jù)類型Symbol,表示獨(dú)一無(wú)二的值。它是一種類似于字符串的數(shù)據(jù)類型,它的特點(diǎn)如下:

  • Symbol的值是唯一的,用來(lái)解決命名沖突的問(wèn)題
  • Symbol值不能與其他類型數(shù)據(jù)進(jìn)行運(yùn)算
  • Symbol定義的對(duì)象屬性不能使用for...in遍歷循環(huán),但是可以使用Reflect.ownKeys 來(lái)獲取對(duì)象的所有鍵名
let s1 = Symbol();
console.log(typeof s1); // "symbol"

let s2 = Symbol('hello');
let s3 = Symbol('hello');
console.log(s2 === s3); // false

基于以上特性,Symbol 屬性類型比較適合用于兩類場(chǎng)景中:常量值和對(duì)象屬性。

(1)避免常量值重復(fù)

getValue 函數(shù)會(huì)根據(jù)傳入字符串參數(shù) key 執(zhí)行對(duì)應(yīng)代碼邏輯:

function getValue(key) {
  switch(key){
    case 'A':
      ...
    case 'B':
      ...
  }
}
getValue('B');

這段代碼對(duì)調(diào)用者而言非常不友好,因?yàn)榇a中使用了魔術(shù)字符串(Magic string,指的是在代碼之中多次出現(xiàn)、與代碼形成強(qiáng)耦合的某一個(gè)具體的字符串或者數(shù)值),導(dǎo)致調(diào)用 getValue 函數(shù)時(shí)需要查看函數(shù)代碼才能找到參數(shù) key 的可選值。所以可以將參數(shù) key 的值以常量的方式聲明:

const KEY = {
  alibaba: 'A',
  baidu: 'B',
}
function getValue(key) {
  switch(key){
    case KEY.alibaba:
      ...
    case KEY.baidu:
      ...
  }
}
getValue(KEY.baidu);

但這樣也并非完美,假設(shè)現(xiàn)在要在 KEY 常量中加入一個(gè) key,根據(jù)對(duì)應(yīng)的規(guī)則,很有可能會(huì)出現(xiàn)值重復(fù)的情況:

const KEY = {
  alibaba: 'A',
  baidu: 'B',
  tencent: 'B'
}

這就會(huì)出現(xiàn)問(wèn)題:

getValue(KEY.baidu) // 等同于 getValue(KEY.tencent)

所以在這種場(chǎng)景下更適合使用 Symbol,不需要關(guān)心值本身,只關(guān)心值的唯一性:

const KEY = {
  alibaba: Symbol(),
  baidu: Symbol(),
  tencent: Symbol()
}

(2)避免對(duì)象屬性覆蓋

函數(shù) fn 需要對(duì)傳入的對(duì)象參數(shù)添加一個(gè)臨時(shí)屬性 user,但可能該對(duì)象參數(shù)中已經(jīng)有這個(gè)屬性了,如果直接賦值就會(huì)覆蓋之前的值。此時(shí)就可以使用 Symbol 來(lái)避免這個(gè)問(wèn)題。創(chuàng)建一個(gè) Symbol 數(shù)據(jù)類型的變量,然后將該變量作為對(duì)象參數(shù)的屬性進(jìn)行賦值和讀取,這樣就能避免覆蓋的情況:

function fn(o) { // {user: {id: xx, name: yy}}
  const s = Symbol()
  o[s] = 'zzz'
}

8. 集合 Set

ES6提供了新的數(shù)據(jù)結(jié)構(gòu)Set(集合)。它類似于數(shù)組,但是成員的值都是唯一的,集合實(shí)現(xiàn)了iterator接口,所以可以使用擴(kuò)展運(yùn)算符和 for…of 進(jìn)行遍歷。

Set的屬性和方法:

屬性和方法

概述

size

返回集合的元素個(gè)數(shù)

add

增加一個(gè)新的元素,返回當(dāng)前的集合

delete

刪除元素,返回布爾值

has

檢查集合中是否包含某元素,返回布爾值

clear

清空集合,返回undefined

//創(chuàng)建一個(gè)空集合
let s = new Set();
//創(chuàng)建一個(gè)非空集合
let s1 = new Set([1,2,3,1,2,3]);
//返回集合的元素個(gè)數(shù)
console.log(s1.size);       // 3
//添加新元素
console.log(s1.add(4));     // {1,2,3,4}
//刪除元素
console.log(s1.delete(1));  //true
//檢測(cè)是否存在某個(gè)值
console.log(s1.has(2));     // true
//清空集合
console.log(s1.clear());    //undefined

由于集合中元素的唯一性,所以在實(shí)際應(yīng)用中,可以使用set來(lái)實(shí)現(xiàn)數(shù)組去重:

let arr = [1,2,3,2,1]
Array.from(new Set(arr))  // {1, 2, 3}

這里使用了Array.form()方法來(lái)將數(shù)組集合轉(zhuǎn)化為數(shù)組。

可以通過(guò)set來(lái)求兩個(gè)數(shù)組的交集和并集:

// 模擬求交集 
let intersection = new Set([...set1].filter(x => set2.has(x)));

// 模擬求差集
let difference = new Set([...set1].filter(x => !set2.has(x)));

用以下方法可以進(jìn)行數(shù)組與集合的相互轉(zhuǎn)化:

// Set集合轉(zhuǎn)化為數(shù)組
const arr = [...mySet]
const arr = Array.from(mySet)

// 數(shù)組轉(zhuǎn)化為Set集合
const mySet = new Set(arr)

9. Map

ES6提供了Map數(shù)據(jù)結(jié)構(gòu),它類似于對(duì)象,也是鍵值隊(duì)的集合,但是它的鍵值的范圍不限于字符串,可以是任何類型(包括對(duì)象)的值,也就是說(shuō), Object 結(jié)構(gòu)提供了“ 字符串—值” 的對(duì)應(yīng), Map 結(jié)構(gòu)提供了“ 值—值” 的對(duì)應(yīng), 是一種更完善的 Hash 結(jié)構(gòu)實(shí)現(xiàn)。如果需要“ 鍵值對(duì)” 的數(shù)據(jù)結(jié)構(gòu), Map 比 Object 更合適。Map也實(shí)現(xiàn)了iterator接口,所以可以使用擴(kuò)展運(yùn)算符和 for…of 進(jìn)行遍歷。

Map的屬性和方法:

屬性和方法

概述

size

返回Map的元素個(gè)數(shù)

set

增加一個(gè)新的元素,返回當(dāng)前的Map

get

返回鍵名對(duì)象的鍵值

has

檢查Map中是否包含某元素,返回布爾值

clear

清空Map,返回undefined

//創(chuàng)建一個(gè)空 map
let m = new Map();
//創(chuàng)建一個(gè)非空 map
let m2 = new Map([
 ['name', 'hello'],
]);
//獲取映射元素的個(gè)數(shù)
console.log(m2.size);          // 1
//添加映射值
console.log(m2.set('age', 6)); // {"name" => "hello", "age" => 6}
//獲取映射值
console.log(m2.get('age'));    // 6
//檢測(cè)是否有該映射
console.log(m2.has('age'));    // true
//清除
console.log(m2.clear());       // undefined

需要注意, 只有對(duì)同一個(gè)對(duì)象的引用, Map 結(jié)構(gòu)才將其視為同一個(gè)鍵:

let map = new Map(); 
map.set(['a'], 555); 
map.get(['a']) // undefined

上面代碼的set和get方法, 表面是針對(duì)同一個(gè)鍵, 但實(shí)際上這是兩個(gè)值, 內(nèi)存地址是不一樣的, 因此get方法無(wú)法讀取該鍵, 所以會(huì)返回undefined。

由上可知, Map 的鍵實(shí)際上是跟內(nèi)存地址綁定的, 只要內(nèi)存地址不一樣, 就視為兩個(gè)鍵。這就解決了同名屬性碰撞( clash) 的問(wèn)題,在擴(kuò)展庫(kù)時(shí), 如果使用對(duì)象作為鍵名, 就不用擔(dān)心自己的屬性與原來(lái)的屬性同名。

如果 Map 的鍵是一個(gè)簡(jiǎn)單類型的值( 數(shù)字、 字符串、 布爾值), 則只要兩個(gè)值嚴(yán)格相等, Map 將其視為一個(gè)鍵, 包括0和 - 0。另外, 雖然NaN不嚴(yán)格相等于自身, 但 Map 將其視為同一個(gè)鍵。

let map = new Map(); 
map.set(NaN, 123); 
map.get(NaN) // 123 
map.set(-0, 123); 
map.get(+0) // 123

10. 模塊化

ES6中首次引入模塊化開(kāi)發(fā)規(guī)范ES Module,讓Javascript首次支持原生模塊化開(kāi)發(fā)。ES Module把一個(gè)文件當(dāng)作一個(gè)模塊,每個(gè)模塊有自己的獨(dú)立作用域,那如何把每個(gè)模塊聯(lián)系起來(lái)呢?核心點(diǎn)就是模塊的導(dǎo)入與導(dǎo)出。

(1)export 導(dǎo)出模塊

  • 正常導(dǎo)出:
// 方式一
export var first = 'test';
export function func() {
    return true;
}

// 方式二
var first = 'test';
var second = 'test';
function func() {
    return true;
}
export {first, second, func};
  • as關(guān)鍵字:
var first = 'test';
export {first as second};

as關(guān)鍵字可以重命名暴露出的變量或方法,經(jīng)過(guò)重命名后同一變量可以多次暴露出去。

  • export default

export default會(huì)導(dǎo)出默認(rèn)輸出,即用戶不需要知道模塊中輸出的名字,在導(dǎo)入的時(shí)候?yàn)槠渲付ㄈ我饷帧?/p>

// 導(dǎo)出
export default function () {
  console.log('foo');
}
// 導(dǎo)入
import customName from './export-default';

注意: 導(dǎo)入默認(rèn)模塊時(shí)不需要大括號(hào),導(dǎo)出默認(rèn)的變量或方法可以有名字,但是對(duì)外無(wú)效。export default只能使用一次。

(2)import 導(dǎo)入模塊

  • 正常導(dǎo)入:
import {firstName, lastName, year} from './profile';
復(fù)制代碼

導(dǎo)入模塊位置可以是相對(duì)路徑也可以是絕對(duì)路徑,.js可以省略,如果不帶路徑只是模塊名,則需要通過(guò)配置文件告訴引擎查找的位置。

  • as關(guān)鍵字:
import { lastName as surname } from './profile';

import 命令會(huì)被提升到模塊頭部,所以寫的位置不是那么重要,但是不能使用表達(dá)式和變量來(lái)進(jìn)行導(dǎo)入。

  • 加載整個(gè)模塊(無(wú)輸出)
import 'lodash'; //僅僅是加載而已,無(wú)法使用
  • 加載整個(gè)模塊(有輸出)
import * as circle from './circle';
console.log('圓面積:' + circle.area(4));
console.log('圓周長(zhǎng):' + circle.circumference(14));

注意: import * 會(huì)忽略default輸出

(3)導(dǎo)入導(dǎo)出復(fù)合用法

  • 先導(dǎo)入后導(dǎo)出
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, boo};
  • 整體先導(dǎo)入再輸出以及default
// 整體輸出
export * from 'my_module';
// 導(dǎo)出default,正如前面所說(shuō),export default 其實(shí)導(dǎo)出的是default變量
export { default } from 'foo';
// 具名接口改default
export { es6 as default } from './someModule';

(4)模塊的繼承

export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}

注意: export * 會(huì)忽略default。

11. 字符串方法

(1)includes()

includes():該方法用于判斷字符串是否包含指定的子字符串。如果找到匹配的字符串則返回 true,否則返回 false。該方法的語(yǔ)法如下:

string.includes(searchvalue, start)

該方法有兩個(gè)參數(shù):

  • searchvalue:必需,要查找的字符串;
  • start:可選,設(shè)置從那個(gè)位置開(kāi)始查找,默認(rèn)為 0。
let str = 'Hello world!';

str.includes('o')  // 輸出結(jié)果:true
str.includes('z')  // 輸出結(jié)果:false
str.includes('e', 2)  // 輸出結(jié)果:false

(2)startsWith()

startsWith():該方法用于檢測(cè)字符串是否以指定的子字符串開(kāi)始。如果是以指定的子字符串開(kāi)頭返回 true,否則 false。其語(yǔ)法和上面的includes()方法一樣。

let str = 'Hello world!';

str.startsWith('Hello') // 輸出結(jié)果:true
str.startsWith('Helle') // 輸出結(jié)果:false
str.startsWith('wo', 6) // 輸出結(jié)果:true

(3)endsWith()

endsWith():該方法用來(lái)判斷當(dāng)前字符串是否是以指定的子字符串結(jié)尾。如果傳入的子字符串在搜索字符串的末尾則返回 true,否則將返回 false。其語(yǔ)法如下:

string.endsWith(searchvalue, length)

該方法有兩個(gè)參數(shù):

  • searchvalue:必需,要搜索的子字符串;
  • length:設(shè)置字符串的長(zhǎng)度,默認(rèn)值為原始字符串長(zhǎng)度 string.length。
let str = 'Hello world!';

str.endsWith('!')       // 輸出結(jié)果:true
str.endsWith('llo')     // 輸出結(jié)果:false
str.endsWith('llo', 5)  // 輸出結(jié)果:true

可以看到,當(dāng)?shù)诙€(gè)參數(shù)設(shè)置為5時(shí),就會(huì)從字符串的前5個(gè)字符中進(jìn)行檢索,所以會(huì)返回true。

(4)repeat()

repeat() 方法返回一個(gè)新字符串,表示將原字符串重復(fù)n次:

'x'.repeat(3)     // 輸出結(jié)果:"xxx"
'hello'.repeat(2) // 輸出結(jié)果:"hellohello"
'na'.repeat(0)    // 輸出結(jié)果:""

如果參數(shù)是小數(shù),會(huì)向下取整:

'na'.repeat(2.9) // 輸出結(jié)果:"nana"

如果參數(shù)是負(fù)數(shù)或者Infinity,會(huì)報(bào)錯(cuò):

'na'.repeat(Infinity)   // RangeError
'na'.repeat(-1)         // RangeError

如果參數(shù)是 0 到-1 之間的小數(shù),則等同于 0,這是因?yàn)闀?huì)先進(jìn)行取整運(yùn)算。0 到-1 之間的小數(shù),取整以后等于-0,repeat視同為 0。

'na'.repeat(-0.9)   // 輸出結(jié)果:""

如果參數(shù)是NaN,就等同于 0:

'na'.repeat(NaN)    // 輸出結(jié)果:""

如果repeat的參數(shù)是字符串,則會(huì)先轉(zhuǎn)換成數(shù)字。

'na'.repeat('na')   // 輸出結(jié)果:""
'na'.repeat('3')    // 輸出結(jié)果:"nanana"

12. 數(shù)組方法

(1)reduce()

reduce() 方法對(duì)數(shù)組中的每個(gè)元素執(zhí)行一個(gè)reducer函數(shù)(升序執(zhí)行),將其結(jié)果匯總為單個(gè)返回值。其使用語(yǔ)法如下:

arr.reduce(callback,[initialValue])

reduce 為數(shù)組中的每一個(gè)元素依次執(zhí)行回調(diào)函數(shù),不包括數(shù)組中被刪除或從未被賦值的元素,接受四個(gè)參數(shù):初始值(或者上一次回調(diào)函數(shù)的返回值),當(dāng)前元素值,當(dāng)前索引,調(diào)用 reduce 的數(shù)組。

(1) callback (執(zhí)行數(shù)組中每個(gè)值的函數(shù),包含四個(gè)參數(shù))

  • previousValue (上一次調(diào)用回調(diào)返回的值,或者是提供的初始值(initialValue))
  • currentValue (數(shù)組中當(dāng)前被處理的元素)
  • index (當(dāng)前元素在數(shù)組中的索引)
  • array (調(diào)用 reduce 的數(shù)組)

(2) initialValue (作為第一次調(diào)用 callback 的第一個(gè)參數(shù)。)

let arr = [1, 2, 3, 4]
let sum = arr.reduce((prev, cur, index, arr) => {
    console.log(prev, cur, index);
    return prev + cur;
})
console.log(arr, sum);

輸出結(jié)果如下:

1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10

再來(lái)加一個(gè)初始值看看:

let arr = [1, 2, 3, 4]
let sum = arr.reduce((prev, cur, index, arr) => {
    console.log(prev, cur, index);
    return prev + cur;
}, 5)
console.log(arr, sum);

輸出結(jié)果如下:

5 1 0
6 2 1
8 3 2
11 4 3
[1, 2, 3, 4] 15

通過(guò)上面例子,可以得出結(jié)論:如果沒(méi)有提供initialValue,reduce 會(huì)從索引1的地方開(kāi)始執(zhí)行 callback 方法,跳過(guò)第一個(gè)索引。如果提供initialValue,從索引0開(kāi)始。

注意,該方法如果添加初始值,就會(huì)改變?cè)瓟?shù)組,將這個(gè)初始值放在數(shù)組的最后一位。

(2)filter()

filter()方法用于過(guò)濾數(shù)組,滿足條件的元素會(huì)被返回。它的參數(shù)是一個(gè)回調(diào)函數(shù),所有數(shù)組元素依次執(zhí)行該函數(shù),返回結(jié)果為true的元素會(huì)被返回。該方法會(huì)返回一個(gè)新的數(shù)組,不會(huì)改變?cè)瓟?shù)組。

let arr = [1, 2, 3, 4, 5]
arr.filter(item => item > 2) 
// 結(jié)果:[3, 4, 5]

可以使用filter()方法來(lái)移除數(shù)組中的undefined、null、NAN等值

let arr = [1, undefined, 2, null, 3, false, '', 4, 0]
arr.filter(Boolean)
// 結(jié)果:[1, 2, 3, 4]

(3)Array.from

Array.from 的設(shè)計(jì)初衷是快速基于其他對(duì)象創(chuàng)建新數(shù)組,準(zhǔn)確來(lái)說(shuō)就是從一個(gè)類似數(shù)組的可迭代對(duì)象中創(chuàng)建一個(gè)新的數(shù)組實(shí)例。其實(shí),只要一個(gè)對(duì)象有迭代器,Array.from 就能把它變成一個(gè)數(shù)組(注意:該方法會(huì)返回一個(gè)的數(shù)組,不會(huì)改變?cè)瓕?duì)象)。

從語(yǔ)法上看,Array.from 有 3 個(gè)參數(shù):

  • 類似數(shù)組的對(duì)象,必選;
  • 加工函數(shù),新生成的數(shù)組會(huì)經(jīng)過(guò)該函數(shù)的加工再返回;
  • this 作用域,表示加工函數(shù)執(zhí)行時(shí) this 的值。

這三個(gè)參數(shù)里面第一個(gè)參數(shù)是必選的,后兩個(gè)參數(shù)都是可選的:

var obj = {0: 'a', 1: 'b', 2:'c', length: 3};

Array.from(obj, function(value, index){
  console.log(value, index, this, arguments.length);
  return value.repeat(3);   //必須指定返回值,否則返回 undefined
}, obj);

結(jié)果如圖:

以上結(jié)果表明,通過(guò) Array.from 這個(gè)方法可以自定義加工函數(shù)的處理方式,從而返回想要得到的值;如果不確定返回值,則會(huì)返回 undefined,最終生成的是一個(gè)包含若干個(gè) undefined 元素的空數(shù)組。

實(shí)際上,如果這里不指定 this,加工函數(shù)就可以是一個(gè)箭頭函數(shù)。上述代碼可以簡(jiǎn)寫為以下形式。

Array.from(obj, (value) => value.repeat(3));
//  控制臺(tái)打印 (3) ["aaa", "bbb", "ccc"]

除了上述 obj 對(duì)象以外,擁有迭代器的對(duì)象還包括 String、Set、Map 等,Array.from 都可以進(jìn)行處理:

// String
Array.from('abc');                             // ["a", "b", "c"]
// Set
Array.from(new Set(['abc', 'def']));           // ["abc", "def"]
// Map
Array.from(new Map([[1, 'ab'], [2, 'de']]));   // [[1, 'ab'], [2, 'de']]

(1)fill()

使用fill()方法可以向一個(gè)已有數(shù)組中插入全部或部分相同的值,開(kāi)始索引用于指定開(kāi)始填充的位置,它是可選的。如果不提供結(jié)束索引,則一直填充到數(shù)組末尾。如果是負(fù)值,則將從負(fù)值加上數(shù)組的長(zhǎng)度而得到的值開(kāi)始。該方法的語(yǔ)法如下:

array.fill(value, start, end)

其參數(shù)如下:

  • value:必需。填充的值;
  • start:可選。開(kāi)始填充位置;
  • end:可選。停止填充位置 (默認(rèn)為 array.length)。

使用示例如下:

const arr = [0, 0, 0, 0, 0];

// 用5填充整個(gè)數(shù)組
arr.fill(5);
console.log(arr); // [5, 5, 5, 5, 5]
arr.fill(0);      // 重置

// 用5填充索引大于等于3的元素
arr.fill(5, 3);
console.log(arr); // [0, 0, 0, 5, 5]
arr.fill(0);      // 重置

// 用5填充索引大于等于1且小于等于3的元素
arr.fill(5, 3);
console.log(arr); // [0, 5, 5, 0, 0]
arr.fill(0);      // 重置

// 用5填充索引大于等于-1的元素
arr.fill(5, -1);
console.log(arr); // [0, 0, 0, 0, 5]
arr.fill(0);      // 重置

二、ES7 新特性(2016)

1. Array.includes()

includes() 方法用來(lái)判斷一個(gè)數(shù)組是否包含一個(gè)指定的值,如果包含則返回 true,否則返回false。該方法不會(huì)改變?cè)瓟?shù)組。其語(yǔ)法如下:

arr.includes(searchElement, fromIndex)

該方法有兩個(gè)參數(shù):

  • searchElement:必須,需要查找的元素值。
  • fromIndex:可選,從fromIndex 索引處開(kāi)始查找目標(biāo)值。如果為負(fù)值,則按升序從 array.length + fromIndex 的索引開(kāi)始搜 (即使從末尾開(kāi)始往前跳 fromIndex 的絕對(duì)值個(gè)索引,然后往后搜尋)。默認(rèn)為 0。
[1, 2, 3].includes(2);  //  true
[1, 2, 3].includes(4);  //  false
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

在 ES7 之前,通常使用 indexOf 來(lái)判斷數(shù)組中是否包含某個(gè)指定值。但 indexOf 在語(yǔ)義上不夠明確直觀,同時(shí) indexOf 內(nèi)部使用 === 來(lái)判等,所以存在對(duì) NaN 的誤判,includes 則修復(fù)了這個(gè)問(wèn)題:

[1, 2, NaN].indexOf(NaN);   // -1
[1, 2, NaN].includes(NaN);  //  true

注意:使用includes()比較字符串和字符時(shí)區(qū)分大小寫。

2. 指數(shù)操作符

ES7 還引入了指數(shù)操作符 ,用來(lái)更為方便的進(jìn)行指數(shù)計(jì)算,它與 Math.pow() 等效:

Math.pow(2, 10));  // 1024
2**10;           // 1024

三、ES8 新特性(2017)

1. padStart()和padEnd()

padStart()和padEnd()方法用于補(bǔ)齊字符串的長(zhǎng)度。如果某個(gè)字符串不夠指定長(zhǎng)度,會(huì)在頭部或尾部補(bǔ)全。

(1)padStart()

padStart()用于頭部補(bǔ)全。該方法有兩個(gè)參數(shù),其中第一個(gè)參數(shù)是一個(gè)數(shù)字,表示字符串補(bǔ)齊之后的長(zhǎng)度;第二個(gè)參數(shù)是用來(lái)補(bǔ)全的字符串。

如果原字符串的長(zhǎng)度,等于或大于指定的最小長(zhǎng)度,則返回原字符串:

'x'.padStart(1, 'ab') // 'x'

如果用來(lái)補(bǔ)全的字符串與原字符串,兩者的長(zhǎng)度之和超過(guò)了指定的最小長(zhǎng)度,則會(huì)截去超出位數(shù)的補(bǔ)全字符串:

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

如果省略第二個(gè)參數(shù),默認(rèn)使用空格補(bǔ)全長(zhǎng)度:

'x'.padStart(4, 'ab') // 'a   '

padStart()的常見(jiàn)用途是為數(shù)值補(bǔ)全指定位數(shù),筆者最近做的一個(gè)需求就是將返回的頁(yè)數(shù)補(bǔ)齊為三位,比如第1頁(yè)就顯示為001,就可以使用該方法來(lái)操作:

"1".padStart(3, '0')   // 輸出結(jié)果: '001'
"15".padStart(3, '0')  // 輸出結(jié)果: '015'

(2)padEnd()

padEnd()用于尾部補(bǔ)全。該方法也是接收兩個(gè)參數(shù),第一個(gè)參數(shù)是字符串補(bǔ)全生效的最大長(zhǎng)度,第二個(gè)參數(shù)是用來(lái)補(bǔ)全的字符串:

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

2. Object.values()和Object.entries()

在ES5中就引入了Object.keys方法,在ES8中引入了跟Object.keys配套的Object.values和Object.entries,作為遍歷一個(gè)對(duì)象的補(bǔ)充手段,供for...of循環(huán)使用。它們都用來(lái)遍歷對(duì)象,它會(huì)返回一個(gè)由給定對(duì)象的自身可枚舉屬性(不含繼承的和Symbol屬性)組成的數(shù)組,數(shù)組元素的排列順序和正常循環(huán)遍歷該對(duì)象時(shí)返回的順序一致,這個(gè)三個(gè)元素返回的值分別如下:

  • Object.keys():返回包含對(duì)象鍵名的數(shù)組;
  • Object.values():返回包含對(duì)象鍵值的數(shù)組;
  • Object.entries():返回包含對(duì)象鍵名和鍵值的數(shù)組。
let obj = { 
  id: 1, 
  name: 'hello', 
  age: 18 
};
console.log(Object.keys(obj));   // 輸出結(jié)果: ['id', 'name', 'age']
console.log(Object.values(obj)); // 輸出結(jié)果: [1, 'hello', 18]
console.log(Object.entries(obj));   // 輸出結(jié)果: [['id', 1], ['name', 'hello'], ['age', 18]

注意

  • Object.keys()方法返回的數(shù)組中的值都是字符串,也就是說(shuō)不是字符串的key值會(huì)轉(zhuǎn)化為字符串。
  • 結(jié)果數(shù)組中的屬性值都是對(duì)象本身可枚舉的屬性,不包括繼承來(lái)的屬性。

3. 函數(shù)擴(kuò)展

ES2017 規(guī)定函數(shù)的參數(shù)列表的結(jié)尾可以為逗號(hào):

function person( name, age, sex, ) {}

該特性的主要作用是方便使用git進(jìn)行多人協(xié)作開(kāi)發(fā)時(shí)修改同一個(gè)函數(shù)減少不必要的行變更。

4. Object.values

之前可以通過(guò) Object.keys 來(lái)獲取一個(gè)對(duì)象所有的 key。在ES8中提供了 Object.values 來(lái)獲取對(duì)象所有的 value 值:

const person = {
  name: "zhangsan",
  age: 18,
  height: 188,
};

console.log(Object.values(person)); // ['zhangsan', 18, 188]

四、ES9 新特性(2018)

1. for await…of

for await...of方法被稱為異步迭代器,該方法是主要用來(lái)遍歷異步對(duì)象。

for await...of 語(yǔ)句會(huì)在異步或者同步可迭代對(duì)象上創(chuàng)建一個(gè)迭代循環(huán),包括 String,Array,類數(shù)組,Map, Set和自定義的異步或者同步可迭代對(duì)象。這個(gè)語(yǔ)句只能在 async function內(nèi)使用:

function Gen (time) {
  return new Promise((resolve,reject) => {
    setTimeout(function () {
       resolve(time)
    },time)
  })
}

async function test () {
   let arr = [Gen(2000),Gen(100),Gen(3000)]
   for await (let item of arr) {
      console.log(Date.now(),item)
   }
}
test()

輸出結(jié)果:

2. Promise.finally

ES2018 為 Promise 添加了 finally() 方法,表示無(wú)論 Promise 實(shí)例最終成功或失敗都會(huì)執(zhí)行的方法:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const one = '1';
    reject(one);
  }, 1000);
});

promise
  .then(() => console.log('success'))
  .catch(() => console.log('fail'))
  .finally(() => console.log('finally'))

finally() 函數(shù)不接受參數(shù),finally() 內(nèi)部通常不知道 promise 實(shí)例的執(zhí)行結(jié)果,所以通常在 finally() 方法內(nèi)執(zhí)行的是與 promise 狀態(tài)無(wú)關(guān)的操作。

3. 對(duì)象的擴(kuò)展運(yùn)算符

在ES6中就引入了擴(kuò)展運(yùn)算符,但是它只能作用于數(shù)組,ES2018中的擴(kuò)展運(yùn)算符可以作用于對(duì)象:

(1)將元素組織成對(duì)象

const obj = {a: 1, b: 2, c: 3};
const {a, ...rest} = obj;
console.log(rest);    // 輸出 {b: 2, c: 3}

(function({a, ...obj}) {
  console.log(obj);    // 輸出 {b: 2, c: 3}
}({a: 1, b: 2, c: 3}));

(2)將對(duì)象擴(kuò)展為元素

const obj = {a: 1, b: 2, c: 3};
const newObj ={...obj, d: 4};
console.log(newObj);  // 輸出 {a: 1, b: 2, c: 3, d: 4}

(3)可以用來(lái)合并對(duì)象

const obj1 = {a: 1, b:2};
const obj2 = {c: 3, d:4};
const mergedObj = {...obj1, ...obj2};
console.log(mergedObj);  // 輸出 {a: 1, b: 2, c: 3, d: 4}

4. 對(duì)象的 Rest

在對(duì)象的解構(gòu)中,除了已經(jīng)指定的屬性之外,rest將會(huì)拷貝對(duì)象其他的所有可枚舉屬性:

const obj = {foo: 1, bar: 2, baz: 3};
const {foo, ...rest} = obj;

console.log(rest); // {bar: 2, baz: 3}

如果用在函數(shù)參數(shù)中,rest 表示所有剩下的參數(shù):

function func({param1, ...rest}) {
    return rest;
}

console.log(func({param1:1, b:2, c:3, d:4}))  // {b: 2, c: 3, d: 4}

注意,在對(duì)象字面量中,rest運(yùn)算符只能放在對(duì)象的最頂層,并且只能使用一次,要放在最后:

const {...rest, foo} = obj; // Uncaught SyntaxError: Rest element must be last element
const {foo, ...rest1, ...rest2} = obj; // Uncaught SyntaxError: Rest element must be last element

五、ES10 新特性(2019)

1. trimStart() 和 trimEnd()

在ES10之前,JavaScript提供了trim()方法,用于移除字符串首尾空白符。在ES9中提出了trimStart()和trimEnd() 方法用于移除字符串首尾的頭尾空白符,空白符包括:空格、制表符 tab、換行符等其他空白符等。

(1)trimStart()

trimStart() 方法的的行為與trim()一致,不過(guò)會(huì)返回一個(gè)從原始字符串的開(kāi)頭刪除了空白的新字符串,不會(huì)修改原始字符串:

const s = '  abc  ';

s.trimStart()   // "abc  "

(2)trimStart()

trimEnd() 方法的的行為與trim()一致,不過(guò)會(huì)返回一個(gè)從原始字符串的結(jié)尾刪除了空白的新字符串,不會(huì)修改原始字符串:

const s = '  abc  ';

s.trimEnd()   // "  abc"

注意,這兩個(gè)方法都不適用于null、undefined、Number類型。

2. flat()和flatMap()

(1)flat()

在ES2019中,flat()方法用于創(chuàng)建并返回一個(gè)新數(shù)組,這個(gè)新數(shù)組包含與它調(diào)用flat()的數(shù)組相同的元素,只不過(guò)其中任何本身也是數(shù)組的元素會(huì)被打平填充到返回的數(shù)組中:

[1, [2, 3]].flat()        // [1, 2, 3]
[1, [2, [3, 4]]].flat()   // [1, 2, [3, 4]]

在不傳參數(shù)時(shí),flat()默認(rèn)只會(huì)打平一級(jí)嵌套,如果想要打平更多的層級(jí),就需要傳給flat()一個(gè)數(shù)值參數(shù),這個(gè)參數(shù)表示要打平的層級(jí)數(shù):

[1, [2, [3, 4]]].flat(2)  // [1, 2, 3, 4]

如果數(shù)組中存在空項(xiàng),會(huì)直接跳過(guò):

[1, [2, , 3]].flat());    //  [1, 2, 3]

如果傳入的參數(shù)小于等于0,就會(huì)返回原數(shù)組:

[1, [2, [3, [4, 5]]]].flat(0);    //  [1, [2, [3, [4, 5]]]]
[1, [2, [3, [4, 5]]]].flat(-10);  //  [1, [2, [3, [4, 5]]]]

(2)flatMap()

flatMap()方法使用映射函數(shù)映射每個(gè)元素,然后將結(jié)果壓縮成一個(gè)新數(shù)組。它與 map 和連著深度值為1的 flat 幾乎相同,但 flatMap 通常在合并成一種方法的效率稍微高一些。該方法會(huì)返回一個(gè)新的數(shù)組,其中每個(gè)元素都是回調(diào)函數(shù)的結(jié)果,并且結(jié)構(gòu)深度 depth 值為1。

[1, 2, 3, 4].flatMap(x => x * 2);      //  [2, 4, 6, 8]
[1, 2, 3, 4].flatMap(x => [x * 2]);    //  [2, 4, 6, 8]

[1, 2, 3, 4].flatMap(x => [[x * 2]]);  //  [[2], [4], [6], [8]]
[1, 2, 3, 4].map(x => [x * 2]);        //  [[2], [4], [6], [8]]

3. Object.fromEntries()

Object.fromEntries()方法可以把鍵值對(duì)列表轉(zhuǎn)換為一個(gè)對(duì)象。該方法相當(dāng)于 Object.entries() 方法的逆過(guò)程。Object.entries()方法返回一個(gè)給定對(duì)象自身可枚舉屬性的鍵值對(duì)數(shù)組,而Object.fromEntries() 方法把鍵值對(duì)列表轉(zhuǎn)換為一個(gè)對(duì)象。

const object = { key1: 'value1', key2: 'value2' }
 
const array = Object.entries(object)  // [ ["key1", "value1"], ["key2", "value2"] ]
 
 
Object.fromEntries(array)             // { key1: 'value1', key2: 'value2' }

使用該方法主要有以下兩個(gè)用途:

(1)將數(shù)組轉(zhuǎn)成對(duì)象

const entries = [
  ['foo', 'bar'],
  ['baz', 42]
]
Object.fromEntries(entries)  //  { foo: "bar", baz: 42 }

(2)將 Map 轉(zhuǎn)成對(duì)象

const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
])
Object.fromEntries(entries)  //  { foo: "bar", baz: 42 }

4. Symbol描述

通過(guò) Symbol() 創(chuàng)建符號(hào)時(shí),可以通過(guò)參數(shù)提供字符串作為描述:

let dog = Symbol("dog");  // dog 為描述

在 ES2019 之前,獲取一個(gè) Symbol 值的描述需要通過(guò) String 方法 或 toString 方法:

String(dog);              // "Symbol(dog)" 
dog.toString();           // "Symbol(dog)"

ES2019 補(bǔ)充了屬性 description,用來(lái)直接訪問(wèn)描述:

dog.description;  // dog

5. toString()

ES2019 對(duì)函數(shù)的 toString() 方法進(jìn)行了擴(kuò)展,以前這個(gè)方法只會(huì)輸出函數(shù)代碼,但會(huì)省略注釋和空格。ES2019 的 toString()則會(huì)保留注釋、空格等,即輸出的是原始代碼:

function sayHi() {
  /* dog */
  console.log('wangwang');
}

sayHi.toString();  // 將輸出和上面一樣的原始代碼

6. catch

在 ES2019 以前,catch 會(huì)帶有參數(shù),但是很多時(shí)候 catch 塊是多余的。而現(xiàn)在可以不帶參數(shù):

// ES2019 之前
try {
   ...
} catch(error) {
   ...
}

// ES2019 之后
try {
   ...
} catch {
   ...
}

六、ES11 新特性(2020)

1. BigInt

在 JavaScript 中,數(shù)值類型 Number 是 64 位浮點(diǎn)數(shù),所以計(jì)算精度和表示范圍都有一定限制。ES2020 新增了 BigInt 數(shù)據(jù)類型,這也是 JavaScript 引入的第八種基本類型。BigInt 可以表示任意大的整數(shù)。其語(yǔ)法如下:

BigInt(value);

其中 value 是創(chuàng)建對(duì)象的數(shù)值??梢允亲址蛘哒麛?shù)。

在 JavaScript 中,Number 基本類型可以精確表示的最大整數(shù)是253。因此早期會(huì)有這樣的問(wèn)題:

let max = Number.MAX_SAFE_INTEGER;    // 最大安全整數(shù)

let max1 = max + 1
let max2 = max + 2

max1 === max2   // true

有了BigInt之后,這個(gè)問(wèn)題就不復(fù)存在了:

let max = BigInt(Number.MAX_SAFE_INTEGER);

let max1 = max + 1n
let max2 = max + 2n

max1 === max2   // false

可以通過(guò)typeof操作符來(lái)判斷變量是否為BigInt類型(返回字符串"bigint"):

typeof 1n === 'bigint'; // true 
typeof BigInt('1') === 'bigint'; // true

還可以通過(guò)Object.prototype.toString方法來(lái)判斷變量是否為BigInt類型(返回字符串"[object BigInt]"):

Object.prototype.toString.call(10n) === '[object BigInt]';    // true

注意,BigInt 和 Number 不是嚴(yán)格相等的,但是寬松相等:

10n === 10 // false 
10n == 10  // true

Number 和 BigInt 可以進(jìn)行比較:

1n < 2;    // true 
2n > 1;    // true 
2 > 2;     // false 
2n > 2;    // false 
2n >= 2;   // true

2. 空值合并運(yùn)算符(??)

在編寫代碼時(shí),如果某個(gè)屬性不為 null 和 undefined,那么就獲取該屬性,如果該屬性為 null 或 undefined,則取一個(gè)默認(rèn)值:

const name = dogName ? dogName : 'default';

可以通過(guò) || 來(lái)簡(jiǎn)化:

const name =  dogName || 'default';

但是 || 的寫法存在一定的缺陷,當(dāng) dogName 為 0 或 false 的時(shí)候也會(huì)走到 default 的邏輯。所以 ES2020 引入了 ?? 運(yùn)算符。只有 ?? 左邊為 null 或 undefined時(shí)才返回右邊的值:

const dogName = false; 
const name =  dogName ?? 'default';  // name = false;

3. 可選鏈操作符(?.)

在開(kāi)發(fā)過(guò)程中,我們經(jīng)常需要獲取深層次屬性,例如 system.user.addr.province.name。但在獲取 name 這個(gè)屬性前需要一步步的判斷前面的屬性是否存在,否則并會(huì)報(bào)錯(cuò):

const name = (system && system.user && system.user.addr && system.user.addr.province && system.user.addr.province.name) || 'default';

為了簡(jiǎn)化上述過(guò)程,ES2020 引入了「鏈判斷運(yùn)算符」?.,可選鏈操作符( ?. )允許讀取位于連接對(duì)象鏈深處的屬性的值,而不必明確驗(yàn)證鏈中的每個(gè)引用是否有效。?. 操作符的功能類似于 . 鏈?zhǔn)讲僮鞣?,不同之處在于,在引用為null 或 undefined 的情況下不會(huì)引起錯(cuò)誤,該表達(dá)式短路返回值是 undefined。與函數(shù)調(diào)用一起使用時(shí),如果給定的函數(shù)不存在,則返回 undefined。

const name = system?.user?.addr?.province?.name || 'default';

當(dāng)嘗試訪問(wèn)可能不存在的對(duì)象屬性時(shí),可選鏈操作符將會(huì)使表達(dá)式更短、更簡(jiǎn)明。在探索一個(gè)對(duì)象的內(nèi)容時(shí),如果不能確定哪些屬性必定存在,可選鏈操作符也是很有幫助的。

可選鏈有以下三種形式:

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

在使用TypeScript開(kāi)發(fā)時(shí),這個(gè)操作符可以解決很多問(wèn)題。

4. Promise.allSettled

Promise.allSettled 的參數(shù)接受一個(gè) Promise 的數(shù)組,返回一個(gè)新的 Promise。唯一的不同在于,執(zhí)行完之后不會(huì)失敗,也就是說(shuō)當(dāng) Promise.allSettled 全部處理完成后,我們可以拿到每個(gè) Promise 的狀態(tài),而不管其是否處理成功。

下面使用 allSettled 實(shí)現(xiàn)的一段代碼:

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
  console.log(results);
});
// 返回結(jié)果:
// [
//    { status: 'fulfilled', value: 2 },
//    { status: 'rejected', reason: -1 }
// ]

可以看到,Promise.allSettled 最后返回的是一個(gè)數(shù)組,記錄傳進(jìn)來(lái)的參數(shù)中每個(gè) Promise 的返回值,這就是和 all 方法不太一樣的地方。你也可以根據(jù) all 方法提供的業(yè)務(wù)場(chǎng)景的代碼進(jìn)行改造,其實(shí)也能知道多個(gè)請(qǐng)求發(fā)出去之后,Promise 最后返回的是每個(gè)參數(shù)的最終狀態(tài)。

5. String.matchAll()

matchAll() 是新增的字符串方法,它返回一個(gè)包含所有匹配正則表達(dá)式的結(jié)果及分組捕獲組的迭代器。因?yàn)榉祷氐氖潜闅v器,所以通常使用for...of循環(huán)取出。

for (const match of 'abcabc'.matchAll(/a/g)) {
    console.log(match)
}
//["a", index: 0, input: "abcabc", groups: undefined]
//["a", index: 3, input: "abcabc", groups: undefined]

需要注意,該方法的第一個(gè)參數(shù)是一個(gè)正則表達(dá)式對(duì)象,如果傳的參數(shù)不是一個(gè)正則表達(dá)式對(duì)象,則會(huì)隱式地使用 new RegExp(obj) 將其轉(zhuǎn)換為一個(gè) RegExp 。另外,RegExp必須是設(shè)置了全局模式g的形式,否則會(huì)拋出異常 TypeError。

七、ES12 新特性(2021)

1. String.replaceAll()

replaceAll()方法會(huì)返回一個(gè)全新的字符串,所有符合匹配規(guī)則的字符都將被替換掉,替換規(guī)則可以是字符串或者正則表達(dá)式。

let string = 'hello world, hello ES12'
string.replace(/hello/g,'hi')    // hi world, hi ES12
string.replaceAll('hello','hi')  // hi world, hi ES12

注意的是,replaceAll 在使用正則表達(dá)式的時(shí)候,如果非全局匹配(/g),會(huì)拋出異常:

let string = 'hello world, hello ES12'
string.replaceAll(/hello/,'hi') 
// Uncaught TypeError: String.prototype.replaceAll called with a non-global

2. 數(shù)字分隔符

數(shù)字分隔符可以在數(shù)字之間創(chuàng)建可視化分隔符,通過(guò) _下劃線來(lái)分割數(shù)字,使數(shù)字更具可讀性,可以放在數(shù)字內(nèi)的任何地方:

const money = 1_000_000_000
//等價(jià)于
const money = 1000000000

該新特性同樣支持在八進(jìn)制數(shù)中使用:

const number = 0o123_456
//等價(jià)于
const number = 0o123456

3. Promise.any

Promise.any是是 ES2021 新增的特性,它接收一個(gè) Promise 可迭代對(duì)象(例如數(shù)組),只要其中的一個(gè) promise 成功,就返回那個(gè)已經(jīng)成功的 promise 如果可迭代對(duì)象中沒(méi)有一個(gè) promise 成功(即所有的 promises 都失敗/拒絕),就返回一個(gè)失敗的 promise 和 AggregateError 類型的實(shí)例,它是 Error 的一個(gè)子類,用于把單一的錯(cuò)誤集合在一起

const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.resolve('result'),
]

Promise.any(promises).then((value) => {
  console.log('value: ', value)
}).catch((err) => {
  console.log('err: ', err)
})

// 輸出結(jié)果:value:  result

如果所有傳入的 promises 都失?。?/p>

const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.reject('ERROR C'),
]

Promise.any(promises).then((value) => {
  console.log('value:', value)
}).catch((err) => {
  console.log('err:', err)
  console.log(err.message)
  console.log(err.name)
  console.log(err.errors)
})

輸出結(jié)果:

err:AggregateError: All promises were rejected
All promises were rejected
AggregateError
["ERROR A", "ERROR B", "ERROR C"]

4. 邏輯賦值操作符

ES12中新增了幾個(gè)邏輯賦值操作符,可以用來(lái)簡(jiǎn)化一些表達(dá)式:

// 等同于 a = a || b
a ||= b;
// 等同于 c = c && d
c &&= d;
// 等同于 e = e ?? f
e ??= f;

八、ES13 新特性(2022)

1. Object.hasOwn()

在ES2022之前,可以使用 Object.prototype.hasOwnProperty() 來(lái)檢查一個(gè)屬性是否屬于對(duì)象。

Object.hasOwn 特性是一種更簡(jiǎn)潔、更可靠的檢查屬性是否直接設(shè)置在對(duì)象上的方法:

const example = {
  property: '123'
};

console.log(Object.prototype.hasOwnProperty.call(example, 'property'));
console.log(Object.hasOwn(example, 'property'));

2. at()

at() 是一個(gè)數(shù)組方法,用于通過(guò)給定索引來(lái)獲取數(shù)組元素。當(dāng)給定索引為正時(shí),這種新方法與使用括號(hào)表示法訪問(wèn)具有相同的行為。當(dāng)給出負(fù)整數(shù)索引時(shí),就會(huì)從數(shù)組的最后一項(xiàng)開(kāi)始檢索:

const array = [0,1,2,3,4,5];

console.log(array[array.length-1]);  // 5
console.log(array.at(-1));  // 5

console.log(array[array.lenght-2]);  // 4
console.log(array.at(-2));  // 4

除了數(shù)組,字符串也可以使用at()方法進(jìn)行索引:

const str = "hello world";

console.log(str[str.length - 1]);  // d
console.log(str.at(-1));  // d

3. error.cause

在 ECMAScript 2022 規(guī)范中,new Error() 中可以指定導(dǎo)致它的原因:

function readFiles(filePaths) {
  return filePaths.map(
    (filePath) => {
      try {
        // ···
      } catch (error) {
        throw new Error(
          `While processing ${filePath}`,
          {cause: error}
        );
      }
    });
}

4. Top-level Await

在ES2017中,引入了 async 函數(shù)和 await 關(guān)鍵字,以簡(jiǎn)化 Promise 的使用,但是 await 關(guān)鍵字只能在 async 函數(shù)內(nèi)部使用。嘗試在異步函數(shù)之外使用 await 就會(huì)報(bào)錯(cuò):SyntaxError - SyntaxError: await is only valid in async function。

頂層 await 允許我們?cè)?nbsp;async 函數(shù)外面使用 await 關(guān)鍵字。它允許模塊充當(dāng)大型異步函數(shù),通過(guò)頂層 await,這些 ECMAScript 模塊可以等待資源加載。這樣其他導(dǎo)入這些模塊的模塊在執(zhí)行代碼之前要等待資源加載完再去執(zhí)行。

由于 await 僅在 async 函數(shù)中可用,因此模塊可以通過(guò)將代碼包裝在 async 函數(shù)中來(lái)在代碼中包含 await:

// a.js
  import fetch  from "node-fetch";
  let users;

  export const fetchUsers = async () => {
    const resp = await fetch('https://jsonplaceholder.typicode.com/users');
    users =  resp.json();
  }
  fetchUsers();

  export { users };

  // usingAwait.js
  import {users} from './a.js';
  console.log('users: ', users);
  console.log('usingAwait module');

我們還可以立即調(diào)用頂層async函數(shù)(IIAFE):

import fetch  from "node-fetch";
  (async () => {
    const resp = await fetch('https://jsonplaceholder.typicode.com/users');
    users = resp.json();
  })();
  export { users };

這樣會(huì)有一個(gè)缺點(diǎn),直接導(dǎo)入的 users 是 undefined,需要在異
當(dāng)前名稱:你需要知道的ES2015—ES2023開(kāi)發(fā)技巧!
網(wǎng)站地址:http://www.dlmjj.cn/article/dpjipss.html