
模塊
The old way
傳統(tǒng)JavaScript有相當(dāng)多的模塊化方案(AMD,CommonJS,UMD等)。大體都是通過一個(gè)外部函數(shù)返回一個(gè)攜帶各種閉包的對(duì)象,達(dá)到將公用的API暴露出來的目的。類似這樣
function Hello(name) {
function greeting() {
console.log( "Hello " + name + "!" );
}
// public API
return {
greeting: greeting
};
}
var me = Hello( "Kyle" );
me.greeting(); // Hello Kyle!
之所以會(huì)誕生出這么多方案,無疑是需求跑在了前面,而語法不直接支持這些。于是乎ES6吸收了一些C系語言的思想,直接支持模塊化。
Before the new way
在具體介紹語法之前,來看看ES6模塊化和以往有何不同
- ES6模塊化是基于文件的,一個(gè)文件只能有一個(gè)模塊;
ES6 modules are file-based, meaning one module per file. At this time, there is no standardized way of combining multiple modules into a single file.(ES6模塊化是基于文件的,意味著每一個(gè)文件只有一個(gè)模塊。與此同時(shí),沒有一個(gè)將多個(gè)模塊合并在同一個(gè)文件內(nèi)的標(biāo)準(zhǔn)化的方式)
- ES6模塊化是單例的;
Every time you import that module into another module, you get a reference to the one centralized instance. If you want to be able to produce multiple module instances, your module will need to provide some sort of factory to do it.(每次引用一個(gè)模塊,實(shí)際上只是獲取了一個(gè)中心實(shí)例的引用。如果你想生成多個(gè)模塊實(shí)例,你的模塊需要提供某種工廠模式來實(shí)現(xiàn))
The new way
exports & import
一個(gè)模塊包含多個(gè)獨(dú)立的子功能,如果需要公開相應(yīng)的功能模塊,直接用export關(guān)鍵字即可,來看一個(gè)例子:
// exports.js
export function foo(canvas, options) {
//可以導(dǎo)出function
}
export class bar {
//可以導(dǎo)出class
}
export {foo,bar} //可以是Object
導(dǎo)入模塊
import {foo, bar} from "kittydar.js";
//重命名
import {foo as fooRename} from "kittydar.js";
import {bar as barRename} from "kittydar.js";
export default
ES6完全支持
import語句從歷史項(xiàng)目AMD或CommonJS模塊化方案中導(dǎo)入模塊。
來看代碼
//傳統(tǒng)CommonJS寫法
module.export = {
field1: value1,
field2: function(){
//implements
}
}
//ES6寫法
//exportDefault.js
export default {
field1: value1,
field2: function(){
//implements
}
};
兩者幾乎等價(jià),這意味著export輸出模塊本質(zhì)上和CommonJS模塊化方案沒什么區(qū)別,ES6完全可以從歷史代碼引入模塊,減少代碼遷移的痛苦。
import expDef from 'exportDefault.js';
import $ from 'jQuery.js';
import _ from 'lodash'
循環(huán)依賴
A依賴B,B依賴A,怎么做到不出問題呢?
《You Don't Know JS ES6》書中給出了一個(gè)例子:
//Module A
import bar from "B";
export default function foo(x) {
if(x>10)return bar(x-1);
return x * 2;
}
//Module B
import foo from "A";
export default function bar(y) {
if(y>5)return foo(y/2);
return y * 3;
}
我們來試想一下,foo和bar兩個(gè)函數(shù)在同一個(gè)函數(shù)作用域內(nèi),運(yùn)行起來自然是沒問題的,然而在模塊化環(huán)境下,ES6則需要做一些額外的工作才能使得如此循環(huán)依賴生效。但是,怎么做到呢?
In essence, the mutual imports, along with the static verification that’s done to validate both import statements, virtually composes the two separate module scopes (via the bindings), such that foo(..) can call bar(..) and vice versa. This is symmetric to if they had originally been declared in the same scope.
大概意思是本質(zhì)來說,實(shí)際上這樣的循環(huán)依賴將兩個(gè)模塊(A和B)的函數(shù)作用域虛擬地聯(lián)合在了一起,如此一來foo可以調(diào)用bar,bar也可以調(diào)用foo。這和將foo和bar兩個(gè)函數(shù)聲明在同一個(gè)函數(shù)作用域的實(shí)際效果是一樣的。
模塊對(duì)象 & 聚合模塊
使用import *實(shí)際上導(dǎo)出的是模塊命名空間對(duì)象,將所有模塊的屬性全部導(dǎo)出。
import * as cows from "cows";
cows.moon();
聚合模塊又是什么呢?很好理解,將其他細(xì)小模塊整理起來,在同一個(gè)模塊里聚合,并導(dǎo)出。類似于先import...from了模塊,然后再將其export,這樣的操作無非就是把兩步合成一步來做。
// 導(dǎo)入"sri-lanka"并將它導(dǎo)出的內(nèi)容的一部分重新導(dǎo)出
export {Tea, Cinnamon} from "sri-lanka";
// 導(dǎo)入"equatorial-guinea"并將它導(dǎo)出的內(nèi)容的一部分重新導(dǎo)出
export {Coffee, Cocoa} from "equatorial-guinea";
// 導(dǎo)入"singapore"并將它導(dǎo)出的內(nèi)容全部導(dǎo)出
export * from "singapore";
Classes 類
墻裂聲明:此類非彼類,JavaScript中的Classes和其他語言Classes雖然類似,但有本質(zhì)的區(qū)別,而JS中所謂的Classes只不過是語法糖。
實(shí)現(xiàn)原理
At the heart of the new ES6 class mechanism is the class keyword, which identifies a block where the contents define the members of a function’s prototype.(class機(jī)制核心在于class關(guān)鍵字,這標(biāo)識(shí)了一個(gè)定義了函數(shù)原型上面的成員的語句塊)
有點(diǎn)拗口,直接來個(gè)例子:
class Foo {
constructor(a,b) {
this.x = a;
this.y = b;
}
gimmeXY() {
return this.x * this.y;
}
}
//等價(jià)于
function Foo(a,b) {
this.x = a;
this.y = b;
}
Foo.prototype.gimmeXY = function() {
return this.x * this.y;
}
可以這么說,你所看到的class的內(nèi)容,實(shí)際上就是該函數(shù)的原型對(duì)象(Prototype Object)本身。
我們趁機(jī)來復(fù)習(xí)一下JavaScript的Prototype和Constructor吧。
每一個(gè)函數(shù)都包含一個(gè)prototype屬性,這個(gè)屬性是指向一個(gè)對(duì)象的引用,這個(gè)對(duì)象稱之為“原型對(duì)象”(prototype object)。每一個(gè)函數(shù)包含不同的原型對(duì)象。當(dāng)將函數(shù)用作構(gòu)造函數(shù)的時(shí)候,新創(chuàng)建的對(duì)象會(huì)從原型對(duì)象上繼承屬性。
在JavaScript中,類的實(shí)現(xiàn)是基于其原型繼承機(jī)制的。如果兩個(gè)實(shí)例都從同一個(gè)原型對(duì)象上繼承了屬性,我們說他們是同一個(gè)類的實(shí)例。
如果兩個(gè)對(duì)象繼承自同一個(gè)原型,往往意味著(但不是絕對(duì))他們是由同一個(gè)構(gòu)造函數(shù)創(chuàng)建并初始化的
構(gòu)造函數(shù)是用來初始化新創(chuàng)建的對(duì)象的。使用new調(diào)用構(gòu)造函數(shù)會(huì)自動(dòng)創(chuàng)建一個(gè)新對(duì)象,因此構(gòu)造函數(shù)本身只需初始化這個(gè)新對(duì)象的狀態(tài)即可。調(diào)用構(gòu)造函數(shù)的一個(gè)重要特征是,構(gòu)造函數(shù)的prototye屬性被用作新對(duì)象的原型。這意味著通過同一個(gè)構(gòu)造函數(shù)創(chuàng)建的所有對(duì)象都繼承自一個(gè)相同的對(duì)象,因此他們是同一個(gè)類的成員。
---引自《大犀?!?/p>
認(rèn)真看看下面的Demo
function Foo(a,b) {
this.x = a;
this.y = b;
console.log('Foo is constructor');
return 'ok';
}
function Bar(a,b) {
this.x = a;
this.y = b;
console.log('Bar is constructor');
return 'ok';
}
Foo.prototype.getXYmutiply = function() {
return this.x * this.y;
}
//通過原型對(duì)象傳遞實(shí)現(xiàn)方法和屬性的繼承
Bar.prototype = Foo.prototype;//原型對(duì)象傳遞(繼承)
var bar = new Bar(5,4);//Bar is constructor
var foo = new Foo(5,6);//Foo is constructor
/** 此時(shí) bar 和 foo 是同一個(gè)原型對(duì)象 **/
console.log(Bar.prototype === Foo.prototype);//true
console.log(Bar.prototype.constructor);//function Foo(){[....]}
Bar.prototype.constructor = Bar;
bar = new Bar(5,4);//Bar is constructor
console.log(Bar.prototype.constructor);//function Bar(){[....]}
console.log(bar.getXYmutiply());//20
所以我們很清楚,ES6所謂的Classes特性只是在ES5基礎(chǔ)上做了美化,用貼近其他語言語法(Java)的形式,模擬出其他語言有的面向?qū)ο蟮奶匦裕ɡ^承,多態(tài),封裝),使得代碼看起來更加明確易懂。
extends & super
接著上面的Foo Bar例子
class Foo {
constructor(a,b) {
this.x = a;
this.y = b;
}
gimmeXY() {
return this.x * this.y;
}
}
class Bar extends Foo {
constructor(a,b,c) {
super( a, b );
this.z = c;
}
gimmeXYZ() {
return super.gimmeXY() * this.z;
}
}
var b = new Bar(5,15,25);
b.x; // 5
b.y; // 15
b.z; // 25
b.gimmeXY();//75
b.gimmeXYZ(); // 1875
仔細(xì)想想,在pre-ES6的時(shí)代,要模擬面向?qū)ο笫且患艿疤鄣氖虑?。首先我們熟知面向?qū)ο笕齻€(gè)重要特征,繼承、多態(tài)、封裝。尤其是繼承,傳統(tǒng)的做法都是通過父類將原型對(duì)象賦值給子類原型對(duì)象實(shí)現(xiàn)的,與此同時(shí)還要注意,在原型對(duì)象賦值以后,需要更正子類原型對(duì)象中的構(gòu)造方法,blabla...特別啰嗦。ES6這次索性把以上我們說的一整套流程固定成了幾個(gè)語法糖,在明確語義的同時(shí),減少了重復(fù)啰嗦的代碼實(shí)現(xiàn)。
再來看一組代碼對(duì)照,一切都會(huì)很清晰了。
function Foo() {
this.a = 1;
}
function Bar() {
this.b = 2;
Foo.call( this );
}
// `Bar` "extends" `Foo`
Bar.prototype = Object.create( Foo.prototype );
//等價(jià)于
class Foo {
constructor() {
this.a = 1;
}
}
class Bar extends Foo {
constructor() {
this.b = 2;
super();
}
}
new.target & static
老規(guī)矩,先看代碼
class Foo {
static answer = 42;
static cool() {
console.log( "cool" );
}
// ..
}
class Bar extends Foo {
constructor() {
console.log( new.target.answer );
}
}
Foo.answer;// 42
Bar.answer;// 42
var b = new Bar();// 42
b.cool();// "cool"
b.answer;// undefined -- `answer` is static on `Foo`
Be careful not to get confused that static members are on the class’s prototype chain. They’re actually on the dual/parallel chain between the function constructors.(靜態(tài)成員并不在類的原型鏈里,而是存在于兩個(gè)函數(shù)間的構(gòu)造方法內(nèi))
如上所說,離開了構(gòu)造方法,使用new.target是無效的。
模塊化編程部分暫時(shí)介紹到這里,如果遇到什么沒介紹到的,我會(huì)及時(shí)補(bǔ)充進(jìn)來。