ES6核心特性 轉

摘要: 聊JS離不開ES6啊!

前言

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

一、開發環境配置

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

1. babel

爲啥需要babel?

ES6 提供了許多新特性,但並不是所有的瀏覽器都能夠完美支持。下圖是各個瀏覽器對ES6兼容性一覽表(以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": []
}

驗證babel配置是否成功

·創建./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文件。

如何配置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 聲明變量,新的聲明方式支持用大括號表示的塊級作用域,這會帶來一些好處:

不再需要立即執行的函數表達式(IIFE)

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

循環體中的閉包不再有問題

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

防止重複聲明變量

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

三、數組的擴展

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))將僞數組轉換爲數組

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(),並且不存在由於參數不同而導致的重載,而且他們的行爲非常統一。

數組實例的 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

數組實例的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

數組實例的 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指向,接下來我們詳細介紹:

縮減代碼

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]

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

使用注意點

  • 函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。
  • 不可以當作構造函數,也就是說,不可以使用new命令,否則會拋出一個錯誤。
  • 不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用 rest 參數代替。
  • 不可以使用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 新增瞭解構,這是將一個數據結構分解爲更小的部分的過程。

解構爲何有用?

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

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

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

這就是ES6爲何要給對象與數組添加解構。當把數據結構分解爲更小的部分時,從中提取你要的數據會變得容易許多。

對象

上個例子中如果採用對象解構的方法,就很容易獲取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");//不會報錯

數組

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

注意點

當使用解構來配合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;

再舉個例子,工作中常用到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的使用流程

  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 接口,就可以完成遍歷操作(即依次處理該數據結構的所有成員)

Iterator的作用:

  • 爲各種數據結構,提供一個統一的、簡便的訪問接口;
  • 使得數據結構的成員能夠按某種次序排列
  • ES6創造了一種新的遍歷命令for...of循環,Iterator接口主要供for...of消費。

原生具備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   

幾種遍歷方式比較

  • 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博客點贊和關注,感激不盡!

參考文章

關於Fundebug

Fundebug專注於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了9億+錯誤事件,得到了Google、360、金山軟件、百姓網等衆多知名用戶的認可。歡迎免費試用!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章