一篇文章幫你弄懂ES6模塊化

前端模塊化是前端工程化的基石。時下,大前端時代中對模塊的運用更是無處不在,而放眼將來,es6中所提出的import和export的形式彷佛將統一先後端的模塊化加載。前端

1、模塊化概述

隔離不一樣的js文件,僅暴露當前模塊所須要的其餘模塊,這就是模塊化思想。es6

在學習ES6的模塊化以前先複習一下以前出現的模塊化,比較經常使用的有三種規範定義:CommonJS、AMD、CMD。express

它們的特色與相互間的區別是:後端

  1. CommonJS適用於服務端,寫法爲:前端工程化

    var clock = require('clock.js')
    clock.start();
    複製代碼

    上例表示,clock的調用必須等待clock.js請求加載成功,換句話說,是同步操做,而這也致使了CommonJS普遍應用於服務端而不是客戶端(服務器讀取模塊都是在本地磁盤,加載速度快,而若是是在客戶端則容易出現‘假死’狀態)那麼能不能用異步加載模塊呢?瀏覽器

  2. AMD (Asynchronous Module Definition) 就是異步加載模塊,多用於瀏覽器requireJs應用了這一規範),寫法爲:緩存

    require([module],callback);
    
    // eg
    require(['clock.js'],function(clock){
      clock.start();
    })
    複製代碼

    雖然實現了異步加載,規避了瀏覽器的「假死」問題,可是也存在缺點: 一開始就把全部依賴寫出來是不符合邏輯順序的。那麼,能不能像CommonJS同樣用的時候才require,而後還能支持異步加載後執行呢服務器

  3. CMD (Common Module Definition) 則是依賴就近,用的時候再requireseajs推崇的規範 ),寫法爲: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個方法,稱爲「編譯時加載

2、ES6模塊化的語法規範

嚴格模式

在ES6模塊中自動採用嚴格模式。規定:

  • 變量必須先聲明
  • 函數參數不能有同名屬性
  • 不能使用with
  • 對只讀屬性賦值、delete不可刪除屬性直接報錯
  • 不可刪除變量delete prop、只能刪除屬性delete global[prop]
  • eval不會再外層做用域引入變量
  • evalarguments不可從新賦值
  • arguments不會自動反應函數參數變化
  • 禁止this指向全局
  • 增長保留字:static、interface、protected等。

注意:在ES6模塊中,頂層thisundefined,不該該被使用。

export命令

第一種:

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 };
複製代碼

注意:

  1. export語句輸出的接口是對應值的引用,也就是一種動態綁定關係,經過該接口能夠獲取模塊內部實時的值。

    對比CommonJS規範:CommonJS模塊輸出的是值的緩存,不存在動態更新。

  2. export命令規定要處於模塊頂層,一旦出如今塊級做用域內,就會報錯,import同理

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模式:雖然foobar在兩個語句中加載,可是對應的是同一個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和import的複合寫法

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;
複製代碼

import()方法

前面提到過,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();
複製代碼

3、不一樣規範間加載

import加載CommonJS模塊

  • 使用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模塊

  • 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'}
    複製代碼

4、對比CommonJS

有了新歡也不能忘了舊愛,讓咱們再來繼續對比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 執行完畢 執行完畢複製代碼
相關文章
相關標籤/搜索