ES6核心特性

浪裡行舟發表於2018-05-22

前言

ES6 雖提供了許多新特性,但我們實際工作中用到頻率較高並不多,根據二八法則,我們應該用百分之八十的精力和時間,好好專研這百分之二十核心特性,將會收到事半功倍的奇效!寫文章不容易,請大家多多支援與關注!本文首發地址GitHub部落格(含思維導圖)

ES6核心特性

一、開發環境配置

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

1.babel

為啥需要babel?

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

export各個瀏覽器相容性一覽表

由上圖可知,有些瀏覽器對於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?

·npm install webpack babel-loader --save-dev
·建立並配置 webpack.config.js//webpack.config.js檔案與package.json同級
·配置 package.json中的scripts
·執行 npm start
複製程式碼
//配置 webpack.config.js  針對.js結尾的檔案除了node_modules都用babel解析
module.exports = {
    entry: './src/index.js',
    output: {
        path: __dirname,
        filename: './build/bundle.js'
    },
    module: {
        rules: [{
            test: /\.js?$/,
            exclude: /(node_modules)/,
            loader: 'babel-loader'
        }]
    }
}
複製程式碼
//配置 package.json中的scripts
"scripts": {
    "start": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
複製程式碼

二、塊級作用域

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()方法來提供一種明確清晰的方式以解決這方面的需求。

Array.from(btns).forEach(item=>console.log(item))將偽陣列轉換為陣列
複製程式碼

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 中分別如何實現繼承:

//傳統建構函式繼承
function Animal() {
    this.eat = function () {
        alert('Animal eat')
    }
}
function Dog() {
    this.bark = function () {
        alert('Dog bark')
    }
}
Dog.prototype = new Animal()// 繫結原型,實現繼承
var hashiqi = new Dog()
hashiqi.bark()//Dog bark
hashiqi.eat()//Animal eat
複製程式碼
//ES6繼承
class Animal {
    constructor(name) {
        this.name = name
    }
    eat() {
        alert(this.name + ' eat')
    }
}
class Dog extends Animal {
    constructor(name) {
        super(name) // 有extend就必須要有super,它代表父類的建構函式,即Animal中的constructor
        this.name = name
    }
    say() {
        alert(this.name + ' say')
    }
}
const dog = new Dog('哈士奇')
dog.say()//哈士奇 say
dog.eat()//哈士奇 eat
複製程式碼

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

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

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

十、Promise的基本使用和原理

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

回撥地獄

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

Promise原理

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

Promise原理

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命令後面,不使用大括號。

如果覺得文章對你有些許幫助,歡迎在我的GitHub部落格點贊和關注,感激不盡!

參考文章

ES6筆記(一):ES6所改良的javascript“缺陷”

在 ES6 中 改良的 5 個 JavaScript “缺陷”

ECMAScript 6 入門

深入理解ES6

ES6的rest引數和擴充套件運算子

相關文章