ECMAScript 6新特性簡記

ECMAScript 6.0是JavaScript語言的2015年6月的發佈版。

一.let和const命令

  • let:用來聲明變量,用法類似於var,但是隻在let命令所在的代碼塊內有效。
    var a = [];
    for (let i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 6
  • const:聲明一個只讀的常量。對於複合類型的變量,變量名不指向數據,而是指向數據所在的地址。const命令只是保證變量名指向的地址不變,並不保證該地址的數據不變,所以將一個對象聲明爲常量必須非常小心。

    const foo = {};
    foo.prop = 123;
    
    foo.prop;// 123
    foo = {}; // TypeError: "foo" is read-only

    注:對於letconst來說,變量一旦聲明過就不能再重新聲明;但var可以

 

二.變量的解構賦值

  • 數組解構賦值:這種寫法屬於“模式匹配”,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。
    let [x, y, ...z] = ['a'];
    x; // "a"
    y; // undefined
    z; // []
  • 對象的解構賦值
    let { foo, bar } = { foo: "aaa", bar: "bbb" };
    foo // "aaa"
    bar // "bbb

    let foo;
    ({foo} = {foo: 1});

     

  • 字符串解構賦值
    const [a, b, c, d, e] = 'hello';
    a // "h"
    b // "e"
    c // "l"
    d // "l"
    e // "o"

     

  • 函數參數的解構賦值
    function add([x, y]){
      return x + y;
    }
    
    add([1, 2]); // 3

     

三.字符串的擴展

  • 模板字符串(template string):用反引號(`)標識,它可以當作普通字符串使用,也可以用來定義多行字符串,或者在字符串中嵌入變量(寫在${}之中)
    const tmpl = addrs => `
      <table>
      ${addrs.map(addr => `
        <tr><td>${addr.first}</td></tr>
        <tr><td>${addr.last}</td></tr>
      `).join('')}
      </table>
    `;
    
    const data = [
        { first: '<Jane>', last: 'Bond' },
        { first: 'Lars', last: '<Croft>' },
    ];
    
    console.log(tmpl(data));
    // <table>
    //
    //   <tr><td><Jane></td></tr>
    //   <tr><td>Bond</td></tr>
    //
    //   <tr><td>Lars</td></tr>
    //   <tr><td><Croft></td></tr>
    //
    // </table>

     

四.數組的擴展

  • Array.from():用於將類似數組的對象(array-like object)和可遍歷(iterable)的對象(包括ES6新增的數據結構Set和Map)轉爲真正的數組
    Array.from(arrayLike, x => x * x);
    // 等同於
    Array.from(arrayLike).map(x => x * x);
    
    Array.from([1, 2, 3], (x) => x * x)
    // [1, 4, 9]

     

五.函數的擴展

  • 默認值與解構賦值的默認值結合起來使用
function fetch(url, { body = '', method = 'GET', headers = {} }) {
  console.log(method);
}

fetch('http://example.com', {})
// "GET"

fetch('http://example.com')
// 報錯

 

function fetch(url, { method = 'GET' } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

注:函數的length屬性返回沒有指定默認值的參數個數 

  • rest參數(...變量名):rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中,rest 參數之後不能再有其他參數
  • function push(array, ...items) {
      items.forEach(function(item) {
        array.push(item);
        console.log(item);
      });
    }
    
    var a = [];
    push(a, 1, 2, 3)

     

  • 擴展運算符(...):擴展運算符好比 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列
    console.log(...[1, 2, 3])
    // 1 2 3
    
    console.log(1, ...[2, 3, 4], 5)
    // 1 2 3 4 5
    
    [...document.querySelectorAll('div')]
    // [<div>, <div>, <div>]

     

  • 箭頭函數:如果箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return語句返回
var getTempItem = id => ({ id: id, name: "Temp" });

箭頭函數使用說明:

(1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。

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

(3)不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用Rest參數代替。

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

 

  • 尾調用優化

尾調用(Tail Call)指函數最後一步調用另一個函數。函數調用自身,稱爲遞歸;函數尾調用自身,稱爲尾遞歸。 遞歸非常耗費內存,因爲需要同時保存成千上百個調用幀,很容易發生“棧溢出”錯誤(stack overflow)。但對於尾遞歸來說,由於只存在一個調用幀,所以永遠不會發生“棧溢出”錯誤。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

上面代碼是一個階乘函數,計算n的階乘,最多需要保存n個調用記錄,複雜度 O(n) 。如果改寫成尾遞歸,只保留一個調用記錄,複雜度 O(1) 。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

 下爲fibonacci 遞歸調用:

function Fibonacci (n) {
  if ( n <= 1 ) {return 1};

  return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Fibonacci(10); // 89
// Fibonacci(100)
// Fibonacci(500)
// 堆棧溢出了

使用尾遞歸優化過的fibonacci 算法如下:

function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity

在正常模式下,函數內部有兩個變量,可以跟蹤函數的調用棧。

  1. func.arguments:返回調用時函數的參數。
  2. func.caller:返回調用當前函數的那個函數。

尾調用優化發生時,函數的調用棧會改寫,因此上面兩個變量就會失真。嚴格模式禁用這兩個變量,所以尾調用模式僅在嚴格模式下生效。在正常模式下或者那些不支持該功能的環境中,採用“循環”換掉“遞歸”,減少調用棧,就不會溢出。

 

function sum(x, y) {
  if (y > 0) {
    return sum(x + 1, y - 1);
  } else {
    return x;
  }
}

sum(1, 100000)
// Uncaught RangeError: Maximum call stack size exceeded(…)

 

蹦牀函數(trampoline)可以將上述遞歸執行轉爲循環:

function trampoline(f) {
  while (f && f instanceof Function) {
    f = f();
  }
  return f;
}

function sum(x, y) {
  if (y > 0) {
    return sum.bind(null, x + 1, y - 1);
  } else {
    return x;
  }
}

trampoline(sum(1, 100000))
// 100001

 蹦牀函數並不是真正的尾遞歸優化,下面的實現纔是:

function tco(f) {
  var value;
  var active = false;
  var accumulated = [];

  return function accumulator() {
    accumulated.push(arguments);
    if (!active) {
      active = true;
      while (accumulated.length) {
        value = f.apply(this, accumulated.shift());
      }
      active = false;
      return value;
    }
  };
}

var sum = tco(function(x, y) {
  if (y > 0) {
    return sum(x + 1, y - 1)
  }
  else {
    return x
  }
});

sum(1, 100000)
// 100001

 六.對象的擴展

  • 屬性的簡潔表示法:屬性名可爲變量名, 屬性值可爲變量的值
    var foo = 'bar';
    var baz = {foo};
    baz // {foo: "bar"}
    
    // 等同於
    var baz = {foo: foo};

     

  • 屬性名表達式

    var lastWord = 'last word';
    
    var a = {
      'first word': 'hello',
      [lastWord]: 'world'
    };
    
    a['first word'] // "hello"
    a[lastWord] // "world"
    a['last word'] // "world"

     

七.Set和Map數據結構

  • Set:類似於數組,但是成員的值都是唯一的,沒有重複的值
    // 去除數組的重複成員
    [...new Set(array)]
  • Map:類似於對象,也是鍵值對的集合,但是“鍵”的範圍不限於字符串,各種類型的值(包括對象)都可以當作鍵
    var map = new Map([
      ['name', '張三'],
      ['title', 'Author']
    ]);
    
    map.size // 2
    map.has('name') // true
    map.get('name') // "張三"
    map.has('title') // true
    map.get('title') // "Author"

.Proxy

Proxy 用於修改某些操作的默認行爲,等同於在語言層面做出修改,所以屬於一種“元編程”(meta programming),即對編程語言進行編程。Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。

  • this問題:目標對象內部的this關鍵字會指向 Proxy 代理,這時this可綁定原始對象
    const target = new Date('2015-01-01');
    const handler = {
      get(target, prop) {
        if (prop === 'getDate') {
          return target.getDate.bind(target);
        }
        return Reflect.get(target, prop);
      }
    };
    const proxy = new Proxy(target, handler);
    
    proxy.getDate() // 1

.Reflect

Reflect對象一共有13個靜態方法:

Reflect.apply(target,thisArg,args)
Reflect.construct(target,args)
Reflect.get(target,name,receiver)
Reflect.set(target,name,value,receiver)
Reflect.defineProperty(target,name,desc)
Reflect.deleteProperty(target,name)
Reflect.has(target,name)
Reflect.ownKeys(target)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)
  • Reflect.get(target, name, receiver) 

    var myObject = {
      foo: 1,
      bar: 2,
      get baz() {
        return this.foo + this.bar;
      },
    };
    
    var myReceiverObject = {
      foo: 4,
      bar: 4,
    };
    
    Reflect.get(myObject, 'baz', myReceiverObject) // 8

     

  • 使用-Proxy-實現觀察者模式
    const person = observable({
      name: '張三',
      age: 20
    });
    
    function print() {
      console.log(`${person.name}, ${person.age}`)
    }
    
    observe(print);
    person.name = '李四';
    // 輸出
    // 李四, 20
    
    
    const queuedObservers = new Set();
    
    const observe = fn => queuedObservers.add(fn);
    const observable = obj => new Proxy(obj, {set});
    
    function set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      queuedObservers.forEach(observer => observer());
      return result;
    }

     

十.Promise

Promise 是異步編程的一種解決方案,比傳統的解決方案回調函數和事件更合理強大。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('Resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// Resolved

 

十一.Iterator和for...of循環

Iterator的作用有三個:

  1. 爲各種數據結構,提供一個統一的、簡便的訪問接口
  2. 使得數據結構的成員能夠按某種次序排列
  3. 創造了一種新的遍歷命令for...of循環,Iterator接口主要供for...of消費
const arr = ['red', 'green', 'blue'];

for(let v of arr) {
  console.log(v); // red green blue
}

const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);

for(let v of obj) {
  console.log(v); // red green blue
}

 

十二.Generator

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

  1. function關鍵字與函數名之間有一個星號
  2. 函數體內部使用yield語句,定義不同的內部狀態
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]
  • Generator函數的this

    function* g() {
        this.a = 11;
    }
    
    g.prototype.hello = function () {
      return 'hi!';
    };
    
    let obj = g();
    
    obj instanceof g // true
    obj.hello() // 'hi!'
    obj.a // undefined
  • Generator 函數:可以暫停執行和恢復執行,可以作爲異步編程的完整解決方案——函數體內外的數據交換和錯誤處理機制

    function* gen(x){
      try {
        var y = yield x + 2;
      } catch (e){
        console.log(e);
      }
      return y;
    }
    
    var g = gen(1);
    g.next();
    g.throw('出錯了'); 
  • JavaScript 語言的 Thunk 函數:Thunk 函數將多參數函數替換成一個只接受回調函數作爲參數的單參數函數

    // 正常版本的readFile(多參數版本)
    fs.readFile(fileName, callback);
    
    // Thunk版本的readFile(單參數版本)
    var Thunk = function (fileName) {
      return function (callback) {
        return fs.readFile(fileName, callback);
      };
    };
    
    var readFileThunk = Thunk(fileName);
    readFileThunk(callback);

      

十三.async函數

async 函數是Generator 函數的語法糖,返回一個 Promise 對象,可以使用then方法添加回調函數。 async 函數return語句返回值或拋出異常,Promise 對象狀態發生變化,then和catch方法回調函數捕獲處理。正常情況下,await命令後面是一個 Promise 對象,如果不是,會被轉成一個立即resolve的 Promise 對象。

async function f() {
  await Promise.reject('出錯了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出錯了

 

十四.Class

基本上,ES6的class可以看作只是一個語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 調用父類的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 調用父類的toString()
  }
}

 

  • 原生構造函數無法繼承

    ECMAScript的原生構造函數大致有下面這些: Boolean(),Number(),String(),Array(),Date(),Function(),RegExp(),Error(),Object()

  • Class的取值函數(getter)和存值函數(setter)
    class MyClass {
      constructor() {
        // ...
      }
      get prop() {
        return 'getter';
      }
      set prop(value) {
        console.log('setter: '+value);
      }
    }
    
    let inst = new MyClass();
    
    inst.prop = 123;
    // setter: 123
    
    inst.prop
    // 'getter'
  • Class 的 Generator 方法:方法之前加上星號(*),就表示該方法是一個 Generator 函數
    class Foo {
      constructor(...args) {
        this.args = args;
      }
      * [Symbol.iterator]() {
        for (let arg of this.args) {
          yield arg;
        }
      }
    }
    
    for (let x of new Foo('hello', 'world')) {
      console.log(x);
    }
    // hello
    // world
  • Mixin模式的實現
    function mix(...mixins) {
      class Mix {}
    
      for (let mixin of mixins) {
        copyProperties(Mix, mixin);
        copyProperties(Mix.prototype, mixin.prototype);
      }
    
      return Mix;
    }
    
    function copyProperties(target, source) {
      for (let key of Reflect.ownKeys(source)) {
        if ( key !== "constructor"
          && key !== "prototype"
          && key !== "name"
        ) {
          let desc = Object.getOwnPropertyDescriptor(source, key);
          Object.defineProperty(target, key, desc);
        }
      }
    }

     

十五.Module

ES6 的模塊自動採用嚴格模式,不管你有沒有在模塊頭部加上"use strict"

嚴格模式主要有以下限制:

  1. 變量必須聲明後再使用
  2. 函數的參數不能有同名屬性,否則報錯
  3. 不能使用with語句
  4. 不能對只讀屬性賦值,否則報錯
  5. 不能使用前綴0表示八進制數,否則報錯
  6. 不能刪除不可刪除的屬性,否則報錯
  7. 不能刪除變量delete prop,會報錯,只能刪除屬性delete global[prop]
  8. eval不會在它的外層作用域引入變量
  9. evalarguments不能被重新賦值
  10. arguments不會自動反映函數參數的變化
  11. 不能使用arguments.callee
  12. 不能使用arguments.caller
  13. 禁止this指向全局對象
  14. 不能使用fn.callerfn.arguments獲取函數調用的堆棧
  15. 增加了保留字(比如protectedstaticinterface
  • export 命令和import命令:export命令定義了模塊的對外接口,通過import命令加載模塊

    // circle.js
    
    export function area(radius) {
      return Math.PI * radius * radius;
    }
    
    export function circumference(radius) {
      return 2 * Math.PI * radius;
    }
    
    // main.js
    import * as circle from './circle';
    
    console.log('圓面積:' + circle.area(4));
    console.log('圓周長:' + circle.circumference(14));
  • 模塊的整體加載:星號(*)指定一個對象,所有輸出值都加載在這個對象上面
    import * as circle from './circle';

     

  • export default 命令:爲模塊指定默認輸出

    // export-default.js
    export default function () {
      console.log('foo');
    }
    
    // import-default.js
    import customName from './export-default';
    customName(); // 'foo'

     

  • 模塊的繼承

    // circleplus.js
    
    export * from 'circle';
    export var e = 2.71828182846;
    export default function(x) {
      return Math.exp(x);
    }

     

  • 瀏覽器的模塊加載

    <script type="module" src="foo.js"></script>

    瀏覽器對於帶有type="module"<script>,都是異步加載外部腳本,不會造成堵塞瀏覽器。對於外部的模塊腳本(上例是foo.js),有幾點需要注意:

    1. 該腳本自動採用嚴格模塊。
    2. 該腳本內部的頂層變量,都只在該腳本內部有效,外部不可見。
    3. 該腳本內部的頂層的this關鍵字,返回undefined,而不是指向window
  • 循環加載:ES6處理“循環加載”與CommonJS有本質的不同,是動態引用,如果使用import從一個模塊加載變量(即import foo from 'foo'),那些變量不會被緩存,而是成爲一個指向被加載模塊的引用
    // a.js
    
    // 這一行建立一個引用,
    // 從`b.js`引用`bar`
    import {bar} from './b.js';
    
    export function foo() {
      // 執行時第一行輸出 foo
      console.log('foo');
      // 到 b.js 執行 bar
      bar();
      console.log('執行完畢');
    }
    foo();
    
    // b.js
    
    // 建立`a.js`的`foo`引用
    import {foo} from './a.js';
    
    export function bar() {
      // 執行時,第二行輸出 bar
      console.log('bar');
      // 遞歸執行 foo,一旦隨機數
      // 小於等於0.5,就停止執行
      if (Math.random() > 0.5) {
        foo();
      }
    }

     

  • 跨模塊常量:const聲明的常量只在當前代碼塊有效,如果想設置跨模塊的常量(即跨多個文件),可以採用下面的寫法

    // constants.js 模塊
    export const A = 1;
    export const B = 3;
    export const C = 4;
    
    // test1.js 模塊
    import * as constants from './constants';
    console.log(constants.A); // 1
    console.log(constants.B); // 3
    
    // test2.js 模塊
    import {A, B} from './constants';
    console.log(A); // 1
    console.log(B); // 3

      

十六.SIMD(Single Instruction/Multiple Data):單指令/多數據

 

SIMD 提供12種數據類型,總長度都是128個二進制位。

 

  • Float32x4:四個32位浮點數
  • Float64x2:兩個64位浮點數
  • Int32x4:四個32位整數
  • Int16x8:八個16位整數
  • Int8x16:十六個8位整數
  • Uint32x4:四個無符號的32位整數
  • Uint16x8:八個無符號的16位整數
  • Uint8x16:十六個無符號的8位整數
  • Bool32x4:四個32位布爾值
  • Bool16x8:八個16位布爾值
  • Bool8x16:十六個8位布爾值
  • Bool64x2:兩個64位布爾值

 

每種數據類型被x符號分隔成兩部分,後面的部分表示通道數,前面的部分表示每個通道的寬度和類型。比如,Float32x4就表示這個值有4個通道,每個通道是一個32位浮點數。

 

var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);
var c = SIMD.Float32x4.add(a, b); // Float32x4[6, 8, 10, 12]

 

 

參考:http://es6.ruanyifeng.com/ (由於原作內容太多,爲精簡,也爲實際開發備料,以及日後再次查閱之用)

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