ES6語言特性的總結(3)

在ES5中,因爲沒有類的概念,因此若是要使用面向對象編程,就須要利用原型繼承的方式。一般是建立一個構造器,而後將方法指派到該構造器的原型上。
就像這樣:javascript

function Cat(name) {
  this.name = name;
}

Cat.prototype.speak = function() {
  console.log('Mew!');
}複製代碼

ES6引入了class關鍵字後就再也不須要這樣作了。不過須要明白的是ES6中的類僅僅是以上面這種方式做爲基礎的一個語法糖而已。
ES6中類聲明已class關鍵字開始,其後是類的名稱;剩餘部分的語法部分看起來就像對象字面量中的方法簡寫,而且在方法之間不須要使用逗號。同時容許你在其中使用特殊的 constructor 方法名稱直接定義一個構造器,而不須要先定義一個函數再把它看成構造器使用。前端

class Cat {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log('Mew!');
  }
}複製代碼

類聲明與ES5仿類的區別

雖然ES6的類聲明是ES5方式的一個語法糖,可是與之相比,仍是存在一些區別的。java

  1. 類聲明不會被提高,這與函數定義不一樣。類聲明的行爲與 let 類似,所以在程序的執行到達聲明處以前,類會存在於暫時性死區內。
  2. 類聲明中的全部代碼會自動運行在嚴格模式下,而且也沒法退出嚴格模式。
  3. 類的全部方法都是不可枚舉的,這是對於自定義類型的顯著變化,後者必須用 Object.defineProperty() 才能將方法改變爲不可枚舉。
  4. 類的全部方法內部都沒有 [[Construct]] ,所以使用 new 來調用它們會拋出錯誤。
  5. 調用類構造器時不使用 new ,會拋出錯誤。
  6. 試圖在類的方法內部重寫類名,會拋出錯誤。

訪問器屬性

自有屬性須要在類構造器中建立,而類還容許你在原型上定義訪問器屬性。webpack

class Person {
  constructor(name, age) {
    this.age = age;
    this.name = name;
  }

  get firstName() {
    return this.name.split(' ')[0];
  }

  set firstName(value) {
    let lastName = this.name.split(' ')[1];
    this.name = value + ' ' + lastName;
  }
}

let person = new Person('Michael Jackson', 35);
console.log(person.firstName); //'Michael'
person.firstName = 'Marry';
console.log(person.name); // 'Marry Jackson'複製代碼

在讀取訪問器屬性的時候,會調用getter方法,而寫入值的時候,會調用setter方法。這相似於ES5中使用Object.definePropery的方法。web

靜態成員

靜態成員在ES5中通常是直接定義在構造器上的,如:編程

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.createAdult = function(name) {
  return new Person(name, 18);
};複製代碼

而在ES6中提供了static關鍵字簡化了聲明靜態成員的方式:promise

class Person {
  constructor(name, age) {
    this.age = age;
    this.name = name;
  }

  static createAdult(name) {
    return new Person(name, 18);
  }

}複製代碼

繼承

ES5中實現繼承的方式有不少種,可是若是要實現嚴格的繼承,步驟較爲繁瑣。爲了簡化繼承的關係,ES6中使用類讓這項工做變得更簡單。若是你熟悉面嚮對象語言,如java等,那麼extends這個關鍵你必定不會陌生。一樣的,在ES6中使用extends 關鍵字來指定當前類所須要繼承的函數便可。生成的類的原型會被自動調整,而你還能調用 super() 方法來訪問基類的構造器。瀏覽器

class Person {
    constructor(country) {
      this.country = country;
    }
}

class Chinese extends Person{
    constructor() {
      super('China');
    }

    speak() {
      console.log('I come from ' + this.country);
    }
}複製代碼

派生類中的方法老是會屏蔽基類中的同名方法,所以,若是你須要使用父類中定義的方法的話,可使用super關鍵字來進行訪問。如:異步

class Person {
    constructor(country) {
      this.country = country;
    }

    speak() {
      console.log('I come from ' + this.country);
    }
}

class Chinese extends Person{
    constructor() {
      super('China');
    }

    speak() {
        super.speak();
        console.log('I am a Chinese');
    }
}

const chinese = new Chinese();
chinese.speak();
//I come from China.
//I am a Chinese.複製代碼

從表達式中派生類

另外一個在ES6中比較高級的地方是,能夠從表達式中派生出類來:async

let SerializableMixin = {
    serialize() {
        return JSON.stringify(this);
    }
};

let AreaMixin = {
    getArea() {
        return this.length * this.width;
    }
};

//混入
function mixin(...mixins) {
    var base = function() {};
    Object.assign(base.prototype, ...mixins);
    return base;
}

class Square extends mixin(AreaMixin, SerializableMixin) {
    constructor(length) {
        super();
        this.length = length;
        this.width = length;
    }
}

var x = new Square(3);
console.log(x.getArea());               // 9
console.log(x.serialize());             // "{"length":3,"width":3}"複製代碼

繼承內置對象

利用extends繼承內置對象的時候,容易出現的一個問題是會返回內置對象實例的方式,在繼承後會返回子類的實例。如:

class SubArray extends Array {

}

const subArr = new SubArray(1,2,3);
const filteredArr = subArr.filter(value => value > 1); 
console.assert(filteredArr instanceof SubArray);  //true複製代碼

若是須要想讓其返回實例類型是Array能夠利用Symbol.species這個符號來處理:

class SubArray extends Array {
//這裏使用static,代表是靜態訪問器屬性
  static get [Symbol.species]() {
    return Array;
  }
}複製代碼

定義抽象類

利用以前介紹的new.target能夠實現一個抽象類,原理就是當用戶調用new直接建立實例的時候,拋出錯誤。:

class BaseClass {
  constructor() {
    if(new.target === BaseClass) {
      throw new Error('該類不能直接實例化')
    }
  }
}複製代碼

模塊

隨着項目的規模愈來愈大,如今模塊化已經成爲開發過程當中必備的流程。以前,咱們可能借助RequireJS等工具進行模塊化管理,而如今ES6已經提供了模塊系統。

先來了解一下基本語法:

基本的導出導入

模塊( Modules )本質上就是 包含JS 代碼的文件。在一個js文件中,你可使用export關鍵字,將代碼公開給其餘模塊。

// sayHello.js
export function sayHello() {
  console.log('hello');
}

// funcs.js
export function fun1() { .... }
export function func2() { .... }
export const value1 = 'value1';複製代碼

如上面的例子中所示,你能夠在文件中導出全部的最外層函數以及varletconst聲明的變量。而這些導出的變量或公開部分則能夠被其餘文件利用import語法進行導入後引用。

//單個導入
import {sayHello} from './sayHello.js';
//多個導入
import {func1, func2} from './funs.js';
sayHello(); // hello複製代碼

爲了確保瀏覽器與Node.js之間保持良好的兼容性,建議使用相對路徑的寫法。

若是須要將整個模塊當作單一的對象進行導入,可使用*通配符:

//使用as關鍵字爲導出對象設置別名,模塊中全部導出都將做爲屬性存在
import * as funcs from './funcs.js';

funcs.func1();
funsc.func2();複製代碼

重命名導出與導入

若是不想用原來模塊中的命名,能夠經過as關鍵字來指定別名。

//as前面爲模塊原先的名稱,後面是別名,使用別名後sayHello爲undefined
import { sayHello as say } from './sayHello.js';

say();複製代碼

默認值

你可使用export關鍵字來導出默認模塊:

// sayHello.js
export default function() {
  console.log('hello');
}

// main.js
import sayHello from './sayHello.js';
sayHello();複製代碼

能夠注意到,這裏默認導出的時候,不須要使用花括號,而直接爲其命名便可。這種寫法也較爲簡潔。當一個文件中,同時存在默認導出模塊和非默認導出模塊的時候,導出的時候,默認導出模塊須要寫在前面,例如:

import sayHello,{ func1 } from './sayHello.js'; //此處略去導出過程
//或者使用以下方式
import {default as sayHello, func1} from './sayHello.js';複製代碼

無綁定導出

當一個文件中沒有使用export語句進行導出的時候,其實咱們仍是能夠import進行導入的。一般是被用於建立polyfill與shim的時候。

//sayHello.js
const name = 'scq000';
function sayHello() {
    console.log('hello');
}

// main.js
import './sayHello.js';

sayHello();
console.log(name);複製代碼

加載模塊

雖說如今在項目中一般都使用webpack來處理模塊代碼,但也須要知道其餘加載模塊的方式。

你可使用<script type="module">的方式進行模塊的加載,默認瀏覽器會採用defer屬性,一旦頁面文檔徹底被解析後,模塊就會按次序執行。若是須要異步加載的話,能夠加上async關鍵字。

另外,若是是使用Web Worker或Server Worker之類的worker的話,能夠經過下面這種方式加載模塊:

let worker = new Worker('module.js', { type: 'module' });複製代碼

迭代器與生成器

迭代器和生成器一般是一塊兒來使用的。迭代器的目的是爲了更加方便地遍歷對象,而生成器用來生成可迭代的對象。使用迭代器的過程當中,你能夠結合for...of語句以及...擴展符來遍歷對象的值。

迭代器

在ES6中,迭代器是專門用來設計迭代的對象,帶有特殊的接口。全部的迭代器都帶有next方法,用來返回一個結果。這裏咱們來手工實現一個迭代器:

function createIterator() {
    var i = 0;

    return {
        next() {
            var done = false;
            var value;
            if (i < 3) {
                value = i * 2;
                i++;
            } else {
                done = true;
                value = undefined;
            }
            return { value: value, done: done }
        }
    }
}

let iterator = new createIterator();
iterator.next(); // {value: 0, done: false}
iterator.next(); // {value: 0, done: false}
iterator.next(); // {value: 4, done: false}
iterator.next(); // {value: undefined, done: true}複製代碼

集合對象(Set、Map、Array)提供了三種內置的迭代器:entries,keys,values,這三個方法都會返回一個迭代器,用來方便地獲取鍵值對等信息。ES6中定義了可迭代對象(iterable object),如Set、Map、Array以及字符串等均可以利用for...of語法來進行遍歷操做。原理其實就是調用它們內置的默認迭代器。對於用戶自定義的對象,若是也要讓它們支持for...of語法,則須要去定義Symbol.iterator屬性。具體例子,能夠查看符號那一部分的內容。

生成器

生成器(generator)是可以返回迭代器的函數。一般定義的時候,咱們會利用function關鍵字以後的(*)號表示,使用yield語句輸出每一次的數據。

function *getNum() {
  yield 1;
  yield 2;
  yield 3;
}

const nums = getNum();

for(let num of nums) {
  console.log(num);
}
//1,2,3複製代碼

Promise與異步編程

這部分的內容我在前端的異步解決方案之Promise和Await/Async中有詳細的闡述,若是感興趣的能夠看一下。

代理與反射接口

爲了讓開發者可以建立內置對象,ES6經過代理proxy )的方式暴露了對象上的內部工做。使用代理可以攔截並改變 JS 引擎的底層操做,如日誌、對象虛擬化等。而反射reflect )則是反映了對底層的默認行爲操做。

接下來這個例子,將演示如何利用代理和反射的方式對對象的內置行爲作修改:

//要修改的默認對象
let target = {
    name: 'scq000',
    age: 23
};

//代理對象
let proxy = new Proxy(target, {
  has(trapTarget, key) {
    if(key === 'age') {
      return false;
    }else {
    //調用默認的行爲
      return Reflect.has(trapTarget, key);
    }
  }
});

console.log('value' in proxy); //true
console.log('age' in proxy); //false複製代碼

能夠看到,上面這個例子使用代理對象攔截了in操做符的默認行爲並做出了修改。has這個方法稱做陷阱函數,它可以響應對in操做的訪問操做。trapTarget則是這個函數的目標對象,has方法接受一個額外的參數key是對應着須要檢查的屬性。一旦檢查到屬性名爲age,則返回false,這樣就能隱藏這個屬性。

如下是一些經常使用的代理陷阱以及反射所對應的默認行爲:

代理陷阱 被重寫的行爲 默認行爲
get/set 讀取/寫入一個屬性值 Reflect.get/Reflect.set
has in運算符 Reflect.has
deleteProperty delete運算符 Reflect.deleteProperty
getPropertyOf/setPropertyOf Object.getPropertyOf/setPropertyOf Reflefct.getPropertyOf/setPropertyOf

目前,反射和代理在瀏覽器上還不支持,主要仍是用在NodeJS編程上。這一部分的功能在實際開發中並非特別經常使用,所以,這裏不作過多介紹。若是感興趣的話,能夠自行查找相關文檔。

相關文章
相關標籤/搜索