類
在ES5中受啥,由于沒(méi)有類的概念,所以如果要使用面向?qū)ο缶幊痰姆绞秸买迹托枰迷屠^承的方式兜辞。通常是創(chuàng)建一個(gè)構(gòu)造器,然后將方法指派到該構(gòu)造器的原型上间螟。
就像這樣:
function Cat(name) {
this.name = name;
}
Cat.prototype.speak = function() {
console.log('Mew!');
}
ES6引入了class
關(guān)鍵字后就不再需要這樣做了逗抑。不過(guò)需要明白的是ES6中的類僅僅是以上面這種方式作為基礎(chǔ)的一個(gè)語(yǔ)法糖而已。
ES6中類聲明已class
關(guān)鍵字開(kāi)始寒亥,其后是類的名稱邮府;剩余部分的語(yǔ)法部分看起來(lái)就像對(duì)象字面量中的方法簡(jiǎn)寫(xiě),并且在方法之間不需要使用逗號(hào)溉奕。同時(shí)允許你在其中使用特殊的 constructor 方法名稱直接定義一個(gè)構(gòu)造器褂傀,而不需要先定義一個(gè)函數(shù)再把它當(dāng)作構(gòu)造器使用。
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log('Mew!');
}
}
類聲明與ES5仿類的區(qū)別
雖然ES6的類聲明是ES5方式的一個(gè)語(yǔ)法糖加勤,但是與之相比仙辟,還是存在一些區(qū)別的。
- 類聲明不會(huì)被提升鳄梅,這與函數(shù)定義不同叠国。類聲明的行為與 let 相似,因此在程序的執(zhí)行到達(dá)聲明處之前戴尸,類會(huì)存在于暫時(shí)性死區(qū)內(nèi)粟焊。
- 類聲明中的所有代碼會(huì)自動(dòng)運(yùn)行在嚴(yán)格模式下,并且也無(wú)法退出嚴(yán)格模式孙蒙。
- 類的所有方法都是不可枚舉的项棠,這是對(duì)于自定義類型的顯著變化,后者必須用 Object.defineProperty() 才能將方法改變?yōu)椴豢擅杜e挎峦。
- 類的所有方法內(nèi)部都沒(méi)有 [[Construct]] 香追,因此使用 new 來(lái)調(diào)用它們會(huì)拋出錯(cuò)誤。
- 調(diào)用類構(gòu)造器時(shí)不使用 new 坦胶,會(huì)拋出錯(cuò)誤透典。
- 試圖在類的方法內(nèi)部重寫(xiě)類名,會(huì)拋出錯(cuò)誤顿苇。
訪問(wèn)器屬性
自有屬性需要在類構(gòu)造器中創(chuàng)建峭咒,而類還允許你在原型上定義訪問(wèn)器屬性。
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'
在讀取訪問(wèn)器屬性的時(shí)候岖圈,會(huì)調(diào)用getter方法讹语,而寫(xiě)入值的時(shí)候,會(huì)調(diào)用setter方法蜂科。這類似于ES5中使用Object.definePropery
的方法顽决。
靜態(tài)成員
靜態(tài)成員在ES5中一般是直接定義在構(gòu)造器上的短条,如:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.createAdult = function(name) {
return new Person(name, 18);
};
而在ES6中提供了static
關(guān)鍵字簡(jiǎn)化了聲明靜態(tài)成員的方式:
class Person {
constructor(name, age) {
this.age = age;
this.name = name;
}
static createAdult(name) {
return new Person(name, 18);
}
}
繼承
ES5中實(shí)現(xiàn)繼承的方式有很多種,但是如果要實(shí)現(xiàn)嚴(yán)格的繼承才菠,步驟較為繁瑣茸时。為了簡(jiǎn)化繼承的關(guān)系,ES6中使用類讓這項(xiàng)工作變得更簡(jiǎn)單赋访。如果你熟悉面向?qū)ο笳Z(yǔ)言可都,如java等,那么extends這個(gè)關(guān)鍵你一定不會(huì)陌生蚓耽。同樣的渠牲,在ES6中使用extends
關(guān)鍵字來(lái)指定當(dāng)前類所需要繼承的函數(shù)即可。生成的類的原型會(huì)被自動(dòng)調(diào)整步悠,而你還能調(diào)用 super() 方法來(lái)訪問(wèn)基類的構(gòu)造器签杈。
class Person {
constructor(country) {
this.country = country;
}
}
class Chinese extends Person{
constructor() {
super('China');
}
speak() {
console.log('I come from ' + this.country);
}
}
派生類中的方法總是會(huì)屏蔽基類中的同名方法,因此鼎兽,如果你需要使用父類中定義的方法的話答姥,可以使用super
關(guān)鍵字來(lái)進(jìn)行訪問(wèn)。如:
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.
從表達(dá)式中派生類
另一個(gè)在ES6中比較高級(jí)的地方是谚咬,可以從表達(dá)式中派生出類來(lái):
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}"
繼承內(nèi)置對(duì)象
利用extends
繼承內(nèi)置對(duì)象的時(shí)候鹦付,容易出現(xiàn)的一個(gè)問(wèn)題是會(huì)返回內(nèi)置對(duì)象實(shí)例的方式,在繼承后會(huì)返回子類的實(shí)例择卦。如:
class SubArray extends Array {
}
const subArr = new SubArray(1,2,3);
const filteredArr = subArr.filter(value => value > 1);
console.assert(filteredArr instanceof SubArray); //true
如果需要想讓其返回實(shí)例類型是Array
可以利用Symbol.species
這個(gè)符號(hào)來(lái)處理:
class SubArray extends Array {
//這里使用static敲长,表明是靜態(tài)訪問(wèn)器屬性
static get [Symbol.species]() {
return Array;
}
}
定義抽象類
利用之前介紹的new.target
可以實(shí)現(xiàn)一個(gè)抽象類,原理就是當(dāng)用戶調(diào)用new
直接創(chuàng)建實(shí)例的時(shí)候互捌,拋出錯(cuò)誤潘明。:
class BaseClass {
constructor() {
if(new.target === BaseClass) {
throw new Error('該類不能直接實(shí)例化')
}
}
}
模塊
隨著項(xiàng)目的規(guī)模越來(lái)越大行剂,現(xiàn)在模塊化已經(jīng)成為開(kāi)發(fā)過(guò)程中必備的流程秕噪。之前,我們可能借助RequireJS等工具進(jìn)行模塊化管理厚宰,而現(xiàn)在ES6已經(jīng)提供了模塊系統(tǒng)腌巾。
先來(lái)了解一下基本語(yǔ)法:
基本的導(dǎo)出導(dǎo)入
模塊( Modules )本質(zhì)上就是 包含JS 代碼的文件。在一個(gè)js文件中铲觉,你可以使用export
關(guān)鍵字澈蝙,將代碼公開(kāi)給其他模塊。
// sayHello.js
export function sayHello() {
console.log('hello');
}
// funcs.js
export function fun1() { .... }
export function func2() { .... }
export const value1 = 'value1';
如上面的例子中所示撵幽,你可以在文件中導(dǎo)出所有的最外層函數(shù)
灯荧、類
以及var
、let
或const
聲明的變量盐杂。而這些導(dǎo)出的變量或公開(kāi)部分則可以被其他文件利用import
語(yǔ)法進(jìn)行導(dǎo)入后引用逗载。
//單個(gè)導(dǎo)入
import {sayHello} from './sayHello.js';
//多個(gè)導(dǎo)入
import {func1, func2} from './funs.js';
sayHello(); // hello
為了確保瀏覽器與Node.js之間保持良好的兼容性哆窿,建議使用相對(duì)路徑的寫(xiě)法。
如果需要將整個(gè)模塊當(dāng)做單一的對(duì)象進(jìn)行導(dǎo)入厉斟,可以使用*
通配符:
//使用as關(guān)鍵字為導(dǎo)出對(duì)象設(shè)置別名挚躯,模塊中所有導(dǎo)出都將作為屬性存在
import * as funcs from './funcs.js';
funcs.func1();
funsc.func2();
重命名導(dǎo)出與導(dǎo)入
如果不想用原來(lái)模塊中的命名,可以通過(guò)as
關(guān)鍵字來(lái)指定別名擦秽。
//as前面為模塊原先的名稱码荔,后面是別名,使用別名后sayHello為undefined
import { sayHello as say } from './sayHello.js';
say();
默認(rèn)值
你可以使用export
關(guān)鍵字來(lái)導(dǎo)出默認(rèn)模塊:
// sayHello.js
export default function() {
console.log('hello');
}
// main.js
import sayHello from './sayHello.js';
sayHello();
可以注意到感挥,這里默認(rèn)導(dǎo)出的時(shí)候缩搅,不需要使用花括號(hào),而直接為其命名即可触幼。這種寫(xiě)法也較為簡(jiǎn)潔誉己。當(dāng)一個(gè)文件中,同時(shí)存在默認(rèn)導(dǎo)出模塊和非默認(rèn)導(dǎo)出模塊的時(shí)候域蜗,導(dǎo)出的時(shí)候巨双,默認(rèn)導(dǎo)出模塊需要寫(xiě)在前面,例如:
import sayHello,{ func1 } from './sayHello.js'; //此處略去導(dǎo)出過(guò)程
//或者使用如下方式
import {default as sayHello, func1} from './sayHello.js';
無(wú)綁定導(dǎo)出
當(dāng)一個(gè)文件中沒(méi)有使用export
語(yǔ)句進(jìn)行導(dǎo)出的時(shí)候霉祸,其實(shí)我們還是可以import
進(jìn)行導(dǎo)入的筑累。通常是被用于創(chuàng)建polyfill與shim的時(shí)候。
//sayHello.js
const name = 'scq000';
function sayHello() {
console.log('hello');
}
// main.js
import './sayHello.js';
sayHello();
console.log(name);
加載模塊
雖然說(shuō)現(xiàn)在在項(xiàng)目中通常都使用webpack來(lái)處理模塊代碼丝蹭,但也需要知道其他加載模塊的方式慢宗。
你可以使用<script type="module">
的方式進(jìn)行模塊的加載,默認(rèn)瀏覽器會(huì)采用defer
屬性奔穿,一旦頁(yè)面文檔完全被解析后镜沽,模塊就會(huì)按次序執(zhí)行。如果需要異步加載的話贱田,可以加上async
關(guān)鍵字缅茉。
另外,如果是使用Web Worker或Server Worker之類的worker的話男摧,可以通過(guò)下面這種方式加載模塊:
let worker = new Worker('module.js', { type: 'module' });
迭代器與生成器
迭代器和生成器通常是一起來(lái)使用的蔬墩。迭代器的目的是為了更加方便地遍歷對(duì)象,而生成器用來(lái)生成可迭代的對(duì)象耗拓。使用迭代器的過(guò)程中拇颅,你可以結(jié)合for...of
語(yǔ)句以及...
擴(kuò)展符來(lái)遍歷對(duì)象的值。
迭代器
在ES6中乔询,迭代器是專門用來(lái)設(shè)計(jì)迭代的對(duì)象樟插,帶有特殊的接口。所有的迭代器都帶有next
方法,用來(lái)返回一個(gè)結(jié)果黄锤。這里我們來(lái)手工實(shí)現(xiàn)一個(gè)迭代器:
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}
集合對(duì)象(Set麻献、Map、Array)提供了三種內(nèi)置的迭代器:entries,keys,values猜扮,這三個(gè)方法都會(huì)返回一個(gè)迭代器勉吻,用來(lái)方便地獲取鍵值對(duì)等信息。ES6中定義了可迭代對(duì)象(iterable object)旅赢,如Set齿桃、Map、Array以及字符串等都可以利用for...of
語(yǔ)法來(lái)進(jìn)行遍歷操作煮盼。原理其實(shí)就是調(diào)用它們內(nèi)置的默認(rèn)迭代器短纵。對(duì)于用戶自定義的對(duì)象,如果也要讓它們支持for...of
語(yǔ)法僵控,則需要去定義Symbol.iterator
屬性香到。具體例子,可以查看符號(hào)那一部分的內(nèi)容报破。
生成器
生成器(generator)是能夠返回迭代器的函數(shù)悠就。通常定義的時(shí)候,我們會(huì)利用function
關(guān)鍵字之后的(*)號(hào)表示充易,使用yield
語(yǔ)句輸出每一次的數(shù)據(jù)梗脾。
function *getNum() {
yield 1;
yield 2;
yield 3;
}
const nums = getNum();
for(let num of nums) {
console.log(num);
}
//1,2,3
Promise與異步編程
這部分的內(nèi)容我在前端的異步解決方案之Promise和Await/Async中有詳細(xì)的闡述,如果感興趣的可以看一下盹靴。
代理與反射接口
為了讓開(kāi)發(fā)者能夠創(chuàng)建內(nèi)置對(duì)象炸茧,ES6通過(guò)代理( proxy )的方式暴露了對(duì)象上的內(nèi)部工作。使用代理能夠攔截并改變 JS 引擎的底層操作稿静,如日志梭冠、對(duì)象虛擬化等。而反射( reflect )則是反映了對(duì)底層的默認(rèn)行為操作改备。
接下來(lái)這個(gè)例子控漠,將演示如何利用代理和反射的方式對(duì)對(duì)象的內(nèi)置行為做修改:
//要修改的默認(rèn)對(duì)象
let target = {
name: 'scq000',
age: 23
};
//代理對(duì)象
let proxy = new Proxy(target, {
has(trapTarget, key) {
if(key === 'age') {
return false;
}else {
//調(diào)用默認(rèn)的行為
return Reflect.has(trapTarget, key);
}
}
});
console.log('value' in proxy); //true
console.log('age' in proxy); //false
可以看到,上面這個(gè)例子使用代理對(duì)象攔截了in
操作符的默認(rèn)行為并作出了修改绍妨。has
這個(gè)方法稱作陷阱函數(shù)润脸,它能夠響應(yīng)對(duì)in
操作的訪問(wèn)操作。trapTarget
則是這個(gè)函數(shù)的目標(biāo)對(duì)象他去,has
方法接受一個(gè)額外的參數(shù)key
是對(duì)應(yīng)著需要檢查的屬性。一旦檢查到屬性名為age
倒堕,則返回false
,這樣就能隱藏這個(gè)屬性灾测。
以下是一些常用的代理陷阱以及反射所對(duì)應(yīng)的默認(rèn)行為:
代理陷阱 | 被重寫(xiě)的行為 | 默認(rèn)行為 |
---|---|---|
get/set | 讀取/寫(xiě)入一個(gè)屬性值 | Reflect.get/Reflect.set |
has | in運(yùn)算符 | Reflect.has |
deleteProperty | delete運(yùn)算符 | Reflect.deleteProperty |
getPropertyOf/setPropertyOf | Object.getPropertyOf/setPropertyOf | Reflefct.getPropertyOf/setPropertyOf |
目前,反射和代理在瀏覽器上還不支持,主要還是用在NodeJS編程上媳搪。這一部分的功能在實(shí)際開(kāi)發(fā)中并不是特別常用铭段,因此,這里不做過(guò)多介紹秦爆。如果感興趣的話序愚,可以自行查找相關(guān)文檔。