Module模塊

ES6的Class只是面向對象編程的語法糖,升級了ES5的構造函數的原型鏈繼承的寫法,並沒有實際的解決模塊化的問題。
module功能就是爲了解決這個問題而提出的。

1.export命令

模塊功能主要由兩個命令構成:export和import。export命令用於規定模塊的對外接口,import命令用於輸入其他模塊提供的功能。
一個模塊就是一個獨立的文件,如果希望外部能夠讀取模塊內部的某個變量,就必須使用export關鍵字輸出這個變量。如下:

//寫法一
export var first="michael";
export var last = "lion";
export var year = "mouse";

//寫法二
 var first="michael";
 var last = "lion";
 var year = "mouse";
export {first,last,year};

如此就用export命令對外部輸出了3個變量,這兩種寫法都是等價的,但是應該優先考慮使用寫法二,因爲這樣寫就可以在腳本尾部一眼看清輸出了哪些變量。

其次,export命令除了輸出變量,還可以輸出函數或者類。
如:

export function multiply(x,y){
    renturn x*y;
}  //對外輸出函數multiply

通常情況下,export輸出的變量就是本來的名字,但是可以使用as關鍵字重命名。
如下:

function v1(){ ... }
function v2(){ ... }
export {
    v1 as streamV1;
    v2 as streamV2;
    v2 as streamLastVersion
};

上面的代碼使用as關鍵字重命名函數V1和V2的對外接口,重命名後,V2可以用不同的名字輸出兩次。
最後,export命令可以出現在模塊的任何位置,只要處於模塊頂層即可。

export語句輸出的值是動態綁定,綁定其所在的模塊。

export var foo = "bar";
setTimeout(() =>foo = "baz" ,500);

上面的代碼輸出變量foo,值爲bar,500毫秒之後變成baz。

2.import

使用export命令定義了模塊的對外接口之後,其他js文件就可以通過import命令加載這個模塊。
如:

import {first,last,year} from './profile';
function setName(element){
    element.textContent = first + ' ' + last;
}

上面的import就是用來加載profile.js文件,並從中輸入變量。
import命令接受一個對象,用{}表示,裏面制定要從其他模塊導入的變量的名字,{}中的變量名字必須與被導入的模塊對外接口的名稱相同。
如果想爲變量重新取一個名字,要在import中使用as關鍵字,將輸入的變量重新命名。

import {last as surname} from './profile';

注意: import 命令具有提升效果,會提升到整個模塊的頭部首先執行。
例如:

foo();
import {foo} from 'my_modult';

上面的代碼不會報錯,因爲import優先於foo之前執行。
如果在一個模塊中先輸入後輸出同一個模塊,import語句可以和export語句寫在一起。
例如:

export {es6 as default} from './someModule';
//相當於:
import {es6} from './someModule';
export default es6;

上面的代碼中,import語句可以和export語句寫在一起,但是從性能的角度考慮,這樣做是不太好的,所以一般採用標準寫法。

3.模塊的整體加載

除了指定加載某個輸出值,還可以使用整體加載,只要使用(*)制定一個對象,這樣所有輸出值都會加載在這個對象上。
例如,下面的first.js文件,輸出兩個方法:area和circumference:

//circum.js
export function area(radius){
    return Math.PI*radius*radius;
}
export function circumference(radius){
    return 2*Math.PI*radius;
}
//現在加載這個模塊
//main.js
import {area,circum} from './circle';
console.log("圓形面積:"+area(4));
console.log("圓周長:"+circle.circumference(14));

上面的寫法是逐一指定加載扥方法,整體加載的寫法如下:

import *as circum from './circle';
console.log("圓形面積:"+area(4));
console.log("圓周長:"+circle.circumference(14));

4.module命令

module命令可以代替import語句,達到整體輸入模塊的作用。

//main.js
module  circum from './circle';
console.log("圓形面積:"+area(4));
console.log("圓周長:"+circle.circumference(14));

module後跟一個變量,表示輸入的模塊定義在該變量上。

5.export default命令

爲了方便使用者,使其不用閱讀文檔就能加載模塊,就要用到export defalut命令,爲模塊指定默認輸出。
例如:

//export-default.js
export default function (){
    console.log("foo");
}

上面的模塊文件export-defalut.js的代碼,它的默認輸出是一個函數。
在其他模塊中輸出該模塊時,import命令可以爲該匿名函數指定任意名字,如下:

import-default.js
import customName from './export-default.js'
customName();    //foo

上面的import命令可以用任意名稱指向export-default.js輸出的方法,這樣就不用必須知道元模塊的函數名字了。但是注意的是,這時的import後面不用使用{}括起來。

export default命令用在非匿名函數前也是可以的。
如下:

//export-defalut.js
export default function foo(){
    console.log("foo");
}
//或者:
function foo(){
    console.log("foo");
}
export default foo;

可以這樣做的原因,只是因爲,foo函數在模塊外部是無效的,加載的時候將其視同爲匿名函數。

比較:

使用export default命令時,對應的import語句不需要使用大括號,但是隻使用export命令時,import是要加括號的。

理由:
export defalut命令用於指定模塊的默認輸出,當然,一個模塊只能有一個默認輸出,因此export default命令只能使用一次,所以,其對應的import命令後面纔不用加上大括號,因爲只可能對應一個方法。

本質上,export default命令就是輸出一個叫做default的變量或者方法,然後系統允許你爲她起任意的名字,所以下面的寫法也是有效的:

//module.js
function add(x,y){
    return x*y;
};
export {add as default};
//app.js
import {default as xxx} from './module';

有了export dedfault命令,輸入模塊時就非常直觀了。
其次,如果想在一條import語句中同時輸入默認方法和其他變量,可以如下寫法:

import customName,{otherMethod} from './export-default'

如果要輸出默認值,只需要將值跟在export default之後即可。

//export default也可以用來輸出類
export default 42
//myclass.js
export default class{...}
//main.js
import myclass from 'myclass'
let o = new myclass();

6.模塊的繼承

模塊之間也是存在繼承的,比如:
假設有一個circleplus模塊繼承了circle模塊。

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

上面的export *就表示輸出circle模塊的所有屬性和方法。
注意:
export *命令會忽略circle模塊的default方法,之後又輸出了自定義的e變量和默認方法。
這時也可以將circle的屬性或方法改名後再輸出。

//circleplus.js
export {area as circleArea}from 'circle';

上面的代碼表示,只輸出circle模塊的area方法,且將其改名爲circleArea。
加載上面的模塊寫法如下:

//main.js
module math from 'circleplus';
import exp from 'circleplus';
console.log(exp(math.E));

上面的import exp表示,將circleplus模塊的默認方法加載爲exp方法。

7.ES6模塊加載的實質

首先,ES6模塊加載機制與commonJs模塊完全不同。
commonJs模塊輸出的是一個值的複製品,但ES6模塊輸出的是值的引用。

commonJs模塊輸入的是被輸出值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個被輸出的值了。如:

//lib.js
var counter = 3;
function incCounter(){
    counter++;
}
moudle.exports = {
    counter:counter,
    incCounter:incCounter,// 輸出內部變量counter和改寫這個變量的內部方法incCounter
};
//然後加載該模塊:
//main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;

console.log(counter);  //3
incCounter();
console.log(counter);  //3

這段代碼就表示,在counter輸出後,lib.js模塊內部的變化就影響不到counter了。

其次:
ES6模塊的運行機制與commonJs不一樣,它遇到模塊加載命令import時不會去執行模塊,只會生成一個動態的只讀索引,等到真的要用到這個值的時候,纔會到模塊中去取值,所以只要原值變了,輸入值也是會跟着改變的。
因此,ES6模塊是動態引用,並不會緩存值,模塊裏面的變量綁定其所在的模塊。
例如:

//lib.js
export let counter = 3;
export function incCounter(){
    counter++;
}

//main1.js
import {counter,incCounter} from './lib';

console.log(counter);  //3
incCounter();
console.log(counter);  //3

上面的代碼表示,ES6模塊輸入的變量是活的,完全能夠反應出模塊lib.js內部的變化。

最後,由於ES6輸入的模塊變量只是一個“符號鏈接”,所以這個變量是隻讀的,對它進行重新賦值,是會報錯的。
如:

//lib.js
export let obj = {};
//main.js
import {obj} from './lib';
obj.prop = 123;  //ok
obj = {}; //TypeError

上面的代碼中,main從lib輸入變量obj,可以對obj添加屬性,但是重新賦值就會報錯。
因爲變量obj指向的地址是隻讀的,不能重新賦值,這樣就好比main.js創建了一個名爲obj的const變量。

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