1. 程式人生 > >ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

(內容同步自小鄒的頭條號:滬漂程式設計師的生活史)

前言

ES6 雖提供了許多新特性,但我們實際工作中用到頻率較高並不多,根據二八法則,我們應該用百分之八十的精力和時間,好好專研這百分之二十核心特性,將會收到事半功倍的奇效

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

 

一、開發環境配置

這部分著重介紹:babel 編譯 ES6 語法,如何用 webpack 實現模組化。

1.babel

為啥需要 babel?

ES6 提供了許多新特性,但並不是所有的瀏覽器都能夠完美支援。下圖是各個瀏覽器對 ES6 相容性一覽表(以 export 為例)

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

 

由上圖可知,有些瀏覽器對於 ES6 並不是很友好,針對 ES6 的相容性問題,很多團隊為此開發出了多種語法解析轉換工具(比如 babel,jsx,traceur 等),可以把我們寫的 ES6 語法轉換成 ES5,相當於在 ES6 和瀏覽器之間做了一個翻譯官。其中Babel是一個廣泛使用的轉碼器,可以將 ES6 程式碼轉為 ES5 程式碼,從而在現有環境執行。

如何配置 babel?

·首先要先安裝node.js,執行npm init,然後會生成package.json檔案
·npm install --save-dev babel-core babel-preset-es2015 babel-preset-latest
·建立並配置.babelrc檔案//存放在專案的根目錄下,與node_modules同級
·npm install -g babel-cli
·babel-version

Babel 的配置檔案是.babelrc,存放在專案的根目錄下。該檔案用來設定轉碼規則和外掛,具體內容如下:

//.babelrc檔案
{
 "presets": ["es2015", "latest"],
 "plugins": []
}

驗證配置是否成功

·建立./src/index.js
·內容:[1,2,3].map(item=>item+1);
·執行babel./src/index.js

執行後得到以下部分,說明已經成功配置了 babel

"use strict";
[1, 2, 3].map(function (item) {
 return item + 1;
});

2.webpack

為啥要使用 WebPack?

現今的很多網頁其實可以看做是功能豐富的應用,它們擁有著複雜的 JavaScript 程式碼和一大堆依賴包,模快化工具就應運而生了,其中 webpack 功能強大深受人們喜愛。

Webpack 的工作方式是:把你的專案當做一個整體,通過一個給定的主檔案(如:index.js),Webpack 將從這個檔案開始找到你的專案的所有依賴檔案,使用 loaders 處理它們,最後打包為一個(或多個)瀏覽器可識別的 JavaScript 檔案。

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

如何配置 webpack?

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

 

二、塊級作用域

ES5 只有全域性作用域和函式作用域(例如,我們必須將程式碼包在函式內來限制作用域),這導致很多問題:

情況 1:內層變數覆蓋外層變數

var tmp = new Date();
function f() {
 console.log(tmp); //undefined
 if (false) {
 var tmp = "hello world";
 }
}

情況 2:變數洩露,成為全域性變數

var s = 'hello';
for (var i = 0; i < s.length; i++) {
 console.log(s[i]);
}
console.log(i); // 5

ES6 提供 let 和 const 來代替 var 宣告變數,新的宣告方式支援用大括號表示的塊級作用域,這會帶來一些好處:

1.不再需要立即執行的函式表示式(IIFE)

在 ES5 中,我們需要構造一個立即執行的函式表示式去保證我們不汙染全域性作用域。在 ES6 中, 我們可以使用更簡單的大括號({}),然後使用 const 或者 let 代替 var 來達到同樣的效果。

2.迴圈體中的閉包不再有問題

在 ES5 中,如果迴圈體內有產生一個閉包,訪問閉包外的變數,會產生問題。在 ES6,你可以使用 “let” 來避免問題。

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

3.防止重複宣告變數

ES6 不允許在同一個作用域內用 let 或 const 重複宣告同名變數。這對於防止在不同的 js 庫中存在重複宣告的函式表示式十分有幫助。

三、陣列的擴充套件

1. Array.from() : 將偽陣列物件或可遍歷物件轉換為真陣列

如果一個物件的所有鍵名都是正整數或零,並且有 length 屬性,那麼這個物件就很像陣列,稱為偽陣列。典型的偽陣列有函式的 arguments 物件,以及大多數 DOM 元素集,還有字串。

...
<button>測試1</button>
<br>
<button>測試2</button>
<br>
<button>測試3</button>
<br>
<script type="text/javascript">
let btns = document.getElementsByTagName("button")
console.log("btns",btns);//得到一個偽陣列
btns.forEach(item=>console.log(item)) 
//Uncaught TypeError: btns.forEach is not a function
</script>

針對偽陣列,沒有陣列一般方法,直接遍歷便會出錯,ES6 新增 Array.from()方法來提供一種明確清晰的方式以解決這方面的需求。

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

2.Array.of(v1, v2, v3) : 將一系列值轉換成陣列

當呼叫 new Array( )構造器時,根據傳入引數的型別與數量的不同,實際上會導致一些不同的結果, 例如:

let items = new Array(2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // undefined
console.log(items[1]) ;
let items = new Array(1, 2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // 1
console.log(items[1]) ; // 2

當使用單個數值引數來呼叫 Array 構造器時,陣列的長度屬性會被設定為該引數。 如果使用多個引數(無論是否為數值型別)來呼叫,這些引數也會成為目標陣列的項。陣列的這種行為既混亂又有風險,因為有時可能不會留意所傳引數的型別。

ES6 引入了 Array.of( )方法來解決這個問題。該方法的作用非常類似 Array 構造器,但在使用單個數值引數的時候並不會導致特殊結果。Array.of( )方法總會建立一個包含所有傳入引數的陣列,而不管引數的數量與型別

let items = Array.of(1, 2);
console.log(items.length); // 2
console.log(items[0]); // 1
console.log(items[1]); // 2
items = Array.of(2);
console.log(items.length); // 1
console.log(items[0]); // 2

Array.of 基本上可以用來替代 Array()或 newArray(),並且不存在由於引數不同而導致的過載,而且他們的行為非常統一。

3.陣列例項的 find() 和 findIndex()

陣列例項的 find 方法,用於找出第一個符合條件的陣列成員。它的引數是一個回撥函式,所有陣列成員依次執行該回調函式,直到找出第一個返回值為 true 的成員,然後返回該成員。如果沒有符合條件的成員,則返回 undefined。

[1, 4, -5, 10].find((n) => n < 0) // -5

陣列例項的 findIndex 方法的用法與 find 方法非常類似,返回第一個符合條件的陣列成員的位置,如果所有成員都不符合條件,則返回-1。

[1, 5, 10, 15].findIndex(function(value, index, arr) {
 return value > 9;
}) // 2

4.陣列例項的 includes()

Array.prototype.includes 方法返回一個布林值,表示某個陣列是否包含給定的值。該方法的第二個引數表示搜尋的起始位置,預設為 0。如果第二個引數為負數,則表示倒數的位置,如果這時它大於陣列長度(比如第二個引數為-4,但陣列長度為 3),則會重置為從 0 開始。

[1, 2, 3].includes(2) // true
[1, 2, 3].includes(3, -1); // true
[1, 2, 3, 5, 1].includes(1, 2); // true

沒有該方法之前,我們通常使用陣列的 indexOf 方法,檢查是否包含某個值。indexOf 方法有兩個缺點,一是不夠語義化,它的含義是找到引數值的第一個出現位置,所以要去比較是否不等於-1,表達起來不夠直觀。二是,它內部使用嚴格相等運算子(===)進行判斷,這會導致對 NaN 的誤判

[NaN].indexOf(NaN) // -1
[NaN].includes(NaN) // true

5.陣列例項的 entries(),keys() 和 values()

ES6 提供 entries(),keys()和 values(),用於遍歷陣列。它們都返回一個遍歷器物件,可以用 for…of 迴圈進行遍歷,唯一的區別是 keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。

for (let index of ['a', 'b'].keys()) {
 console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
 console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
 console.log(index, elem);
}
// 0 "a"
// 1 "b"

四、箭頭函式

ES6 允許使用“箭頭”(=>)定義函式。它主要有兩個作用:縮減程式碼和改變 this 指向,接下來我們詳細介紹:

1. 縮減程式碼

const double1 = function(number){
 return number * 2; //ES5寫法
}
const double2 = (number) => {
 return number * 2; //ES6寫法
}
const double4 = number => number * 2; //可以進一步簡化

多個引數記得加括號

 const double6 = (number,number2) => number + number2;

如果箭頭函式的程式碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用 return 語句返回

 const double = (number,number2) => {
 sum = number + number2
 return sum;
 }

由於大括號被解釋為程式碼塊,所以如果箭頭函式直接返回一個物件,必須在物件外面加上括號,否則會報錯

// 報錯
let getTempItem = id => { id: id, name: "Temp" };
// 不報
let getTempItem = id => ({ id: id, name: "Temp" });

此外還有個好處就是簡化回撥函式

// 正常函式寫法
[1,2,3].map(function (x) {
 return x * x;
});
// 箭頭函式寫法
[1,2,3].map(x => x * x);//[1, 4, 9]

2. 改變 this 指向

長期以來,JavaScript 語言的 this 物件一直是一個令人頭痛的問題,在物件方法中使用 this,必須非常小心。箭頭函式”繫結”this,很大程度上解決了這個困擾。我們不妨先看一個例子:

const team = {
 members:["Henry","Elyse"],
 teamName:"es6",
 teamSummary:function(){
 return this.members.map(function(member){
 return `${member}隸屬於${this.teamName}小組`; // this不知道該指向誰了
 })
 }
}
console.log(team.teamSummary());
//["Henry隸屬於undefined小組", "Elyse隸屬於undefined小組"]

teamSummary 函式裡面又嵌了個函式,這導致內部的 this 的指向發生了錯亂。

那如何修改:

方法一、let self = this

const team = {
 members:["Henry","Elyse"],
 teamName:"es6",
 teamSummary:function(){
 let self = this;
 return this.members.map(function(member){
 return `${member}隸屬於${self.teamName}小組`;
 })
 }
}
console.log(team.teamSummary());
//["Henry隸屬於es6小組", "Elyse隸屬於es6小組"]

方法二、bind 函式

const team = {
 members:["Henry","Elyse"],
 teamName:"es6",
 teamSummary:function(){
 return this.members.map(function(member){
 // this不知道該指向誰了
 return `${member}隸屬於${this.teamName}小組`;
 }.bind(this))
 }
}
console.log(team.teamSummary());
//["Henry隸屬於es6小組", "Elyse隸屬於es6小組"]

方法三、 箭頭函式

const team = {
 members:["Henry","Elyse"],
 teamName:"es6",
 teamSummary:function(){
 return this.members.map((member) => {
 // this指向的就是team物件
 return `${member}隸屬於${this.teamName}小組`;
 })
 }
}
console.log(team.teamSummary());
//["Henry隸屬於es6小組", "Elyse隸屬於es6小組"]

3.使用注意點

(1)函式體內的 this 物件,就是定義時所在的物件,而不是使用時所在的物件。

(2)不可以當作建構函式,也就是說,不可以使用 new 命令,否則會丟擲一個錯誤。

(3)不可以使用 arguments 物件,該物件在函式體內不存在。如果要用,可以用 rest 引數代替。

(4)不可以使用 yield 命令,因此箭頭函式不能用作 Generator 函式。

五、rest 引數

ES6 引入 rest 引數(形式為…變數名),用於獲取函式的多餘引數,這樣就不需要使用 arguments 物件了。

rest 引數搭配的變數是一個數組,該變數將多餘的引數放入陣列中。

我們舉個例子:如何實現一個求和函式?

傳統寫法:

function addNumbers(a,b,c,d,e){
 var numbers = [a,b,c,d,e];
 return numbers.reduce((sum,number) => {
 return sum + number;
 },0)
 }
 console.log(addNumbers(1,2,3,4,5));//15

ES6 寫法:

 function addNumbers(...numbers){
 return numbers.reduce((sum,number) => {
 return sum + number;
 },0)
 }
 console.log(addNumbers(1,2,3,4,5));//15

也可以與解構賦值組合使用

var array = [1,2,3,4,5,6];
var [a,b,...c] = array;
console.log(a);//1
console.log(b);//2
console.log(c);//[3, 4, 5, 6]

rest 引數還可以與箭頭函式結合

const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)// [1,2,3,4,5]

注意:① 每個函式最多隻能宣告一個 rest 引數,而且 rest 引數必須是最後一個引數,否則報錯。

②rest 引數不能用於物件字面量 setter 之中

let object = {
 set name(...value){ //報錯
 //執行一些邏輯
 }
}

六、展開運算子

與剩餘引數關聯最密切的就是擴充套件運算子。剩餘引數允許你把多個獨立的引數合併到一個數組中;而擴充套件運算子則允許將一個數組分割,並將各個項作為分離的引數傳給函式。

當用在字串或陣列前面時稱為擴充套件運算子,個人覺得可以理解為 rest 引數的逆運算,用於將陣列或字串進行拆解。有些時候,函式不允許傳入陣列,此時使用展開運算子就很方便,不信的話,咱們看個例子:Math.max()方法,它接受任意數量的引數,並會返回其中的最大值。

let value1 = 25,
let value2 = 50;
console.log(Math.max(value1, value2)); // 50

但若想處理陣列中的值,此時該如何找到最大值?Math.max()方法並不允許你傳入一個數組。其實你可以像使用 rest 引數那樣在該陣列前新增…,並直接將其傳遞給 Math.max()

let values = [25,50,75, 100]
//等價於console.log(Math.max(25,50,75,100));
console.log(Math.max(...values)); //100

擴充套件運算子還可以與其他引數混用

let values = [-25,-50,-75,-100]
console.log(Math.max(...values,0)); //0

擴充套件運算子拆解字串與陣列

var array = [1,2,3,4,5];
console.log(...array);//1 2 3 4 5
var str = "String";
console.log(...str);//S t r i n g

還可以實現拼接

var defaultColors = ["red","greed"];
var favoriteColors = ["orange","yellow"];
var fallColors = ["fire red","fall orange"];
console.log(["blue","green",...fallColors,...defaultColors,...favoriteColors]
//["blue", "green", "fire red", "fall orange", "red", "greed", "orange", "yellow"]

七、解構賦值----更方便的資料訪問

ES6 新增瞭解構,這是將一個數據結構分解為更小的部分的過程。

1.解構為何有用?

在 ES5 及更早版本中,從物件或陣列中獲取資訊、並將特定資料存入本地變數,需要書寫許多並且相似的程式碼。例如:

 var expense = {
 type: "es6",
 amount:"45"
 };
 var type = expense.type;
 var amount = expense.amount;
 console.log(type,amount);

此程式碼提取了 expense 物件的 type 與 amount 值,並將其存在同名的本地變數上。雖然 這段程式碼看起來簡單,但想象一下若有大量變數需要處理,你就必須逐個為其賦值;並且若有一個巢狀的資料結構需要遍歷以尋找資訊,你可能會為了一點資料而挖掘整個結構。

這就是 ES6 為何要給物件與陣列新增解構。當把資料結構分解為更小的部分時,從中提取你要的資料會變得容易許多。

2.物件

上個例子中如果採用物件解構的方法,就很容易獲取 expense 物件的 type 與 amount 值。

const { type,amount } = expense;
console.log(type,amount);

我們再來看個例子:

let node = {type:"Identifier", name:"foo"},
type = "Literal",name = 5;
({type,name}= node);// 使用解構來分配不同的值
console.log(type); // "Identifier"
console.log(name); // "foo"

注意:你必須用圓括號包裹解構賦值語句,這是因為暴露的花括號會被解析為程式碼塊語句,而塊語句不允許在賦值操作符(即等號)左側出現。圓括號標示了裡面的花括號並不是塊語句、而應該被解釋為表示式,從而允許完成賦值操作。

預設值:

可以選擇性地定義一個預設值,以便在指定屬性不存在時使用該值。若要這麼做,需要在 屬性名後面新增一個等號並指定預設值,就像這樣:

let node = {
 type: "Identifier",
 name: "foo"
};
let {
 type,
 name,
 value = true
} = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // true

巢狀物件解構:

使用類似於物件字面量的語法,可以深入到巢狀的物件結構中去提取你想要的資料。

let node = {
 type: "Identifier",
 name: "foo",
 loc: {
 start: {
 line: 1,
 column: 1
 },
 end: {
 line: 1,
 column: 4
 }
 }
};
let { loc: { start }} = node;
console.log(start.line); // 1
console.log(start.column); // 1

本例中的解構模式使用了花括號,表示應當下行到 node 物件的 loc 屬性內部去尋找 start 屬性。

必須傳值的解構引數

function setCookie(name, value, {
 secure,
 path,
 domain,
 expires
}) {
 // 設定cookie的程式碼
}
 setCookie("type", "js");//報錯

在此函式內,name 與 value 引數是必需的,而 secure、path、domain 與 expires 則不是。預設情況下呼叫函式時未給引數解構傳值會丟擲錯誤。像上例中如果 setCookie 不傳第三個引數,就會報錯。若解構引數是可選的,可以給解構的引數提供預設值來處理這種錯誤。

function setCookie(name, value, {
 secure,
 path,
 domain,
 expires
} = {}) {}
setCookie("type", "js");//不會報錯

3.陣列

const names = ["Henry","Bucky","Emily"];
const [name1,name2,name3] = names;
console.log(name1,name2,name3);//Henry Bucky Emily
const [name,...rest] = names;//結合展開運算子
console.log(rest);//["Bucky", "Emily"]

用{}解構返回陣列個數

const {length} = names;
console.log(length);//3

陣列解構也可以用於賦值上下文,但不需要用小括號包裹表示式。這點跟物件解構的約定不同。

let colors = ["red", "green", "blue"],
 firstColor = "black",
 secondColor = "purple";
[firstColor, secondColor] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

預設值:陣列解構賦值同樣允許在陣列任意位置指定預設值。當指定位置的項不存在、或其值為 undefined,那麼該預設值就會被使用。

let colors = ["red"];
let [firstColor, secondColor = "green"] = colors;
console.log(firstColor); // "red"
console.log(secondColor);// "green"

與 rest 引數搭配

在 ES5 中常常使用 concat()方法來克隆陣列,例如:

//在ES5中克隆陣列
var colors = ["red", "green", "blue"];
var clonedColors = colors.concat();
console.log(clonedColors); //"[red,green,blue]"

在 ES6 中,你可以使用剩餘項的語法來達到同樣效果

//在ES6中克隆陣列
let colors = ["red", "green", "blue"];
let [...clonedColors] = colors;
console.log(clonedColors); //[red,green,blue]

接下我們看個例子:如何將陣列轉化為物件

const points = [
 [4,5],
 [10,1],
 [0,40]
];
//期望得到的資料格式如下,如何實現?
// [
// {x:4,y:5},
// {x:10,y:1},
// {x:0,y:40}
// ]
let newPoints = points.map(pair => {
 const [x,y] = pair;
 return {x,y}
})
//還可以通過以下辦法,更為簡便
let newPoints = points.map(([x,y]) => {
 return {x,y}
})
console.log(newPoints);

混合解構

const people = [
 {name:"Henry",age:20},
 {name:"Bucky",age:25},
 {name:"Emily",age:30}
];
//es5 寫法
var age = people[0].age;
console.log(age);
//es6 解構
const [age] = people;
console.log(age);//第一次解構陣列 {name:"Henry",age:20}
const [{age}] = people;//再一次解構物件
console.log(age);//20

4.注意點

當使用解構來配合 var、let、const 來宣告變數時,必須提供初始化程式(即等號右邊的值)。下面的程式碼都會因為缺失初始化程式而丟擲語法錯誤:

var { type, name }; // 語法錯誤!
let { type, name }; // 語法錯誤!
const { type, name }; // 語法錯誤!

八、模板字串(template string)

模板字串是增強版的字串,用反引號(`)標識。它可以當作普通字串使用,也可以用來定義多行字串,或者在字串中嵌入變數。 模板字串中嵌入變數和函式,需要將變數名寫在${}之中。

let name = "Henry";
function makeUppercase(word){
 return word.toUpperCase();
}
let template =
 `
 <h1>${makeUppercase('Hello')}, ${name}!</h1>//可以存放函式和變數
 <p>感謝大家收看我們的視訊, ES6為我們提供了很多遍歷好用的方法和語法!</p>
 <ul>
 <li>1</li>
 <li>2</li>
 <li>3</li>
 <li>4</li>
 <li>5</li>
 </ul>
 `;
document.getElementById('template').innerHTML = template;

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

 

再舉個例子,工作中常用到 ElementUI 庫,在自定義一個彈出框時,使用模板字串就很方便:

 await this.$alert(
 `<p><strong>確認是否升級${
 this.lectureName
 }</strong><br>(若已存在講義套件,升級後請重新生成)</p>`,
 {
 dangerouslyUseHTMLString: true
 }
 )

九、Class 和傳統建構函式有何區別

從概念上講,在 ES6 之前的 JS 中並沒有和其他面嚮物件語言那樣的“類”的概念。長時間裡,人們把使用 new 關鍵字通過函式(也叫構造器)構造物件當做“類”來使用。由於 JS 不支援原生的類,而只是通過原型來模擬,各種模擬類的方式相對於傳統的面向物件方式來說非常混亂,尤其是處理當子類繼承父類、子類要呼叫父類的方法等等需求時。

ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,作為物件的模板。通過 class 關鍵字,可以定義類。但是類只是基於原型的面向物件模式的語法糖

對比在傳統建構函式和 ES6 中分別如何實現類:

//傳統建構函式
function MathHandle(x,y){
 this.x=x;
 this.y=y;
}
MathHandle.prototype.add =function(){
 return this.x+this.y;
};
var m=new MathHandle(1,2);
console.log(m.add())
//class語法
class MathHandle {
 constructor(x,y){
 this.x=x;
 this.y=y;
}
 add(){
 return this.x+this.y;
 }
}
const m=new MathHandle(1,2);
console.log(m.add())

這兩者有什麼聯絡?其實這兩者本質是一樣的,只不過是語法糖寫法上有區別。所謂語法糖是指計算機語言中新增的某種語法,這種語法對語言的功能沒有影響,但是更方便程式設計師使用。比如這裡 class 語法糖讓程式更加簡潔,有更高的可讀性。

typeof MathHandle //"function"

MathHandle===MathHandle.prototype.constructor //true

對比在傳統建構函式和 ES6 中分別如何實現繼承:

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

Class 之間可以通過 extends 關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。

Class 和傳統建構函式有何區別

  • Class 在語法上更加貼合面向物件的寫法
  • Class 實現繼承更加易讀、易理解,對初學者更加友好
  • 本質還是語法糖,使用 prototype

十、Promise 的基本使用和原理

在 JavaScript 的世界中,所有程式碼都是單執行緒執行的。由於這個“缺陷”,導致 JavaScript 的所有網路操作,瀏覽器事件,都必須是非同步執行。Promise 是非同步程式設計的一種解決方案,比傳統的解決方案(回撥函式和事件)更合理和更強大。

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

ES6 中的 promise 的出現給我們很好的解決了回撥地獄的問題,所謂的回撥地獄是指當太多的非同步步驟需要一步一步執行,或者一個函式裡有太多的非同步操作,這時候就會產生大量巢狀的回撥,使程式碼巢狀太深而難以閱讀和維護。ES6 認識到了這點問題,現在 promise 的使用,完美解決了這個問題。

Promise 原理

一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise 物件的狀態改變,只有兩種可能:從 pending 變為 fulfilled 和從 pending 變為 rejected。promise 物件初始化狀態為 pending ;當呼叫 resolve(成功),會由 pending => fulfilled ;當呼叫 reject(失敗),會由 pending => rejected。具體流程見下圖:

ES6核心知識點概括說明,集大成者(收藏不看則虧系列)

Promise 的使用流程

  1. new Promise 一個例項,而且要 return
  2. new Promise 時要傳入函式,函式有 resolve reject 兩個引數
  3. 成功時執行 resolve,失敗時執行 reject
  4. then 監聽結果
function loadImg(src){
 const promise=new Promise(function(resolve,reject){
 var img=document.createElement('img')
 img.onload=function(){
 resolve(img)
 }
 img.onerror=function(){
 reject()
 }
 img.src=src
 })
 return promise//返回一個promise例項
}
var src="http://www.imooc.com/static/img/index/logo_new.png"
var result=loadImg(src)
result.then(function(img){
 console.log(img.width)//resolved(成功)時候的回撥函式
},function(){
 console.log("failed")//rejected(失敗)時候的回撥函式
})
result.then(function(img){
 console.log(img.height)
})

promise 會讓程式碼變得更容易維護,像寫同步程式碼一樣寫非同步程式碼,同時業務邏輯也更易懂。

十一、Iterator 和 for…of 迴圈

JavaScript 原有的表示“集合”的資料結構,主要是陣列(Array)和物件(Object),ES6 又添加了 Map 和 Set。這樣就需要一種統一的介面機制,來處理所有不同的資料結構。遍歷器(Iterator)就是這樣一種機制。它是一種介面,為各種不同的資料結構提供統一的訪問機制。任何資料結構只要部署 Iterator 介面,就可以完成遍歷操作(即依次處理該資料結構的所有成員)

1.Iterator 的作用:

  • 為各種資料結構,提供一個統一的、簡便的訪問介面;
  • 使得資料結構的成員能夠按某種次序排列
  • ES6 創造了一種新的遍歷命令 for…of 迴圈,Iterator 介面主要供 for…of 消費。

2.原生具備 iterator 介面的資料(可用 for of 遍歷)

  • Array
  • set 容器
  • map 容器
  • String
  • 函式的 arguments 物件
  • NodeList 物件
let arr3 = [1, 2, 'kobe', true];
for(let i of arr3){
 console.log(i); // 1 2 kobe true
}
let str = 'abcd';
for(let item of str){
 console.log(item); // a b c d
}
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
 console.log(e);
}
// Gecko
// Trident
// Webkit

3.幾種遍歷方式比較

  • for of 迴圈不僅支援陣列、大多數偽陣列物件,也支援字串遍歷,此外還支援 Map 和 Set 物件遍歷。
  • for in 迴圈可以遍歷字串、物件、陣列,不能遍歷 Set/Map
  • forEach 迴圈不能遍歷字串、物件,可以遍歷 Set/Map

十二、ES6 模組化

ES6 在語言標準的層面上,實現了模組功能,而且實現得相當簡單,旨在成為瀏覽器和伺服器通用的模組解決方案。其模組功能主要由兩個命令構成:export 和 import。export 命令用於規定模組的對外介面,import 命令用於輸入其他模組提供的功能。

/** 定義模組 math.js **/
var basicNum = 0;
var add = function (a, b) {
 return a + b;
};
export { basicNum, add };
/** 引用模組 **/
import { basicNum, add } from './math';
function test(ele) {
 ele.textContent = add(99 + basicNum);
}

如上例所示,使用 import 命令的時候,使用者需要知道所要載入的變數名或函式名,否則無法載入。為了給使用者提供方便,讓他們不用閱讀文件就能載入模組,就要用到 export default 命令,為模組指定預設輸出。

// export-default.js
export default function () {
 console.log('foo');
}

上面程式碼是一個模組檔案 export-default.js,它的預設輸出是一個函式。

其他模組載入該模組時,import 命令可以為該匿名函式指定任意名字。

// import-default.js
import customName from './export-default';
customName(); // 'foo'

上面程式碼的 import 命令,可以用任意名稱指向 export-default.js 輸出的方法,這時就不需要知道原模組輸出的函式名。需要注意的是,這時 import 命令後面,不使用大括號。