ES6核心特性

前言

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

image

一、開發(fā)環(huán)境配置

這部分著重介紹:babel 編譯 ES6 語法,如何用 webpack 實現(xiàn)模塊化。

1.babel

為啥需要 babel?

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

image

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em; overflow-wrap: break-word !important;">export各個瀏覽器兼容性一覽表</figcaption>

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

如何配置 babel?

·首先要先安裝node.js,運行npm init,然后會生成package.json文件·npm install --save-dev babel-core babel-preset-es2015 babel-preset-latest·創(chuàng)建并配置.babelrc文件//存放在項目的根目錄下,與node_modules同級·npm install -g babel-cli·babel-version

Babel 的配置文件是.babelrc,存放在項目的根目錄下。該文件用來設置轉(zhuǎn)碼規(guī)則和插件,具體內(nèi)容如下:

//.babelrc文件{    "presets": ["es2015", "latest"],    "plugins": []}

驗證配置是否成功

·創(chuàng)建./src/index.js·內(nèi)容:[1,2,3].map(item=>item+1);·運行babel./src/index.js

運行后得到以下部分,說明已經(jīng)成功配置了 babel

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

2.webpack

為啥要使用 WebPack?

現(xiàn)今的很多網(wǎng)頁其實可以看做是功能豐富的應用,它們擁有著復雜的 JavaScript 代碼和一大堆依賴包,??旎ぞ呔蛻\而生了,其中 webpack 功能強大深受人們喜愛。
Webpack 的工作方式是:把你的項目當做一個整體,通過一個給定的主文件(如:index.js),Webpack 將從這個文件開始找到你的項目的所有依賴文件,使用 loaders 處理它們,最后打包為一個(或多個)瀏覽器可識別的 JavaScript 文件。

image

如何配置 webpack?

·npm install webpack babel-loader --save-dev·創(chuàng)建并配置 webpack.config.js//webpack.config.js文件與package.json同級·配置 package.json中的scripts·運行 npm start
//配置 webpack.config.js  針對.js結(jié)尾的文件除了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 只有全局作用域和函數(shù)作用域(例如,我們必須將代碼包在函數(shù)內(nèi)來限制作用域),這導致很多問題:

情況 1:內(nèi)層變量覆蓋外層變量

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.不再需要立即執(zhí)行的函數(shù)表達式(IIFE)
在 ES5 中,我們需要構(gòu)造一個立即執(zhí)行的函數(shù)表達式去保證我們不污染全局作用域。在 ES6 中, 我們可以使用更簡單的大括號({}),然后使用 const 或者 let 代替 var 來達到同樣的效果。

2.循環(huán)體中的閉包不再有問題
在 ES5 中,如果循環(huán)體內(nèi)有產(chǎn)生一個閉包,訪問閉包外的變量,會產(chǎn)生問題。在 ES6,你可以使用 “l(fā)et” 來避免問題。

image

3.防止重復聲明變量
ES6 不允許在同一個作用域內(nèi)用 let 或 const 重復聲明同名變量。這對于防止在不同的 js 庫中存在重復聲明的函數(shù)表達式十分有幫助。

三、數(shù)組的擴展

1. Array.from() : 將偽數(shù)組對象或可遍歷對象轉(zhuǎn)換為真數(shù)組

如果一個對象的所有鍵名都是正整數(shù)或零,并且有 length 屬性,那么這個對象就很像數(shù)組,稱為偽數(shù)組。典型的偽數(shù)組有函數(shù)的 arguments 對象,以及大多數(shù) 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);//得到一個偽數(shù)組btns.forEach(item=>console.log(item)) Uncaught TypeError: btns.forEach is not a function</script>

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

Array.from(btns).forEach(item=>console.log(item))將偽數(shù)組轉(zhuǎn)換為數(shù)組
image

2.Array.of(v1, v2, v3) : 將一系列值轉(zhuǎn)換成數(shù)組

當調(diào)用 new Array( )構(gòu)造器時,根據(jù)傳入?yún)?shù)的類型與數(shù)量的不同,實際上會導致一些不同的結(jié)果, 例如:

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

當使用單個數(shù)值參數(shù)來調(diào)用 Array 構(gòu)造器時,數(shù)組的長度屬性會被設置為該參數(shù)。 如果使用多個參數(shù)(無論是否為數(shù)值類型)來調(diào)用,這些參數(shù)也會成為目標數(shù)組的項。數(shù)組的這種行為既混亂又有風險,因為有時可能不會留意所傳參數(shù)的類型。

ES6 引入了 Array.of( )方法來解決這個問題。該方法的作用非常類似 Array 構(gòu)造器,但在使用單個數(shù)值參數(shù)的時候并不會導致特殊結(jié)果。Array.of( )方法總會創(chuàng)建一個包含所有傳入?yún)?shù)的數(shù)組,而不管參數(shù)的數(shù)量與類型

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

Array.of 基本上可以用來替代 Array()或 newArray(),并且不存在由于參數(shù)不同而導致的重載,而且他們的行為非常統(tǒng)一。

3.數(shù)組實例的 find() 和 findIndex()

數(shù)組實例的 find 方法,用于找出第一個符合條件的數(shù)組成員。它的參數(shù)是一個回調(diào)函數(shù),所有數(shù)組成員依次執(zhí)行該回調(diào)函數(shù),直到找出第一個返回值為 true 的成員,然后返回該成員。如果沒有符合條件的成員,則返回 undefined。

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

數(shù)組實例的 findIndex 方法的用法與 find 方法非常類似,返回第一個符合條件的數(shù)組成員的位置,如果所有成員都不符合條件,則返回-1。

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

4.數(shù)組實例的 includes()

Array.prototype.includes 方法返回一個布爾值,表示某個數(shù)組是否包含給定的值。該方法的第二個參數(shù)表示搜索的起始位置,默認為 0。如果第二個參數(shù)為負數(shù),則表示倒數(shù)的位置,如果這時它大于數(shù)組長度(比如第二個參數(shù)為-4,但數(shù)組長度為 3),則會重置為從 0 開始。

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

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

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

5.數(shù)組實例的 entries(),keys() 和 values()

ES6 提供 entries(),keys()和 values(),用于遍歷數(shù)組。它們都返回一個遍歷器對象,可以用 for…of 循環(huán)進行遍歷,唯一的區(qū)別是 keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。

for (let index of ['a', 'b'].keys()) {  console.log(index);}// 0// 1for (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"http:// 1 "b"

四、箭頭函數(shù)

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

1. 縮減代碼

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

多個參數(shù)記得加括號

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

如果箭頭函數(shù)的代碼塊部分多于一條語句,就要使用大括號將它們括起來,并且使用 return 語句返回

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

由于大括號被解釋為代碼塊,所以如果箭頭函數(shù)直接返回一個對象,必須在對象外面加上括號,否則會報錯。

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

此外還有個好處就是簡化回調(diào)函數(shù)

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

2. 改變 this 指向

長期以來,JavaScript 語言的 this 對象一直是一個令人頭痛的問題,在對象方法中使用 this,必須非常小心。箭頭函數(shù)”綁定”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 函數(shù)里面又嵌了個函數(shù),這導致內(nèi)部的 this 的指向發(fā)生了錯亂。
那如何修改:

方法一、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 函數(shù)

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小組"]

方法三、 箭頭函數(shù)

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)函數(shù)體內(nèi)的 this 對象,就是定義時所在的對象,而不是使用時所在的對象。

(2)不可以當作構(gòu)造函數(shù),也就是說,不可以使用 new 命令,否則會拋出一個錯誤。

(3)不可以使用 arguments 對象,該對象在函數(shù)體內(nèi)不存在。如果要用,可以用 rest 參數(shù)代替。

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

五、rest 參數(shù)

ES6 引入 rest 參數(shù)(形式為…變量名),用于獲取函數(shù)的多余參數(shù),這樣就不需要使用 arguments 對象了。

rest 參數(shù)搭配的變量是一個數(shù)組,該變量將多余的參數(shù)放入數(shù)組中。
我們舉個例子:如何實現(xiàn)一個求和函數(shù)?

傳統(tǒng)寫法:

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

也可以與解構(gòu)賦值組合使用

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

rest 參數(shù)還可以與箭頭函數(shù)結(jié)合

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

注意:① 每個函數(shù)最多只能聲明一個 rest 參數(shù),而且 rest 參數(shù)必須是最后一個參數(shù),否則報錯。

②rest 參數(shù)不能用于對象字面量 setter 之中

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

六、展開運算符

與剩余參數(shù)關聯(lián)最密切的就是擴展運算符。剩余參數(shù)允許你把多個獨立的參數(shù)合并到一個數(shù)組中;而擴展運算符則允許將一個數(shù)組分割,并將各個項作為分離的參數(shù)傳給函數(shù)。

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

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

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

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

擴展運算符還可以與其他參數(shù)混用

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

擴展運算符拆解字符串與數(shù)組

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

還可以實現(xiàn)拼接

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"]

七、解構(gòu)賦值----更方便的數(shù)據(jù)訪問

ES6 新增了解構(gòu),這是將一個數(shù)據(jù)結(jié)構(gòu)分解為更小的部分的過程。

1.解構(gòu)為何有用?

在 ES5 及更早版本中,從對象或數(shù)組中獲取信息、并將特定數(shù)據(jù)存入本地變量,需要書寫許多并且相似的代碼。例如:

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

此代碼提取了 expense 對象的 type 與 amount 值,并將其存在同名的本地變量上。雖然 這段代碼看起來簡單,但想象一下若有大量變量需要處理,你就必須逐個為其賦值;并且若有一個嵌套的數(shù)據(jù)結(jié)構(gòu)需要遍歷以尋找信息,你可能會為了一點數(shù)據(jù)而挖掘整個結(jié)構(gòu)。

這就是 ES6 為何要給對象與數(shù)組添加解構(gòu)。當把數(shù)據(jù)結(jié)構(gòu)分解為更小的部分時,從中提取你要的數(shù)據(jù)會變得容易許多。

2.對象

上個例子中如果采用對象解構(gòu)的方法,就很容易獲取 expense 對象的 type 與 amount 值。

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

我們再來看個例子:

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

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

默認值:
可以選擇性地定義一個默認值,以便在指定屬性不存在時使用該值。若要這么做,需要在 屬性名后面添加一個等號并指定默認值,就像這樣:

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

嵌套對象解構(gòu):
使用類似于對象字面量的語法,可以深入到嵌套的對象結(jié)構(gòu)中去提取你想要的數(shù)據(jù)。

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); //    1console.log(start.column); //    1

本例中的解構(gòu)模式使用了花括號,表示應當下行到 node 對象的 loc 屬性內(nèi)部去尋找 start 屬性。

必須傳值的解構(gòu)參數(shù)

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

在此函數(shù)內(nèi),name 與 value 參數(shù)是必需的,而 secure、path、domain 與 expires 則不是。默認情況下調(diào)用函數(shù)時未給參數(shù)解構(gòu)傳值會拋出錯誤。像上例中如果 setCookie 不傳第三個參數(shù),就會報錯。若解構(gòu)參數(shù)是可選的,可以給解構(gòu)的參數(shù)提供默認值來處理這種錯誤。

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

3.數(shù)組

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

用{}解構(gòu)返回數(shù)組個數(shù)

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

數(shù)組解構(gòu)也可以用于賦值上下文,但不需要用小括號包裹表達式。這點跟對象解構(gòu)的約定不同。

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

默認值:數(shù)組解構(gòu)賦值同樣允許在數(shù)組任意位置指定默認值。當指定位置的項不存在、或其值為 undefined,那么該默認值就會被使用。

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

與 rest 參數(shù)搭配

在 ES5 中常常使用 concat()方法來克隆數(shù)組,例如:

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

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

//在ES6中克隆數(shù)組let colors = ["red", "green", "blue"];let [...clonedColors] = colors;console.log(clonedColors); //[red,green,blue]

接下我們看個例子:如何將數(shù)組轉(zhuǎn)化為對象

const points = [  [4,5],  [10,1],  [0,40]];//期望得到的數(shù)據(jù)格式如下,如何實現(xiàn)?// [//   {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);

混合解構(gòu)

const people = [  {name:"Henry",age:20},  {name:"Bucky",age:25},  {name:"Emily",age:30}];//es5 寫法var age = people[0].age;console.log(age);//es6 解構(gòu)const [age] = people;console.log(age);//第一次解構(gòu)數(shù)組 {name:"Henry",age:20}const [{age}] = people;//再一次解構(gòu)對象console.log(age);//20

4.注意點

當使用解構(gòu)來配合 var、let、const 來聲明變量時,必須提供初始化程序(即等號右邊的值)。下面的代碼都會因為缺失初始化程序而拋出語法錯誤:

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

八、模板字符串(template string)

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

let name = "Henry";function makeUppercase(word){  return word.toUpperCase();}let template =  `  <h1>${makeUppercase('Hello')}, ${name}!</h1>//可以存放函數(shù)和變量  <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;
image

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

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

九、Class 和傳統(tǒng)構(gòu)造函數(shù)有何區(qū)別

從概念上講,在 ES6 之前的 JS 中并沒有和其他面向?qū)ο笳Z言那樣的“類”的概念。長時間里,人們把使用 new 關鍵字通過函數(shù)(也叫構(gòu)造器)構(gòu)造對象當做“類”來使用。由于 JS 不支持原生的類,而只是通過原型來模擬,各種模擬類的方式相對于傳統(tǒng)的面向?qū)ο蠓绞絹碚f非常混亂,尤其是處理當子類繼承父類、子類要調(diào)用父類的方法等等需求時。
ES6 提供了更接近傳統(tǒng)語言的寫法,引入了 Class(類)這個概念,作為對象的模板。通過 class 關鍵字,可以定義類。但是類只是基于原型的面向?qū)ο竽J降恼Z法糖。

對比在傳統(tǒng)構(gòu)造函數(shù)和 ES6 中分別如何實現(xiàn)類:

//傳統(tǒng)構(gòu)造函數(shù)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())

這兩者有什么聯(lián)系?其實這兩者本質(zhì)是一樣的,只不過是語法糖寫法上有區(qū)別。所謂語法糖是指計算機語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程序員使用。比如這里 class 語法糖讓程序更加簡潔,有更高的可讀性。

typeof MathHandle //"function"MathHandle===MathHandle.prototype.constructor //true

對比在傳統(tǒng)構(gòu)造函數(shù)和 ES6 中分別如何實現(xiàn)繼承:

//傳統(tǒng)構(gòu)造函數(shù)繼承function Animal() {    this.eat = function () {        alert('Animal eat')    }}function Dog() {    this.bark = function () {        alert('Dog bark')    }}Dog.prototype = new Animal()// 綁定原型,實現(xiàn)繼承var hashiqi = new Dog()hashiqi.bark()//Dog barkhashiqi.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,它代表父類的構(gòu)造函數(shù),即Animal中的constructor        this.name = name    }    say() {        alert(this.name + ' say')    }}const dog = new Dog('哈士奇')dog.say()//哈士奇 saydog.eat()//哈士奇 eat

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

Class 和傳統(tǒng)構(gòu)造函數(shù)有何區(qū)別

  • Class 在語法上更加貼合面向?qū)ο蟮膶懛?/p>

  • Class 實現(xiàn)繼承更加易讀、易理解,對初學者更加友好

  • 本質(zhì)還是語法糖,使用 prototype

十、Promise 的基本使用和原理

在 JavaScript 的世界中,所有代碼都是單線程執(zhí)行的。由于這個“缺陷”,導致 JavaScript 的所有網(wǎng)絡操作,瀏覽器事件,都必須是異步執(zhí)行。Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案(回調(diào)函數(shù)和事件)更合理和更強大。

image

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em; overflow-wrap: break-word !important;">回調(diào)地獄</figcaption>

ES6 中的 promise 的出現(xiàn)給我們很好的解決了回調(diào)地獄的問題,所謂的回調(diào)地獄是指當太多的異步步驟需要一步一步執(zhí)行,或者一個函數(shù)里有太多的異步操作,這時候就會產(chǎn)生大量嵌套的回調(diào),使代碼嵌套太深而難以閱讀和維護。ES6 認識到了這點問題,現(xiàn)在 promise 的使用,完美解決了這個問題。

Promise 原理

一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結(jié)果。Promise 對象的狀態(tài)改變,只有兩種可能:從 pending 變?yōu)?fulfilled 和從 pending 變?yōu)?rejected。promise 對象初始化狀態(tài)為 pending ;當調(diào)用 resolve(成功),會由 pending => fulfilled ;當調(diào)用 reject(失敗),會由 pending => rejected。具體流程見下圖:

image

<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em; overflow-wrap: break-word !important;">Promise原理</figcaption>

Promise 的使用流程

  1. new Promise 一個實例,而且要 return

  2. new Promise 時要傳入函數(shù),函數(shù)有 resolve reject 兩個參數(shù)

  3. 成功時執(zhí)行 resolve,失敗時執(zhí)行 reject

  4. then 監(jiān)聽結(jié)果

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(成功)時候的回調(diào)函數(shù)},function(){    console.log("failed")//rejected(失敗)時候的回調(diào)函數(shù)})result.then(function(img){    console.log(img.height)})

promise 會讓代碼變得更容易維護,像寫同步代碼一樣寫異步代碼,同時業(yè)務邏輯也更易懂。

十一、Iterator 和 for…of 循環(huán)

JavaScript 原有的表示“集合”的數(shù)據(jù)結(jié)構(gòu),主要是數(shù)組(Array)和對象(Object),ES6 又添加了 Map 和 Set。這樣就需要一種統(tǒng)一的接口機制,來處理所有不同的數(shù)據(jù)結(jié)構(gòu)。遍歷器(Iterator)就是這樣一種機制。它是一種接口,為各種不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的訪問機制。任何數(shù)據(jù)結(jié)構(gòu)只要部署 Iterator 接口,就可以完成遍歷操作(即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員)

1.Iterator 的作用:

  • 為各種數(shù)據(jù)結(jié)構(gòu),提供一個統(tǒng)一的、簡便的訪問接口;

  • 使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列

  • ES6 創(chuàng)造了一種新的遍歷命令 for…of 循環(huán),Iterator 接口主要供 for…of 消費。

2.原生具備 iterator 接口的數(shù)據(jù)(可用 for of 遍歷)

  • Array

  • set 容器

  • map 容器

  • String

  • 函數(shù)的 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 循環(huán)不僅支持數(shù)組、大多數(shù)偽數(shù)組對象,也支持字符串遍歷,此外還支持 Map 和 Set 對象遍歷。

  • for in 循環(huán)可以遍歷字符串、對象、數(shù)組,不能遍歷 Set/Map

  • forEach 循環(huán)不能遍歷字符串、對象,可以遍歷 Set/Map

十二、ES6 模塊化

ES6 在語言標準的層面上,實現(xiàn)了模塊功能,而且實現(xiàn)得相當簡單,旨在成為瀏覽器和服務器通用的模塊解決方案。其模塊功能主要由兩個命令構(gòu)成:export 和 import。export 命令用于規(guī)定模塊的對外接口,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 命令的時候,用戶需要知道所要加載的變量名或函數(shù)名,否則無法加載。為了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到 export default 命令,為模塊指定默認輸出。

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

上面代碼是一個模塊文件 export-default.js,它的默認輸出是一個函數(shù)。
其他模塊加載該模塊時,import 命令可以為該匿名函數(shù)指定任意名字。

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

上面代碼的 import 命令,可以用任意名稱指向 export-default.js 輸出的方法,這時就不需要知道原模塊輸出的函數(shù)名。需要注意的是,這時 import 命令后面,不使用大括號。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 情商高從本質(zhì)上來看就是能在大多數(shù)場合中恰當?shù)乩斫鈩e人的感受并采取合適的方法進行回應。 那么針對這個定義,說明每個人...
    記憶教練趙俊祥閱讀 364評論 1 1
  • 很多股友問我:為什么要開股指賬戶? “賺錢!”沒錯,就喜歡最直接的回答。 什么是股指?通俗一點:一支可以買漲買跌、...
    有為_9f88閱讀 579評論 1 0

友情鏈接更多精彩內(nèi)容