在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!');
}
}複製代碼
雖然ES6的類聲明是ES5方式的一個語法糖,可是與之相比,仍是存在一些區別的。java
自有屬性須要在類構造器中建立,而類還容許你在原型上定義訪問器屬性。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';複製代碼
如上面的例子中所示,你能夠在文件中導出全部的最外層函數
、類
以及var
、let
或const
聲明的變量。而這些導出的變量或公開部分則能夠被其餘文件利用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和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編程上。這一部分的功能在實際開發中並非特別經常使用,所以,這裏不作過多介紹。若是感興趣的話,能夠自行查找相關文檔。