前端模塊化是前端工程化的基石。時下,大前端時代中對模塊的運用更是無處不在,而放眼將來,es6中所提出的import和export的形式彷佛將統一先後端的模塊化加載。前端
隔離不一樣的js文件,僅暴露當前模塊所須要的其餘模塊,這就是模塊化思想。es6
在學習ES6的模塊化以前先複習一下以前出現的模塊化,比較經常使用的有三種規範定義:CommonJS、AMD、CMD。express
它們的特色與相互間的區別是:後端
CommonJS適用於服務端,寫法爲:前端工程化
var clock = require('clock.js')
clock.start();
複製代碼
上例表示,clock
的調用必須等待clock.js
請求加載成功,換句話說,是同步操做,而這也致使了CommonJS普遍應用於服務端而不是客戶端(服務器讀取模塊都是在本地磁盤,加載速度快,而若是是在客戶端則容易出現‘假死’狀態)那麼能不能用異步加載模塊呢?瀏覽器
AMD (Asynchronous Module Definition) 就是異步加載模塊,多用於瀏覽器( requireJs應用了這一規範),寫法爲:緩存
require([module],callback);
// eg
require(['clock.js'],function(clock){
clock.start();
})
複製代碼
雖然實現了異步加載,規避了瀏覽器的「假死」問題,可是也存在缺點: 一開始就把全部依賴寫出來是不符合邏輯順序的。那麼,能不能像CommonJS同樣用的時候才require,而後還能支持異步加載後執行呢?服務器
CMD (Common Module Definition) 則是依賴就近,用的時候再require( seajs推崇的規範 ),寫法爲:app
define(function(require,exports,module){
var clock = require('clock.js');
clock.start();
})
複製代碼
AMD和CMD的區別是對依賴模塊的執行時機不一樣,而不是加載處理方式不一樣,兩者皆爲異步加載模塊。dom
AMD依賴前置,js能夠方便地清楚依賴模塊有哪些,當即加載;
CMD就近依賴,開發者能夠在須要用到依賴的時候再require,可是對於js處理器來講,須要把代碼處理爲字符串解析一遍才知道依賴了哪些模塊,即犧牲性能來得到開發的便利,雖然實際上解析的時間短到能夠忽略,可是也有不少人詬病CMD這一點。
ES6的模塊化設計思想是儘可能靜態化,使得編譯時就能肯定模塊的依賴關係。
對比CommonJS和ES6模塊:
// CommonJS
let { start, exists, readFile } = require('fs')
// 至關於
// let _fs = require('fs')
// let start = _fs.start, exists = _fs.exists, readFile = _fs.readFile
// ES6
import { start, exists, readFile } from 'fs'
複製代碼
上述例子中,CommonJS的實質是總體加載fs模塊生成一個_fs
對象,以後再從對象中分別讀取3個方法,稱爲「運行時加載」。而ES6模塊是加載3個方法,稱爲「編譯時加載」
在ES6模塊中自動採用嚴格模式。規定:
with
delete
不可刪除屬性直接報錯delete prop
、只能刪除屬性delete global[prop]
eval
不會再外層做用域引入變量eval
和arguments
不可從新賦值arguments
不會自動反應函數參數變化this
指向全局注意:在ES6模塊中,頂層
this
爲undefined
,不該該被使用。
第一種:
export var a = '123';
export const _b = '2323'
export let c = '2222'
複製代碼
第二種:
var a = '123';
const _b = '2323'
let c = '2222'
export {a, _b, c}; // 推薦
複製代碼
第三種(第二種基礎上加上as關鍵詞重命名)
var a = '123';
const _b = '2323'
let c = '2222'
export {
a as stream1,
_b as stream2,
c as stream3 };
複製代碼
注意:
export語句輸出的接口是對應值的引用,也就是一種動態綁定關係,經過該接口能夠獲取模塊內部實時的值。
對比CommonJS規範:CommonJS模塊輸出的是值的緩存,不存在動態更新。
export命令規定要處於模塊頂層,一旦出如今塊級做用域內,就會報錯,import同理。
第一種:
import {a, _b ,c} from './profile'
複製代碼
第二種:
import {stream1 as firstVal} from './profile'
複製代碼
import 是靜態執行,不能夠應用表達式、變量和if結構。
if(x == 1){ import { foo } from 'module1' }else{ //... } 複製代碼
import語句是Singleton模式:雖然foo
和bar
在兩個語句中加載,可是對應的是同一個my_module
實例。
import { foo } from './module1'
import { bar } from './module1'
// 至關於
import {foo,bar} from './module1'
複製代碼
可使用*來指定一個對象,全部輸出值都加載到這個對象上:
import * as circle from './module1'
circle.foo();
circle.bar();
複製代碼
因爲模塊總體加載所在的對象都是能夠靜態分析的,因此不容許運行時改變。
import * as circle from './module1'
// 下面兩行都是不容許的
circle.foo = 123;
circle.bar = function(){}
複製代碼
export default命令能夠爲模塊默認輸出
// module2.js
export default function(){
console.log('123')
}
// 至關於
function a(){
console.log('123')
}
export {a as default};
複製代碼
import命令能夠爲匿名函數指定任意名字
import defaultFn from './module2'
// 至關於
import {default as defaultFn} from './module2'
複製代碼
export { foo, bar} from 'my_module';
// 等同於
import {foo,bar} from 'my_module';
export{foo,bar};
複製代碼
export {es6 as default} from './someModule'
// 等同於
import {es6} from './someModule'
export default es6;
複製代碼
前面提到過,require是動態加載,便可以在用的時候再require;而import是靜態執行,只能處於代碼最頂層,不能夠存在於塊級做用域中。這致使import沒法在運行中執行(相似於AMD的缺點)。 因而就有了一種提案:引入import()函數,相似於Node的require函數(CommonJS),可是它實現了異步加載。
定義:import()函數接收與import相同的參數,返回一個Promise對象,加載獲取到的值做爲then方法的回調參數。
const main = document.querySelector('main')
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContext = err.message;
})
複製代碼
// 加載得到接口參數:
import('./module1.js')
.then(({default:defaultFn,foo,bar}) => {
console.log(defaultFn)
})
複製代碼
// 同時加載多個模塊並應用於async函數中
async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1,js'),
import('./module2.js'),
import('./module3.js')
])
}
main();
複製代碼
使用import命令加載CommonJS模塊,Node會自動將module.exports屬性當作模塊的默認輸出,即等同於export default
// a.js
module.exports = {
foo: 'hello',
bar: 'world'
}
// 在import引入時等同於
export default {
foo: 'hello',
bar: 'world'
}
複製代碼
CommonJs模塊是運行時肯定輸出接口,因此採用import命令加載CommonJS模塊時,只能使用總體輸入(*)。
import {readfile} from 'fs' //當'fs'爲CommonJS模塊時錯誤
// 總體輸入
import * as express from 'express'
const app = express.default();
複製代碼
require命令加載ES6模塊時,全部的輸出接口都會成爲輸入對象的屬性。
// es.js
let foo = {bar : 'my-default'};
exxport default foo;
foo = null;
// cjs.js
const es_namespace = require('./es')
console.log(es_namespace.default);// {bar:'my-default'}
複製代碼
有了新歡也不能忘了舊愛,讓咱們再來繼續對比CommonJS和ES6模塊化的區別,進一步體會理解ES6模塊化的特性。
CommonJS一個模塊對應一個腳本文件,require命令每次加載一個模塊就會執行整個腳本,而後生成一個對象。這個對象一旦生成,之後再次執行相同的require命令都會直接到緩存中取值。也就是說:CommonJS模塊不管加載多少次,都只會在第一次加載時運行一次,之後再加載時就返回第一次運行的結果,除非手動清除系統緩存。
// a.js
exports.done = false;
var b = require('./b.js'); // 1. a.js暫停執行,轉到執行b.js ; b.js完畢後回來,b:{done:true}
console.log('在a.js中,b.done=%j',b.done); // 5. '在a.js中,b.done=true'
exports.done = true;
console.log('a.js執行完畢') // 6. 'a.js執行完畢'
// b.js
exports.done = false;
var a = require('./b.js') // 2. a:{done:false}
console.log('在b.js中,a.done=%j',a.done); // 3. '在b.js中,a.done=false'
exports.done = true;
console.log('b.js執行完畢') // 4. 'b.js執行完畢',繼續執行a.js
// main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在main.js中,a.done=%j,b.done=%j',a.done,b.done); //'在main.js中,a.done=true,b.done=true'
複製代碼
從上面也能夠看到:1. CommonJS輸入的是被輸出值的複製,而不是引用。2. 因爲CommonJS模塊遇到循環加載返回的是當前已經執行的部分的值,而不是代碼所有執行後的值(上面的第2步註釋)
對比:ES6模塊是動態引用,變量不會被緩存
// a.js
import {bar} from './b.js';
export function foo(){
console.log('foo')
bar();
console.log('執行完畢')
}
foo();
// b.js
import {foo} from './a.js' // 若是爲CommonJS,這裏直接就返回undefined值且不會再更改
export function bar(){
console.log('bar')
if(Math.random() > 0.5){
foo();
}
}
// 執行結果可能爲:foo bar 執行完畢
// 執行結果也可能爲: foo bar foo bar 執行完畢 執行完畢複製代碼