深刻理解es module

模塊系統的做用

傳統script標籤的代碼加載容易致使全局做用域污染,並且要維繫一系列script的書寫順序,項目一大,維護起來愈來愈困難。模塊系統經過聲明式的暴露和引用模塊使得各個模塊之間的依賴變得明顯。javascript

es module如何工做的

這部分推薦去看es-modules-a-cartoon-deep-dive,原文裏有圖,如下的內容是我的理解整理。html

分三步:java

  1. 構造,尋找而且下載全部的文件而且解析成模塊記錄(Module Records)(包含當前模塊代碼的抽象語法樹,當前模塊的依賴模塊的信息)。
  2. 實例化,將模塊記錄實例化將各個模塊之間的import,export部分對應的都在內存中指向到一塊兒(linking)
  3. 執行,將import, export內存裏指向的地址填上實際的值。

構造階段(Construction)

構造階段要作三件事情:node

解釋(interpret)import後的模塊指示符(module specifier)成實際url或者文件地址

不一樣平臺根據本身平臺的模塊解析算法(Module Resolution Algorithm)解釋模塊指示符,瀏覽器端目前只接受url作爲指示符。不過瀏覽器未來會一樣支持內置模塊好比kv-storagegit

模塊指示符裏的變量

模塊指示符裏不能有變量可是node中commonJS是能夠有的,由於在commonJS的模塊代碼裏,require聲明前的代碼是會先執行的,es module是最後一步再去執行,這一步才知道各個變量的具體值是多少。因此能夠在node中有以下寫法:es6

require(`${path}/sum.js`);
複製代碼

不過es module裏有另外一種寫法動態引入import()能夠支持在代碼執行時動態引入模塊,能夠在指示符裏攜帶變量github

import(`${path}/sum.js`);
複製代碼

瀏覽器根據url下載文件或者node根據文件地址去加載文件

將文件解析成模塊記錄

瀏覽器解析常規js文件時會解析完後再執行。和模塊的解析策略不同,這裏要告訴瀏覽器解析的是個模塊。在html中:算法

<script type="module"> import {sum} from "./sum.js" </script>
複製代碼

ps: 在node中由於沒有瀏覽器這種相似打tag的形式,有種方案是模塊文件是.mjs後綴結尾的方案,不過目前還沒有敲定。瀏覽器

解析模塊文件爲模塊記錄,找到依賴的模塊再去下載模塊而後解析成模塊記錄,直到全部的模塊都解析成模塊記錄爲止。模塊記錄會存在當前全局的一個模塊映射裏(Module Map),能夠理解成一個緩存,下次再有相同url的模塊請求就直接從模塊映射裏拿出模塊記錄便可。緩存

實例化階段

將上面獲得的模塊記錄類實例化。 首先在內存中指定位置給各個模塊的export導出的變量或者函數,接着將模塊中對應的import部分一樣指向對應的export的內存地址。 舉個🌰

// main.js
import {obj} from "./obj.js"

// obj.js
const obj = {a: 123};
export {obj}
複製代碼

obj.js文件裏導出的objmain.js文件裏引用的obj是指向同一個內存地址的,這中方法就是動態綁定(live binding)。

<script type='module'> import {obj} from "./obj.js" console.log(obj); //{a: 123} setTimeout(() => { console.log(obj) //{b: 233} }, 2000); </script>
複製代碼
let obj = {
        a: 123
    };
    setTimeout(() => {
        obj = { b: 233 };
    }, 1000);
    export { obj };
複製代碼

下面咱們看下node中一樣的代碼的效果。

// test1.js
    var obj = require("./test2.js");
    console.dir(obj); // {a: 123}
    setTimeout(() => {
        console.dir(obj); // {a: 123}
    }, 2000);
// test2.js
    let obj = { a: 123 };
    setTimeout(() => {
    obj = { b: 233 };
    }, 1000);
    module.exports = obj;
複製代碼

在commonJS中require一個對象是在內存中複製一份導出模塊的對象。動態綁定主要解決的問題就是循環引用的問題,循環引用在下面的執行階段進行解釋。 注意: es module中能夠在模塊導出的部分更改導出值如上面代碼所示,可是不能在引入部分更改。

import {obj} from "./sum.js"
    obj = '233'  // Uncaught TypeError: Assignment to constant variable.
複製代碼

如上報錯會提示不能給常量賦值,不過若是是對象的話能夠更改內部的key,因爲動態綁定的緣由,導出部分也會發生改變

// main.js
    import {obj} from "./obj.js"
    setTimeout(() => {
        obj.a = '嘻嘻'
    }, 1000);
// obj.js
    let obj = { a: 123 };
    console.log(obj); // {a: 123}
    setTimeout(() => {
        console.log(obj); // {a: "嘻嘻"}
    }, 2000);
    export { obj };
複製代碼

執行階段(evaluate)

原文中是evaluate,我這裏理解成了執行,若有不對歡迎指出。引擎開始執行模塊了,每一個模塊只會被執行一次。在上面提到過的module map裏的模塊記錄裏會存有當前模塊的狀態是實例化中仍是實例完成仍是執行完成等。能夠避免同一個模塊文件被屢次執行。

循環引用問題

以下在node中,兩個模塊互相引用。

// test1.js
    var b = require("./test2").b;
    console.dir("test1: " + b);  // 'test1: test2' 🥈
    var a = "test1";
    exports.a = a;
// test2.js
    var a = require("./test1").a;
    console.log("test2: " + a);  // test2: undefined 🥇
    var b = "test2";
    setTimeout(() => {
        console.log("test2: " + a); // test2: undefined 🥉
    }, 1000);
    exports.b = b;

    node test1.js // 啓動
複製代碼

ps: emoji裏表示打印順序 node執行某個模塊時會將當前模塊的代碼放入函數中,向這個函數傳遞module, module.exports, __dirname等參數。初始的module就是一個空對象。 test1.js執行遇到require('./test2)時會進入test2模塊開始執行,這個時候又碰到引用test1模塊的東西;由於test1模塊沒有執行完成,它的module.exports仍是空對象,因此這個時候test2裏的aundefined。由於commonJS不是動態綁定的,so等到test1模塊執行完a變量裏仍是undefined es module

// es1 
    import { b } from "./es2.js";
    console.log("es1: " + b); // es1: es2 🥈
    var a = "es1";
    export { a };
// es2
    import { a } from "./es1.js";
    console.log("es2: " + a); // es2: undefined 🥇
    var b = "es2";
    setTimeout(() => {
        console.log("es2: " + a); // es2: es1 🥉
    }, 1000);
    export { b };
複製代碼

以上代碼入口是es1文件。根據打印順序來看先是執行的es2模塊,以後es1裏的a填充了實際值,因爲是動態綁定es2中的a中的值也在以後能取到值了。

es module的好處

  1. 動態綁定解決了循環調用的問題(見上文)
  2. 靜態分析(statically analysis) 由於在代碼未執行階段就已經知道當前模塊導入了什麼,導出了什麼,因此有些工具就能夠進行靜態分析。好比vscode中引入模塊代碼時會提示當前模塊裏導出的內容。

es module的壞處

  1. 兼容性
  2. 還沒有有針對node的解決方案

參考資料

es-modules-a-cartoon-deep-dive

module-exports

ecma262

相關文章
相關標籤/搜索