我理解的ES6

前言

我是經過阮一峯老師的ES6教程入門的,基本上是把ES6的幾個核心特性過了一遍,可是面試官一問深我就???了,仍是實際運用的太少。javascript

本篇文章也偏總結類,結合我親身經歷的高頻面試題,建議你們必需要對箭頭函數、Promise、Generator、async等內容深刻理解。前端

工具:Babel是一個 ES6 轉碼器,能夠將 ES6 代碼轉爲 ES5 代碼,以便兼容那些還沒支持ES6的平臺。java

String字符串優化

新增了字符串模板,在拼接大段字符串時,用反斜槓取代以往的字符串相加的形式,能保留全部空格和換行,使得字符串拼接看起來更加直觀,更加優雅。es6

新增了includes()方法,用於取代傳統的只能用indexOf查找包含字符的方法, 此外還新增了startsWith(), endsWith(), padStart(),padEnd(),repeat()等方法,可方便的用於查找,補全字符串。面試

Array數組優化

數組解構賦值: 如ES6能夠直接以let [a,b,c] = [1,2,3]形式進行變量賦值,映射關係更清晰。編程

擴展運算符json

  • 能夠將一個數組轉爲用逗號分隔的參數序列。
console.log(...[1, 2, 3])  // 1 2 3
複製代碼
  • 能夠實現數組的複製和解構賦值 (let a = [2,3,4]; let b = [...a])數組

  • 能夠取代arguments對象和apply方法,輕鬆獲取未知參數個數狀況下的參數集合。promise

使用擴展運算符替代函數的apply方法:bash

// ES5 的寫法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6 寫法
let args = [0, 1, 2];
f(...args);
複製代碼

JS中遍歷數組的方法:

  1. for循環
  2. forEach
myArray.forEach(function(value){
    console.log(value);
})
複製代碼

沒法中途跳出forEach循環,break命令或return命令都不能奏效。

  1. for... in

for…in主要是爲遍歷對象而設計的,不適用於遍歷數組。

遍歷數組的缺點:

  • 數組的鍵名是數字,可是for…in循環是以字符串做爲鍵名的。
  • 某些狀況下,for…in循環會以任意順序遍歷鍵名。
  1. for...of (ES6)
for(let value of myArray){
    console.log(value);
}
複製代碼
  • 不一樣用於forEach方法,它能夠與break、continue和return配合使用。
  • 提供了遍歷全部數據結構的統一操做接口。

for of 和 for in 的總結:

  • 推薦在循環對象屬性的時候,使用for...in, 在遍歷數組的時候的時候使用for...of。
  • for...in循環出的是key,for...of循環出的是value
  • 注意,for...of是ES6新引入的特性。修復了ES5引入的for...in的不足
  • for...of不能循環普通的對象,須要經過和Object.keys()搭配使用

Object類型優化

  1. 對象屬性變量式聲明:

ES6能夠直接以變量形式聲明對象屬性或者方法。比傳統的鍵值對形式聲明更加簡潔,更加方便,語義更加清晰。

let [apple, orange] = ['red appe', 'yellow orange'];
let myFruits = {apple, orange};    
// let myFruits = {apple: 'red appe', orange: 'yellow orange'};
複製代碼
  1. 對象的擴展運算符(...)

可將一個數組轉爲用逗號分隔的參數序列,主要用於函數調用。 console.log(...[1, 2, 3]) // 1 2 3

  1. super 關鍵字

ES6在Class類裏新增了相似this的關鍵字super。同this老是指向當前函數所在的對象不一樣,super關鍵字老是指向當前函數所在對象的原型對象。

箭頭函數

基本使用:

若是 return 值就只有一行表達式,能夠省去 return,默認表示該行是返回值,不然須要加一個大括號和 return。若是參數只有一個,也能夠省去括號,兩個則須要加上括號。

var f = v => v*2;
// 等價於
var f = function(v){
  return v*2;
}

// 判斷偶數
var isEven = n => n % 2 == 0;

// 須要加 return
var = (a, b) => {
  if(a >= b)
    return a;
  return b;
}
複製代碼

ES6 引入 rest 參數(形式爲...變量名),用於獲取函數的多餘參數,這樣就不須要使用arguments對象了。

rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。

//利用 rest 參數,能夠向該函數傳入任意數目的參數。
function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }
  return sum;
}
add(2, 5, 3) // 10
複製代碼

面試問題:箭頭函數和普通函數的區別

  1. 箭頭函數沒有本身的this對象

函數中的 this 始終是指向函數執行時所在的對象。好比全局函數執行時,this 指向 window,對象的方法執行時,this 指向該對象,這就是函數 this 的可變。

而箭頭函數中的 this 是固定的,箭頭函數繼承本身做用域的上一層的this,就是上一級外部函數的 this 的指向。任何方法都改變不了其指向,如call(), bind(), apply()。

一個例子:

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;
foo.call({ id: 42 });  // id: 42
複製代碼

執行的結果是 42 而不是全局的 21,表示 setTimeout 函數執行的時候,this 指向的不是 window。所以箭頭函數很好地解決了匿名函數和setTimeoutsetInterval的this指向問題,不用再去給其用that變量存儲this。

對箭頭函數中關於 this 的總結:在對象的方法中直接使用箭頭函數,會指向 window,其餘箭頭函數 this 會指向上一層的 this,箭頭函數並無存儲 this。

var obj = {
  id: 1,
  foo: ()=>{
    return this.id;
  }
}
var id = 2;
obj.foo(); // 2
複製代碼
  1. 箭頭函數不能當作構造函數,不能使用new,由於它沒有本身的this,沒法實例化。
  2. 箭頭函數不綁定arguments, 取而代之用rest參數(形式爲...變量名)。也沒有supernew.target
  3. 不可使用yield命令,箭頭函數不可用做Generator函數。
  4. 箭頭函數沒有原型屬性。

Set 和 Map

  1. Set

ES6引入的一種相似Array的新的數據結構,Set實例的成員相似於數組item成員,區別是Set實例的成員都是惟一,不重複的。這個特性能夠輕鬆地實現數組去重

Set自己是一個構造函數,用來生成 Set 數據結構。

const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4
複製代碼
  1. Map

JavaScript 的對象(Object),本質上是鍵值對的集合,可是傳統上只能用字符串看成鍵。這給它的使用帶來了很大的限制。

Map 是ES6引入的一種相似Object的新的數據結構,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"
複製代碼

let 和 const

沒有塊級做用域回來帶不少難以理解的問題,好比for循環var變量泄露,變量覆蓋等問題。

let 聲明的變量擁有本身的塊級做用域,形如for (let x...)的循環在每次迭代時都爲x建立新的綁定。且修復了var聲明變量帶來的變量提高問題。(必須聲明 'use strict' 後才能使用let聲明變量,不然瀏覽並不能顯示結果)

「變量提高」現象:即變量能夠在聲明以前使用,值爲undefined。爲了糾正這種現象,let命令改變了語法行爲,它所聲明的變量必定要在聲明後使用,不然報錯。

ES5 只有全局做用域和函數做用域,沒有塊級做用域。

塊級做用域的出現,實際上使得得到普遍應用的當即執行函數表達式(IIFE)再也不必要了。

// IIFE 寫法
(function () {
  var tmp = ...;
  ...
}());

// 塊級做用域寫法
{
  let tmp = ...;
  ...
}
複製代碼

const 聲明一個只讀的常量。一旦聲明,常量的值就不能改變。 const 聲明的變量不得改變值,這意味着,const一旦聲明變量,就必須當即初始化。

const的做用域與let命令相同:只在聲明所在的塊級做用域內有效。

總結: 使用var聲明的變量,其做用域爲該語句所在的函數內,且存在變量提高現象; 使用let聲明的變量,其做用域爲該語句所在的代碼塊內,不存在變量提高; 使用const聲明的是常量,在後面出現的代碼中不能再修改該常量的值。

Promise

主要做用是用來解決JS回調機制產生的「回調地獄」。 回調地獄帶來的負面做用有如下幾點:

  • 代碼臃腫, 可讀性差, 複用性差, 容易滋生 bug。
  • 耦合度太高,可維護性差。
  • 只能在回調裏處理異常。

Promise它不是新的語法功能,而是一種新的寫法,將回調函數的嵌套,改爲鏈式調用。

new Promise(請求1)
    .then(請求2(請求結果1))
    .then(請求3(請求結果2))
    .catch(處理異常(異常信息))
複製代碼

Promise 使用總結:

  1. 能夠經過兩種方式初始化一個 Promise 對象,都會返回一個 Promise 對象。
  • new Promise(fn)
  • Promise.resolve(fn)
  1. 而後調用上一步返回的 promise 對象的 then 方法,註冊回調函數。 then 中的回調函數能夠有一個參數,也能夠不帶參數。若是 then 中的回調函數依賴上一步的返回結果,那麼要帶上參數。

  2. 最後註冊 catch 異常處理函數,處理前面回調中可能拋出的異常。

簡單例子:

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}

timeout(100).then((value) => {
  console.log(value);
});
複製代碼

timeout方法返回一個Promise實例,表示一段時間之後纔會發生的結果。過了指定的時間(ms參數)之後,Promise實例的狀態變爲resolved,就會觸發then方法綁定的回調函數。

一些經常使用API:

Promise.race

類方法,多個 Promise 任務同時執行,返回最早執行結束的 Promise 任務的結果,無論這個 Promise 結果是成功仍是失敗。

Promise.all

類方法,多個 Promise 任務同時執行。 若是所有成功執行,則以數組的方式返回全部 Promise 任務的執行結果。 若是有一個 Promise 任務 rejected,則只返回 rejected 任務的結果。

若是後續任務是異步任務的話,必須return 一個 新的 promise 對象。若是後續任務是同步任務,只需 return 一個結果便可。

new Promise(買菜)
//用買好的菜作飯
.then((買好的菜)=>{
    return new Promise(作飯);
})
複製代碼

一個 Promise 對象有三個狀態,而且狀態一旦改變,便不能再被更改成其餘狀態:

  • pending,異步任務正在進行。
  • resolved (也能夠叫fulfilled),異步任務執行成功。
  • rejected,異步任務執行失敗。

generator 以及 async/await 語法使異步處理更加接近同步代碼寫法,可讀性更好,同時異常捕獲和同步代碼的書寫趨於一致。

(async ()=>{
    let 蔬菜 = await 買菜();
    let 飯菜 = await 作飯(蔬菜);
    let 送飯結果 = await 送飯(飯菜);
    let 通知結果 = await 通知我(送飯結果);
})();
複製代碼

Generator

Generator 函數會返回一個遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。

形式上,Generator 函數是一個普通函數,可是有兩個特徵。

  • function關鍵字與函數名之間有一個星號;
  • 函數體內部使用yield表達式,定義不一樣的內部狀態。
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }
複製代碼

Generator 函數的調用方法與普通函數同樣,也是在函數名後面加上一對圓括號。不一樣的是,調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,也就是上一章介紹的遍歷器對象。

下一步,必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法能夠恢復執行。

async和await

ES2017 標準引入了 async 函數,使得異步操做變得更加方便。

async 函數就是 Generator 函數的語法糖。

async函數就是將 Generator 函數的星號(*)替換成async,將yield替換成await。而且返回一個 Promise,可使用then方法添加回調函數。 當函數執行的時候,一旦遇到await就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。

async函數對 Generator 函數的改進:

  1. 內置執行器 async函數自帶執行器,不像 Generator 函數,須要調用next方法,或者用co模塊,才能真正執行。
  2. 更好的語義
  3. 更好的適用性
  4. 返回值是 Promise 進一步說,async函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。

例子:getJSON函數返回一個promise,這個promise成功resolve時會返回一個json對象。咱們只是調用這個函數,打印返回的JSON對象,而後返回」done」。

// promise
const makeRequest = () =>
  getJSON()
    .then(data => {
      console.log(data)
      return "done"
    })
makeRequest()
複製代碼
//使用Async/Await
const makeRequest = async () => {
  console.log(await getJSON())
  return "done"
}
makeRequest()

//async函數會隱式地返回一個promise,該promise的reosolve值就是函數return的值。(示例中reosolve值就是字符串」done」)
複製代碼

Async的優缺點:

優點: 處理 then 的調用鏈可以更清晰準確的寫出代碼。

缺點: 濫用 await 可能會致使性能問題,由於 await 會阻塞代碼,也許以後的異步代碼並不依賴於前者,但仍然須要等待前者完成,致使代碼失去了併發性。

Iterator

是ES6中一個很重要概念,它並非對象,也不是任何一種數據類型。爲Set、Map、Array、Object新增一個統一的遍歷API。部署了Iterator接口的對象(可遍歷對象)均可以經過for...of去遍歷。

class

ES6 的class能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已,能夠看作是構造函數的另外一種寫法。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

//ES6的class改寫
class Point {
  //構造方法
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
複製代碼

上面代碼定義了一個「類」,能夠看到裏面有一個constructor方法,這就是構造方法,而this關鍵字則表明實例對象。也就是說,ES5 的構造函數Point,對應 ES6 的Point類的構造方法。

Point類除了構造方法,還定義了一個toString方法。注意,定義「類」的方法的時候,前面不須要加上function這個關鍵字,直接把函數定義放進去了就能夠了。另外,方法之間不須要逗號分隔,加了會報錯。

class實現繼承: Class 能夠經過extends關鍵字實現繼承,這比 ES5 的經過修改原型鏈實現繼承,要清晰和方便不少。

總結

平常前端代碼開發中,有哪些值得用ES6去改進的編程優化或者規範:

  • 經常使用箭頭函數來取代var self = this;的作法。
  • 經常使用let取代var命令。
  • 經常使用數組/對象的結構賦值來命名變量,結構更清晰,語義更明確,可讀性更好。
  • 在長字符串多變量組合場合,用模板字符串來取代字符串累加,能取得更好地效果和閱讀體驗。
  • 用Class類取代傳統的構造函數,來生成實例化對象。
  • 在大型應用開發中,要保持module模塊化開發思惟,分清模塊之間的關係,經常使用import、export方法。
相關文章
相關標籤/搜索