2022年了,這些ES7-ES12的知識點你都掌握了嘛?
聽說現(xiàn)在大家在工作中ES6語法都已經(jīng)用的爐火純青了,那ES7-ES12的新特性你現(xiàn)在都用上了嘛?很多的新特性在開發(fā)中還是很實用的,也解決了很多js存在的問題。自己熬夜爆肝一個周末,總結(jié)出了ES7-ES12的語法,希望對你能有一定的幫助。最后,了解真相,你才能獲得真正的自由!
ES2016(ES7)
Array.prototype.includes()
includes()?方法用來判斷一個數(shù)組是否包含一個指定的值,如果包含則返回?true,否則返回?false。
語法
arr.includes(valueToFind[,?fromIndex])
valueToFind,需要查找的元素值。
fromIndex?可選 從fromIndex?索引處開始查找?valueToFind。如果為負值(即從末尾開始往前跳?fromIndex?的絕對值個索引,然后往后搜尋)。默認為 0。
示例
const?arr?=?['es6',?'es7',?'es8']
console.log(arr.includes('es7'))?//?true
console.log(arr.includes('es7',?1))?//?true
console.log(arr.includes('es7',?2))?//?false
console.log(arr.includes("es7",?-1));?//?fsle
console.log(arr.includes("es7",?-2));?//?true
注意點
使用 includes()查找字符串是區(qū)分大小寫的。
const?arr?=?["es6",?"es7",?"es8",?"a"];
console.log(arr.includes("A"));?//?false
使用 includes()只能判斷簡單類型的數(shù)據(jù),對于復(fù)雜類型的數(shù)據(jù),比如對象類型的數(shù)組,二維數(shù)組,這些是無法判斷的.
const?arr?=?['es6',?['es7',?'es8'],?'es9',{name:"jimmy"}]
console.log(arr.includes(["es7",?"es8"]));?//?false
console.log(arr.includes({name:"jimmy"}));?//?false
能識別NaN,indexOf是不能識別NaN的
const?arr?=?['es6',?'es7',?NaN,?'es8']
console.log(arr.includes(NaN))?//?true
console.log(arr.indexOf(NaN))?//?-1
最后,如果只想知道某個值是否在數(shù)組中存在,而并不關(guān)心它的索引位置,建議使用includes(),如果想獲取一個值在數(shù)組中的位置,那么使用indexOf方法。
冪運算符 **
比如我們想求2的10次方。
自己寫函數(shù)實現(xiàn)
function?pow(x,?y)?{
????let?result?=?1
????for?(let?i?=?0;?i?????????result?*=?x
????}
????return?result
}
console.log(pow(2,?10))?//?1024
Math.pow()
console.log(Math.pow(2,?10));?//?1024
冪運算符 **
console.log(2?**?10);?//?1024
基本求冪
2?**?3???//?8
3?**?2???//?9
3?**?2.5?//?15.588457268119896
10?**?-1?//?0.1
NaN?**?2?//?NaN
注意
冪運算符的兩個*號之間不能出現(xiàn)空格,否則語法會報錯。
ES2017(ES8)
Object.values()
Object.values方法返回一個數(shù)組,成員是參數(shù)對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值。
const?obj?=?{
??name:?"jimmy",
??age:?18,
??height:?188,
};
console.log(Object.values(obj));?//?[?'jimmy',?18,?188?]
Object.entries()
Object.entries() 方法返回一個數(shù)組,成員是參數(shù)對象自身的(不含繼承的)所有可遍歷屬性的鍵值對數(shù)組。
const?obj?=?{
??name:?"jimmy",
??age:?18,
??height:?188,
};
console.log(Object.entries(obj));?//?[?[?'name',?'jimmy'?],?[?'age',?18?],?[?'height',?188?]?]
console.log(Object.entries([1,?2,?3]));?//?[?[?'0',?1?],?[?'1',?2?],?[?'2',?3?]?]
Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors()?方法用來獲取一個對象的所有自身屬性的描述符。
const?obj?=?{
??name:?"jimmy",
??age:?18,
};
const?desc?=?Object.getOwnPropertyDescriptors(obj);
console.log(desc);??
//?打印結(jié)果
{
??name:?{
????value:?'jimmy',
????writable:?true,
????enumerable:?true,
????configurable:?true
??},
??age:?{?
???value:?18,?
???writable:?true,
???enumerable:?true,?
???configurable:?true?
??}
}
上面打印結(jié)果中的
value表示當前對象的默認值writable表示對象屬性是否可以修改enumerable表示當前這個屬性是否可以出現(xiàn)在對象的枚舉屬性中configurable表示當前對象的屬性能否用delete刪除
那這些對象的屬性我們怎么設(shè)置和修改他們呢,我們可以使用es5的 Object.defineProperty()
const?obj?=?{};
Object.defineProperty(obj,?"name",?{
??value:?"jimmy",
??writable:?true,
??configurable:?true,
??enumerable:?true,
});
Object.defineProperty(obj,?"age",?{
??value:?34,
??writable:?true,
??configurable:?true,
??enumerable:?true,
});
console.log(obj);?//?{?name:?'jimmy',?age:?34?}
接下來我們演示下,一些屬性設(shè)置為false的情況
const?obj?=?{};
Object.defineProperty(obj,?"name",?{
??value:?"jimmy",
??writable:?false,
??configurable:?false,
??enumerable:?true,
});
console.log(obj);?//?{?name:?'jimmy'?}
obj.name?=?"chimmy";
console.log(obj);?//?{?name:?'jimmy'?}
delete?obj.name
console.log(obj);?//?{?name:?'jimmy'?}
我們可以看到設(shè)置 writable: false和configurable: false,為false時,對象的name對象的值不能改變和不能被刪除,打印出來還是原來的對象。
設(shè)置enumerable為false時
const?obj?=?{};
Object.defineProperty(obj,?"name",?{
??value:?"jimmy",
??writable:?true,
??configurable:?true,
??enumerable:?false,
});
console.log(obj);?//?{?}
for?(let?key?in?obj)?{
??console.log(key);?//?""
}
當設(shè)置enumerable: false時,表示對象的屬性不可被枚舉,這時打印對象為空,遍歷對象的鍵也為空。
String.prototype.padStart
把指定字符串填充到字符串頭部,返回新字符串。
語法
str.padStart(targetLength [, padString])
targetLength
當前字符串需要填充到的目標長度。如果這個數(shù)值小于當前字符串的長度,則返回當前字符串本身。
padString?可選
填充字符串。如果字符串太長,使填充后的字符串長度超過了目標長度,則只保留最左側(cè)的部分,其他部分會被截斷。此參數(shù)的默認值為 " "
示例
'abc'.padStart(10);?????????//?"???????abc"
'abc'.padStart(10,?"foo");??//?"foofoofabc"
'abc'.padStart(6,"123465");?//?"123abc"
'abc'.padStart(8,?"0");?????//?"00000abc"
'abc'.padStart(1);??????????//?"abc"
應(yīng)用場景
日期格式化:yyyy-mm-dd的格式:
const?now?=?new?Date()
const?year?=?now.getFullYear()
//?月份和日期?如果是一位前面給它填充一個0
const?month?=?(now.getMonth()?+?1).toString().padStart(2,?'0')
const?day?=?(now.getDate()).toString().padStart(2,?'0')
console.log(year,?month,?day)
console.log(?`${year}-${month}-${day}`?)?//輸入今天的日期?2021-12-31
數(shù)字替換(手機號,銀行卡號等)
const?tel?=?'18781268679'
const?newTel?=?tel.slice(-4).padStart(tel.length,?'*')
console.log(newTel)?//?*******5678
String.prototype.padEnd
把指定字符串填充到字符串尾部,返回新字符串。
語法同上
示例
'abc'.padEnd(10);??????????//?"abc???????"
'abc'.padEnd(10,?"foo");???//?"abcfoofoof"
'abc'.padEnd(6,?"123456");?//?"abc123"
'abc'.padEnd(1);???????????//?"abc"
應(yīng)用場景
在JS前端我們處理時間戳的時候單位是ms毫秒,但是,后端同學(xué)返回的時間戳則不一樣是毫秒,可能只有10位,以s秒為單位。所以,我們在前端處理這個時間戳的時候,保險起見,要先做一個13位的補全,保證單位是毫秒。
//?偽代碼
console.log(new?Date().getTime())?//?時間戳?13位的
timestamp?=?+String(timestamp).padEnd(13,?'0')
尾逗號 Trailing commas
ES8 允許函數(shù)的最后一個參數(shù)有尾逗號(Trailing comma)。此前,函數(shù)定義和調(diào)用時,都不允許最后一個參數(shù)后面出現(xiàn)逗號。
function?clownsEverywhere(
????param1,
????param2
)?{
????/*?...?*/
}
clownsEverywhere(
????'foo',
????'bar'
)
上面代碼中,如果在param2或bar后面加一個逗號,就會報錯。
如果像上面這樣,將參數(shù)寫成多行(即每個參數(shù)占據(jù)一行),以后修改代碼的時候,想為函數(shù)clownsEverywhere添加第三個參數(shù),或者調(diào)整參數(shù)的次序,就勢必要在原來最后一個參數(shù)后面添加一個逗號。這對于版本管理系統(tǒng)來說,就會顯示添加逗號的那一行也發(fā)生了變動。這看上去有點冗余,因此新的語法允許定義和調(diào)用時,尾部直接可以加上一個逗號。
function?clownsEverywhere(
????param1,
????param2,
)?{
????/*?...?*/
}
clownsEverywhere(
????'foo',
????'bar',
)
這樣的規(guī)定也使得,函數(shù)參數(shù)與數(shù)組和對象的尾逗號規(guī)則,保持一致了。
async/await
介紹
我們都知道使用 Promise 能很好地解決回調(diào)地獄的問題,但如果處理流程比較復(fù)雜的話,那么整段代碼將充斥著 then,語義化不明顯,代碼不能很好地表示執(zhí)行流程,那有沒有比 Promise 更優(yōu)雅的異步方式呢?那就是async/await!我們一起來揭開它神秘的面撒吧!
前面添加了async的函數(shù)在執(zhí)行后都會自動返回一個Promise對象:
function?foo()?{
????return?'jimmy'
}
console.log(foo())?//?'jimmy'
添加async后
async?function?foo()?{
????return?'jimmy'?//?Promise.resolve('jimmy')
}
console.log(foo())?//?Promise
foo()
async函數(shù)中使用await,那么await這里的代碼就會變成同步的了,意思就是說只有等await后面的Promise執(zhí)行完成得到結(jié)果才會繼續(xù)下去,await就是等待。請看下面的示例:
function?timeout()?{
????return?new?Promise(resolve?=>?{
????????setTimeout(()?=>?{
????????????console.log(1)
????????????resolve()
????????},?1000)
????})
}
//?不加async和await是2、1???加了是1、2
async?function?foo()?{
????await?timeout()?
????console.log(2)
}
foo()
使用場景
假如有這樣一個使用場景:需要先請求 a 鏈接,等返回信息之后,再請求 b 鏈接的另外一個資源。下面代碼展示的是使用 fetch 來實現(xiàn)這樣的需求,fetch 被定義在 window 對象中,它返回的是一個 Promise 對象。
fetch('https://blog.csdn.net/')
??.then(response?=>?{
????console.log(response)
????return?fetch('https://juejin.im/')
??})
??.then(response?=>?{
????console.log(response)
??})
??.catch(error?=>?{
????console.log(error)
??})
雖然上述代碼可以實現(xiàn)這個需求,但語義化不明顯,代碼不能很好地表示執(zhí)行流程?;谶@個原因,ES8 引入了 async/await,這是 JavaScript 異步編程的一個重大改進,提供了在不阻塞主線程的情況下使用同步代碼實現(xiàn)異步訪問資源的能力,并且使得代碼邏輯更加清晰。
async?function?foo?()?{
??try?{
????let?response1?=?await?fetch('https://blog.csdn.net/')
????console.log(response1)
????let?response2?=?await?fetch('https://juejin.im/')
????console.log(response2)
??}?catch?(err)?{
????console.error(err)
??}
}
foo()
通過上面代碼,你會發(fā)現(xiàn)整個異步處理的邏輯都是使用同步代碼的方式來實現(xiàn)的,而且還支持 try catch 來捕獲異常,這感覺就在寫同步代碼,所以是非常符合人的線性思維的。
注意點
await 只能在 async 標記的函數(shù)內(nèi)部使用,單獨使用會觸發(fā) Syntax error。 await后面需要跟異步操作,不然就沒有意義,而且await后面的Promise對象不必寫then,因為await的作用之一就是獲取后面Promise對象成功狀態(tài)傳遞出來的參數(shù)。
async/await的缺陷
了解Async/await是非常有用的,但還有一些缺點需要考慮。
Async/await?讓你的代碼看起來是同步的,在某種程度上,也使得它的行為更加地同步。?await?關(guān)鍵字會阻塞其后的代碼,直到promise完成,就像執(zhí)行同步操作一樣。它確實可以允許其他任務(wù)在此期間繼續(xù)運行,但您自己的代碼被阻塞。
這意味著您的代碼可能會因為大量await的promises相繼發(fā)生而變慢。每個await都會等待前一個完成,而你實際想要的是所有的這些promises同時開始處理(就像我們沒有使用async/await時那樣)。
有一種模式可以緩解這個問題——通過將?Promise?對象存儲在變量中來同時開始它們,然后等待它們?nèi)繄?zhí)行完畢。如果想更加深入的了解,請參考 MDN[1]
ES2018(ES9)
Object Rest & Spread
在 ES9 新增 Object 的 Rest & Spread 方法,直接看下示例:
const?input?=?{
??a:?1,
??b:?2,
??c:?3,
}
const?output?=?{
??...input,
??c:?4
}
console.log(output)?//?{a:?1,?b:?2,?c:?4}
這塊代碼展示了 spread 語法,可以把 input 對象的數(shù)據(jù)都拓展到 output 對象,這個功能很實用。需要注意的是,如果存在相同的屬性名,只有最后一個會生效。
注意點
const?obj?=?{?x:?{?y:?10?}?};
const?copy1?=?{?...obj?};
const?copy2?=?{?...obj?};
obj.x.y?=?"jimmy";
console.log(copy1,?copy2);?//?x:?{y:?"jimmy"}?x:?{y:?"jimmy"}
console.log(copy1.x?===?copy2.x);?//?→?true
如果屬性的值是一個對象的話,該對象的引用會被拷貝,而不是生成一個新的對象。
我們再來看下 Object rest 的示例:
const?input?=?{
??a:?1,
??b:?2,
??c:?3
}
let?{?a,?...rest?}?=?input
console.log(a,?rest)?//?1?{b:?2,?c:?3}
當對象 key-value 不確定的時候,把必選的 key 賦值給變量,用一個變量收斂其他可選的 key 數(shù)據(jù),這在之前是做不到的。注意,rest 屬性必須始終出現(xiàn)在對象的末尾,否則將拋出錯誤。
for await of
異步迭代器(for-await-of):循環(huán)等待每個Promise對象變?yōu)閞esolved狀態(tài)才進入下一步。
我們知道 for...of 是同步運行的,看如下代碼
function?TimeOut(time){
????return?new?Promise(function(resolve,?reject)?{
????????setTimeout(function()?{
????????????resolve(time)
????????},?time)
????})
}
async?function?test()?{
????let?arr?=?[TimeOut(2000),?TimeOut(1000),?TimeOut(3000)]
????for?(let?item?of?arr)?{??
?????console.log(Date.now(),item.then(console.log))
????}
}
test()
上面打印結(jié)果如下圖

上述代碼證實了 for of 方法不能遍歷異步迭代器,得到的結(jié)果并不是我們所期待的,于是 for await of 就粉墨登場啦!
ES9 中可以用 for...await...of 的語法來操作
function?TimeOut(time)?{
????return?new?Promise(function(resolve,?reject)?{
????????setTimeout(function()?{
????????????resolve(time)
????????},?time)
????})
}
async?function?test()?{
????let?arr?=?[TimeOut(2000),?TimeOut(1000),?TimeOut(3000)]
????for?await?(let?item?of?arr)?{
????????console.log(Date.now(),?item)
????}
}
test()
//?1560092345730?2000
//?1560092345730?1000
//?1560092346336?3000
for await of 環(huán)等待每個Promise對象變?yōu)閞esolved狀態(tài)才進入下一步。所有打印的結(jié)果為 2000,1000,3000
Promise.prototype.finally()
Promise.prototype.finally() 方法返回一個Promise,在promise執(zhí)行結(jié)束時,無論結(jié)果是fulfilled或者是rejected,在執(zhí)行then()和catch()后,都會執(zhí)行finally指定的回調(diào)函數(shù)。這為指定執(zhí)行完promise后,無論結(jié)果是fulfilled還是rejected都需要執(zhí)行的代碼提供了一種方式,避免同樣的語句需要在then()和catch()中各寫一次的情況。
示例
new?Promise((resolve,?reject)?=>?{
????setTimeout(()?=>?{
????????resolve('success')
????????//?reject('fail')
????},?1000)
}).then(res?=>?{
????console.log(res)
}).catch(err?=>?{
????console.log(err)
}).finally(()?=>?{
????console.log('finally')
})
使用場景
loading關(guān)閉
需要每次發(fā)送請求,都會有l(wèi)oading提示,請求發(fā)送完畢,就需要關(guān)閉loading提示框,不然界面就無法被點擊。不管請求成功或是失敗,這個loading都需要關(guān)閉掉,這時把關(guān)閉loading的代碼寫在finally里再合適不過了
String 擴展
放松對標簽?zāi)0謇镒址D(zhuǎn)義的限制, 遇到不合法的字符串轉(zhuǎn)義會返回undefined,并且從raw上可獲取原字符串。
下面是一個es6 的標簽?zāi)0?如果對這個語法感到陌生,請參考 標簽?zāi)0?/span>[2]
const?foo?=?(a,?b,?c)?=>?{
????console.log(a)
????console.log(b)
????console.log(c)
}
const?name?=?'jimmy'
const?age?=?18
foo?`這是${name},他的年齡是${age}歲`
參數(shù)打印如下:
ES9開始,模板字符串允許嵌套支持常見轉(zhuǎn)義序列,移除對ECMAScript在帶標簽的模版字符串中轉(zhuǎn)義序列的語法限制。
function?foo(a,?b,?c)?{
????console.log(a,?b,?c)
}
//?在標簽函數(shù)中使用?
//?unicode字符\u{61}?對應(yīng)的值為?a
//?unicode字符\u{62}?對應(yīng)的值為?b
//?\unicode?是一個無效的unicode字符
foo?`\u{61}?and?\u{62}`?
foo?`\u{61}?and?\unicode`

注意點
在模板字符串中,如果輸入無效的unicode字符,還是會報錯。只有在便簽?zāi)0逯?從es9開始才不會報錯。
?let?string?=?`\u{61}?and?\unicode`;
?console.log(string);?//?Uncaught?SyntaxError:?Invalid?Unicode?escape?sequence
ES2019(ES10)
Object.fromEntries()
方法 Object.fromEntries() 把鍵值對列表轉(zhuǎn)換為一個對象,這個方法是和 Object.entries() 相對的。
Object.fromEntries([
????['foo',?1],
????['bar',?2]
])
//?{foo:?1,?bar:?2}
案例1:Object 轉(zhuǎn)換操作
const?obj?=?{
????name:?'jimmy',
????age:?18
}
const?entries?=?Object.entries(obj)
console.log(entries)
//?[Array(2),?Array(2)]
//?ES10
const?fromEntries?=?Object.fromEntries(entries)
console.log(fromEntries)
//?{name:?"jimmy",?age:?18}
案例2:Map 轉(zhuǎn) Object
const?map?=?new?Map()
map.set('name',?'jimmy')
map.set('age',?18)
console.log(map)?//?{'name'?=>?'jimmy',?'age'?=>?18}
const?obj?=?Object.fromEntries(map)
console.log(obj)
//?{name:?"jimmy",?age:?18}
案例3:過濾
course表示所有課程,想請求課程分數(shù)大于80的課程組成的對象:
const?course?=?{
????math:?80,
????english:?85,
????chinese:?90
}
const?res?=?Object.entries(course).filter(([key,?val])?=>?val?>?80)
console.log(res)?//?[?[?'english',?85?],?[?'chinese',?90?]?]
console.log(Object.fromEntries(res))?//?{?english:?85,?chinese:?90?}
案例4:url的search參數(shù)轉(zhuǎn)換
//?let?url?=?"https://www.baidu.com?name=jimmy&age=18&height=1.88"
//?queryString?為?window.location.search
const?queryString?=?"?name=jimmy&age=18&height=1.88";
const?queryParams?=?new?URLSearchParams(queryString);
const?paramObj?=?Object.fromEntries(queryParams);
console.log(paramObj);?//?{?name:?'jimmy',?age:?'18',?height:?'1.88'?}
Array.prototype.flat()
語法
let?newArray?=?arr.flat([depth])
depth?可選
指定要提取嵌套數(shù)組的結(jié)構(gòu)深度,默認值為 1。
示例
flat()?方法會按照一個可指定的深度遞歸遍歷數(shù)組,并將所有元素與遍歷到的子數(shù)組中的元素合并為一個新數(shù)組返回。
const?arr1?=?[0,?1,?2,?[3,?4]];
console.log(arr1.flat());??//??[0,?1,?2,?3,?4]
const?arr2?=?[0,?1,?2,?[[[3,?4]]]];
console.log(arr2.flat(2));??//??[0,?1,?2,?[3,?4]]
//使用?Infinity,可展開任意深度的嵌套數(shù)組
var?arr4?=?[1,?2,?[3,?4,?[5,?6,?[7,?8,?[9,?10]]]]];
arr4.flat(Infinity);?//?[1,?2,?3,?4,?5,?6,?7,?8,?9,?10]
//?`flat()`?方法會移除數(shù)組中的空項:
var?arr5?=?[1,?2,?,?4,?5];
arr5.flat();?//?[1,?2,?4,?5]
Array.prototype.flatMap()
flatMap() 方法首先使用映射函數(shù)映射每個元素,然后將結(jié)果壓縮成一個新數(shù)組。從方法的名字上也可以看出來它包含兩部分功能一個是 map,一個是 flat(深度為1)。
語法
var?new_array?=?arr.flatMap(function?callback(currentValue[,?index[,?array]])?{
????//?返回新數(shù)組的元素
}[,?thisArg])
callback
可以生成一個新數(shù)組中的元素的函數(shù),可以傳入三個參數(shù):
currentValue
當前正在數(shù)組中處理的元素
index
可選 數(shù)組中正在處理的當前元素的索引。
array
可選 被調(diào)用的?map?數(shù)組
thisArg可選
執(zhí)行?callback?函數(shù)時?使用的this?值。
示例
const?numbers?=?[1,?2,?3]
numbers.map(x?=>?[x?*?2])?//?[[2],?[4],?[6]]
numbers.flatMap(x?=>?[x?*?2])?//?[2,?4,?6]
這個示例可以簡單對比下 map 和 flatMap 的區(qū)別。當然還可以看下下面的示例:
let?arr?=?['今天天氣不錯',?'',?'早上好']
arr.map(s?=>?s.split(''))
//?[["今",?"天",?"天",?"氣",?"不",?"錯"],[""],["早",?"上",?"好"]]
arr.flatMap(s?=>?s.split(''))
//?["今",?"天",?"天",?"氣",?"不",?"錯",?"",?"早",?"上",?"好"]
flatMap?方法與?map?方法和深度depth為1的?flat?幾乎相同.
String.prototype.trimStart()
trimStart() 方法從字符串的開頭刪除空格,trimLeft()是此方法的別名。
let?str?=?'???foo??'
console.log(str.length)?//?8
str?=?str.trimStart()?//?或str.trimLeft()
console.log(str.length)?//?5
String.prototype.trimEnd()
trimEnd() 方法從一個字符串的右端移除空白字符,trimRight 是 trimEnd 的別名。
let?str?=?'???foo??'
console.log(str.length)?//?8
str?=?str.trimEnd()?//?或str.trimRight()
console.log(str.length)?//?6
可選的Catch Binding
在 ES10 之前我們都是這樣捕獲異常的:
try?{
????//?tryCode
}?catch?(err)?{
????//?catchCode
}
在這里 err 是必須的參數(shù),在 ES10 可以省略這個參數(shù):
try?{
????console.log('Foobar')
}?catch?{
????console.error('Bar')
}
應(yīng)用
驗證參數(shù)是否為json格式
這個需求我們只需要返回true或false,并不關(guān)心catch的參數(shù)。
const?validJSON?=?json?=>?{
????try?{
????????JSON.parse(json)
????????return?true
????}?catch?{
????????return?false
????}
}
Symbol.prototype.description
我們知道,Symbol 的描述只被存儲在內(nèi)部的?Description?,沒有直接對外暴露,我們只有調(diào)用 Symbol 的 toString() 時才可以讀取這個屬性:
const?name?=?Symbol('es')
console.log(name.toString())?//?Symbol(es)
console.log(name)?//?Symbol(es)
console.log(name?===?'Symbol(es)')?//?false
console.log(name.toString()?===?'Symbol(es)')?//?true
現(xiàn)在可以通過 description 方法獲取 Symbol 的描述:
const?name?=?Symbol('es')
console.log(name.description)?//?es
name.description?=?"es2"?//?只讀屬性?并不能修改描述符
console.log(name.description?===?'es')?//?true
//?如果沒有描述符?輸入undefined
const?s2?=?Symbol()
console.log(s2.description)?//?undefined
JSON.stringify() 增強能力
JSON.stringify 在 ES10 修復(fù)了對于一些超出范圍的 Unicode 展示錯誤的問題。因為 JSON 都是被編碼成 UTF-8,所以遇到 0xD800–0xDFFF 之內(nèi)的字符會因為無法編碼成 UTF-8 進而導(dǎo)致顯示錯誤。在 ES10 它會用轉(zhuǎn)義字符的方式來處理這部分字符而非編碼的方式,這樣就會正常顯示了。
//?\uD83D\uDE0E??emoji?多字節(jié)的一個字符
console.log(JSON.stringify('\uD83D\uDE0E'))?//?打印出笑臉
//?如果我們只去其中的一部分??\uD83D?這其實是個無效的字符串
//?之前的版本?,這些字符將替換為特殊字符,而現(xiàn)在將未配對的代理代碼點表示為JSON轉(zhuǎn)義序列
console.log(JSON.stringify('\uD83D'))?//?"\ud83d"
修訂 Function.prototype.toString()
以前函數(shù)的toString方法來自O(shè)bject.prototype.toString(),現(xiàn)在的 Function.prototype.toString() 方法返回一個表示當前函數(shù)源代碼的字符串。以前只會返回這個函數(shù),不包含注釋、空格等。
function?foo()?{
????//?es10新特性
????console.log('imooc')
}
console.log(foo.toString())?
//?打印如下
//?function?foo()?{
//??//?es10新特性
//??console.log("imooc");
//?}
將返回注釋、空格和語法等詳細信息。
ES2020(ES11)
空值合并運算符(Nullish coalescing Operator)
空值合并操作符( ?? )是一個邏輯操作符,當左側(cè)的操作數(shù)為?null或者undefined時,返回其右側(cè)操作數(shù),否則返回左側(cè)操作數(shù)。
const?foo?=?undefined????"foo"
const?bar?=?null????"bar"
console.log(foo)?//?foo
console.log(bar)?//?bar
與邏輯或操作符(||)不同,邏輯或操作符會在左側(cè)操作數(shù)為假值時返回右側(cè)操作數(shù)。也就是說,如果使用?||?來為某些變量設(shè)置默認值,可能會遇到意料之外的行為。比如為假值(例如'',0,NaN,false)時。見下面的例子。
const?foo?=?""????'default?string';
const?foo2?=?""?||?'default?string';
console.log(foo);?//?""
console.log(foo2);?//?"default?string"
const?baz?=?0????42;
const?baz2?=?0?||?42;
console.log(baz);?//?0
console.log(baz2);?//?42
注意點
將????直接與?AND(&&)和 OR(||)操作符組合使用是不可取的。
null?||?undefined????"foo";?//?拋出?SyntaxError
true?||?undefined????"foo";?//?拋出?SyntaxError
可選鏈 Optional chaining
介紹
可選鏈操作符(??.?)允許讀取位于連接對象鏈深處的屬性的值,而不必明確驗證鏈中的每個引用是否有效。?.?操作符的功能類似于?.?鏈式操作符,不同之處在于,在引用為?null?或者?undefined 的情況下不會引起錯誤,該表達式短路返回值是?undefined。與函數(shù)調(diào)用一起使用時,如果給定的函數(shù)不存在,則返回?undefined。
當嘗試訪問可能不存在的對象屬性時,可選鏈操作符將會使表達式更短、更簡明。在探索一個對象的內(nèi)容時,如果不能確定哪些屬性必定存在,可選鏈操作符也是很有幫助的。
const?user?=?{
????address:?{
????????street:?'xx街道',
????????getNum()?{
????????????return?'80號'
????????}
????}
}
在之前的語法中,想獲取到深層屬性或方法,不得不做前置校驗,否則很容易命中?Uncaught TypeError: Cannot read property...?這種錯誤,這極有可能讓你整個應(yīng)用掛掉。
const?street?=?user?&&?user.address?&&?user.address.street
const?num?=?user?&&?user.address?&&?user.address.getNum?&&?user.address.getNum()
console.log(street,?num)
用了 Optional Chaining ,上面代碼會變成
const?street2?=?user?.address?.street
const?num2?=?user?.address?.getNum?.()
console.log(street2,?num2)
可選鏈中的 ? 表示如果問號左邊表達式有值, 就會繼續(xù)查詢問號后面的字段。根據(jù)上面可以看出,用可選鏈可以大量簡化類似繁瑣的前置校驗操作,而且更安全。
常見用法
??//?對象中使用
??let?obj?=?{
????name:?"jimmy",
????age:?"18",
??};
??let?property?=?"age";
??let?name?=?obj?.name;
??let?age?=?obj?.age;
??let?ages?=?obj?.[property];
??let?sex?=?obj?.sex;
??console.log(name);?//?jimmy
??console.log(age);?//?18
??console.log(ages);?//?18
??console.log(sex);?//?undefined
??
??//?數(shù)組中使用
??let?arr?=?[1,2,2];
??let?arrayItem?=?arr?.[42];?//?undefined
??
??//?函數(shù)中使用
??let?obj?=?{
???func:?function?()?{
?????console.log("I?am?func");
???},
??};
??obj?.func();?//?I?am?func
與空值合并操作符一起使用
let?customer?=?{
??name:?"jimmy",
??details:?{?age:?18?}
};
let?customerCity?=?customer?.city????"成都";
console.log(customerCity);?//?"成都"
注意點
可選鏈不能用于賦值
let?object?=?{};
object?.property?=?1;?//?Uncaught?SyntaxError:?Invalid?left-hand?side?in?assignment
globalThis
在以前,從不同的 JavaScript 環(huán)境中獲取全局對象需要不同的語句。在 Web 中,可以通過?window、self?取到全局對象,在 Node.js 中,它們都無法獲取,必須使用?global。
在松散模式下,可以在函數(shù)中返回?this?來獲取全局對象,但是在嚴格模式和模塊環(huán)境下,this?會返回?undefined。
以前想要獲取全局對象,可通過一個全局函數(shù)
const?getGlobal?=?()?=>?{
????if?(typeof?self?!==?'undefined')?{
????????return?self
????}
????if?(typeof?window?!==?'undefined')?{
????????return?window
????}
????if?(typeof?global?!==?'undefined')?{
????????return?global
????}
????throw?new?Error('無法找到全局對象')
}
const?globals?=?getGlobal()
console.log(globals)
現(xiàn)在globalThis?提供了一個標準的方式來獲取不同環(huán)境下的全局?this? 對象(也就是全局對象自身)。不像?window?或者?self?這些屬性,它確??梢栽谟袩o窗口的各種環(huán)境下正常工作。所以,你可以安心的使用?globalThis,不必擔(dān)心它的運行環(huán)境。
為便于記憶,你只需要記住,全局作用域中的?this?就是globalThis。以后就用globalThis就行了。
BigInt
BigInt?是一種內(nèi)置對象,它提供了一種方法來表示大于?2的53次方?- 1?的整數(shù)。這原本是 Javascript中可以用?Number?表示的最大數(shù)字。BigInt?可以表示任意大的整數(shù)。
使用 BigInt 有兩種方式:
方式一:數(shù)字后面增加n
const?bigInt?=?9007199254740993n
console.log(bigInt)
console.log(typeof?bigInt)?//?bigint
//?`BigInt`?和?[`Number`]不是嚴格相等的,但是寬松相等的。
console.log(1n?==?1)?//?true
console.log(1n?===?1)?//?false
//?`Number`?和?`BigInt`?可以進行比較。
1n?2?//???true
2n?>?1?//???true
方式二:使用 BigInt 函數(shù)
const?bigIntNum?=?BigInt(9007199254740993n)
console.log(bigIntNum)
運算
let?number?=?BigInt(2);
let?a?=?number?+?2n;?//?4n
let?b?=?number?*?10n;?//?20n
let?c?=?number?-?10n;?//?-8n
console.log(a);
console.log(b);
console.log(c);
注意點
BigInt不能用于?[Math]?對象中的方法;不能和任何?[Number]?實例混合運算,兩者必須轉(zhuǎn)換成同一種類型。在兩種類型來回轉(zhuǎn)換時要小心,因為?BigInt?變量在轉(zhuǎn)換成?[Number]?變量時可能會丟失精度。
String.prototype.matchAll()
matchAll() ?方法返回一個包含所有匹配正則表達式的結(jié)果及分組捕獲組的迭代器。
const?regexp?=?/t(e)(st(\d?))/g;
const?str?=?'test1test2';
const?array?=?[...str.matchAll(regexp)];
console.log(array[0]);??//?["test1",?"e",?"st1",?"1"]
console.log(array[1]);?//?["test2",?"e",?"st2",?"2"]
Promise.allSettled()
我們都知道 Promise.all() 具有并發(fā)執(zhí)行異步任務(wù)的能力。但它的最大問題就是如果其中某個任務(wù)出現(xiàn)異常(reject),所有任務(wù)都會掛掉,Promise直接進入reject 狀態(tài)。
場景:現(xiàn)在頁面上有三個請求,分別請求不同的數(shù)據(jù),如果一個接口服務(wù)異常,整個都是失敗的,都無法渲染出數(shù)據(jù)
我們需要一種機制,如果并發(fā)任務(wù)中,無論一個任務(wù)正?;蛘弋惓#紩祷貙?yīng)的的狀態(tài),這就是Promise.allSettled的作用
const?promise1?=?()?=>?{
??return?new?Promise((resolve,?reject)?=>?{
????setTimeout(()?=>?{
??????resolve("promise1");
??????//???reject("error?promise1?");
????},?3000);
??});
};
const?promise2?=?()?=>?{
??return?new?Promise((resolve,?reject)?=>?{
????setTimeout(()?=>?{
??????resolve("promise2");
??????//???reject("error?promise2?");
????},?1000);
??});
};
const?promise3?=?()?=>?{
??return?new?Promise((resolve,?reject)?=>?{
????setTimeout(()?=>?{
??????//???resolve("promise3");
??????reject("error?promise3?");
????},?2000);
??});
};
//??Promise.all?會走到catch里面
Promise.all([promise1(),?promise2(),?promise3()])
??.then((res)?=>?{
????console.log(res);?
??})
??.catch((error)?=>?{
????console.log("error",?error);?//?error?promise3?
??});
??
//?Promise.allSettled?不管有沒有錯誤,三個的狀態(tài)都會返回
Promise.allSettled([promise1(),?promise2(),?promise3()])
??.then((res)?=>?{
????console.log(res);??
????//?打印結(jié)果?
????//?[
????//????{status:?'fulfilled',?value:?'promise1'},?
????//????{status:?'fulfilled',value:?'promise2'},
????//????{status:?'rejected',?reason:?'error?promise3?'}
????//?]
??})
??.catch((error)?=>?{
????console.log("error",?error);?
??});
Dynamic Import(按需 import)
import()可以在需要的時候,再加載某個模塊。
button.addEventListener('click',?event?=>?{
??import('./dialogBox.js')
??.then(dialogBox?=>?{
????dialogBox.open();
??})
??.catch(error?=>?{
????/*?Error?handling?*/
??})
});
上面代碼中,import()方法放在click事件的監(jiān)聽函數(shù)之中,只有用戶點擊了按鈕,才會加載這個模塊。
ES2021(ES12)
邏輯運算符和賦值表達式(&&=,||=,??=)
&&=
邏輯與賦值 x &&= y等效于:
x?&&?(x?=?y);
上面的意思是,當x為真時,x=y。具體請看下面的示例:
let?a?=?1;
let?b?=?0;
a?&&=?2;
console.log(a);?//?2
b?&&=?2;
console.log(b);??//?0
||=
邏輯或賦值(x ||= y)運算僅在?x?為false時賦值。
x ||= y?等同于:x || (x = y);
const?a?=?{?duration:?50,?title:?''?};
a.duration?||=?10;
console.log(a.duration);?//?50
a.title?||=?'title?is?empty.';
console.log(a.title);?//?"title?is?empty"
??=
邏輯空賦值運算符?(x ??= y) 僅在?x?是?nullish[3]?(null?或?undefined) 時對其賦值。
x ??= y?等價于:x ?? (x = y);
示例一
const?a?=?{?duration:?50?};
a.duration???=?10;
console.log(a.duration);?//?50
a.speed???=?25;
console.log(a.speed);?//?25
示例二
function?config(options)?{
??options.duration???=?100;
??options.speed???=?25;
??return?options;
}
config({?duration:?125?});?//?{?duration:?125,?speed:?25?}
config({});?//?{?duration:?100,?speed:?25?}
String.prototype.replaceAll()
介紹
replaceAll() ?方法返回一個新字符串,新字符串中所有滿足?pattern?的部分都會被replacement?替換。pattern可以是一個字符串或一個RegExp,replacement可以是一個字符串或一個在每次匹配被調(diào)用的函數(shù)。
原始字符串保持不變。
示例
'aabbcc'.replaceAll('b',?'.');?//?'aa..cc'
使用正則表達式搜索值時,它必須是全局的。
'aabbcc'.replaceAll(/b/,?'.');
TypeError:?replaceAll?must?be?called?with?a?global?RegExp
這將可以正常運行:
'aabbcc'.replaceAll(/b/g,?'.');
"aa..cc"
數(shù)字分隔符
歐美語言中,較長的數(shù)值允許每三位添加一個分隔符(通常是一個逗號),增加數(shù)值的可讀性。比如,1000可以寫作1,000。
ES2021中允許 JavaScript 的數(shù)值使用下劃線(_)作為分隔符。
let?budget?=?1_000_000_000_000;
budget?===?10?**?12?//?true
這個數(shù)值分隔符沒有指定間隔的位數(shù),也就是說,可以每三位添加一個分隔符,也可以每一位、每兩位、每四位添加一個。
123_00?===?12_300?//?true
12345_00?===?123_4500?//?true
12345_00?===?1_234_500?//?true
小數(shù)和科學(xué)計數(shù)法也可以使用數(shù)值分隔符。
//?小數(shù)
0.000_001
//?科學(xué)計數(shù)法
1e10_000
數(shù)值分隔符有幾個使用注意點。
不能放在數(shù)值的最前面(leading)或最后面(trailing)。 不能兩個或兩個以上的分隔符連在一起。 小數(shù)點的前后不能有分隔符。 科學(xué)計數(shù)法里面,表示指數(shù)的 e或E前后不能有分隔符。
下面的寫法都會報錯。
//?全部報錯
3_.141
3._141
1_e12
1e_12
123__456
_1464301
1464301_
Promise.any
方法接受一組 Promise 實例作為參數(shù),包裝成一個新的 Promise 實例返回。
const?promise1?=?()?=>?{
??return?new?Promise((resolve,?reject)?=>?{
????setTimeout(()?=>?{
??????resolve("promise1");
??????//??reject("error?promise1?");
????},?3000);
??});
};
const?promise2?=?()?=>?{
??return?new?Promise((resolve,?reject)?=>?{
????setTimeout(()?=>?{
??????resolve("promise2");
??????//?reject("error?promise2?");
????},?1000);
??});
};
const?promise3?=?()?=>?{
??return?new?Promise((resolve,?reject)?=>?{
????setTimeout(()?=>?{
??????resolve("promise3");
??????//?reject("error?promise3?");
????},?2000);
??});
};
Promise.any([promise1(),?promise2(),?promise3()])
??.then((first)?=>?{
????//?只要有一個請求成功?就會返回第一個請求成功的
????console.log(first);?//?會返回promise2
??})
??.catch((error)?=>?{
????//?所有三個全部請求失敗?才會來到這里
????console.log("error",?error);
??});
只要參數(shù)實例有一個變成fulfilled狀態(tài),包裝實例就會變成fulfilled狀態(tài);如果所有參數(shù)實例都變成rejected狀態(tài),包裝實例就會變成rejected狀態(tài)。
Promise.any()跟Promise.race()方法很像,只有一點不同,就是Promise.any()不會因為某個 Promise 變成rejected狀態(tài)而結(jié)束,必須等到所有參數(shù) Promise 變成rejected狀態(tài)才會結(jié)束。
WeakRef and Finalizers
這兩個新特性,都應(yīng)該盡量避免使用,所以這里不做過多的講解。如感興趣,請參考
WeakRef[4]
Finalizers[5]
寫在最后
本人水平有限,如有錯誤,歡迎評論區(qū)指出。(ps:ES6系列正在寫)
關(guān)于本文
作者:Jimmy_fx
https://juejin.cn/post/7046217976176967711
推薦閱讀: 年底 JS 燒腦面試題大賞
你說你會ES6?那你倒是用啊?。?!
7張圖,20分鐘就能搞定的async/await原理!



