ES6+新特性說明
(補充)一、ES6新特性(2015)
1. 新的基本類型 symbol
symbol 是一種基本數據類型 (primitive data type)。Symbol()
函數會返回symbol類型的值,該類型具有靜態屬性和靜態方法。它的靜態屬性會暴露幾個內建的成員對象;它的靜態方法會暴露全局的symbol註冊,且類似於內建對象類,但作爲構造函數來說它並不完整,因爲它不支持語法:"new Symbol()
"。
每個從Symbol()
返回的symbol值都是唯一的。(所以不能進行比較)一個symbol值能作爲對象屬性的標識符;這是該數據類型僅有的目的。
const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');
console.log(typeof symbol1);
//output: "symbol"
console.log(symbol3.toString());
//output: "Symbol(foo)"
console.log(Symbol('foo') === Symbol('foo'));
//output: false
應用場景
使用Symbol來作爲對象屬性名(key)
在這之前,我們通常定義或訪問對象的屬性時可以使用字符串,比如下面的代碼:
let obj = {
abc: 123,
"hello": "world"
}
console.log(obj["abc"]); // 123
console.log(obj["hello"]); // 'world'
obj["sym"] = "sym";
console.log(obj); // {abc: 123, hello: "world", sym: "sym"}
console.log(obj.abc);// 123
ES6後, Symbol
可同樣用於對象屬性的定義和訪問:
const SYM_NAME = Symbol()
const SYM_AGE = Symbol()
let obj = {
[SYM_NAME]: "chen"
}
obj[SYM_AGE] = 18
console.log(obj[SYM_NAME]); // "chen"
console.log(obj[SYM_AGE]); // 18
console.log(obj); // {Symbol(): "chen", Symbol(): 18}
隨之而來的是另一個非常值得注意的問題:就是當使用了Symbol作爲對象的屬性key後,在對該對象進行key的枚舉時,會有什麼不同?在實際應用中,我們經常會需要使用Object.keys()
或者for...in
來枚舉對象的屬性名,那在這方面,Symbol類型的key表現的會有什麼不同之處呢?來看以下示例代碼:
let obj = {
[Symbol('SYM_NAME')]: 'chen',
age: 18,
sex:'male'
}
Object.keys(obj) // ['age', 'sex']
for (let p in obj) {
console.log(p) // 分別會輸出:'age' 和 'sex'
}
Object.getOwnPropertyNames(obj) // ['age', 'sex']
由上可知,Symbol類型的key是不能通過Object.keys()
或者for...in
來枚舉的,它未被包含在對象自身的屬性名集合(property names)之中。所以,利用該特性,我們可以把一些不需要對外操作和訪問的屬性使用Symbol來定義。也正因爲這樣一個特性,當使用JSON.stringify()
將對象轉換成JSON字符串的時候,Symbol屬性也會被排除在輸出內容之外:
JSON.stringify(obj) // {"age":18,"sex":"male"}
我們可以利用這一特點來更好的設計我們的數據對象,讓“對內限制性操作”和“對外選擇性輸出”變得更加優雅。
限制性操作:
簡而言之就是如果有需要,硬是要獲取,還是有方法的,只不過有條件:
// 使用Object的API
Object.getOwnPropertySymbols(obj) // [Symbol(name)]
// 使用新增的反射API,將symbol反射回去
Reflect.ownKeys(obj) // [Symbol(name), 'age', 'sex']
使用Symbol定義類的私有屬性/方法
我們知道在JavaScript中,是沒有如Java等面嚮對象語言的訪問控制關鍵字private
的,類上所有定義的屬性或方法都是可公開訪問的。因此這對我們進行API的設計時造成了一些困擾。
而有了Symbol以及模塊化機制,類的私有屬性和方法才變成可能。例如:
- a.js
/* 聲明一個唯一的常量 */
const PASSWORD = Symbol();
class Login {
constructor(username, password) {
this.username = username
/* 私有化 */
this[PASSWORD] = password
}
checkPassword(pwd) {
return this[PASSWORD] === pwd
}
}
export default Login
- b.js
import Login from './a.js';
const login = new Login('admin', '123456');
login.checkPassword('123456');
console.log(login.PASSWORD); //undefined
console.log(login[PASSWORD]); //undefined
console.log(login["PASSWORD"]); //undefined
由於Symbol常量PASSWORD
被定義在a.js所在的模塊中,外面的模塊獲取不到這個Symbol,也不可能再創建一個一模一樣的Symbol出來(因爲Symbol是唯一的),因此這個PASSWORD
的Symbol只能被限制在a.js內部使用,所以使用它來定義的類屬性是沒有辦法被模塊外訪問到的,達到了一個私有化的效果。
擴展
symbol共享
通常情況下,我們在一個瀏覽器窗口中(window),使用Symbol()
函數來定義和Symbol實例就足夠了。但是,如果你的應用涉及到多個window(最典型的就是頁面中使用了 <iframe>
),並需要這些window中使用的某些Symbol是同一個,那就不能使用Symbol()函數了,因爲用它在不同window中創建的Symbol實例總是唯一的,而我們需要的是在所有這些window環境下保持一個共享的Symbol。這種情況下,我們就需要使用另一個API來創建或獲取Symbol,那就是Symbol.for()
,它可以註冊或獲取一個window間全局的Symbol實例:
let gs1 = Symbol.for('global_symbol_1') //註冊一個全局Symbol
let gs2 = Symbol.for('global_symbol_1') //獲取全局Symbol
gs1 === gs2 // true
這樣一個Symbol不光在單個window中是唯一的,在多個相關window間也是唯一的了。
可以參考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol
二、ES7新特性(2016)
ES2016添加了兩個小的特性來說明標準化過程:
- Array.prototype.includes()
- 指數操作符
1. Array.prototype.includes()
includes()
函數用來判斷一個數組是否包含一個指定的值,如果包含則返回 true
,否則返回false
。
includes
函數與 indexOf
函數很相似,下面兩個表達式是等價的:
arr.includes(x)
arr.indexOf(x) >=0
接下來我們來判斷數字中是否包含某個元素:
在ES7之前的做法
使用indexOf()
驗證數組中是否存在某個元素,這時需要根據返回值是否爲-1來判斷:
let arr = ['1', '2', '3'];
if (arr.indexOf('1') !== -1){
console.log('1存在');
}
使用ES7的includes()
使用includes()驗證數組中是否存在某個元素,這樣更加直觀簡單:
let arr = ['1', '2', '3'];
if (arr.includes('1')){
console.log('1存在');
}
2. 指數操作符
在ES7中引入了指數運算符**
,**
具有與Math.pow(..)
等效的計算結果。
不使用指數操作符
使用自定義的遞歸函數calculateExponent或者Math.pow()進行指數運算:
function calculateExponent(base, exponent){
if (exponent === 1){
return base;
}else{
return base * calculateExponent(base, exponent - 1);
}
}
console.log(calculateExponent(2, 10)); // 輸出1024
/* 使用 Math.pow() */
console.log(Math.pow(2, 10)); // 輸出1024
使用指數操作符
使用指數運算符**,就像+、-等操作符一樣:
console.log(2**10); //=> 1024
三、ES8新特性(2017)
-
async/await
-
Object.values()
-
Object.entries()
-
string padding
-
Object.getOwnPropertyDescriptors()
1. async/await(重點)
ES2018引入異步迭代器(asynchronous iterators),這就像常規迭代器,除了next()
方法返回一個Promise。因此await
可以和for...of
循環一起使用,以串行的方式運行異步操作。例如:
/* 模板 */
async function process(array) {
for await (let i of array) {
doSomething(i);
}
}
實例:
迭代異步可迭代對象
/* 可迭代對象 */
var iter = {
[Symbol.iter]() {
return {
i: 0,
next() {
if (this.i < 3) {
return Promise.resolve({ value: this.i++, done: false });
}else{
return Promise.resolve({ done: true }); /* 結束迭代 */
}
}
};
}
};
/* 異步迭代器 */
/* 自調用函數 */
(async function() {
for await (num of iter) {
console.log(num);
}
})();
//輸出:
//0
//1
//2
迭代異步生成器
/* 生成器 */
async function* asyncGenerator() {
var i = 0;
while (i < 3) {
yield i++;
}
}
/* 異步迭代器 */
/* 自調用函數 */
(async function() {
for await (num of asyncGenerator()) {
console.log(num);
}
})();
//輸出:
//0
//1
//2
深入理解async/await
async/await其實是Promise的語法糖,它能實現的效果都能用then鏈來實現,這也和我們之前提到的一樣,它是爲優化then鏈而開發出來的。從字面上來看,async是“異步”的簡寫,await譯爲等待,所以我們很好理解async聲明function是異步的,await等待某個操作完成。當然語法上強制規定await只能出現在asnyc函數中,我們先來看看async函數返回了什麼:
async function test(){
return 'hello world';
}
let result = test();
console.log(result)
這個async聲明的異步函數把return後面直接量通過Promise.resolve()返回Promise對象,所以如果這個最外層沒有用await調用的話,是可以用原來then鏈的方式來調用的:
async function test(){
return 'hello world'
}
let result = test()
console.log(result)
result.then(v=>{
console.log(v) //hello world
})
聯想一下Promise特點——異步無等待,所以當沒有await語句執行async函數,它就會立即執行,返回一個Promise對象,非阻塞,與普通的Promise對象函數一致。
重點就在await,它等待什麼呢?
按照語法說明,await等待的是一個Promise對象,或者是其他值(也就是說可以等待任何值),如果等待的是Promise對象,則返回Promise的處理結果;如果是其他值,則返回該值本身。並且await會暫停當前async function的執行,等待Promise的處理完成。若Promise正常處理(fulfillded),其將回調的resolve函數參數作爲await表達式的值,繼續執行async function;若Promise處理異常(rejected),await表達式會把Promise異常原因拋出;另外如果await操作符後面的表達式不是一個Promise對象,則返回該值本身。
實例:
function Asy(x){
return new Promise(resolve=>{setTimeout(() =>resolve(x), 2000)})
}
async function Awa(){
let res = await Asy(1);
console.log(res); // 2秒鐘之後出現1,再次等待3s,處理完後再處理下一條
console.log(3) // 2秒鐘之後出現3
}
Awa();//異步,不進入主線程
console.log(4);//同步任務
//輸出:
//4
//1
//3
關於: EvenLoop
2. Object.values()
Object.values()
是一個與Object.keys()
類似的新函數,但返回的是Object自身屬性的所有值,不包括繼承的值。
假設我們要遍歷如下對象obj
的所有值:
const obj = {a: 'chen', b: 'yao', c: 'dao'};
不使用Object.values()
/* 通過鍵值對來獲取obj中的屬性值 */
const vals = Object.keys(obj).map(key=>obj[key]);
console.log(vals); //=> ["chen", "yao", "dao"]
使用Object.values()
const values = Object.values(obj);
console.log(values); //=> ["chen", "yao", "dao"]
從上述代碼中可以看出Object.values()
爲我們省去了遍歷key,並根據這些key獲取value的步驟。 簡單明瞭~
3. Object.entries()
Object.entries()
函數返回一個給定對象自身可枚舉屬性的鍵值對的數組。
假設我們要獲取如下對象obj
可枚舉屬性的鍵值對的數組:
const obj = {a: 0, b: 1, c: 2};
不使用Object.values()
/* 通過鍵值對來獲取obj中的屬性值 */
Object.keys(obj).map(key=>{
console.log('keys:'+key+' value:'+obj[key])
});
//keys:a value:0
//keys:b value:1
//keys:c value:2
使用Object.entries()
for(let [key,value] of Object.entries(obj)){
console.log(`key: ${key} value:${value}`)
}
//keys:a value:0
//keys:b value:1
//keys:c value:2
4. string padding
在ES8中String新增了兩個實例函數String.prototype.padStart
和String.prototype.padEnd
,允許將空字符串或其他字符串添加到原始字符串的開頭或結尾。
語法:
-
字符串.padStart(targetLength,[padString]) ;
-
字符串.padEnd(targetLength,[padString]) ;
說明與定義:
-
targetLength:當前字符串需要填充到的目標長度。如果這個數值小於當前字符串的長度,則返回當前字符串本身。
-
padString:(可選)填充字符串。如果字符串太長,使填充後的字符串長度超過了目標長度,則只保留最左側的部分,其他部分會被截斷,此參數的缺省值爲 " "。
實例:
/* padStart */
console.log('0.0'.padStart(4,'10')) //10.0
console.log('0.0'.padStart(8,'10')) //101010.0
console.log('0.0'.padStart(7,'10')) //10100.0
console.log('0.0'.padStart(1,'10')) //0.0
/* padEnd */
console.log('0.0'.padEnd(4,'10')) //0.01
console.log('0.0'.padEnd(8,'10')) //0.010101
console.log('0.0'.padEnd(7,'10')) //0.01010
console.log('0.0'.padEnd(1,'10')) //0.0
5. Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors()
函數用來獲取一個對象的所有自身屬性的描述符,如果沒有任何自身屬性,則返回空對象。
語法:
- Object.getOwnPropertyDescriptors(obj)
返回obj
對象的所有自身屬性的描述符,如果沒有任何自身屬性,則返回空對象。
const obj2 = {
name: 'yaodao',
get age() { return '20' }
};
Object.getOwnPropertyDescriptors(obj2)
// {name: {…}, age: {…}}
//
//{
// name:{
// value: "yaodao",
// writable: true, //可寫
// enumerable: true, //可枚舉
// configurable: true, //可配置
// __proto__: Object,
// },
// age:{
// get: ƒ age(),
// set: undefined,
// enumerable: true,
// configurable: true,
// __proto__: Object
// __proto__: Object
// }
//}
在 vue 響應式原理中起重要的作用。
四、ES9新特性(2018)
- 異步迭代
- Promise.finally()
- Rest/Spread 屬性
1. 異步迭代
在async/await
的某些時刻,你可能嘗試在同步循環中調用異步函數。例如:
function Asy(x){
return new Promise(resolve=>{setTimeout(() =>resolve(x), 2000)})
}
async function Awa(){
let res = await Asy(1);
console.log(res); // 2秒鐘之後出現1,再次等待3s,處理完後再處理下一條
console.log(3) // 2秒鐘之後出現3
}
Awa();//異步,不進入主線程
console.log(4);//同步任務
//輸出:
//4
//1
//3
ES2018引入異步迭代器(asynchronous iterators),這就像常規迭代器,除了next()
方法返回一個Promise。因此await
可以和for...of
循環一起使用,以串行的方式運行異步操作。例如:
async function process(array) {
for await (let i of array) {
doSomething(i);
}
}
2. Promise.finally()
一個Promise調用鏈要麼成功到達最後一個.then()
,要麼失敗觸發.catch()
。在某些情況下,你想要在無論Promise運行成功還是失敗,運行相同的代碼,例如清除,刪除對話,關閉數據庫連接等。
.finally()
允許你指定最終的邏輯:
function doSomething() {
doSomething1()
.then(doSomething2)
.then(doSomething3)
.catch(err => {
console.log(err);
})
.finally(() => {
// finish here!
});
}
3. Rest/Spread 屬性
ES2015引入了Rest參數(…)和擴展運算符(…)。三個點(…)僅用於數組。Rest參數語法允許我們將一個不定數量的參數表示爲一個數組。
function rest(p1, p2, ...p3) {
console.log(p1); //1
console.log(p2); //2
console.log(p3); //[3,4,5]
}
rest(1, 2, 3, 4, 5);
ES2018爲對象解構提供了和數組一樣的Rest參數()和展開操作符,一個簡單的例子:
const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...x } = myObject;
console.log(a); //=> 1
console.log({...x}); //=> {b: 2, c: 3}
或者你可以使用它給函數傳遞參數:
function rest({ a, ...x }) {
console.log(a);
console.log({...x})
}
rest({a: 1,b: 2,c: 3});
//輸出:
//1
//{b: 2, c: 3}
五、ES10新特性(2019)
-
新增了Array的
flat()
方法和flatMap()
方法 -
新增了String的
trimStart()
方法和trimEnd()
方法 -
Object.fromEntries()
-
新的基本數據類型
BigInt
-
標準化 globalThis 對象
-
修改
catch
綁定 -
動態導入
1. 新增了Array的flat()
方法和flatMap()
方法
flat()
和flatMap()
本質上就是是歸納(reduce) 與 合併(concat)的操作。
Array.prototype.flat()
flat()
方法會按照一個可指定的深度遞歸遍歷數組,並將所有元素與遍歷到的子數組中的元素合併爲一個新數組返回。 可用於數組降維,以及去除數組的空項 。參數:n(降維深度)。
/* 數組維數請從左到右數"[" */
/* 降維就從深維度開始往淺維度按照降維深度降維,極限剩下一維 */
var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]
var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]
//使用 Infinity 作爲深度,展開任意深度的嵌套數組
arr3.flat(Infinity);
// [1, 2, 3, 4, 5, 6]
還可以利用flat()
方法的特性來去除數組的空項
var arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]
Array.prototype.flatMap()
flatMap()
方法首先使用映射函數映射每個元素,然後將結果壓縮成一個新數組。它與 map 和 深度值1的 flat 幾乎相同,但 flatMap 通常在合併成一種方法的效率稍微高一些。 這裏我們拿map方法與flatMap方法做一個比較。
var arr1 = [1, 2, 3, 4];
arr1.map(x => [x * 2]);
// [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]
/* 會按照flatMap()構建數組維度 */
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]
2. 新增了String的trimStart()
方法和trimEnd()
方法
分別去除字符串首尾空白字符
let str = " Space around ";
console.log(str.trimEnd()); //=> " Space around"
console.log(str.trimStart()); //=> "Space around "
console.log(str.trim()); //=> "Space around"
3. Object.fromEntries()
Object.entries()
方法的作用是返回一個給定對象自身可枚舉屬性的鍵值對數組,其排列與使用 for…in 循環遍歷該對象時返回的順序一致(區別在於 for-in 循環也枚舉原型鏈中的屬性)。
而Object.fromEntries()
則是 Object.entries()
的反轉。
let obj = { apple : 10, orange : 20, banana : 30 };
/* 獲取鍵值對 */
let entries = Object.entries(obj);
console.log(entries);
/* 通過鍵值對反轉回obj */
let fromEntries = Object.fromEntries(entries);
console.log(fromEntries);
結果:
4. 新的基本數據類型BigInt
參考blog: https://segmentfault.com/a/1190000019912017?utm_source=tag-newest
5. 標準化 globalThis
對象
這在ES10之前, globalThis
還沒有標準化。
在產品代碼中,你可以自己編寫這個怪物,在多個平臺上“標準化”它:
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
但即使這樣也不總是奏效。因此,ES10 添加了 globalThis 對象,從現在開始,該對象用於在任何平臺上訪問全局作用域:
// 訪問全局數組構造函數
var arr = globalThis.Array(0, 1, 2);
console.log(arr);
//=> [0, 1, 2]
// 類似於 ES5 之前的 window.v = { flag: true }
globalThis.v = { flag: true };
console.log(window.v);
//=> { flag: true }
6. 修改 catch
綁定
在過去,try/catch 語句中的 catch 語句需要一個變量。 try/catch 語句幫助捕獲終端級別的錯誤:
try {
//...
}
catch(error) {
//...
console.log( error );
}
在某些情況下,所需的錯誤變量是未使用的。
在 ES10 中,捕獲錯誤的變量是可選的
現在可以跳過錯誤變量:
try {
//...
return true;
}
catch{
//...
return false;
}
7. 動態導入
現在可以將導入分配給變量:
element.addEventListener('click', async() => {
/* 導入給一個常量 */
const module = await import(`./api-scripts/button-click.js`);
module.clickEvent();
})