【複習資料】ES6/ES7/ES8/ES9資料整理(我的整理)

1、介紹

如今的網絡上已經有各樣關於 ECMAScript 規範介紹和分析的文章,而我本身從新學習一遍這些規範,整理出這麼一份筆記,比較精簡,主要內容涵蓋ES6ES7ES8ES9,後續會增長面試題框架入門等筆記,歡迎吐槽交流。
這份資料的ES6部分將會參考阮一峯老師的 ECMAScript6入門 ,精簡和整理出快速實用的內容。
另外ES7/ES8/ES9則會從網絡綜合參考和整理。javascript

ES全稱ECMAScript:
目前JavaScript使用的ECMAScript版本爲ECMAScript-262html

ECMAScript版本 發佈時間 新增特性
ECMAScript 2009(ES5) 2009年11月 擴展了Object、Array、Function的功能等
ECMAScript 2015(ES6) 2015年6月 類,模塊化,箭頭函數,函數參數默認值等
ECMAScript 2016(ES7) 2016年3月 includes,指數操做符
ECMAScript 2017(ES8) 2017年6月 async/await,Object.values(),Object.entries(),St123 ; b['myfun'] => 'hi'ring padding等

本文博客 CuteECMAScript
本文開源地址 CuteECMAScript
我的博客 ping'anの博客java

2、正文

1. ES6

1.1 let 和 const命令

在ES6中,咱們一般實用 let 表示變量const 表示常量,而且 letconst 都是塊級做用域,且在當前做用域有效不能重複聲明。node

1.1.1 let 命令

let 命令的用法和 var 類似,可是 let 只在所在代碼塊內有效。
基礎用法git

{
    let a = 1;
    let b = 2;
}
複製代碼

而且 let 有如下特色:es6

  • 不存在變量提高:
    在ES6以前,咱們 var 聲明一個變量一個函數,都會伴隨着變量提高的問題,致使實際開發過程常常出現一些邏輯上的疑惑,按照通常思惟習慣,變量都是須要先聲明後使用。
// var 
console.log(v1); // undefined
var v1 = 2;
// 因爲變量提高 代碼實際以下
var v1;
console.log(v1)
v1 = 2;

// let 
console.log(v2); // ReferenceError
let v2 = 2;
複製代碼
  • 不容許重複聲明:
    letconst相同做用域下,都不能重複聲明同一變量,而且不能在函數內從新聲明參數
// 1. 不能重複聲明同一變量
// 報錯
function f1 (){
    let a = 1;
    var a = 2;
}
// 報錯
function f2 (){
    let a = 1;
    let a = 2;
}

// 2. 不能在函數內從新聲明參數
// 報錯
function f3 (a1){
    let a1; 
}
// 不報錯
function f4 (a2){
    {
        let a2
    }
}
複製代碼

1.1.2 const 命令

const 聲明一個只讀常量
基礎用法github

const PI = 3.1415926;
console.log(PI);  // 3.1415926

複製代碼

注意點面試

  • const 聲明後,沒法修改值;
const PI = 3.1415926;
PI = 3; 
// TypeError: Assignment to constant variable.
複製代碼
  • const 聲明時,必須賦值;
const a ; 
// SyntaxError: Missing initializer in const declaration.
複製代碼
  • const 聲明的常量,let 不能重複聲明;
const PI = 3.1415926;
let PI = 0;  
// Uncaught SyntaxError: Identifier 'PI' has already been declared
複製代碼

1.2 變量的解構賦值

解構賦值概念:在ES6中,直接從數組和對象中取值,按照對應位置,賦值給變量的操做。ajax

1.2.1 數組

基礎用法正則表達式

// ES6 以前
let a = 1;
let b = 2;

// ES6 以後
let [a, b] = [1, 2];
複製代碼

本質上,只要等號兩邊模式一致,左邊變量便可獲取右邊對應位置的值,更多用法:

let [a, [[b], c]] = [1, [[2], 3]];
console.log(a, b, c); // 1, 2, 3

let [ , , c] = [1, 2, 3];
console.log(c);       // 3

let [a, , c] = [1, 2, 3];
console.log(a,c);     // 1, 3

let [a, ...b] = [1, 2, 3];
console.log(a,b);     // 1, [2,3]

let [a, b, ..c.] = [1];
console.log(a, b, c); // 1, undefined, []
複製代碼

注意點

  • 若是解構不成功,變量的值就等於undefined
let [a] = [];     // a => undefined
let [a, b] = [1]; // a => 1 , b => undefined
複製代碼
  • 當左邊模式多於右邊,也能夠解構成功。
let [a, b] = [1, 2, 3];
console.log(a, b); // 1, 2
複製代碼
  • 兩邊模式不一樣,報錯。
let [a] = 1;
let [a] = false;
let [a] = NaN;
let [a] = undefined;
let [a] = null;
let [a] = {};
複製代碼

指定解構的默認值
基礎用法

let [a = 1] = [];      // a => 1
let [a, b = 2] = [a];  // a => 1 , b => 2
複製代碼

特殊狀況:

let [a = 1] = [undefined]; // a => 1
let [a = 1] = [null];      // a => null
複製代碼

右邊模式對應的值,必須嚴格等於undefined,默認值才能生效,而null不嚴格等於undefined

1.2.2 對象的解構賦值

與數組解構不一樣的是,對象解構不須要嚴格按照順序取值,而只要按照變量名去取對應屬性名的值,若取不到對應屬性名的值,則爲undefined

基礎用法

let {a, b} = {a:1, b:2};  // a => 1 , b => 2
let {a, b} = {a:2, b:1};  // a => 2 , b => 1
let {a} = {a:3, b:2, c:1};// a => 3
let {a} = {b:2, c:1};     // a => undefined
複製代碼

注意點

  • 變量名屬性名不一致,則須要修更名稱。
let {a:b} = {a:1, c:2}; 
// error: a is not defined
// b => 1
複製代碼

對象的解構賦值的內部機制,是先找到同名屬性,而後再賦給對應的變量。真正被賦值的是後者,而不是前者。
上面代碼中,a 是匹配的模式,b纔是變量。真正被賦值的是變量b,而不是模式a

  • 對象解構也支持嵌套解構
let obj = {
    a:[ 1, { b: 2}]
};
let {a, a: [c, {b}]} = obj;
// a=>[1, {b: 2}], b => 2, c => 1
複製代碼

指定解構的默認值

let {a=1} = {};        // a => 1
let {a, b=1} = {a:2};  // a => 2, b => 1

let {a:b=3} = {};      // b => 3
let {a:b=3} = {a:4};   // b = >4
// a是模式,b是變量 牢記

let {a=1} = {a:undefined};  // a => 1
let {a=1} = {a:null};   // a => null
// 由於null與undefined不嚴格相等,因此賦值有效
// 致使默認值1不會生效。
複製代碼

1.2.3 字符串的解構賦值

字符串的解構賦值中,字符串被轉換成了一個相似數組的對象基礎用法

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

let {length:len} = 'hello';// len => 5
複製代碼

1.2.4 數值和布爾值的解構賦值

解構賦值的規則是,只要等號右邊的值不是對象或數組,就先將其轉爲對象。因爲undefinednull沒法轉爲對象,因此對它們進行解構賦值,都會報錯。

// 數值和布爾值的包裝對象都有toString屬性
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true

let { prop: x } = undefined; // TypeError
let { prop: y } = null;      // TypeError
複製代碼

1.2.5 函數參數的解構賦值

基礎用法

function fun ([a, b]){
    return a + b;
}
fun ([1, 2]); // 3
複製代碼

指定默認值的解構:

function fun ({a=0, b=0} = {}){
    return [a, b];
}
fun ({a:1, b:2}); // [1, 2]
fun ({a:1});      // [1, 0]
fun ({});         // [0, 0]
fun ();           // [0, 0]

function fun ({a, b} = {a:0, b:0}){
    return [a, b];
}
fun ({a:1, b:2}); // [1, 2]
fun ({a:1});      // [1, undefined]
fun ({});         // [undefined, undefined]
fun ();           // [0, 0]
複製代碼

1.2.6 應用

  • 交換變量的值:
let a = 1,b = 2;
[a, b] = [b, a]; // a =>2 , b => 1 
複製代碼
  • 函數返回多個值:
// 返回一個數組
function f (){
    return [1, 2, 3];
}
let [a, b, c] = f(); // a=>1, b=>2, c=>3

// 返回一個對象
function f (){
    return {a:1, b:2};
}
let {a, b} = f();    // a=>1, b=>2
複製代碼
  • 快速對應參數: 快速的將一組參數與變量名對應。
function f([a, b, c]) {...}
f([1, 2, 3]);

function f({a, b, c}) {...}
f({b:2, c:3, a:1});
複製代碼
  • 提取JSON數據
let json = {
    name : 'leo',
    age: 18
}
let {name, age} = json;
console.log(name,age); // leo, 18
複製代碼
  • 遍歷Map結構:
const m = new Map();
m.set('a',1);
m.set('b',2);
for (let [k, v] of m){
    console.log(k + ' : ' + v);
}
// 獲取鍵名
for (let [k] of m){...}
// 獲取鍵值
for (let [,k] of m){...}
複製代碼
  • 輸入模塊的指定方法: 用於按需加載模塊中須要用到的方法。
const {log, sin, cos} = require('math');
複製代碼

1.3 字符串的拓展

1.3.1 includes(),startsWith(),endsWith()

在咱們判斷字符串是否包含另外一個字符串時,ES6以前,咱們只有typeof方法,ES6以後咱們又多了三種方法:

  • includes():返回布爾值,表示是否找到參數字符串
  • startsWith():返回布爾值,表示參數字符串是否在原字符串的頭部
  • endsWith():返回布爾值,表示參數字符串是否在原字符串的尾部
let a = 'hello leo';
a.startsWith('leo');   // false
a.endsWith('o');       // true
a.includes('lo');      // true
複製代碼

而且這三個方法都支持第二個參數,表示起始搜索的位置。

let a = 'hello leo';
a.startsWith('leo',1);   // false
a.endsWith('o',5);       // true
a.includes('lo',6);      // false
複製代碼

endsWith 是針對前 n 個字符,而其餘兩個是針對從第n個位置直到結束。

1.3.2 repeat()

repeat方法返回一個新字符串,表示將原字符串重複n次。
基礎用法

'ab'.repeat(3);        // 'ababab'
'ab'.repeat(0);        // ''
複製代碼

特殊用法:

  • 參數爲小數,則取整
'ab'.repeat(2.3);      // 'abab'
複製代碼
  • 參數爲負數Infinity,則報錯
'ab'.repeat(-1);       // RangeError
'ab'.repeat(Infinity); // RangeError
複製代碼
  • 參數爲0到-1的小數NaN,則取0
'ab'.repeat(-0.5);     // ''
'ab'.repeat(NaN);      // ''
複製代碼
  • 參數爲字符串,則轉成數字
'ab'.repeat('ab');     // ''
'ab'.repeat('3');      // 'ababab'
複製代碼

1.3.3 padStart(),padEnd()

用於將字符串頭部尾部補全長度,padStart()頭部補全padEnd()尾部補全
這兩個方法接收2個參數,第一個指定字符串最小長度,第二個用於補全的字符串
基礎用法

'x'.padStart(5, 'ab');   // 'ababx'
'x'.padEnd(5, 'ab');     // 'xabab'
複製代碼

特殊用法:

  • 原字符串長度,大於或等於指定最小長度,則返回原字符串。
'xyzabc'.padStart(5, 'ab'); // 'xyzabc'
複製代碼
  • 用來補全的字符串長度和原字符串長度之和,超過指定最小長度,則截去超出部分的補全字符串。
'ab'.padStart(5,'012345'); // "012ab"
複製代碼
  • 省略第二個參數,則用空格補全。
'x'.padStart(4);           // ' x'
'x'.padEnd(4);           // 'x '
複製代碼

1.3.4 模版字符串

用於拼接字符串,ES6以前:

let a = 'abc' + 
    'def' + 
    'ghi';
複製代碼

ES6以後:

let a = ` abc def ghi `
複製代碼

拼接變量: 在**反引號(`)**中使用${}包裹變量或方法。

// ES6以前
let a = 'abc' + v1 + 'def';

// ES6以後
let a = `abc${v1}def`
複製代碼

1.4 正則的拓展

1.4.1 介紹

在ES5中有兩種狀況。

  • 參數是字符串,則第二個參數爲正則表達式的修飾符。
let a = new RegExp('abc', 'i');
// 等價於
let a = /abx/i;
複製代碼
  • 參數是正則表達式,返回一個原表達式的拷貝,且不能有第二個參數,不然報錯。
let a = new RegExp(/abc/i);
//等價於
let a = /abx/i;

let a = new RegExp(/abc/, 'i');
// Uncaught TypeError
複製代碼

ES6中使用:
第一個參數是正則對象,第二個是指定修飾符,若是第一個參數已經有修飾符,則會被第二個參數覆蓋。

new RegExp(/abc/ig, 'i');
複製代碼

1.4.2 字符串的正則方法

經常使用的四種方法:match()replace()search()split()

1.4.3 u修飾符

添加u修飾符,是爲了處理大於uFFFF的Unicode字符,即正確處理四個字節的UTF-16編碼。

/^\uD83D/u.test('\uD83D\uDC2A'); // false
/^\uD83D/.test('\uD83D\uDC2A');  // true
複製代碼

因爲ES5以前不支持四個字節UTF-16編碼,會識別爲兩個字符,致使第二行輸出true,加入u修飾符後ES6就會識別爲一個字符,因此輸出false

注意:
加上u修飾符後,會改變下面正則表達式的行爲:

  • (1)點字符 點字符(.)在正則中表示除了換行符之外的任意單個字符。對於碼點大於0xFFFF的Unicode字符,點字符不能識別,必須加上u修飾符。
var a = "𠮷";
/^.$/.test(a);  // false
/^.$/u.test(a); // true
複製代碼
  • (2)Unicode字符表示法 使用ES6新增的大括號表示Unicode字符時,必須在表達式添加u修飾符,才能識別大括號。
/\u{61}/.test('a');      // false
/\u{61}/u.test('a');     // true
/\u{20BB7}/u.test('𠮷'); // true
複製代碼
  • (3)量詞 使用u修飾符後,全部量詞都會正確識別碼點大於0xFFFF的 Unicode 字符。
/a{2}/.test('aa');    // true
/a{2}/u.test('aa');   // true
/𠮷{2}/.test('𠮷𠮷');  // false
/𠮷{2}/u.test('𠮷𠮷'); // true
複製代碼
  • (4)i修飾符 不加u修飾符,就沒法識別非規範的K字符。
/[a-z]/i.test('\u212A') // false
/[a-z]/iu.test('\u212A') // true
複製代碼

檢查是否設置u修飾符: 使用unicode屬性。

const a = /hello/;
const b = /hello/u;

a.unicode // false
b.unicode // true
複製代碼

1.4.4 y修飾符

y修飾符與g修飾符相似,也是全局匹配,後一次匹配都是從上一次匹配成功的下一個位置開始。區別在於,g修飾符只要剩餘位置中存在匹配便可,而y修飾符是必須從剩餘第一個開始。

var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"] 剩餘 '_aa_a'
r2.exec(s) // null
複製代碼

lastIndex屬性: 指定匹配的開始位置:

const a = /a/y;
a.lastIndex = 2;  // 從2號位置開始匹配
a.exec('wahaha'); // null
a.lastIndex = 3;  // 從3號位置開始匹配
let c = a.exec('wahaha');
c.index;          // 3
a.lastIndex;      // 4
複製代碼

返回多個匹配
一個y修飾符對match方法只能返回第一個匹配,與g修飾符搭配能返回全部匹配。

'a1a2a3'.match(/a\d/y);  // ["a1"]
'a1a2a3'.match(/a\d/gy); // ["a1", "a2", "a3"]
複製代碼

檢查是否使用y修飾符
使用sticky屬性檢查。

const a = /hello\d/y;
a.sticky;     // true
複製代碼

1.4.5 flags屬性

flags屬性返回全部正則表達式的修飾符。

/abc/ig.flags;  // 'gi'
複製代碼

1.5 數值的拓展

1.5.1 Number.isFinite(), Number.isNaN()

Number.isFinite() 用於檢查一個數值是不是有限的,即不是Infinity,若參數不是Number類型,則一概返回false

Number.isFinite(10);            // true
Number.isFinite(0.5);           // true
Number.isFinite(NaN);           // false
Number.isFinite(Infinity);      // false
Number.isFinite(-Infinity);     // false
Number.isFinite('leo');         // false
Number.isFinite('15');          // false
Number.isFinite(true);          // false
Number.isFinite(Math.random()); // true
複製代碼

Number.isNaN()用於檢查是不是NaN,若參數不是NaN,則一概返回false

Number.isNaN(NaN);      // true
Number.isNaN(10);       // false
Number.isNaN('10');     // false
Number.isNaN(true);     // false
Number.isNaN(5/NaN);    // true
Number.isNaN('true' / 0);      // true
Number.isNaN('true' / 'true'); // true
複製代碼

區別
與傳統全局的isFinite()isNaN()方法的區別,傳統的這兩個方法,是先將參數轉換成數值,再判斷。
而ES6新增的這兩個方法則只對數值有效, Number.isFinite()對於非數值一概返回false,Number.isNaN()只有對於NaN才返回true,其餘一概返回false

isFinite(25);          // true
isFinite("25");        // true
Number.isFinite(25);   // true
Number.isFinite("25"); // false

isNaN(NaN);            // true
isNaN("NaN");          // true
Number.isNaN(NaN);     // true
Number.isNaN("NaN");   // false
複製代碼

1.5.2 Number.parseInt(), Number.parseFloat()

這兩個方法與全局方法parseInt()parseFloat()一致,目的是逐步減小全局性的方法,讓語言更模塊化

parseInt('12.34');     // 12
parseFloat('123.45#'); // 123.45

Number.parseInt('12.34');     // 12
Number.parseFloat('123.45#'); // 123.45

Number.parseInt === parseInt;     // true
Number.parseFloat === parseFloat; // true
複製代碼

1.5.3 Number.isInteger()

用來判斷一個數值是不是整數,若參數不是數值,則返回false

Number.isInteger(10);   // true
Number.isInteger(10.0); // true
Number.isInteger(10.1); // false
複製代碼

1.5.4 Math對象的拓展

ES6新增17個數學相關的靜態方法,只能在Math對象上調用。

  • Math.trunc:
    用來去除小數的小數部分,返回整數部分
    若參數爲非數值,則先轉爲數值
    若參數爲空值沒法截取整數的值,則返回NaN
// 正常使用
Math.trunc(1.1);     // 1
Math.trunc(1.9);     // 1
Math.trunc(-1.1);    // -1
Math.trunc(-1.9);    // -1
Math.trunc(-0.1234); // -0

// 參數爲非數值
Math.trunc('11.22'); // 11
Math.trunc(true);    // 1
Math.trunc(false);   // 0
Math.trunc(null);    // 0

// 參數爲空和沒法取整
Math.trunc(NaN);       // NaN
Math.trunc('leo');     // NaN
Math.trunc();          // NaN
Math.trunc(undefined); // NaN
複製代碼

ES5實現

Math.trunc = Math.trunc || function(x){
    return x < 0 ? Math.ceil(x) : Math.floor(x);
}
複製代碼
  • Math.sign():
    判斷一個數是正數負數是零,對於非數值,會先轉成數值
    返回值:
    • 參數爲正數, 返回 +1
    • 參數爲負數, 返回 -1
    • 參數爲0, 返回 0
    • 參數爲-0, 返回 -0
    • 參數爲其餘值, 返回 NaN
Math.sign(-1);   // -1
Math.sign(1);    // +1
Math.sign(0);    // 0
Math.sign(-0);   // -0
Math.sign(NaN);  // NaN

Math.sign('');   // 0
Math.sign(true); // +1
Math.sign(false);// 0
Math.sign(null); // 0
Math.sign('9');  // +1
Math.sign('leo');// NaN
Math.sign();     // NaN
Math.sign(undefined); // NaN
複製代碼

ES5實現

Math.sign = Math.sign || function (x){
    x = +x;
    if (x === 0 || isNaN(x)){
        return x;
    }
    return x > 0 ? 1: -1;
}
複製代碼
  • Math.cbrt():
    用來計算一個數的立方根,若參數爲非數值則先轉成數值。
Math.cbrt(-1); // -1
Math.cbrt(0);  // 0
Math.cbrt(1);  // 1
Math.cbrt(2);  // 1.2599210498

Math.cbrt('1');   // 1
Math.cbrt('leo'); // NaN
複製代碼

ES5實現

Math.cbrt = Math.cbrt || function (x){
    var a = Math.pow(Math.abs(x), 1/3);
    return x < 0 ? -y : y;
}
複製代碼
  • Math.clz32():
    用於返回一個數的 32 位無符號整數形式有多少個前導 0。
Math.clz32(0) // 32
Math.clz32(1) // 31
Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
Math.clz32(0b00100000000000000000000000000000) // 2
複製代碼
  • Math.imul():
    用於返回兩個數以 32 位帶符號整數形式相乘的結果,返回的也是一個 32 位的帶符號整數。
Math.imul(2, 4)   // 8
Math.imul(-1, 8)  // -8
Math.imul(-2, -2) // 4
複製代碼
  • Math.fround():
    用來返回一個數的2位單精度浮點數形式。
Math.fround(0)   // 0
Math.fround(1)   // 1
Math.fround(2 ** 24 - 1)   // 16777215
複製代碼
  • Math.hypot():
    用來返回全部參數的平方和的平方根
Math.hypot(3, 4);        // 5
Math.hypot(3, 4, 5);     // 7.0710678118654755
Math.hypot();            // 0
Math.hypot(NaN);         // NaN
Math.hypot(3, 4, 'foo'); // NaN
Math.hypot(3, 4, '5');   // 7.0710678118654755
Math.hypot(-3);          // 3
複製代碼
  • Math.expm1():
    用來返回ex - 1,即Math.exp(x) - 1
Math.expm1(-1) // -0.6321205588285577
Math.expm1(0)  // 0
Math.expm1(1)  // 1.718281828459045
複製代碼

ES5實現

Math.expm1 = Math.expm1 || function(x) {
  return Math.exp(x) - 1;
};
複製代碼
  • Math.log1p():
    用來返回1 + x的天然對數,即Math.log(1 + x)。若是x小於-1,返回NaN
Math.log1p(1)  // 0.6931471805599453
Math.log1p(0)  // 0
Math.log1p(-1) // -Infinity
Math.log1p(-2) // NaN
複製代碼

ES5實現

Math.log1p = Math.log1p || function(x) {
  return Math.log(1 + x);
};
複製代碼
  • Math.log10():
    用來返回以 10爲底的x的對數。若是x小於 0,則返回 NaN
Math.log10(2)      // 0.3010299956639812
Math.log10(1)      // 0
Math.log10(0)      // -Infinity
Math.log10(-2)     // NaN
Math.log10(100000) // 5
複製代碼

ES5實現

Math.log10 = Math.log10 || function(x) {
  return Math.log(x) / Math.LN10;
};
複製代碼
  • Math.log2():
    用來返回以 2 爲底的x的對數。若是x小於0,則返回 NaN
Math.log2(3)       // 1.584962500721156
Math.log2(2)       // 1
Math.log2(1)       // 0
Math.log2(0)       // -Infinity
Math.log2(-2)      // NaN
Math.log2(1024)    // 10
Math.log2(1 << 29) // 29
複製代碼

ES5實現

Math.log2 = Math.log2 || function(x) {
  return Math.log(x) / Math.LN2;
};
複製代碼
  • 雙曲函數方法:
    • Math.sinh(x) 返回x的雙曲正弦(hyperbolic sine)
    • Math.cosh(x) 返回x的雙曲餘弦(hyperbolic cosine)
    • Math.tanh(x) 返回x的雙曲正切(hyperbolic tangent)
    • Math.asinh(x) 返回x的反雙曲正弦(inverse hyperbolic sine)
    • Math.acosh(x) 返回x的反雙曲餘弦(inverse hyperbolic cosine)
    • Math.atanh(x) 返回x的反雙曲正切(inverse hyperbolic tangent)

1.5.5 指數運算符

新增的指數運算符(**):

2 ** 2; // 4
2 ** 3; // 8 

2 ** 3 ** 2; // 至關於 2 ** (3 ** 2); 返回 512
複製代碼

指數運算符(**)與Math.pow的實現不相同,對於特別大的運算結果,二者會有細微的差別。

Math.pow(99, 99)
// 3.697296376497263e+197

99 ** 99
// 3.697296376497268e+197
複製代碼

1.6 函數的拓展

1.6.1 參數默認值

// ES6 以前
function f(a, b){
    b = b || 'leo';
    console.log(a, b);
}

// ES6 以後
function f(a, b='leo'){
    console.log(a, b);
}

f('hi');          // hi leo
f('hi', 'jack');  // hi jack
f('hi', '');      // hi leo
複製代碼

注意:

  • 參數變量是默認聲明的,不能用letconst再次聲明:
function f (a = 1){
    let a = 2; // error
}
複製代碼
  • 使用參數默認值時,參數名不能相同:
function f (a, a, b){ ... };     // 不報錯
function f (a, a, b = 1){ ... }; // 報錯
複製代碼

與解構賦值默認值結合使用

function f ({a, b=1}){
    console.log(a,b)
};
f({});         // undefined 1
f({a:2});      // 2 1
f({a:2, b:3}); // 2 3
f();           // 報錯

function f ({a, b = 1} = {}){
    console.log(a, b)
}
f();  // undefined 1
複製代碼

尾參數定義默認值:
一般在尾參數定義默認值,便於觀察參數,而且非尾參數沒法省略。

function f (a=1,b){
    return [a, b];
}
f();    // [1, undefined]
f(2);   // [2, undefined]
f(,2);  // 報錯

f(undefined, 2);  // [1, 2]

function f (a, b=1, c){
    return [a, b, c];
}
f();        // [undefined, 1, undefined]
f(1);       // [1,1,undefined]
f(1, ,2);   // 報錯
f(1,undefined,2); // [1,1,2]
複製代碼

在給參數傳遞默認值時,傳入undefined會觸發默認值,傳入null不會觸發。

function f (a = 1, b = 2){
    console.log(a, b);
}
f(undefined, null); // 1 null
複製代碼

函數的length屬性:
length屬性將返回,沒有指定默認值的參數數量,而且rest參數不計入length屬性。

function f1 (a){...};
function f2 (a=1){...};
function f3 (a, b=2){...};
function f4 (...a){...};
function f5 (a,b,...c){...};

f1.length; // 1
f2.length; // 0
f3.length; // 1
f4.length; // 0
f5.length; // 2
複製代碼

1.6.2 rest 參數

rest參數形式爲(...變量名),其值爲一個數組,用於獲取函數多餘參數。

function f (a, ...b){
    console.log(a, b);
}
f(1,2,3,4); // 1 [2, 3, 4]
複製代碼

注意

  • rest參數只能放在最後一個,不然報錯:
function f(a, ...b, c){...}; // 報錯 
複製代碼
  • 函數的length屬性不包含rest參數。
function f1 (a){...};
function f2 (a,...b){...};
f1(1);   // 1
f2(1,2); // 1
複製代碼

1.6.3 name 屬性

用於返回該函數的函數名。

function f (){...};
f.name;    // f

const f = function g(){...};
f.name;    // g
複製代碼

1.6.4 箭頭函數

使用「箭頭」(=>)定義函數。
基礎使用

// 有1個參數
let f = v => v;
// 等同於
let f = function (v){return v};

// 有多個參數
let f = (v, i) => {return v + i};
// 等同於
let f = function (v, i){return v + i};

// 沒參數
let f = () => 1;
// 等同於
let f = function (){return 1};
複製代碼

箭頭函數與變量結構結合使用

// 正常函數寫法
function f (p) {
    return p.a + ':' + p.b;
}

// 箭頭函數寫法
let f = ({a, b}) => a + ':' + b;
複製代碼

簡化回調函數

// 正常函數寫法
[1, 2, 3].map(function (x){
    return x * x;
})


// 箭頭函數寫法
[1, 2, 3].map(x => x * x);
複製代碼

箭頭函數與rest參數結合

let f = (...n) => n;
f(1, 2, 3); // [1, 2, 3]
複製代碼

注意點

  • 1.箭頭函數內的this老是指向定義時所在的對象,而不是調用時。
  • 2.箭頭函數不能當作構造函數,即不能用new命令,不然報錯。
  • 3.箭頭函數不存在arguments對象,即不能使用,可使用rest參數代替。
  • 4.箭頭函數不能使用yield命令,即不能用做Generator函數。

不適用場景

  • 1.在定義函數方法,且該方法內部包含this
const obj = {
    a:9,
    b: () => {
        this.a --;
    }
}
複製代碼

上述b若是是普通函數,函數內部的this指向obj,可是若是是箭頭函數,則this會指向全局,不是預期結果。

  • 2.須要動態this時。
let b = document.getElementById('myID');
b.addEventListener('click', ()=>{
    this.classList.toggle('on');
})
複製代碼

上訴按鈕點擊會報錯,由於b監聽的箭頭函數中,this是全局對象,若改爲普通函數this就會指向被點擊的按鈕對象。

1.6.5 雙冒號運算符

雙冒號暫時是一個提案,用於解決一些不適用的場合,取代callapplybind調用。
雙冒號運算符(::)的左邊是一個對象,右邊是一個函數。該運算符會自動將左邊的對象,做爲上下文環境(即this對象),綁定到右邊函數上。

f::b;
// 等同於
b.bind(f);

f::b(...arguments);
// 等同於
b.apply(f, arguments);
複製代碼

若雙冒號左邊爲空,右邊是一個對象的方法,則等於將該方法綁定到該對象上。

let f = a::a.b;
// 等同於
let f = ::a.b;
複製代碼

1.7 數組的拓展

1.7.1 拓展運算符

拓展運算符使用(...),相似rest參數的逆運算,將數組轉爲用(,)分隔的參數序列。

console.log(...[1, 2, 3]);   // 1 2 3 
console.log(1, ...[2,3], 4); // 1 2 3 4
複製代碼

拓展運算符主要使用在函數調用。

function f (a, b){
    console.log(a, b);
}
f(...[1, 2]); // 1 2

function g (a, b, c, d, e){
    console.log(a, b, c, d, e);
}
g(0, ...[1, 2], 3, ...[4]); // 0 1 2 3 4
複製代碼

若拓展運算符後面是個空數組,則不產生效果

[...[], 1]; // [1]
複製代碼

替代apply方法

// ES6以前
function f(a, b, c){...};
var a = [1, 2, 3];
f.apply(null, a);

// ES6以後
function f(a, b, c){...};
let a = [1, 2, 3];
f(...a);

// ES6以前
Math.max.apply(null, [3,2,6]);

// ES6以後
Math.max(...[3,2,6]);
複製代碼

拓展運算符的運用

  • (1)複製數組
    一般咱們直接複製數組時,只是淺拷貝,若是要實現深拷貝,可使用拓展運算符。
// 一般狀況 淺拷貝
let a1 = [1, 2];
let a2 = a1; 
a2[0] = 3;
console.log(a1,a2); // [3,2] [3,2]

// 拓展運算符 深拷貝
let a1 = [1, 2];
let a2 = [...a1];
// let [...a2] = a1; // 做用相同
a2[0] = 3;
console.log(a1,a2); // [1,2] [3,2]
複製代碼
  • (2)合併數組
    注意,這裏合併數組,只是淺拷貝。
let a1 = [1,2];
let a2 = [3];
let a3 = [4,5];

// ES5 
let a4 = a1.concat(a2, a3);

// ES6
let a5 = [...a1, ...a2, ...a3];

a4[0] === a1[0]; // true
a5[0] === a1[0]; // true
複製代碼
  • (3)與解構賦值結合
    與解構賦值結合生成數組,可是使用拓展運算符須要放到參數最後一個,不然報錯。
let [a, ...b] = [1, 2, 3, 4]; 
// a => 1 b => [2,3,4]

let [a, ...b] = [];
// a => undefined b => []

let [a, ...b] = ["abc"];
// a => "abc" b => []
複製代碼

1.7.2 Array.from()

類數組對象可遍歷的對象,轉換成真正的數組。

// 類數組對象
let a = {
    '0':'a',
    '1':'b',
    length:2
}
let arr = Array.from(a);

// 可遍歷的對象
let a = Array.from([1,2,3]);
let b = Array.from({length: 3});
let c = Array.from([1,2,3]).map(x => x * x);
let d = Array.from([1,2,3].map(x => x * x));
複製代碼

1.7.3 Array.of()

將一組數值,轉換成數組,彌補Array方法參數不一樣致使的差別。

Array.of(1,2,3);    // [1,2,3]
Array.of(1).length; // 1

Array();       // []
Array(2);      // [,] 1個參數時,爲指定數組長度
Array(1,2,3);  // [1,2,3] 多於2個參數,組成新數組
複製代碼

1.7.4 find()和findIndex()

find()方法用於找出第一個符合條件的數組成員,參數爲一個回調函數,全部成員依次執行該回調函數,返回第一個返回值爲true的成員,若是沒有一個符合則返回undefined

[1,2,3,4,5].find( a => a < 3 ); // 1
複製代碼

回調函數接收三個參數,當前值、當前位置和原數組。

[1,2,3,4,5].find((value, index, arr) => {
    // ...
});
複製代碼

findIndex()方法與find()相似,返回第一個符合條件的數組成員的位置,若是都不符合則返回-1

[1,2,3,4].findIndex((v,i,a)=>{
    return v>2;
}); // 2
複製代碼

1.7.5 fill()

用於用指定值填充一個數組,一般用來初始化空數組,並抹去數組中已有的元素。

new Array(3).fill('a');   // ['a','a','a']
[1,2,3].fill('a');        // ['a','a','a']
複製代碼

而且fill()的第二個和第三個參數指定填充的起始位置結束位置

[1,2,3].fill('a',1,2); //  [1, "a", 3]
複製代碼

1.7.6 entries(),keys(),values()

主要用於遍歷數組,entries()對鍵值對遍歷,keys()對鍵名遍歷,values()對鍵值遍歷。

for (let i of ['a', 'b'].keys()){
    console.log(i)
}
// 0
// 1

for (let e of ['a', 'b'].values()){
    console.log(e)
}
// 'a'
// 'b'

for (let e of ['a', 'b'].entries()){
    console.log(e)
}
// [0, "a"] 
// [1, "b"]
複製代碼

1.7.7 includes()

用於表示數組是否包含給定的值,與字符串的includes方法相似。

[1,2,3].includes(2);     // true
[1,2,3].includes(4);     // false
[1,2,NaN].includes(NaN); // true
複製代碼

第二個參數爲起始位置,默認爲0,若是負數,則表示倒數的位置,若是大於數組長度,則重置爲0開始。

[1,2,3].includes(3,3);    // false
[1,2,3].includes(3,4);    // false
[1,2,3].includes(3,-1);   // true
[1,2,3].includes(3,-4);   // true
複製代碼

1.7.8 flat(),flatMap()

flat()用於將數組一維化,返回一個新數組,不影響原數組。
默認一次只一維化一層數組,若需多層,則傳入一個整數參數指定層數。
若要一維化全部層的數組,則傳入Infinity做爲參數。

[1, 2, [2,3]].flat();        // [1,2,2,3]
[1,2,[3,[4,[5,6]]]].flat(3); // [1,2,3,4,5,6]
[1,2,[3,[4,[5,6]]]].flat('Infinity'); // [1,2,3,4,5,6]
複製代碼

flatMap()是將原數組每一個對象先執行一個函數,在對返回值組成的數組執行flat()方法,返回一個新數組,不改變原數組。
flatMap()只能展開一層。

[2, 3, 4].flatMap((x) => [x, x * 2]); 
// [2, 4, 3, 6, 4, 8] 
複製代碼

1.8 對象的拓展

1.8.1 屬性的簡潔表示

let a = 'a1';
let b = { a };  // b => { a : 'a1' }
// 等同於
let b = { a : a };

function f(a, b){
    return {a, b}; 
}
// 等同於
function f (a, b){
    return {a:a ,b:b};
}

let a = {
    fun () {
        return 'leo';
    }
}
// 等同於
let a = {
    fun : function(){
        return 'leo';
    }
}
複製代碼

1.8.2 屬性名錶達式

JavaScript提供2種方法定義對象的屬性

// 方法1 標識符做爲屬性名
a.f = true;

// 方法2 字符串做爲屬性名
a['f' + 'un'] = true;
複製代碼

延伸出來的還有:

let a = 'hi leo';
let b = {
    [a]: true,
    ['a'+'bc']: 123,
    ['my' + 'fun'] (){
        return 'hi';
    }
};
// b.a => undefined ; b.abc => 123 ; b.myfun() => 'hi'
// b[a] => true ; b['abc'] => 123 ; b['myfun'] => ƒ ['my' + 'fun'] (){ return 'hi'; }
複製代碼

注意
屬性名錶達式不能與簡潔表示法同時使用,不然報錯。

// 報錯
let a1 = 'aa';
let a2 = 'bb';
let b1 = {[a1]};

// 正確
let a1 = 'aa';
let b1 = { [a1] : 'bb'};
複製代碼

1.8.3 Object.is()

Object.is() 用於比較兩個值是否嚴格相等,在ES5時候只要使用相等運算符(==)和嚴格相等運算符(===)就能夠作比較,可是它們都有缺點,前者會自動轉換數據類型,後者的NaN不等於自身,以及+0等於-0

Object.is('a','a');   // true
Object.is({}, {});    // false

// ES5
+0 === -0 ;           // true
NaN === NaN;          // false

// ES6
Object.is(+0,-0);     // false
Object.is(NaN,NaN);   // true
複製代碼

1.8.4 Object.assign()

Object.assign()方法用於對象的合併,將原對象的全部可枚舉屬性複製到目標對象。
基礎用法
第一個參數是目標對象,後面參數都是源對象

let a = {a:1};
let b = {b:2};
Object.assign(a,b);  // a=> {a:1,b:2}
複製代碼

注意

  • 若目標對象與源對象有同名屬性,則後面屬性會覆蓋前面屬性。
let a = {a:1, b:2};
let b = {b:3, c:4};
Object.assign(a, b); // a => {a:1, b:3, c:4}
複製代碼
  • 若只有一個參數,則返回該參數。
let a = {a:1};
Object.assign(a) === a;  // true
複製代碼
  • 若參數不是對象,則先轉成對象後返回。
typeof Object.assign(2); // 'object'
複製代碼
  • 因爲undefinedNaN沒法轉成對象,因此作爲參數會報錯。
Object.assign(undefined) // 報錯
Object.assign(NaN);      // 報錯
複製代碼
  • Object.assign()實現的是淺拷貝。

Object.assign()拷貝獲得的是這個對象的引用。這個對象的任何變化,都會反映到目標對象上面。

let a = {a: {b:1}};
let b = Object.assign({},a);
a.a.b = 2;
console.log(b.a.b);  // 2
複製代碼
  • 將數組當作對象處理,鍵名爲數組下標,鍵值爲數組下標對應的值。
Object.assign([1, 2, 3], [4, 5]); // [4, 5, 3]
複製代碼

1.9 Symbol

1.9.1 介紹

ES6引入Symbol做爲一種新的原始數據類型,表示獨一無二的值,主要是爲了防止屬性名衝突
ES6以後,JavaScript一共有其中數據類型:SymbolundefinednullBooleanStringNumberObject
簡單實用:

let a = Symbol();
typeof a; // "symbol"
複製代碼

注意:

  • Symbol函數不能用new,會報錯。因爲Symbol是一個原始類型,不是對象,因此不能添加屬性,它是相似於字符串的數據類型。
  • Symbol都是不相等的,即便參數相同。
// 沒有參數
let a1 = Symbol();
let a2 = Symbol();
a1 === a2; // false 

// 有參數
let a1 = Symbol('abc');
let a2 = Symbol('abc');
a1 === a2; // false 
複製代碼
  • Symbol不能與其餘類型的值計算,會報錯。
let a = Symbol('hello');
a + " world!";  // 報錯
`${a} world!`;  // 報錯
複製代碼

Symbol能夠顯式轉換爲字符串:

let a1 = Symbol('hello');

String(a1);    // "Symbol(hello)"
a1.toString(); // "Symbol(hello)"
複製代碼

Symbol能夠轉換爲布爾值,但不能轉爲數值:

let a1 = Symbol();
Boolean(a1);
!a1;        // false

Number(a1); // TypeError
a1 + 1 ;    // TypeError
複製代碼

1.9.2 Symbol做爲屬性名

好處:防止同名屬性,還有防止鍵被改寫或覆蓋。

let a1 = Symbol();

// 寫法1
let b = {};
b[a1] = 'hello';

// 寫法2
let b = {
    [a1] : 'hello'
} 

// 寫法3
let b = {};
Object.defineProperty(b, a1, {value : 'hello' });

// 3種寫法 結果相同
b[a1]; // 'hello'
複製代碼

須要注意: Symbol做爲對象屬性名時,不能用點運算符,而且必須放在方括號內。

let a = Symbol();
let b = {};

// 不能用點運算
b.a = 'hello';
b[a] ; // undefined
b['a'] ; // 'hello'

// 必須放在方括號內
let c = {
    [a] : function (text){
        console.log(text);
    }
}
c[a]('leo'); // 'leo'

// 上面等價於 更簡潔
let c = {
    [a](text){
        console.log(text);
    }
}
複製代碼

經常還用於建立一組常量,保證全部值不相等:

let a = {};
a.a1 = {
    AAA: Symbol('aaa'),
    BBB: Symbol('bbb'),
    CCC: Symbol('ccc')
}
複製代碼

1.9.3 應用:消除魔術字符串

魔術字符串:指代碼中屢次出現,強耦合的字符串或數值,應該避免,而使用含義清晰的變量代替。

function f(a){
    if(a == 'leo') {
        console.log('hello');
    }
}
f('leo');   // 'leo' 爲魔術字符串
複製代碼

常使用變量,消除魔術字符串:

let obj = {
    name: 'leo'
};
function f (a){
    if(a == obj.name){
        console.log('hello');
    }
}
f(obj.name); // 'leo'
複製代碼

使用Symbol消除強耦合,使得不需關係具體的值:

let obj = {
    name: Symbol()
};
function f (a){
    if(a == obj.name){
        console.log('hello');
    }
}
f(obj.name);
複製代碼

1.9.4 屬性名遍歷

Symbol做爲屬性名遍歷,不出如今for...infor...of循環,也不被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

let a = Symbol('aa'),b= Symbol('bb');
let obj = {
    [a]:'11', [b]:'22'
}
for(let k of Object.values(obj)){console.log(k)}
// 無輸出

let obj = {};
let aa = Symbol('leo');
Object.defineProperty(obj, aa, {value: 'hi'});

for(let k in obj){
    console.log(k); // 無輸出
}

Object.getOwnPropertyNames(obj);   // []
Object.getOwnPropertySymbols(obj); // [Symbol(leo)]
複製代碼

Object.getOwnPropertySymbols方法返回一個數組,包含當前對象全部用作屬性名的Symbol值。

let a = {};
let a1 = Symbol('a');
let a2 = Symbol('b');
a[a1] = 'hi';
a[a2] = 'oi';

let obj = Object.getOwnPropertySymbols(a);
obj; //  [Symbol(a), Symbol(b)]
複製代碼

另外可使用Reflect.ownKeys方法能夠返回全部類型的鍵名,包括常規鍵名和 Symbol 鍵名。

let a = {
    [Symbol('leo')]: 1,
    aa : 2, 
    bb : 3,
}
Reflect.ownKeys(a); // ['aa', 'bb',Symbol('leo')]
複製代碼

因爲Symbol值做爲名稱的屬性不被常規方法遍歷獲取,所以經常使用於定義對象的一些非私有,且內部使用的方法。

1.9.5 Symbol.for()、Symbol.keyFor()

  • Symbol.for()
    用於重複使用一個Symbol值,接收一個字符串做爲參數,若存在用此參數做爲名稱的Symbol值,返回這個Symbol,不然新建並返回以這個參數爲名稱的Symbol值。
let a = Symbol.for('aaa');
let b = Symbol.for('aaa');

a === b;  // true
複製代碼

Symbol()Symbol.for()區別:

Symbol.for('aa') === Symbol.for('aa'); // true
Symbol('aa') === Symbol('aa');         // false
複製代碼
  • Symbol.keyFor()
    用於返回一個已使用的Symbol類型的key:
let a = Symbol.for('aa');
Symbol.keyFor(a);   // 'aa'

let b = Symbol('aa');
Symbol.keyFor(b);   // undefined
複製代碼

1.9.6 內置的Symbol值

ES6提供11個內置的Symbol值,指向語言內部使用的方法:

  • 1.Symbol.hasInstance
    當其餘對象使用instanceof運算符,判斷是否爲該對象的實例時,會調用這個方法。好比,foo instanceof Foo在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)
class P {
    [Symbol.hasInstance](a){
        return a instanceof Array;
    }
}
[1, 2, 3] instanceof new P(); // true
複製代碼

P是一個類,new P()會返回一個實例,該實例的Symbol.hasInstance方法,會在進行instanceof運算時自動調用,判斷左側的運算子是否爲Array的實例。

  • 2.Symbol.isConcatSpreadable
    值爲布爾值,表示該對象用於Array.prototype.concat()時,是否能夠展開。
let a = ['aa','bb'];
['cc','dd'].concat(a, 'ee'); 
// ['cc', 'dd', 'aa', 'bb', 'ee']
a[Symbol.isConcatSpreadable]; // undefined

let b = ['aa','bb']; 
b[Symbol.isConcatSpreadable] = false; 
['cc','dd'].concat(b, 'ee'); 
// ['cc', 'dd',[ 'aa', 'bb'], 'ee']
複製代碼
  • 3.Symbol.species
    指向一個構造函數,在建立衍生對象時會使用,使用時須要用get取值器。
class P extends Array {
    static get [Symbol.species](){
        return this;
    }
}
複製代碼

解決下面問題:

// 問題: b應該是 Array 的實例,其實是 P 的實例
class P extends Array{}

let a = new P(1,2,3);
let b = a.map(x => x);

b instanceof Array; // true
b instanceof P; // true

// 解決: 經過使用 Symbol.species
class P extends Array {
  static get [Symbol.species]() { return Array; }
}
let a = new P();
let b = a.map(x => x);
b instanceof P;     // false
b instanceof Array; // true
複製代碼
  • 4.Symbol.match
    當執行str.match(myObject),傳入的屬性存在時會調用,並返回該方法的返回值。
class P {
    [Symbol.match](string){
        return 'hello world'.indexOf(string);
    }
}
'h'.match(new P());   // 0
複製代碼
  • 5.Symbol.replace 當該對象被String.prototype.replace方法調用時,會返回該方法的返回值。
let a = {};
a[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(a , 'World') // ["Hello", "World"]
複製代碼
  • 6.Symbol.hasInstance
    當該對象被String.prototype.search方法調用時,會返回該方法的返回值。
class P {
    constructor(val) {
        this.val = val;
    }
    [Symbol.search](s){
        return s.indexOf(this.val);
    }
}
'hileo'.search(new P('leo')); // 2
複製代碼
  • 7.Symbol.split
    當該對象被String.prototype.split方法調用時,會返回該方法的返回值。
// 從新定義了字符串對象的split方法的行爲
class P {
    constructor(val) {
        this.val = val;
    }
    [Symbol.split](s) {
        let i = s.indexOf(this.val);
        if(i == -1) return s;
        return [
            s.substr(0, i),
            s.substr(i + this.val.length)
        ]
    }
}

'helloworld'.split(new P('hello')); // ["hello", ""]
'helloworld'.split(new P('world')); // ["", "world"] 
'helloworld'.split(new P('leo'));   // "helloworld"
複製代碼
  • 8.Symbol.iterator
    對象進行for...of循環時,會調用Symbol.iterator方法,返回該對象的默認遍歷器。
class P {
    *[Symbol.interator]() {
        let i = 0;
        while(this[i] !== undefined ) {
            yield this[i];
            ++i;
        }
    }
}
let a = new P();
a[0] = 1;
a[1] = 2;

for (let k of a){
    console.log(k);
}
複製代碼
  • 9.Symbol.toPrimitive
    該對象被轉爲原始類型的值時,會調用這個方法,返回該對象對應的原始類型值。調用時,須要接收一個字符串參數,表示當前運算模式,運算模式有:
    • Number : 此時須要轉換成數值
    • String : 此時須要轉換成字符串
    • Default : 此時能夠轉換成數值或字符串
let obj = {
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return 123;
      case 'string':
        return 'str';
      case 'default':
        return 'default';
      default:
        throw new Error();
     }
   }
};

2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'
複製代碼
  • 10.Symbol.toStringTag
    在該對象上面調用Object.prototype.toString方法時,若是這個屬性存在,它的返回值會出如今toString方法返回的字符串之中,表示對象的類型。也就是說,這個屬性能夠用來定製[object Object]或[object Array]object後面的那個字符串。
// 例一
({[Symbol.toStringTag]: 'Foo'}.toString())
// "[object Foo]"

// 例二
class Collection {
  get [Symbol.toStringTag]() {
    return 'xxx';
  }
}
let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"
複製代碼
  • 11.Symbol.unscopables
    該對象指定了使用with關鍵字時,哪些屬性會被with環境排除。
// 沒有 unscopables 時
class MyClass {
  foo() { return 1; }
}

var foo = function () { return 2; };

with (MyClass.prototype) {
  foo(); // 1
}

// 有 unscopables 時
class MyClass {
  foo() { return 1; }
  get [Symbol.unscopables]() {
    return { foo: true };
  }
}

var foo = function () { return 2; };

with (MyClass.prototype) {
  foo(); // 2
}
複製代碼

上面代碼經過指定Symbol.unscopables屬性,使得with語法塊不會在當前做用域尋找foo屬性,即foo將指向外層做用域的變量。

1.10 Set和Map數據結構

1.10.1 Set

介紹:
Set數據結構相似數組,但全部成員的值惟一
Set自己爲一個構造函數,用來生成Set數據結構,使用add方法來添加新成員。

let a = new Set();
[1,2,2,1,3,4,5,4,5].forEach(x=>a.add(x));
for(let k of a){
    console.log(k)
};
// 1 2 3 4 5
複製代碼

基礎使用

let a = new Set([1,2,3,3,4]);
[...a]; // [1,2,3,4]
a.size; // 4

// 數組去重
[...new Set([1,2,3,4,4,4])];// [1,2,3,4]
複製代碼

注意

  • Set中添加值的時候,不會類型轉換,即5'5'是不一樣的。
[...new Set([5,'5'])]; // [5, "5"]
複製代碼

屬性和方法

  • 屬性:

    • Set.prototype.constructor:構造函數,默認就是Set函數。
    • Set.prototype.size:返回Set實例的成員總數。
  • 操做方法:

    • add(value):添加某個值,返回 Set 結構自己。
    • delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
    • has(value):返回一個布爾值,表示該值是否爲Set的成員。
    • clear():清除全部成員,沒有返回值。
let a = new Set();
a.add(1).add(2); // a => Set(2) {1, 2}
a.has(2);        // true
a.has(3);        // false
a.delete(2);     // true a => Set(1) {1}
a.clear();       // a => Set(0) {}
複製代碼

數組去重

let a = new Set([1,2,3,3,3,3]);
複製代碼

1.10.2 Set的應用

數組去重

// 方法1
[...new Set([1,2,3,4,4,4])]; // [1,2,3,4]
// 方法2
Array.from(new Set([1,2,3,4,4,4]));    // [1,2,3,4]
複製代碼

遍歷和過濾

let a = new Set([1,2,3,4]);

// map 遍歷操做
let b = new Set([...a].map(x =>x*2));// b => Set(4) {2,4,6,8}

// filter 過濾操做
let c = new Set([...a].filter(x =>(x%2) == 0)); // b => Set(2) {2,4}
複製代碼

獲取並集、交集和差集

let a = new Set([1,2,3]);
let b = new Set([4,3,2]);

// 並集
let c1 = new Set([...a, ...b]);  // Set {1,2,3,4}

// 交集
let c2 = new Set([...a].filter(x => b.has(x))); // set {2,3}

// 差集
let c3 = new Set([...a].filter(x => !b.has(x))); // set {1}
複製代碼
  • 遍歷方法:
    • keys():返回鍵名的遍歷器。
    • values():返回鍵值的遍歷器。
    • entries():返回鍵值對的遍歷器。
    • forEach():使用回調函數遍歷每一個成員

Set遍歷順序是插入順序,當保存多個回調函數,只需按照順序調用。但因爲Set結構沒有鍵名只有鍵值,因此keys()values()是返回結果相同。

let a = new Set(['a','b','c']);
for(let i of a.keys()){console.log(i)};   // 'a' 'b' 'c'
for(let i of a.values()){console.log(i)}; // 'a' 'b' 'c'
for(let i of a.entries()){console.log(i)}; 
// ['a','a'] ['b','b'] ['c','c']
複製代碼

而且 還可使用for...of直接遍歷Set

let a = new Set(['a','b','c']);
for(let k of a){console.log(k)};   // 'a' 'b' 'c'
複製代碼

forEach與數組相同,對每一個成員執行操做,且無返回值。

let a = new Set(['a','b','c']);
a.forEach((v,k) => console.log(k + ' : ' + v));
複製代碼

1.10.3 Map

因爲傳統的JavaScript對象只能用字符串當作鍵,給開發帶來很大限制,ES6增長Map數據結構,使得各類類型的值(包括對象)均可以做爲鍵。
Map結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。 基礎使用

let a = new Map();
let b = {name: 'leo' };
a.set(b,'my name'); // 添加值
a.get(b);           // 獲取值
a.size;      // 獲取總數
a.has(b);    // 查詢是否存在
a.delete(b); // 刪除一個值
a.clear();   // 清空全部成員 無返回
複製代碼

注意

  • 傳入數組做爲參數,指定鍵值對的數組
let a = new Map([
    ['name','leo'],
    ['age',18]
])
複製代碼
  • 若是對同一個鍵屢次賦值,後面的值將覆蓋前面的值
let a = new Map();
a.set(1,'aaa').set(1,'bbb');
a.get(1); // 'bbb'
複製代碼
  • 若是讀取一個未知的鍵,則返回undefined
new Map().get('abcdef'); // undefined
複製代碼
  • 一樣的值的兩個實例,在 Map 結構中被視爲兩個鍵。
let a = new Map();
let a1 = ['aaa'];
let a2 = ['aaa'];
a.set(a1,111).set(a2,222);
a.get(a1); // 111
a.get(a2); // 222
複製代碼

遍歷方法: Map 的遍歷順序就是插入順序。

  • keys():返回鍵名的遍歷器。
  • values():返回鍵值的遍歷器。
  • entries():返回全部成員的遍歷器。
  • forEach():遍歷 Map 的全部成員。
let a = new Map([
    ['name','leo'],
    ['age',18]
])

for (let i of a.keys()){...};
for (let i of a.values()){...};
for (let i of a.entries()){...};
a.forEach((v,k,m)=>{
    console.log(`key:${k},value:${v},map:${m}`)
})
複製代碼

將Map結構轉成數組結構

let a = new Map([
    ['name','leo'],
    ['age',18]
])

let a1 = [...a.keys()];   // a1 => ["name", "age"]
let a2 = [...a.values()]; // a2 =>  ["leo", 18]
let a3 = [...a.entries()];// a3 => [['name','leo'], ['age',18]]
複製代碼

1.10.4 Map與其餘數據結構互相轉換

  • Map 轉 數組
let a = new Map().set(true,1).set({f:2},['abc']);
[...a]; // [[true:1], [ {f:2},['abc'] ]]
複製代碼
  • 數組 轉 Map
let a = [ ['name','leo'], [1, 'hi' ]]
let b = new Map(a);
複製代碼
  • Map 轉 對象 若是全部 Map 的鍵都是字符串,它能夠無損地轉爲對象。
    若是有非字符串的鍵名,那麼這個鍵名會被轉成字符串,再做爲對象的鍵名。
function fun(s) {
  let obj = Object.create(null);
  for (let [k,v] of s) {
    obj[k] = v;
  }
  return obj;
}

const a = new Map().set('yes', true).set('no', false);
fun(a)
// { yes: true, no: false }
複製代碼
  • 對象 轉 Map
function fun(obj) {
  let a = new Map();
  for (let k of Object.keys(obj)) {
    a.set(k, obj[k]);
  }
  return a;
}

fun({yes: true, no: false})
// Map {"yes" => true, "no" => false}
複製代碼
  • Map 轉 JSON
    (1)Map鍵名都是字符串,轉爲對象JSON:
function fun (s) {
    let obj = Object.create(null);
    for (let [k,v] of s) {
        obj[k] = v;
    }
    return JSON.stringify(obj)
}
let a = new Map().set('yes', true).set('no', false);
fun(a);
// '{"yes":true,"no":false}'
複製代碼

(2)Map鍵名有非字符串,轉爲數組JSON:

function fun (map) {
  return JSON.stringify([...map]);
}

let a = new Map().set(true, 7).set({foo: 3}, ['abc']);
fun(a)
// '[[true,7],[{"foo":3},["abc"]]]'
複製代碼
  • JSON 轉 Map
    (1)全部鍵名都是字符串:
function fun (s) {
  let strMap = new Map();
  for (let k of Object.keys(s)) {
    strMap.set(k, s[k]);
  }
  return strMap;
  return JSON.parse(strMap);
}
fun('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
複製代碼

(2)整個 JSON 就是一個數組,且每一個數組成員自己,又是一個有兩個成員的數組:

function fun2(s) {
  return new Map(JSON.parse(s));
}
fun2('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
複製代碼

1.11 Proxy

proxy 用於修改某些操做的默認行爲,能夠理解爲一種攔截外界對目標對象訪問的一種機制,從而對外界的訪問進行過濾和修改,即代理某些操做,也稱「代理器」。

1.11.1 基礎使用

proxy實例化須要傳入兩個參數,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。

let p = new Proxy(target, handler);

let a = new Proxy({}, {
    get: function (target, handler){
        return 'leo';
    }
})
a.name; // leo
a.age;  // leo
a.abcd; // leo
複製代碼

上述a實例中,在第二個參數中定義了get方法,來攔截外界訪問,而且get方法接收兩個參數,分別是目標對象所要訪問的屬性,因此無論外部訪問對象中任何屬性都會執行get方法返回leo
注意

  • 只能使用Proxy實例的對象才能使用這些操做。
  • 若是handler沒有設置攔截,則直接返回原對象。
let target = {};
let handler = {};
let p = new Proxy(target, handler);
p.a = 'leo'; 
target.a;  // 'leo'
複製代碼

同個攔截器函數,設置多個攔截操做

let p = new Proxy(function(a, b){
    return a + b;
},{
    get:function(){
        return 'get方法';
    },
    apply:function(){
        return 'apply方法';
    }
})
複製代碼

Proxy支持的13種攔截操做
13種攔截操做的詳細介紹:打開阮一峯老師的連接

  • get(target, propKey, receiver): 攔截對象屬性的讀取,好比proxy.foo和proxy['foo']。

  • set(target, propKey, value, receiver): 攔截對象屬性的設置,好比proxy.foo = v或proxy['foo'] = v,返回一個布爾值。

  • has(target, propKey): 攔截propKey in proxy的操做,返回一個布爾值。

  • deleteProperty(target, propKey): 攔截delete proxy[propKey]的操做,返回一個布爾值。

  • ownKeys(target): 攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環,返回一個數組。該方法返回目標對象全部自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。

  • getOwnPropertyDescriptor(target, propKey): 攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。

  • defineProperty(target, propKey, propDesc): 攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。

  • preventExtensions(target): 攔截Object.preventExtensions(proxy),返回一個布爾值。

  • getPrototypeOf(target): 攔截Object.getPrototypeOf(proxy),返回一個對象。

  • isExtensible(target): 攔截Object.isExtensible(proxy),返回一個布爾值。

  • setPrototypeOf(target, proto): 攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。若是目標對象是函數,那麼還有兩種額外操做能夠攔截。

  • apply(target, object, args): 攔截 Proxy 實例做爲函數調用的操做,好比proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。

  • construct(target, args): 攔截 Proxy 實例做爲構造函數調用的操做,好比new proxy(...args)。

1.11.2 取消Proxy實例

使用Proxy.revocale方法取消Proxy實例。

let a = {};
let b = {};
let {proxy, revoke} = Proxy.revocale(a, b);

proxy.name = 'leo';  // 'leo'
revoeke();
proxy.name;  // TypeError: Revoked
複製代碼

1.11.3 實現 Web服務的客戶端

const service = createWebService('http://le.com/data');
service.employees().than(json =>{
    const employees = JSON.parse(json);
})

function createWebService(url){
    return new Proxy({}, {
        get(target, propKey, receiver{
            return () => httpGet(url+'/'+propKey);
        })
    })
}
複製代碼

1.12 Promise對象

1.12.1 概念

主要用途:解決異步編程帶來的回調地獄問題
Promise簡單理解一個容器,存放着某個將來纔會結束的事件(一般是一個異步操做)的結果。經過Promise對象來獲取異步操做消息,處理各類異步操做。

Promise對象2特色

  • 對象的狀態不受外界影響

Promise對象表明一個異步操做,有三種狀態:pending(進行中)fulfilled(已成功)rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是「承諾」,表示其餘手段沒法改變。

  • 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果

Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。若是改變已經發生了,你再對Promise對象添加回調函數,也會當即獲得這個結果。這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的。

注意,爲了行文方便,本章後面的resolved統一隻指fulfilled狀態,不包含rejected狀態。

Promise缺點

  • 沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。
  • 若是不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
  • 當處於pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。

1.12.2 基本使用

Promise爲一個構造函數,須要用new來實例化。

let p = new Promise(function (resolve, reject){
   if(/*異步操做成功*/){
       resolve(value);
   } else {
       reject(error);
   }
})
複製代碼

Promise接收一個函數做爲參數,該函數兩個參數resolvereject,有JS引擎提供。

  • resolve做用是將Promise的狀態從pending變成resolved,在異步操做成功時調用,返回異步操做的結果,做爲參數傳遞出去。
  • reject做用是將Promise的狀態從pending變成rejected,在異步操做失敗時報錯,做爲參數傳遞出去。

Promise實例生成之後,能夠用then方法分別指定resolved狀態和rejected狀態的回調函數。

p.then(function(val){
    // success...
},function(err){
    // error...
})
複製代碼

幾個例子來理解

  • 當一段時間事後,Promise狀態便成爲resolved觸發then方法綁定的回調函數。
function timeout (s){
    return new Promise((resolve, reject){
        setTimeout(result,ms, 'done');
    })
}
timeout(100).then(val => {
    console.log(val);
})
複製代碼
  • Promise新建後馬上執行。
let p = new Promise(function(resolve, reject){
    console.log(1);
    resolve();
})
p.then(()=>{
    console.log(2);
})
console.log(3);
// 1
// 3
// 2 
複製代碼

異步加載圖片

function f(url){
    return new Promise(function(resolve, reject){
        const img = new Image ();
        img.onload = function(){
            resolve(img);
        }
        img.onerror = function(){
            reject(new Error(
                'Could not load image at ' + url
            ));
        }
        img.src = url;
    })
}
複製代碼

resolve函數和reject函數的參數爲resolve函數或reject函數
p1的狀態決定了p2的狀態,因此p2要等待p1的結果再執行回調函數。

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail
複製代碼

調用resolvereject不會結束Promise參數函數的執行,除了return:

new Promise((resolve, reject){
    resolve(1);
    console.log(2);
}).then(r => {
    console.log(3);
})
// 2
// 1

new Promise((resolve, reject){
    return resolve(1);
    console.log(2);
})
// 1
複製代碼

1.12.3 Promise.prototype.then()

做用是爲Promise添加狀態改變時的回調函數,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。
then方法返回一個新Promise實例,與原來Promise實例不一樣,所以可使用鏈式寫法,上一個then的結果做爲下一個then的參數。

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});
複製代碼

1.12.4 Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 處理 getJSON 和 前一個回調函數運行時發生的錯誤
  console.log('發生錯誤!', error);
});
複製代碼

若是 Promise 狀態已經變成resolved,再拋出錯誤是無效的。

const p = new Promise(function(resolve, reject) {
  resolve('ok');
  throw new Error('test');
});
p
  .then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
// ok
複製代碼

promise拋出一個錯誤,就被catch方法指定的回調函數捕獲,下面三種寫法相同。

// 寫法一
const p = new Promise(function(resolve, reject) {
  throw new Error('test');
});
p.catch(function(error) {
  console.log(error);
});
// Error: test

// 寫法二
const p = new Promise(function(resolve, reject) {
  try {
    throw new Error('test');
  } catch(e) {
    reject(e);
  }
});
p.catch(function(error) {
  console.log(error);
});

// 寫法三
const p = new Promise(function(resolve, reject) {
  reject(new Error('test'));
});
p.catch(function(error) {
  console.log(error);
});
複製代碼

通常來講,不要在then方法裏面定義Reject 狀態的回調函數(即then的第二個參數),老是使用catch方法。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });
複製代碼

1.12.5 Promise.prototype.finally()

finally方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
複製代碼

finally不接收任何參數,與狀態無關,本質上是then方法的特例。

promise
.finally(() => {
  // 語句
});

// 等同於
promise
.then(
  result => {
    // 語句
    return result;
  },
  error => {
    // 語句
    throw error;
  }
);
複製代碼

上面代碼中,若是不使用finally方法,一樣的語句須要爲成功和失敗兩種狀況各寫一次。有了finally方法,則只須要寫一次。
finally方法老是會返回原來的值。

// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})

// reject 的值是 3
Promise.reject(3).finally(() => {})
複製代碼

1.12.6 Promise.all()

用於將多個 Promise 實例,包裝成一個新的 Promise 實例,參數能夠不是數組,但必須是Iterator接口,且返回的每一個成員都是Promise實例。

const p = Promise.all([p1, p2, p3]);
複製代碼

p的狀態由p1p2p3決定,分紅兩種狀況。

  1. 只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
  2. 只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
// 生成一個Promise對象的數組
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});
複製代碼

上面代碼中,promises是包含 6 個 Promise 實例的數組,只有這 6 個實例的狀態都變成fulfilled,或者其中有一個變爲rejected,纔會調用Promise.all方法後面的回調函數。

注意:若是Promise的參數中定義了catch方法,則rejected後不會觸發Promise.all()catch方法,由於參數中的catch方法執行完後也會變成resolved,當Promise.all()方法參數的實例都是resolved時就會調用Promise.all()then方法。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('報錯了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 報錯了]
複製代碼

若是參數裏面都沒有catch方法,就會調用Promise.all()的catch方法。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result);

const p2 = new Promise((resolve, reject) => {
  throw new Error('報錯了');
})
.then(result => result);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 報錯了
複製代碼

1.12.7 Promise.race()

Promise.all方法相似,也是將多個Promise實例包裝成一個新的Promise實例。

const p = Promise.race([p1, p2, p3]);
複製代碼

Promise.all方法區別在於,Promise.race方法是p1, p2, p3中只要一個參數先改變狀態,就會把這個參數的返回值傳給p的回調函數。

1.12.8 Promise.resolve()

將現有對象轉換成 Promise 對象。

const p = Promise.resolve($.ajax('/whatever.json'));
複製代碼

1.12.9 Promise.reject()

返回一個rejected狀態的Promise實例。

const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))

p.then(null, function (s) {
  console.log(s)
});
// 出錯了
複製代碼

注意,Promise.reject()方法的參數,會原封不動地做爲reject的理由,變成後續方法的參數。這一點與Promise.resolve方法不一致。

const thenable = {
  then(resolve, reject) {
    reject('出錯了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true
複製代碼

1.13 Iterator和 for...of循環

1.13.1 Iterator遍歷器概念

Iterator是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。

Iterator三個做用

  • 爲各類數據結構,提供一個統一的、簡便的訪問接口;
  • 使得數據結構的成員可以按某種次序排列;
  • Iterator 接口主要供ES6新增的for...of消費;

1.13.2 Iterator遍歷過程

  1. 建立一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。
  2. 第一次調用指針對象的next方法,能夠將指針指向數據結構的第一個成員。
  3. 第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。
  4. 不斷調用指針對象的next方法,直到它指向數據結構的結束位置。

每一次調用next方法,都會返回數據結構的當前成員的信息。具體來講,就是返回一個包含valuedone兩個屬性的對象。

  • value屬性是當前成員的值;
  • done屬性是一個布爾值,表示遍歷是否結束;

模擬next方法返回值:

let f = function (arr){
    var nextIndex = 0;
    return {
        next:function(){
            return nextIndex < arr.length ?
            {value: arr[nextIndex++], done: false}:
            {value: undefined, done: true}
        }
    }
}

let a = f(['a', 'b']);
a.next(); // { value: "a", done: false }
a.next(); // { value: "b", done: false }
a.next(); // { value: undefined, done: true }
複製代碼

1.13.3 默認Iterator接口

若數據可遍歷,即一種數據部署了Iterator接口。
ES6中默認的Iterator接口部署在數據結構的Symbol.iterator屬性,即若是一個數據結構具備Symbol.iterator屬性,就能夠認爲是可遍歷
Symbol.iterator屬性自己是函數,是當前數據結構默認的遍歷器生成函數。執行這個函數,就會返回一個遍歷器。至於屬性名Symbol.iterator,它是一個表達式,返回Symbol對象的iterator屬性,這是一個預約義好的、類型爲 Symbol 的特殊值,因此要放在方括號內(參見《Symbol》一章)。

原生具備Iterator接口的數據結構有

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數的 arguments 對象
  • NodeList 對象

1.13.4 Iterator使用場景

  • (1)解構賦值
    對數組和 Set 結構進行解構賦值時,會默認調用Symbol.iterator方法。
let a = new Set().add('a').add('b').add('c');
let [x, y] = a;       // x = 'a' y = 'b'
let [a1, ...a2] = a;  // a1 = 'a' a2 = ['b','c']
複製代碼
  • (2)擴展運算符
    擴展運算符(...)也會調用默認的 Iterator 接口。
let a = 'hello';
[...a];            // ['h','e','l','l','o']

let a = ['b', 'c'];
['a', ...a, 'd'];  // ['a', 'b', 'c', 'd']
複製代碼
  • (2)yield*
    yield*後面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。
let a = function*(){
    yield 1;
    yield* [2,3,4];
    yield 5;
}

let b = a();
b.next() // { value: 1, done: false }
b.next() // { value: 2, done: false }
b.next() // { value: 3, done: false }
b.next() // { value: 4, done: false }
b.next() // { value: 5, done: false }
b.next() // { value: undefined, done: true }
複製代碼
  • (4)其餘場合
    因爲數組的遍歷會調用遍歷器接口,因此任何接受數組做爲參數的場合,其實都調用了遍歷器接口。下面是一些例子。

  • for...of

  • Array.from()

  • Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]])

  • Promise.all()

  • Promise.race()

1.13.5 for...of循環

只要數據結構部署了Symbol.iterator屬性,即具備 iterator 接口,能夠用for...of循環遍歷它的成員。也就是說,for...of循環內部調用的是數據結構的Symbol.iterato方法。
使用場景
for...of可使用在數組SetMap結構類數組對象Genetator對象字符串

  • 數組
    for...of循環能夠代替數組實例的forEach方法。
let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)}; // a b c

a.forEach((ele, index)=>{
    console.log(ele);    // a b c
    console.log(index);  // 0 1 2 
})
複製代碼

for...in對比,for...in只能獲取對象鍵名,不能直接獲取鍵值,而for...of容許直接獲取鍵值。

let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)};  // a b c
for (let k in a){console.log(k)};  // 0 1 2
複製代碼
  • Set和Map
    可使用數組做爲變量,如for (let [k,v] of b){...}
let a = new Set(['a', 'b', 'c']);
for (let k of a){console.log(k)}; // a b c

let b = new Map();
b.set('name','leo');
b.set('age', 18);
b.set('aaa','bbb');
for (let [k,v] of b){console.log(k + ":" + v)};
// name:leo
// age:18
// aaa:bbb
複製代碼
  • 類數組對象
// 字符串
let a = 'hello';
for (let k of a ){console.log(k)}; // h e l l o

// DOM NodeList對象
let b = document.querySelectorAll('p');
for (let k of b ){
    k.classList.add('test');
}

// arguments對象
function f(){
    for (let k of arguments){
        console.log(k);
    }
}
f('a','b'); // a b
複製代碼
  • 對象
    普通對象不能直接使用for...of會報錯,要部署Iterator才能使用。
let a = {a:'aa',b:'bb',c:'cc'};
for (let k in a){console.log(k)}; // a b c
for (let k of a){console>log(k)}; // TypeError
複製代碼

1.13.6 跳出for...of

使用break來實現。

for (let k of a){
    if(k>100)
        break;
    console.log(k);
}
複製代碼

1.14 Generator函數和應用

1.14.1 基本概念

Generator函數是一種異步編程解決方案。
原理
執行Genenrator函數會返回一個遍歷器對象,依次遍歷Generator函數內部的每個狀態。
Generator函數是一個普通函數,有如下兩個特徵:

  • function關鍵字與函數名之間有個星號;
  • 函數體內使用yield表達式,定義不一樣狀態;

經過調用next方法,將指針移向下一個狀態,直到遇到下一個yield表達式(或return語句)爲止。簡單理解,Generator函數分段執行,yield表達式是暫停執行的標記,而next恢復執行。

function * f (){
    yield 'hi';
    yield 'leo';
    return 'ending';
}
let a = f();
a.next();  // {value: 'hi', done : false}
a.next();  // {value: 'leo', done : false}
a.next();  // {value: 'ending', done : true}
a.next();  // {value: undefined, done : false}
複製代碼

1.14.2 yield表達式

yield表達式是暫停標誌,遍歷器對象的next方法的運行邏輯以下:

  1. 遇到yield就暫停執行,將這個yield後的表達式的值,做爲返回對象的value屬性值。
  2. 下次調用next往下執行,直到遇到下一個yield
  3. 直到函數結束或者return爲止,並返回return語句後面表達式的值,做爲返回對象的value屬性值。
  4. 若是該函數沒有return語句,則返回對象的valueundefined

注意:

  • yield只能用在Generator函數裏使用,其餘地方使用會報錯。
// 錯誤1
(function(){
    yiled 1;  // SyntaxError: Unexpected number
})()

// 錯誤2 forEach參數是個普通函數
let a = [1, [[2, 3], 4], [5, 6]];
let f = function * (i){
    i.forEach(function(m){
        if(typeof m !== 'number'){
            yield * f (m);
        }else{
            yield m;
        }
    })
}
for (let k of f(a)){
    console.log(k)
}
複製代碼
  • yield表達式若是用於另外一個表達式之中,必須放在圓括號內。
function * a (){
    console.log('a' + yield);     // SyntaxErro
    console.log('a' + yield 123); // SyntaxErro
    console.log('a' + (yield));     // ok
    console.log('a' + (yield 123)); // ok
}
複製代碼
  • yield表達式用作函數參數或放在表達式右邊,能夠不加括號
function * a (){
    f(yield 'a', yield 'b');    // ok
    lei i = yield;              // ok
}
複製代碼

1.14.3 next方法

yield自己沒有返回值,或者是總返回undefinednext方法可帶一個參數,做爲上一個yield表達式的返回值。

function * f (){
    for (let k = 0; true; k++){
        let a = yield k;
        if(a){k = -1};
    }
}
let g =f();
g.next();    // {value: 0, done: false}
g.next();    // {value: 1, done: false}
g.next(true);    // {value: 0, done: false}
複製代碼

這一特色,可讓Generator函數開始執行以後,能夠從外部向內部注入不一樣值,從而調整函數行爲。

function * f(x){
    let y = 2 * (yield (x+1));
    let z = yield (y/3);
    return (x + y + z);
}
let a = f(5);
a.next();   // {value : 6 ,done : false}
a.next();   // {value : NaN ,done : false} 
a.next();   // {value : NaN ,done : true}
// NaN由於yeild返回的是對象 和數字計算會NaN

let b = f(5);
b.next();     // {value : 6 ,done : false}
b.next(12);   // {value : 8 ,done : false}
b.next(13);   // {value : 42 ,done : false}
// x 5 y 24 z 13
複製代碼

1.14.4 for...of循環

for...of循環會自動遍歷,不用調用next方法,須要注意的是,for...of遇到next返回值的done屬性爲true就會終止,return返回的不包括在for...of循環中。

function * f(){
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    return 5;
}
for (let k of f()){
    console.log(k);
}
// 1 2 3 4 沒有 5 
複製代碼

1.14.5 Generator.prototype.throw()

throw方法用來向函數外拋出錯誤,而且在Generator函數體內捕獲。

let f = function * (){
    try { yield }
    catch (e) { console.log('內部捕獲', e) }
}

let a = f();
a.next();

try{
    a.throw('a');
    a.throw('b');
}catch(e){
    console.log('外部捕獲',e);
}
// 內部捕獲 a
// 外部捕獲 b
複製代碼

1.14.6 Generator.prototype.return()

return方法用來返回給定的值,並結束遍歷Generator函數,若是return方法沒有參數,則返回值的value屬性爲undefined

function * f(){
    yield 1;
    yield 2;
    yield 3;
}
let g = f();
g.next();          // {value : 1, done : false}
g.return('leo');   // {value : 'leo', done " true}
g.next();          // {value : undefined, done : true}
複製代碼

1.14.7 next()/throw()/return()共同點

相同點就是都是用來恢復Generator函數的執行,而且使用不一樣語句替換yield表達式。

  • next()yield表達式替換成一個值。
let f = function * (x,y){
    let r = yield x + y;
    return r;
}
let g = f(1, 2); 
g.next();   // {value : 3, done : false}
g.next(1);  // {value : 1, done : true}
// 至關於把 let r = yield x + y;
// 替換成 let r = 1;
複製代碼
  • throw()yield表達式替換成一個throw語句。
g.throw(new Error('報錯'));  // Uncaught Error:報錯
// 至關於將 let r = yield x + y
// 替換成 let r = throw(new Error('報錯'));
複製代碼
  • next()yield表達式替換成一個return語句。
g.return(2); // {value: 2, done: true}
// 至關於將 let r = yield x + y
// 替換成 let r = return 2;
複製代碼

1.14.8 yield* 表達式

用於在一個Generator中執行另外一個Generator函數,若是沒有使用yield*會沒有效果。

function * a(){
    yield 1;
    yield 2;
}
function * b(){
    yield 3;
    yield * a();
    yield 4;
}
// 等同於
function * b(){
    yield 3;
    yield 1;
    yield 2;
    yield 4;
}
for(let k of b()){console.log(k)}
// 3
// 1
// 2
// 4
複製代碼

1.14.9 應用場景

  1. 控制流管理
    解決回調地獄:
// 使用前
f1(function(v1){
    f2(function(v2){
        f3(function(v3){
            // ... more and more
        })
    })
})

// 使用Promise 
Promise.resolve(f1)
    .then(f2)
    .then(f3)
    .then(function(v4){
        // ...
    },function (err){
        // ...
    }).done();

// 使用Generator
function * f (v1){
    try{
        let v2 = yield f1(v1);
        let v3 = yield f1(v2);
        let v4 = yield f1(v3);
        // ...
    }catch(err){
        // console.log(err)
    }
}
function g (task){
    let obj = task.next(task.value);
  // 若是Generator函數未結束,就繼續調用
  if(!obj.done){
      task.value = obj.value;
      g(task);
  }
}
g( f(initValue) );
複製代碼
  1. 異步編程的使用 在真實的異步任務封裝的狀況:
let fetch = require('node-fetch');
function * f(){
    let url = 'http://www.baidu.com';
    let res = yield fetch(url);
    console.log(res.bio);
}
// 執行該函數
let g = f();
let result = g.next();
// 因爲fetch返回的是Promise對象,因此用then
result.value.then(function(data){
    return data.json();
}).then(function(data){
    g.next(data);
})
複製代碼

1.15 Class語法和繼承

1.15.1 介紹

ES6中的class能夠看做只是一個語法糖,絕大部分功能均可以用ES5實現,而且,類和模塊的內部,默認就是嚴格模式,因此不須要使用use strict指定運行模式

// ES5
function P (x,y){
    this.x = x;
    this.y = y;
}
P.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};
var a = new P(1, 2);

// ES6
class P {
    constructor(x, y){
        this.x = x;
        this.y = y;
    }
    toString(){
        return '(' + this.x + ', ' + this.y + ')';
    }
}
let a = new P(1, 2);
複製代碼

值得注意: ES6的的全部方法都是定義在prototype屬性上,調用類的實例的方法,其實就是調用原型上的方法。

class P {
    constructor(){ ... }
    toString(){ ... }
    toNumber(){ ... }
}
// 等同於
P.prototyoe = {
    constructor(){ ... },
    toString(){ ... },
    toNumber(){ ... }
}

let a = new P();
a.constructor === P.prototype.constructor; // true
複製代碼

類的屬性名可使用表達式

let name = 'leo';
class P {
    constructor (){ ... }
    [name](){ ... }
}
複製代碼

Class不存在變量提高: ES6中的類不存在變量提高,與ES5徹底不一樣:

new P ();   // ReferenceError
class P{...};
複製代碼

Class的name屬性
name屬性老是返回緊跟在class後的類名。

class P {}
P.name;  // 'P'
複製代碼

1.15.2 constructor()方法

constructor()是類的默認方法,經過new實例化時自動調用執行,一個類必須有constructor()方法,不然一個空的constructor()會默認添加。
constructor()方法默認返回實例對象(即this)。

class P { ... }
// 等同於
class P {
    constructor(){ ... }
}
複製代碼

1.15.3 類的實例對象

與ES5同樣,ES6的類必須使用new命令實例化,不然報錯。

class P { ... }
let a = P (1,2);     // 報錯
let b = new P(1, 2); // 正確
複製代碼

與 ES5 同樣,實例的屬性除非顯式定義在其自己(即定義在this對象上),不然都是定義在原型上(即定義在class上)。

class P {
    constructor(x, y){
        this.x = x;
        this.y = y;
    }
    toString(){
        return '(' + this.x + ', ' + this.y + ')';
    }
}
var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false 
point.__proto__.hasOwnProperty('toString') // true
// toString是原型對象的屬性(由於定義在Point類上)
複製代碼

1.15.4 Class表達式

與函數同樣,類也可使用表達式來定義,使用表達式來做爲類的名字,而class後跟的名字,用來指代當前類,只能再Class內部使用。

let a = class P{
    get(){
        return P.name;
    }
}

let b = new a();
b.get(); // P
P.name;  // ReferenceError: P is not defined
複製代碼

若是類的內部沒用到的話,能夠省略P,也就是能夠寫成下面的形式。

let a = class { ... }
複製代碼

1.15.5 私有方法和私有屬性

因爲ES6不提供,只能變通來實現:

  • 1.使用命名加以區別,如變量名前添加_,可是不保險,外面也能夠調用到。
class P {
    // 公有方法
    f1 (x) {
        this._x(x);
    }
    // 私有方法
    _x (x){
        return this.y = x;
    }
}
複製代碼
  • 2.將私有方法移除模塊,再在類內部調用call方法。
class P {
    f1 (x){
        f2.call(this, x);
    }
}
function f2 (x){
    return this.y = x;
}
複製代碼
  • 3.使用Symbol爲私有方法命名。
const a1 = Symbol('a1');
const a2 = Symbol('a2');
export default class P{
    // 公有方法
    f1 (x){
        this[a1](x);
    }
    // 私有方法
    [a1](x){
        return this[a2] = x;
    }
}
複製代碼

1.15.6 this指向問題

類內部方法的this默認指向類的實例,但單獨使用該方法可能報錯,由於this指向的問題。

class P{
    leoDo(thing = 'any'){
        this.print(`Leo do ${thing}`)
    }
    print(text){
        console.log(text);
    }
}
let a = new P();
let { leoDo } = a;
leoDo(); // TypeError: Cannot read property 'print' of undefined
// 問題出在 單獨使用leoDo時,this指向調用的環境,
// 可是leoDo中的this是指向P類的實例,因此報錯
複製代碼

解決方法

  • 1.在類裏面綁定this
class P {
    constructor(){
        this.name = this.name.bind(this);
    }
}
複製代碼
  • 2.使用箭頭函數
class P{
    constructor(){
        this.name = (name = 'leo' )=>{
            this.print(`my name is ${name}`)
        }
    }
}
複製代碼

1.15.7 Class的getter和setter

使用getset關鍵詞對屬性設置取值函數和存值函數,攔截屬性的存取行爲。

class P {
    constructor (){ ... }
    get f (){
        return 'getter';
    }
    set f (val) {
        console.log('setter: ' + val);
    }
}

let a = new P();
a.f = 100;   // setter : 100
a.f;          // getter
複製代碼

1.15.8 Class的generator方法

只要在方法以前加個(*)便可。

class P {
    constructor (...args){
        this.args = args;
    }
    *[Symbol.iterator](){
        for (let arg of this.args){
            yield arg;
        }
    }
}
for (let k of new P('aa', 'bb')){
    console.log(k);
}
// 'aa'
// 'bb'
複製代碼

1.15.9 Class的靜態方法

因爲類至關於實例的原型,全部類中定義的方法都會被實例繼承,若不想被繼承,只要加上static關鍵字,只能經過類來調用,即「靜態方法」。

class P (){
    static f1 (){ return 'aaa' };
}
P.f1();    // 'aa'
let a = new P();
a.f1();    // TypeError: a.f1 is not a function
複製代碼

若是靜態方法包含this關鍵字,則this指向類,而不是實例。

class P {
    static f1 (){
        this.f2();
    }
    static f2 (){
        console.log('aaa');
    }
    f2(){
        console.log('bbb');
    }
}
P.f2();  // 'aaa'
複製代碼

而且靜態方法能夠被子類繼承,或者super對象中調用。

class P{
    static f1(){ return 'leo' };
}
class Q extends P { ... };
Q.f1();  // 'leo'

class R extends P {
    static f2(){
        return super.f1() + ',too';
    }
}
R.f2();  // 'leo , too'
複製代碼

1.15.10 Class的靜態屬性和實例屬性

ES6中明確規定,Class內部只有靜態方法沒有靜態屬性,因此只能經過下面實現。

// 正確寫法
class P {}
P.a1 = 1;
P.a1;      // 1

// 無效寫法
class P {
    a1: 2,          // 無效
    static a1 : 2,  // 無效
}
P.a1;      // undefined
複製代碼

新提案來規定實例屬性和靜態屬性的新寫法

  • 1.類的實例屬性
    類的實例屬性能夠用等式,寫入類的定義中。
class P {
    prop = 100;   // prop爲P的實例屬性 可直接讀取
    constructor(){
        console.log(this.prop); // 100
    }
}
複製代碼

有了新寫法後,就能夠再也不contructor方法裏定義。
爲了可讀性的目的,對於那些在constructor裏面已經定義的實例屬性,新寫法容許直接列出

// 以前寫法:
class RouctCounter extends React.Component {
    constructor(prop){
        super(prop);
        this.state = {
            count : 0
        }
    }
}

// 新寫法
class RouctCounter extends React.Component {
    state;
    constructor(prop){
        super(prop);
        this.state = {
            count : 0
        }
    }
    
}
複製代碼
  • 2.類的靜態屬性
    只要在實例屬性前面加上static關鍵字就能夠。
class P {
    static prop = 100;
    constructor(){console.log(this.prop)}; // 100
}
複製代碼

新寫法方便靜態屬性的表達。

// old 
class P { .... }
P.a = 1;

// new 
class P {
    static a = 1;
}
複製代碼

1.15.11 Class的繼承

主要經過extends關鍵字實現,繼承父類的全部屬性和方法,經過super關鍵字來新建父類構造函數的this對象。

class P { ... }
class Q extends P { ... }

class P { 
    constructor(x, y){
        // ...
    }
    f1 (){ ... }
}
class Q extends P {
    constructor(a, b, c){
        super(x, y);  // 調用父類 constructor(x, y)
        this.color = color ;
    }
    f2 (){
        return this.color + ' ' + super.f1(); 
        // 調用父類的f1()方法
    }
}
複製代碼

子類必須在constructor()調用super()不然報錯,而且只有super方法才能調用父類實例,還有就是,父類的靜態方法,子類也能夠繼承到

class P { 
    constructor(x, y){
        this.x = x;
        this.y = y;
    }
    static fun(){
        console.log('hello leo')
    }
}
// 關鍵點1 調用super
class Q extends P {
    constructor(){ ... }
}
let a = new Q(); // ReferenceError 由於Q沒有調用super

// 關鍵點2 調用super
class R extends P {
    constructor (x, y. z){
        this.z = z; // ReferenceError 沒調用super不能使用
        super(x, y);
        this.z = z; // 正確
    }
}

// 關鍵點3 子類繼承父類靜態方法
R.hello(); // 'hello leo'
複製代碼

super關鍵字
既能夠當函數使用,還能夠當對象使用。

  • 1.當函數調用,表明父類的構造函數,但必須執行一次。
class P {... };
class R extends P {
    constructor(){
        super();
    }
}
複製代碼
  • 2.當對象調用,指向原型對象,在靜態方法中指向父類。
class P {
    f (){ return 2 };
}
class R extends P {
    constructor (){
        super();
        console.log(super.f()); // 2
    }
}
let a = new R()
複製代碼

注意super指向父類原型對象,因此定義在父類實例的方法和屬性,是沒法經過super調用的,可是經過調用super方法能夠把內部this指向當前實例,就能夠訪問到。

class P {
    constructor(){
        this.a = 1;
    }
    print(){
        console.log(this.a);
    }
}
class R extends P {
    get f (){
        return super.a;
    }
}
let b = new R();
b.a; // undefined 由於a是父類P實例的屬性

// 先調用super就能夠訪問
class Q extends P {
    constructor(){
        super();   // 將內部this指向當前實例
        return super.a;
    }
}
let c = new Q();
c.a; // 1

// 狀況3
class J extends P {
    constructor(){
        super();
        this.a = 3;
    }
    g(){
        super.print();
    }
}
let c = new J();
c.g(); // 3 因爲執行了super()後 this指向當前實例
複製代碼

1.16 Module語法和加載實現

1.16.1 介紹

ES6以前用於JavaScript的模塊加載方案,是一些社區提供的,主要有CommonJSAMD兩種,前者用於服務器,後者用於瀏覽器
ES6提供了模塊的實現,使用export命令對外暴露接口,使用import命令輸入其餘模塊暴露的接口。

// CommonJS模塊
let { stat, exists, readFire } = require('fs');

// ES6模塊
import { stat, exists, readFire } = from 'fs';
複製代碼

1.16.2 嚴格模式

ES6模塊自動採用嚴格模式,不管模塊頭部是否有"use strict"
嚴格模式有如下限制

  • 變量必須聲明後再使用
  • 函數的參數不能有同名屬性,不然報錯
  • 不能使用with語句
  • 不能對只讀屬性賦值,不然報錯
  • 不能使用前綴 0 表示八進制數,不然報錯
  • 不能刪除不可刪除的屬性,不然報錯
  • 不能刪除變量delete prop,會報錯,只能刪除屬性delete * global[prop]
  • eval不會在它的外層做用域引入變量
  • evalarguments不能被從新賦值
  • arguments不會自動反映函數參數的變化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局對象
  • 不能使用fn.callerfn.arguments獲取函數調用的堆棧
  • 增長了保留字(好比protectedstaticinterface

特別是,ES6中頂層this指向undefined,即不該該在頂層代碼使用this

1.16.3 export命令

使用export向模塊外暴露接口,能夠是方法,也能夠是變量。

// 1. 變量
export let a = 'leo';
export let b = 100;

// 還能夠
let a = 'leo';
let b = 100;
export {a, b};

// 2. 方法
export function f(a,b){
    return a*b;
}

// 還能夠
function f1 (){ ... }
function f2 (){ ... }
export {
    a1 as f1,
    a2 as f2
}
複製代碼

可使用as重命名函數的對外接口。
特別注意
export暴露的必須是接口,不能是值。

// 錯誤
export 1; // 報錯

let a = 1;
export a; // 報錯

// 正確
export let a = 1; // 正確

let a = 1;
export {a};       // 正確

let a = 1;
export { a as b}; // 正確
複製代碼

暴露方法也是同樣:

// 錯誤
function f(){...};
export f;

// 正確
export function f () {...};

function f(){...};
export {f};
複製代碼

1.16.4 import命令

加載export暴露的接口,輸出爲變量。

import { a, b } from '/a.js';
function f(){
    return a + b;
}
複製代碼

import後大括號指定變量名,須要與export的模塊暴露的名稱一致。
也可使用as爲輸入的變量重命名。

import { a as leo } from './a.js';
複製代碼

import不能直接修改輸入變量的值,由於輸入變量只讀只是個接口,可是若是是個對象,能夠修改它的屬性。

// 錯誤
import {a} from './f.js';
a = {}; // 報錯

// 正確
a.foo = 'leo';  // 不報錯
複製代碼

import命令具備提高效果,會提高到整個模塊頭部最早執行,且屢次執行相同import只會執行一次。

1.16.5 模塊的總體加載

當一個模塊暴露多個方法和變量,引用時能夠用*總體加載。

// a.js
export function f(){...}
export function g(){...}

// b.js
import * as obj from '/a.js';
console.log(obj.f());
console.log(obj.g());
複製代碼

可是,不容許運行時改變:

import * as obj from '/a.js';
// 不容許
obj.a = 'leo';   
obj.b = function(){...}; 
複製代碼

1.16.6 export default 命令

使用export default命令,爲模塊指定默認輸出,引用的時候直接指定任意名稱便可。

// a.js
export default function(){console.log('leo')};

// b.js
import leo from './a.js';
leo(); // 'leo'
複製代碼

export defualt暴露有函數名的函數時,在調用時至關於匿名函數。

// a.js
export default function f(){console.log('leo')};
// 或者
function f(){console.log('leo')};
export default f;

// b.js
import leo from './a.js';
複製代碼

export defualt實際上是輸出一個名字叫default的變量,因此後面不能跟變量賦值語句。

// 正確
export let a= 1;

let a = 1;
export defualt a;

// 錯誤
export default let a = 1;
複製代碼

export default命令的本質是將後面的值,賦給default變量,因此能夠直接將一個值寫在export default以後。

// 正確
export default 1;
// 錯誤
export 1;
複製代碼

1.16.7 export 和 import 複合寫法

經常在先輸入後輸出同一個模塊使用,即轉發接口,將二者寫在一塊兒。

export {a, b} from './leo.js';

// 理解爲
import {a, b} from './leo.js';
export {a, b}
複製代碼

常見的寫法還有:

// 接口更名
export { a as b} from './leo.js';

// 總體輸出
export *  from './leo.js';

// 默認接口更名
export { default as a } from './leo.js';
複製代碼

經常用在模塊繼承

1.16.8 瀏覽器中的加載規則

ES6中,能夠在瀏覽器使用<script>標籤,須要加入type="module"屬性,而且這些都是異步加載,避免瀏覽器阻塞,即等到整個頁面渲染完,再執行模塊腳本,等同於打開了<script>標籤的defer屬性。

<script type="module" src="./a.js"></script>
複製代碼

另外,ES6模塊也能夠內嵌到網頁,語法與外部加載腳本一致:

<script type="module"> import a from './a.js'; </script>
複製代碼

注意點

  • 代碼是在模塊做用域之中運行,而不是在全局做用域運行。模塊內部的頂層變量,外部不可見。
  • 模塊腳本自動採用嚴格模式,無論有沒有聲明use strict
  • 模塊之中,可使用import命令加載其餘模塊(.js後綴不可省略,須要提供絕對 URL 或相對 URL),也可使用export命令輸出對外接口。
  • 模塊之中,頂層的this關鍵字返回undefined,而不是指向window。也就是說,在模塊頂層使用this關鍵字,是無心義的。
  • 同一個模塊若是加載屢次,將只執行一次。

2. ES7

2.1 Array.prototype.includes()方法

includes()用於查找一個值是否在數組中,若是在返回true,不然返回false

['a', 'b', 'c'].includes('a');     // true
['a', 'b', 'c'].includes('d');     // false
複製代碼

includes()方法接收兩個參數,搜索的內容開始搜索的索引,默認值爲0,若搜索值在數組中則返回true不然返回false

['a', 'b', 'c', 'd'].includes('b');      // true
['a', 'b', 'c', 'd'].includes('b', 1);   // true
['a', 'b', 'c', 'd'].includes('b', 2);   // false
複製代碼

indexOf方法對比,下面方法效果相同:

['a', 'b', 'c', 'd'].indexOf('b') > -1;       // true
['a', 'b', 'c', 'd'].includes('b'); // true 
複製代碼

includes()與indexOf對比:

  • includes相比indexOf更具語義化,includes返回的是是否存在的具體結果,值爲布爾值,而indexOf返回的是搜索值的下標。
  • includes相比indexOf更準確,includes認爲兩個NaN相等,而indexOf不會。
let a = [1, NaN, 3];
a.indexOf(NaN);     // -1
a.includes(NaN);    // true
複製代碼

另外在判斷+0-0時,includesindexOf的返回相同。

[1, +0, 3, 4].includes(-0);   // true
[1, +0, 3, 4].indexOf(-0);    // 1
複製代碼

2.2 指數操做符(**)

基本用法:

let a = 3 ** 2 ; // 9
// 等效於
Math.pow(3, 2);  // 9
複製代碼

**是一個運算符,也能夠知足相似假髮的操做,以下:

let a = 3;
a **= 2;    // 9
複製代碼

3. ES8

3.1 async函數

3.1.1 介紹

ES8引入async函數,是爲了使異步操做更加方便,其實它就是Generator函數的語法糖。
async函數使用起來,只要把Generator函數的(*)號換成asyncyield換成await便可。對好比下:

// Generator寫法
const fs = require('fs');
const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};
const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

// async await寫法
const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
複製代碼

對比Genenrator有四個優勢:

  • (1)內置執行器 Generator函數執行須要有執行器,而async函數自帶執行器,即async函數與普通函數如出一轍:
async f();
複製代碼
  • (2)更好的語義 asyncawait,比起星號yield,語義更清楚了。async表示函數裏有異步操做,await表示緊跟在後面的表達式須要等待結果。
  • (3)更廣的適用性 yield命令後面只能是 Thunk 函數或 Promise 對象,而async函數的await命令後面,能夠是 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操做)。
  • (4)返回值是Promise async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你能夠用then方法指定下一步的操做。

進一步說,async函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。

3.1.2 基本用法

async函數返回一個Promise對象,可使用then方法添加回調函數,函數執行時,遇到await就會先返回,等到異步操做完成,在繼續執行。

async function f(item){
    let a = await g(item);
    let b = await h(item);
    return b;
}
f('hello').then(res => {
    console.log(res);
})
複製代碼

async代表該函數內部有異步操做,調用該函數時,會當即返回一個Promise對象。
另外還有個定時的案例,指定時間後執行:

function f (ms){
    return new Promise(res => {
        setTimeout(res, ms);
    });
}
async function g(val, ms){
    await f(ms);
    console.log(val);
}
g('leo', 50);
複製代碼

async函數還有不少使用形式:

// 函數聲明
async function f (){...}

// 函數表達式
let f = async function (){...}

// 對象的方法
let a = {
    async f(){...}
}
a.f().then(...)

// Class的方法
class c {
    constructor(){...}
    async f(){...}
}

// 箭頭函數
let f = async () => {...}
複製代碼

3.1.3 返回Promise對象

async內部return返回的值會做爲then方法的參數,另外只有async函數內部的異步操做執行完,纔會執行then方法指定的回調函數。

async function f(){
    return 'leo';
}
f().then(res => { console.log (res) }); // 'leo'
複製代碼

async內部拋出的錯誤,會被catch接收。

async function f(){
    throw new Error('err');
}
f().then (
    v => console.log(v),
    e => console.log(e)
)
// Error: err
複製代碼

3.1.4 await命令

一般await後面是一個Promise對象,若是不是就返回對應的值。

async function f(){
    return await 10;
}
f().then(v => console.log(v)); // 10
複製代碼

咱們經常將async awaittry..catch一塊兒使用,而且能夠放多個await命令,也是防止異步操做失敗由於中斷後續異步操做的狀況。

async function f(){
    try{
        await Promise.reject('err');
    }catch(err){ ... }
    return await Promise.resolve('leo');
}
f().then(v => console.log(v)); // 'leo'
複製代碼

3.1.5 使用注意

  • (1)await命令放在try...catch代碼塊中,防止Promise返回rejected
  • (2)若多個await後面的異步操做不存在繼發關係,最好讓他們同時執行。
// 效率低
let a = await f();
let b = await g();

// 效率高
let [a, b] = await Promise.all([f(), g()]);
// 或者
let a = f();
let b = g();
let a1 = await a();
let b1 = await b();
複製代碼
  • (3)await命令只能用在async函數之中,若是用在普通函數,就會報錯。
// 錯誤
async function f(){
    let a = [{}, {}, {}];
    a.forEach(v =>{  // 報錯,forEach是普通函數
        await post(v);
    });
}

// 正確
async function f(){
    let a = [{}, {}, {}];
    for(let k of a){
        await post(k);
    }
}
複製代碼

3.2 Promise.prototype.finally()

finally()是ES8中Promise添加的一個新標準,用於指定無論Promise對象最後狀態(是fulfilled仍是rejected)如何,都會執行此操做,而且finally方法必須寫在最後面,即在thencatch方法後面。

// 寫法以下: 
promise
    .then(res => {...})
    .catch(err => {...})
    .finally(() => {...})
複製代碼

finally方法經常使用在處理Promise請求後關閉服務器鏈接:

server.listen(port)
    .then(() => {..})
    .finally(server.stop);
複製代碼

本質上,finally方法是then方法的特例:

promise.finally(() => {...});

// 等同於
promise.then(
    result => {
        // ...
        return result
    }, 
    error => {
        // ...
        throw error
    }
)
複製代碼

3.3 Object.values(),Object.entries()

ES7中新增長的 Object.values()Object.entries()與以前的Object.keys()相似,返回數組類型。
回顧下Object.keys()

var a = { f1: 'hi', f2: 'leo'};
Object.keys(a); // ['f1', 'f2']
複製代碼

3.3.1 Object.values()

返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷屬性的鍵值。

let a = { f1: 'hi', f2: 'leo'};
Object.values(a); // ['hi', 'leo']
複製代碼

若是參數不是對象,則返回空數組:

Object.values(10);   // []
Object.values(true); // []
複製代碼

3.3.2 Object.entries()

返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷屬性的鍵值對數組。

let a = { f1: 'hi', f2: 'leo'};
Object.entries(a); // [['f1','hi'], ['f2', 'leo']]
複製代碼
  • 用途1
    遍歷對象屬性。
let a = { f1: 'hi', f2: 'leo'};
for (let [k, v] of Object.entries(a)){
    console.log(
        `${JSON.stringfy(k)}:${JSON.stringfy(v)}`
    )
}
// 'f1':'hi'
// 'f2':'leo'
複製代碼
  • 用途2: 將對象轉爲真正的Map結構。
let a = { f1: 'hi', f2: 'leo'};
let map = new Map(Object.entries(a));
複製代碼

手動實現Object.entries()方法:

// Generator函數實現: 
function* entries(obj){
    for (let k of Object.keys(obj)){
        yield [k ,obj[k]];
    }
}

// 非Generator函數實現:
function entries (obj){
    let arr = [];
    for(let k of Object.keys(obj)){
        arr.push([k, obj[k]]);
    }
    return arr;
}
複製代碼

3.4 Object.getOwnPropertyDescriptors()

以前有Object.getOwnPropertyDescriptor方法會返回某個對象屬性的描述對象,新增的Object.getOwnPropertyDescriptors()方法,返回指定對象全部自身屬性(非繼承屬性)的描述對象,全部原對象的屬性名都是該對象的屬性名,對應的屬性值就是該屬性的描述對象

let a = {
    a1:1,
    get f1(){ return 100}
}
Object.getOwnPropetyDescriptors(a);
/* { a:{ configurable:true, enumerable:true, value:1, writeable:true} f1:{ configurable:true, enumerable:true, get:f, set:undefined} } */
複製代碼

實現原理:

function getOwnPropertyDescriptors(obj) {
  const result = {};
  for (let key of Reflect.ownKeys(obj)) {
    result[key] = Object.getOwnPropertyDescriptor(obj, key);
  }
  return result;
}
複製代碼

引入這個方法,主要是爲了解決Object.assign()沒法正確拷貝get屬性和set屬性的問題。

let a = {
    set f(v){
        console.log(v)
    }
}
let b = {};
Object.assign(b, a);
Object.a(b, 'f');
/* f = { configurable: true, enumable: true, value: undefined, writeable: true } */
複製代碼

valueundefined是由於Object.assign方法不會拷貝其中的getset方法,使用getOwnPropertyDescriptors配合Object.defineProperties方法來實現正確的拷貝:

let a = {
    set f(v){
        console.log(v)
    }
}
let b = {};
Object.defineProperties(b, Object.getOwnPropertyDescriptors(a));
Object.getOwnPropertyDescriptor(b, 'f')
/* configurable: true, enumable: true, get: undefined, set: function(){...} */
複製代碼

Object.getOwnPropertyDescriptors方法的配合Object.create方法,將對象屬性克隆到一個新對象,實現淺拷貝。

const clone = Object.create(Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj));

// 或者
const shallowClone = (obj) => Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);
複製代碼

3.5 字符串填充 padStart和padEnd

用來爲字符串填充特定字符串,而且都有兩個參數:字符串目標長度填充字段,第二個參數可選,默認空格。

'es8'.padStart(2);          // 'es8'
'es8'.padStart(5);          // ' es8'
'es8'.padStart(6, 'woof');  // 'wooes8'
'es8'.padStart(14, 'wow');  // 'wowwowwowwoes8'
'es8'.padStart(7, '0');     // '0000es8'

'es8'.padEnd(2);            // 'es8'
'es8'.padEnd(5);            // 'es8 '
'es8'.padEnd(6, 'woof');    // 'es8woo'
'es8'.padEnd(14, 'wow');    // 'es8wowwowwowwo'
'es8'.padEnd(7, '6');       // 'es86666'
複製代碼

從上面結果來看,填充函數只有在字符長度小於目標長度時纔有效,若字符長度已經等於或小於目標長度時,填充字符不會起做用,並且目標長度若是小於字符串自己長度時,字符串也不會作截斷處理,只會原樣輸出。

3.6 函數參數列表與調用中的尾部逗號

該特性容許咱們在定義或者調用函數時添加尾部逗號而不報錯:

function es8(var1, var2, var3,) {
  // ...
}
es8(10, 20, 30,);
複製代碼

3.7 共享內存與原子操做

當內存被共享時,多個線程能夠併發讀、寫內存中相同的數據。原子操做能夠確保那些被讀、寫的值都是可預期的,即新的事務是在舊的事務結束以後啓動的,舊的事務在結束以前並不會被中斷。這部分主要介紹了 ES8 中新的構造函數 SharedArrayBuffer 以及擁有許多靜態方法的命名空間對象 Atomic
Atomic 對象相似於 Math 對象,擁有許多靜態方法,因此咱們不能把它當作構造函數。 Atomic 對象有以下經常使用的靜態方法:

  • add /sub :爲某個指定的value值在某個特定的位置增長或者減去某個值
  • and / or /xor :進行位操做
  • load :獲取特定位置的值

4. ES9

4.1 對象的拓展運算符

4.1.1 介紹

對象的拓展運算符,即對象的Rest/Spread屬性,可將對象解構賦值用於從一個對象取值,搜鍵值對分配到指定對象上,與數組的拓展運算符相似:

let  {x, y, ...z} = {x:1, y:2, a:3, b:4};
x;  // 1
y;  // 2
z;  // {a:3, b:4} 
複製代碼

對象的解構賦值要求等號右邊必須是個對象,因此若是等號右邊是undefinednull就會報錯沒法轉成對象。

let {a, ...b} = null;      // 運行時報錯
let {a, ...b} = undefined; // 運行時報錯
複製代碼

解構賦值必須是最後一個參數,不然報錯。

let {...a, b, c} = obj;     // 語法錯誤
let {a, ...b, c} = obj;     // 語法錯誤
複製代碼

注意

  • 1.解構賦值是淺拷貝。
let a = {a1: {a2: 'leo'}};
let {...b} = a;
a.a1.a2 = 'leo';
b.a1.a2 = 'leo';
複製代碼
  • 2.拓展運算符的解構賦值,不能複製繼承自原型對象的屬性。
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3;    // { b: 2 }
o3.a;  // undefined
複製代碼

4.1.2 使用場景

  • 1.取出參數對象全部可遍歷屬性,拷貝到當前對象中。
let a = { a1:1, a2:2 };
let b = { ...a };
b;   // { a1:1, a2:2 }

// 相似Object.assign方法
複製代碼
  • 2.合併兩個對象。
let a = { a1:1, a2:2 };
let b = { b1:11, b2:22 };
let ab = { ...a, ...b }; // {a1: 1, a2: 2, b1: 11, b2: 22}
// 等同於
let ab = Object.assign({}, a, b);
複製代碼
  • 3.將自定義屬性放在拓展運算符後面,覆蓋對象原有的同名屬性。
let a = { a1:1, a2:2, a3:3 };
let r = { ...a, a3:666 };   
// r {a1: 1, a2: 2, a3: 666}

// 等同於
let r = { ...a, ...{ a3:666 }};
// r {a1: 1, a2: 2, a3: 666}

// 等同於
let r = Object.assign({}, a, { a3:666 });
// r {a1: 1, a2: 2, a3: 666}
複製代碼
  • 4.將自定義屬性放在拓展運算符前面,就會成爲設置新對象的默認值。
let a = { a1:1, a2:2 };
let r = { a3:666, ...a };
// r {a3: 666, a1: 1, a2: 2}

// 等同於
let r = Object.assign({}, {a3:666}, a);
// r {a3: 666, a1: 1, a2: 2}

// 等同於
let r = Object.assign({a3:666}, a);
// r {a3: 666, a1: 1, a2: 2}
複製代碼
  • 5.拓展運算符後面可使用表達式。
let a = {
    ...(x>1? {a:!:{}),
    b:2
}
複製代碼
  • 6.拓展運算符後面若是是個空對象,則沒有任何效果。
{...{}, a:1};  // {a:1}
複製代碼
  • 7.若參數是nullundefined則忽略且不報錯。
let a = { ...null, ...undefined }; // 不報錯
複製代碼
  • 8.如有取值函數get則會執行。
// 不會打印 由於f屬性只是定義 而不沒執行
let a = {
    ...a1,
    get f(){console.log(1)}
}

// 會打印 由於f執行了
let a = {
    ...a1,
    ...{
        get f(){console.log(1)}
    }
}
複製代碼

4.2 正則表達式 s 修飾符

在正則表達式中,點(.)能夠表示任意單個字符,除了兩個:用u修飾符解決四個字節的UTF-16字符,另外一個是行終止符。
終止符即表示一行的結束,以下四個字符屬於「行終止符」:

  • U+000A 換行符(\n)
  • U+000D 回車符(\r)
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)
/foo.bar/.test('foo\nbar')
// false
複製代碼

上面代碼中,由於.不匹配\n,因此正則表達式返回false
換個醒,能夠匹配任意單個字符:

/foo[^]bar/.test('foo\nbar')
// true
複製代碼

ES9引入s修飾符,使得.能夠匹配任意單個字符:

/foo.bar/s.test('foo\nbar') // true
複製代碼

這被稱爲dotAll模式,即點(dot)表明一切字符。因此,正則表達式還引入了一個dotAll屬性,返回一個布爾值,表示該正則表達式是否處在dotAll模式。

const re = /foo.bar/s;
// 另外一種寫法
// const re = new RegExp('foo.bar', 's');

re.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'
複製代碼

/s修飾符和多行修飾符/m不衝突,二者一塊兒使用的狀況下,.匹配全部字符,而^$匹配每一行的行首和行尾。

4.3 異步遍歷器

在前面ES6章節中,介紹了Iterator接口,而ES6引入了「異步遍歷器」,是爲異步操做提供原生的遍歷器接口,即valuedone這兩個屬性都是異步產生的。

4.3.1 異步遍歷的接口

經過調用遍歷器的next方法,返回一個Promise對象。

a.next().then( 
    ({value, done}) => {
        //...
    }
)
複製代碼

上述a爲異步遍歷器,調用next後返回一個Promise對象,再調用then方法就能夠指定Promise對象狀態變爲resolve後執行的回調函數,參數爲valuedone兩個屬性的對象,與同步遍歷器一致。
與同步遍歷器同樣,異步遍歷器接口也是部署在Symbol.asyncIterator屬性上,只要有這個屬性,就均可以異步遍歷。

let a = createAsyncIterable(['a', 'b']);
//createAsyncIterable方法用於構建一個iterator接口
let b = a[Symbol.asyncInterator]();

b.next().then( result1 => {
    console.log(result1); // {value: 'a', done:false}
    return b.next();
}).then( result2 => {
    console.log(result2); // {value: 'b', done:false}
    return b.next();
}).then( result3 => {
    console.log(result3); // {value: undefined, done:true}
})
複製代碼

另外next方法返回的是一個Promise對象,因此能夠放在await命令後。

async function f(){
    let a = createAsyncIterable(['a', 'b']);
    let b = a[Symbol.asyncInterator]();
    console.log(await b.next());// {value: 'a', done:false}
    console.log(await b.next());// {value: 'b', done:false}
    console.log(await b.next());// {value: undefined, done:true}
}
複製代碼

還有一種狀況,使用Promise.all方法,將全部的next按順序連續調用:

let a = createAsyncIterable(['a', 'b']);
let b = a[Symbol.asyncInterator]();
let {{value:v1}, {value:v2}} = await Promise.all([
    b.next(), b.next()
])
複製代碼

也能夠一次調用全部next方法,再用await最後一步操做。

async function f(){
    let write = openFile('aaa.txt');
    write.next('hi');
    write.next('leo');
    await write.return();
}
f();
複製代碼

4.3.2 for await...of

for...of用於遍歷同步的Iterator接口,而ES8引入for await...of遍歷異步的Iterator接口。

async function f(){
    for await(let a of createAsyncIterable(['a', 'b'])) {
        console.log(x);
    }
}
// a
// b
複製代碼

上面代碼,createAsyncIterable()返回一個擁有異步遍歷器接口的對象,for...of自動調用這個對象的next方法,獲得一個Promise對象,await用來處理這個Promise,一但resolve就把獲得的值x傳到for...of裏面。
用途
直接把部署了asyncIteable操做的異步接口放入這個循環。

let a = '';
async function f(){
    for await (let b of req) {
        a += b;
    }
    let c = JSON.parse(a);
    console.log('leo', c);
}
複製代碼

next返回的Promise對象被rejectfor await...of就會保錯,用try...catch捕獲。

async function f(){
    try{
        for await (let a of iterableObj()){
            console.log(a);
        }
    }catch(e){
        console.error(e);
    }
}
複製代碼

注意,for await...of循環也能夠用於同步遍歷器。

(async function () {
  for await (let a of ['a', 'b']) {
    console.log(a);
  }
})();
// a
// b
複製代碼

4.3.3 異步Generator函數

就像 Generator 函數返回一個同步遍歷器對象同樣,異步 Generator 函數的做用,是返回一個異步遍歷器對象。
在語法上,異步 Generator 函數就是async函數與 Generator 函數的結合。

async function* f() {
  yield 'hi';
}
const a = f();
a.next().then(x => console.log(x));
// { value: 'hello', done: false }
複製代碼

設計異步遍歷器的目的之一,就是爲了讓Generator函數能用同一套接口處理同步和異步操做。

// 同步Generator函數
function * f(iterable, fun){
    let a = iterabl[Symbol.iterator]();
    while(true){
        let {val, done} = a.next();
        if(done) break;
        yield fun(val);
    }
}

// 異步Generator函數
async function * f(iterable, fun){
    let a = iterabl[Symbol.iterator]();
    while(true){
        let {val, done} = await a.next();
        if(done) break;
        yield fun(val);
    }
}
複製代碼

同步和異步Generator函數相同點:在yield時用next方法停下,將後面表達式的值做爲next()返回對象的value
在異步Generator函數中,同時使用awaityield,簡單樣理解,await命令用於將外部操做產生的值輸入函數內部,yield命令用於將函數內部的值輸出。

(async function () {
  for await (const line of readLines(filePath)) {
    console.log(line);
  }
})()
複製代碼

異步 Generator 函數能夠與for await...of循環結合起來使用。

async function* f(asyncIterable) {
  for await (const line of asyncIterable) {
    yield '> ' + line;
  }
}
複製代碼

4.3.4 yield* 語句

yield*語句跟一個異步遍歷器。

async function * f(){
  yield 'a';
  yield 'b';
  return 'leo';
}
async function * g(){
  const a = yield* f();  // a => 'leo'
}
複製代碼

與同步 Generator 函數同樣,for await...of循環會展開yield*

(async function () {
  for await (const x of gen2()) {
    console.log(x);
  }
})();
// a
// b
複製代碼

5. 知識補充

5.1 塊級做用域

一般指一個函數內部,或者一個代碼塊內部
好比:

function fun1 () {
    // 塊級做用域
    if (true) {
        // 塊級做用域
    }
}
複製代碼

缺點: 1.致使內層變量覆蓋外層變量

var a1 = new Date();
function f1 (){
    console.log(a1); // undefined
    if (false) {
        var a1 = 'hello'
    }
}
複製代碼

輸出 undefined 是由於 if 內的 a1 變量聲明的變量提高,致使內部的 a1 覆蓋外部的 a1,因此輸出爲 undefined

2.變量的全局污染

var a = 'hello';
for (var i = 0; i< a.length; i++) {
    //...
}
console.log(i); // 5
複製代碼

循環結束後,變量 i 的值依然存在,形成變量的全局污染。

5.2 ES5/6對數組空位的處理

數組的空位不是undefined,而是沒有任何值,數組的undefined也是有值。

0 in [undefined,undefined,undefined] // true
0 in [,,,] // false
複製代碼

ES5對空位的處理

  • forEach(), filter(), reduce(), every()some()都會跳過空位。
  • map()會跳過空位,但會保留這個值。
  • join()toString()會將空位視爲undefined,而undefinednull會被處理成空字符串。
[,'a'].forEach((x,i) => console.log(i)); // 1
['a',,'b'].filter(x => true);      // ['a','b']
[,'a'].every(x => x==='a');        // true
[1,,2].reduce((x,y) => x+y);       // 3
[,'a'].some(x => x !== 'a');       // false
[,'a'].map(x => 1);                // [,1]
[,'a',undefined,null].join('#');   // "#a##"
[,'a',undefined,null].toString();  // ",a,,"
複製代碼

ES6對空位的處理
將空位視爲正常值,轉成undefined

Array.from(['a',,'b']);// [ "a", undefined, "b" ]
[...['a',,'b']];       // [ "a", undefined, "b" ]

//copyWithin() 會連空位一塊兒拷貝。 
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]

//fill()會將空位視爲正常的數組位置。
new Array(3).fill('a') // ["a","a","a"]

//for...of循環也會遍歷空位。
let arr = [, ,];
for (let i of arr) {
  console.log(1);
}  // 1 1
複製代碼

entries()keys()values()find()findIndex()會將空位處理成undefined

[...[,'a'].entries()] // [[0,undefined], [1,"a"]]

[...[,'a'].keys()] // [0,1]

[...[,'a'].values()] // [undefined,"a"]

[,'a'].find(x => true) // undefined

[,'a'].findIndex(x => true) // 0
複製代碼

因爲空位的處理規則很是不統一,因此建議避免出現空位。

3、結語

參考文章

推薦文章

相關文章
相關標籤/搜索