重溫ES6核心概念和基本用法

ES6在2015年6月就得以批准,至今已兩年了。近一年多以來陸續看過不少ES6的資料,工做項目中也逐步的用上了不少ES6的特性(let,const,promise,Template strings,Class,箭頭函數等等),不得不說,這些特性給開發帶來了很是多的便利。可是作決定個人ES6知識其實並不夠系統,這也是寫本文的初衷,但願閱讀本文能讓你也能對ES6有更系統的理解,本文並非那種大而全的教程,而是但願在實際工做中,能想起某個新特性能夠解決你當前的問題或者優化當前的代碼,以後再系統學習,應用,用過了確定就會真的掌握了。javascript

本文基於Github上的高贊文章ECMAScript 6 Features/Babel修改過的Learn ES2015,翻譯(寫做?)期間,重溫了阮一峯老師的ECMAScript 6 入門前端

ES6新特性列表

相比ES5,ES6提供了太多的更新,簡單說來,主要爲如下方面(你們能夠依據本身不算清晰的點選擇性查看本文):java

  • Arrows,箭頭函數,jquery

  • Classes,類git

  • Enhanced object literals,加強的對象字面值es6

  • Template strings:模板字符串github

  • Destructuring:解構算法

  • Default + rest + spread:參數默認值,rest參數,擴展運算符編程

  • Let + const:命名聲明的新方式json

  • Iterators + for..of:遍歷器

  • Generators:生成器

  • Unicode:更普遍的編碼支持

  • Modules:語言層面上支持的模塊機制

  • Module loaders:模塊加載器

  • Map + set + weakmap + weakset:新的數據結構

  • Proxies:代理器

  • Symbols:新的基本類型,獨一無二的值

  • Subclassable built-ins:類的繼承

  • Promises:

  • Math + number + string + array + object apis:拓展了一些內置對象的方法

  • Binary and octal literals:二進制八進制字面量

  • Reflect api:操做對象的新api

  • Tail calls:尾調用

Arrows箭頭函數

箭頭函數使用相似於=>這樣的語法定義函數,支持表達式模式和語句模式,不過其最大特色在於和父做用域具備同樣的this。咱們知道普通函數的this 既不指向函數自身也不指向函數的詞法做用域,this 其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏被調用。使用箭頭函數時不再用擔憂this跳來跳去了。
此外若是箭頭函數若是定義在另外一個函數裏面,箭頭函數會共享它父函數的arguments變量。

// 表達式模式箭頭函數
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);
var pairs = evens.map(v => ({even: v, odd: v + 1}));

// 語句模式箭頭函數
nums.forEach(v => {
  if (v % 5 === 0)
    fives.push(v);
});

// 和父做用域具備相同的this
var bob = {
  _name: "Bob",
  _friends: [],
  printFriends() {
    this._friends.forEach(f =>
      console.log(this._name + " knows " + f));
  }
}

function square() {
  let example = () => {
    let numbers = [];
    for (let number of arguments) {
      numbers.push(number * number);
    }

    return numbers;
  };

  return example();
}

square(2, 4, 7.5, 8, 11.5, 21); // returns: [4, 16, 56.25, 64, 132.25, 441]

Classes

JavaScript中其實並不存在真正的類,ES6的類實際上是基於原型鏈模擬面向對象的一種語法糖。其本質上能夠看作是構造函數的另外一種寫法。
與真的類同樣,它支持super繼承,實例,靜態方法和constructor方法。
若是你也使用React,工做中定義模塊時必定沒少寫過class A extends React.Component{}吧。

// 定義類
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
// 經過extends關鍵字實現繼承
class SkinnedMesh extends THREE.Mesh {
  //constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。
  //一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。
  constructor(geometry, materials) {
    // super表示父類的構造函數,用來新建父類的this對象,
    // 子類必須在constructor方法中調用super方法,不然新建實例時會報錯。若是不調用super方法,子類就得不到this對象。
    super(geometry, materials);

    //在構造方法中綁定this,能夠防止實例找不到this
    this.idMatrix = SkinnedMesh.defaultMatrix();
    this.bones = [];
    this.boneMatrices = [];
    //...
  }
  
  // 非定義在this上的方法都會被直接定義在原型鏈上
  update(camera) {
    //...
    // super在此處做爲對象,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。
    super.update();
  }
  // 可使用get和set關鍵字,對某個屬性設置存值函數和取值函數
  get boneCount() {
  // 類的方法內部若是含有this,它默認指向類的實例
    return this.bones.length;
  }
  set matrixType(matrixType) {
    this.idMatrix = SkinnedMesh[matrixType]();
  }
  
  // 加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用
  static defaultMatrix() {
    return new THREE.Matrix4();
  }
}

// 類的全部實例共享一個原型對象
let skin = new SkinnedMesh();
// 靜態方法須要直接經過類調用
SkinnedMesh.defaultMatrix()

對象的拓展

ES6中對象的使用方法得以拓展,主要包括如下幾點:

  • 屬性和方法能夠簡潔表示;

  • 容許以表達式的模式定義屬性名;

  • 能夠經過__proto__讀取或設置當前對象的prototype對象;

  • 使用Object.is({},{})判斷兩個對象是否徹底相對,相似於===;

  • Object.assign(target, source1, source2)合併對象;(淺拷貝)

var obj = {
    // __proto__用以設置當前對象的prototype對象,不推薦使用,推薦使用Object.setPrototypeOf() 
    __proto__: theProtoObj,
    //‘handler:handler’可簡寫爲handler(只須要寫變量名就能夠實現變量名爲變量名,變量值爲屬性值)
    handler,
    // 簡寫在定義方法的時候一樣有效
    toString() {
     // Super calls
     return "d " + super.toString();
    },
    // 方括號內的表達式用以計算屬性名
    [ 'prop_' + (() => 42)() ]: 42
};

模板字符串

模板字符串是一種組合字符串的語法糖,其使用相似於Perl,Python等語言的字符串修改方法相似,它的出現讓咱們拼合字符串時方便多了。目前相互中幾乎全部字符串的拼接都用這個了,異常方便。

  • 模板字符串定義在兩個反撇號中;

  • 在模板字符串中能夠直接換行,格式會得以保留;

  • 經過${}能夠很方便的在模板字符串中添加變量;

// 把字符串放在``(注意不是引號)中就可使用
`In JavaScript '\n' is a line-feed.`

// 模板字符串保留了換行
`In JavaScript this is
 not legal.`

// 在字符串中添加變量的方法,變量直接放在${}中便可
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// 拼合請求時異常方便了
POST`http://foo.org/bar?a=${a}&b=${b}
     Content-Type: application/json
     X-Credentials: ${credentials}
     { "foo": ${foo},
       "bar": ${bar}}`(myOnReadyStateChangeHandler);

Destructuring 解構

解構使用模式匹配的方法綁定變量和值,數組和對象均可使用。解構在綁定失敗的時會實現軟綁定,即沒有匹配值時,返回undefined。使用方法可見示例:

// 數組解構
var [a, , b] = [1,2,3];
// a = 1,b = 3

// React中常見如下用法
var {a, b, c} = this.props;

// 對象解構也能用在函數的參數中
function g({name: x}) {
  console.log(x);
}
g({name: 5})

// 綁定失敗時返回undefined
var [a] = [];
a === undefined;

// 解構時也能夠綁定默認值
var [a = 1] = [];
a === 1;

// 配合默認參數使用結構
function r({x, y, w = 10, h = 10}) {
  return x + y + w + h;
}
r({x:1, y:2}) === 23

默認值,剩餘值和拓展值

  • ES6容許咱們在給變量添加默認值

  • 使用拓展值使得函數調用時可傳入數組做爲連續的參數

  • 利用剩餘值特性咱們能夠把函數尾部的參數轉換爲一個數組,如今使用rest就能夠替換之前的arguments對象了。

// 給函數的參數添加默認值
function f(x, y=12) {
  // y is 12 if not passed (or passed as undefined)
  return x + y;
}
// 能夠只傳參數x的值了
f(3) == 15

// 使用rest
function f(x, ...y) {
  // y is an Array
  return x * y.length;
}
f(3, "hello", true) == 6

// 傳入數組做爲參數
function f(x, y, z) {
  return x + y + z;
}
// 直接傳入數組看成上面函數的參數
f(...[1,2,3]) == 6

Let 和 Const

ES6新增了塊做用域,新增了兩種定義變量的方法,定義變量時推薦使用let替代varlet定義的變量在塊做用域內有效,const用以指定固定值,這兩類新定義的變量不容許在定義前使用,也不容許重複定義。

function f() {
  {
    let x;
    {
      const x = "sneaky";
      // 改變const
      x = "foo";
    }
    // 重複定義會出錯
    let x = "inner";
  }
}

// 在這裏想到一個使用var時新手特別容易犯的問題
for (var i=0; i<10; ++i) {
    setTimeout(function(){
        console.log(i);
    }, i*1000);
}
// 使用var 全部的結果都是10
// 使用let 結果就是預想要的結果
for (let i=0; i<10; ++i) {
    setTimeout(function(){
        console.log(i);
    }, i*1000);
}

Iterators + For..Of

ES6爲部署了Iterator接口的各類不一樣的數據結構提供了統一的訪問機制。其本質是一個指針對象。每次調用next方法,能夠把指針指向數據結構的下一個成員。具體說來,每一次調用next方法,都會返回數據結構的當前成員的信息(一個包含value和done兩個屬性的對象,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束)。

凡是部署了Symbol.iterator屬性的數據結構,就稱爲部署了遍歷器接口。調用這個接口,就會返回一個遍歷器對象。

let fibonacci = {
  // 一個數據結構只要具備Symbol.iterator屬性,就可被認爲是可遍歷的,`Symbol.iterator`是一個表達式,返回Symbol對象的iterator屬性,因此須要放在[]中,本質上它是當前數據結構的遍歷器生成函數。
  [Symbol.iterator]() {
    let pre = 0, cur = 1;
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        return { done: false, value: cur }
      }
    }
  }
}

// fibonacci部署了Symbol.iterator屬性,只要done不爲true就會一直遍歷
for (var n of fibonacci) {
// 調用1000之內的值作遍歷
  if (n > 1000)
    break;
  console.log(n);
}

原生具有Iterator接口的數據結構有如下幾種:數組、某些相似數組的對象(字符串、DOM NodeList 對象、arguments對象)、Set和Map結構。

對象(Object)之因此沒有默認部署Iterator接口,是由於對象的哪一個屬性先遍歷,哪一個屬性後遍歷是不肯定的,須要開發者手動在Symbol.iterator的屬性上部署遍歷器生成方法(原型鏈上的對象具備該方法也可)。

實際使用時需引入polyfill

Generators

能夠從兩個角度理解Generators,它既是狀態機也是一個遍歷器對象生成函數。執行該函數能夠理解爲啓動了遍歷器,以後每次執行next()函數則每次執行到yield處。

值得注意的是執行next()時可添加參數,這實現了在函數運行的不一樣階段,能夠從外部向內部注入不一樣的值,

生成器使用function*yield簡化了迭代過程,使用function*定義的函數返回了一個生成器實例。
生成器是迭代器的子類,可是包含nextthrow。這使得值能夠迴流到生成器,yield是一個能夠返回值的表達式。

for...of循環能夠自動遍歷 Generator 函數時生成的Iterator對象,此時再也不須要調用next方法。

Generatorreturn方法會返回固定的值,終結遍歷Generator函數。返回值的value屬性就是return方法的參數,返回值的done屬性爲true。

結合co模塊能夠實現比Promise更加優雅的異步調用方式

// 使用generator函數實現上述遍歷器對象
var fibonacci = {
  [Symbol.iterator]: function*() {
    var pre = 0, cur = 1;
    for (;;) {
      var temp = pre;
      pre = cur;
      cur += temp;
      yield cur;
    }
  }
}

for (var n of fibonacci) {
  // truncate the sequence at 1000
  if (n > 1000)
    break;
  console.log(n);
}

// 使用co模塊(基於 Promise 對象的自動執行器),能夠實現異步函數的自動執行
var gen = function* () {
  var f1 = yield somethingAsync();
  var f2 = yield anotherThingAsync();
};

var co = require('co');
co(gen);

實際使用時需引入polyfill

Unicode

ES6完整支持全部的Unicode,包括新的Unicode字面量和u模式正則,提供了新的API來處理21bit級別的字符串。這些新加特性使得咱們的JavaScript應用有能力支持各類語言。

// same as ES5.1
"?".length == 2

// 新的正則匹配模式
"?".match(/./u)[0].length == 2

// 新形式
"\u{20BB7}"=="?"=="\uD842\uDFB7"

// codePointAt()可以正確處理4個字節儲存的字符,返回一個字符的碼點
"?".codePointAt(0) == 0x20BB7

// for-of 遍歷字符,以總體輸出
for(var c of "?") {
  console.log(c);
}
// ?

咱們也能夠在JS中寫出Emoji了,頗有趣,對不對:

clipboard.png

Modules

現代JS應用的開發離不開模塊了,ES6對模塊的定義提供了語言層面的支持。規範化了各類JavaScript模塊加載器,支持運行時動態加載模塊,支持異步加載模塊。

ES6 模塊的設計思想,是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量,效率要比 CommonJS 模塊的加載方式高。

// lib/math.js 模塊的定義
export function sum(x, y) {
  return x + y;
}
export var pi = 3.141593;

// app.js 模塊的所有引用
import * as math from "lib/math";
alert("2π = " + math.sum(math.pi, math.pi));

// otherApp.js 模塊的部分引用
import {sum, pi} from "lib/math";
alert("2π = " + sum(pi, pi));

// 模塊導出方法
// lib/mathplusplus.js
export * from "lib/math";
export var e = 2.71828182846;
export default function(x) {
    return Math.log(x);
}

// 混合引入方法
import ln, {pi, e} from "lib/mathplusplus";
alert("2π = " + ln(e)*pi*2);

Module Loaders(其實並不是ES6標準的一部分,只是草案)

模塊加載器支持如下功能:

  • 動態加載

  • 狀態隔離

  • 全局命名空間隔離

  • 編寫鉤子

  • 嵌套

默認的模塊加載器能夠被配置,新的加載器能夠被配置來評估加載獨立上下文中的內容。

// 動態加載 – ‘System’ 是默認的加載器
System.import('lib/math').then(function(m) {
  alert("2π = " + m.sum(m.pi, m.pi));
});

// 新的加載器建立了執行沙盒
var loader = new Loader({
  global: fixup(window) // replace ‘console.log’
});
loader.eval("console.log('hello world!');");

// 能夠直接修改模塊的緩存
System.get('jquery');
System.set('jquery', Module({$: $})); // WARNING: not yet finalized

Map Set WeakMap WeakSet

ES6爲算法提供了新的高效的數據結構,WeakMaps提供了防泄漏的鍵值對錶。

// Set相似於數組,可是成員的值都是惟一的,沒有重複的值。
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;

// Map 相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;

// WeakMap結構與Map結構相似,也是用於生成鍵值對的集合,可是WeakMap只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名,此外WeakMap的鍵名所指向的對象,不計入垃圾回收機制。
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined

// WeakSet 結構與 Set 相似,也是不重複的值的集合,可是WeakSet 的成員只能是對象,而不能是其餘類型的值,此外WeakSet 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet 對該對象的引用
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set

實際使用時需引入polyfill

Proxies

Proxy 用於修改某些操做的默認行爲,等同於在語言層面作出修改,因此屬於一種「元編程」(meta programming),即對編程語言進行編程。

能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。

須要注意的是目前未被Babel支持,使用時需謹慎

// target參數表示所要攔截的目標對象;
var target = {};

// handler參數也是一個對象,用來定製攔截行爲;
var handler = {
  get: function (receiver, name) {
    return `Hello, ${name}!`;
  }
};

// 生成一個Proxy實例
var p = new Proxy(target, handler);
p.world === 'Hello, world!';


// 對函數一樣可使用代理
var target = function () { return 'I am the target'; };
var handler = {
  apply: function (receiver, ...args) {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);
p() === 'I am the proxy';

// Proxy支持的攔截操做以下

var handler =
{
  get:...,
  set:...,
  has:...,
  deleteProperty:...,
  apply:...,
  construct:...,
  getOwnPropertyDescriptor:...,
  defineProperty:...,
  getPrototypeOf:...,
  setPrototypeOf:...,
  enumerate:...,
  ownKeys:...,
  preventExtensions:...,
  isExtensible:...
}

Babel 不支持,使用時應注意

Symbols

Symbol保證每一個屬性的名字都是獨一無二的,這樣就從根本上防止了屬性名的衝突;
它是一種相似於字符串的數據類型,Symbol函數能夠接受一個字符串做爲參數,表示對 Symbol 實例的描述;
Symbols是惟一的,單並不是私有的,經過Object.getOwnPropertySymbols能夠獲取對應的值;
Symbol 值做爲對象屬性名時,不能用點運算符。

var MyClass = (function() {

  // module scoped symbol
  var key = Symbol("key");

  function MyClass(privateData) {
    this[key] = privateData;
  }

  MyClass.prototype = {
    doStuff: function() {
      ... this[key] ...
    }
  };

  return MyClass;
})();

var c = new MyClass("hello")
c["key"] === undefined

因爲語言限制,Babel只提供部分支持,使用時須要注意

內置類的繼承

在ES6中,內置的Array,Date,DOM Element能夠被繼承以拓展了。

// User code of Array subclass
class MyArray extends Array {
    constructor(...args) { super(...args); }
}

var arr = new MyArray();
arr[1] = 12;
arr.length == 2

babel 部分支持,因爲ES5引擎的限制Date,Array,Error不被支持,可是HTMLElement是被支持的

Math + Number + String + Array + Object APIs

ES6 爲不少舊有對象添加了新的API,這些對象包括Math,Array器String,Object,以下:

Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false

Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2

"abcde".includes("cd") // true
"abc".repeat(3) // "abcabcabc"

Array.from(document.querySelectorAll('*')) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1, 2, 3].find(x => x == 3) // 3
[1, 2, 3].findIndex(x => x == 2) // 1
[1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2]
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"

Object.assign(Point, { origin: new Point(0,0) })

babel 經過 polyfill 提供部分支持

二進制和八進制字面量

ES6添加了二進制和八進制數值的字面量定義方法:

0b111110111 === 503 // true
0o767 === 503 // true

babel 只支持字面量形式,不支持 Number("0o767")形式

Promise

Promise爲異步編程提供了一種新的方式,Promise把將來將用到的值當作一等對象,Promise在不少前端庫中已經有所支持了。這個平時用得最多了,還沒使用的推薦試試。

function timeout(duration = 0) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, duration);
    })
}

var p = timeout(1000).then(() => {
    return timeout(2000);
}).then(() => {
    throw new Error("hmm");
}).catch(err => {
    return Promise.all([timeout(100), timeout(200)]);
})

實際使用時需引入polyfill

Reflect API

Reflect對象與Proxy對象同樣,也是 ES6 爲了操做對象而提供的新 API,做用以下:

  • 將Object對象的一些明顯屬於語言內部的方法(好比Object.defineProperty),放到Reflect對象上;

  • 修改某些Object方法的返回結果,讓其變得更合理;

  • 讓Object操做都變成函數行爲,好比name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數行爲。

  • Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法;

var O = {a: 1};
Object.defineProperty(O, 'b', {value: 2});
O[Symbol('c')] = 3;

Reflect.ownKeys(O); // ['a', 'b', Symbol(c)]

function C(a, b){
  this.c = a + b;
}
var instance = Reflect.construct(C, [20, 22]);
instance.c; // 42

實際使用時需引入polyfill

Tail Calls

尾部調用被保證不能無限拓展棧,這讓有無限制輸入時的遞歸算法更加安全。

function factorial(n, acc = 1) {
    'use strict';
    if (n <= 1) return acc;
    return factorial(n - 1, n * acc);
}

// 堆棧愈來愈經常使用,在ES6中其使用更加安全了
factorial(100000)

說明

上文對ES6的新特性都作了簡單的描述,可是關於Reflect APIProxies,因爲本人對他們的理解還不夠透徹,說得可能有些不清不楚。但願閱讀本文讓你有收穫,有任何疑問,你們也能夠一塊兒討論。

有用的連接

相關文章
相關標籤/搜索