前端點滴(ES6+)(二)

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.padStartString.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();
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章