新聞中心
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í)行步驟如下:
- 創(chuàng)建一個(gè)對(duì)象
- 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(也就是將對(duì)象的__proto__屬性指向構(gòu)造函數(shù)的prototype屬性)
- 指向構(gòu)造函數(shù)中的代碼,構(gòu)造函數(shù)中的this指向該對(duì)象(也就是為這個(gè)對(duì)象添加屬性和方法)
- 返回新的對(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) // 12310. 模塊化
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; // dog5. 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 // trueNumber 和 BigInt 可以進(jìn)行比較:
1n < 2; // true
2n > 1; // true
2 > 2; // false
2n > 2; // false
2n >= 2; // true2. 空值合并運(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-global2. 數(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 = 0o1234563. 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)); // d3. 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


咨詢
建站咨詢
