動態

詳情 返回 返回

javascript中的esm是什麼? - 動態 詳情

esm是什麼?

esm 是將 javascript 程序拆分成多個單獨模塊,並能按需導入的標準。和webpack,babel不同的是,esm 是 javascript 的標準功能,在瀏覽器端和 nodejs 中都已得到實現。使用 esm 的好處是瀏覽器可以最優化加載模塊,比使用庫更有效率。

esm 標準通過import, export語法實現模塊變量的導入和導出。

esm 模塊的特點

  • 存在模塊作用域,頂層變量都定義在該作用域,外部不可見;
  • 模塊腳本自動採用嚴格模式;
  • 模塊頂層的this關鍵字返回undefined;
  • esm 是編譯時加載,也就是隻有所有import的模塊都加載完成,才會開始執行;
  • 同一個模塊如果加載多次,只會執行一次。

export
export語句用來導出模塊中的變量。

// 導出變量
export let count = 1;
export const CONST_VAR = 'CONST_VAR';
// 導出函數
export function incCount() {
    count += 1;
}
// 導出類
export class Demo {

}

function add(x) {
    return x + count;
}
// 使用export導出一組變量
export {
    count,
    add,
    // 使用as重命名導出的變量
    add as addCount,
}

// 導出default
export default add

// 合併導出其他模塊的變量
export { name } from './esm_module2.js'
export * from './esm_module2.js'

import
import語句用來導入其他模塊的變量

// 導入變量
import { count, incCount, CONST_VAR } from './esm_module1.js';

// 通過as重命名導入的變量
import { addCount as renamedAddCount } from './esm_module1.js';

// 導入默認
import { default as defaultAdd } from './esm_module1.js';
import add from './esm_module1.js';

// 創建模塊對象
import * as module1 from './esm_module1.js';

export 導出的是值引用
esm 模塊和 commonjs 模塊的一個顯著差異是,cjs 導出的是值得拷貝,esm 導出的是值的引用。當模塊內部的值被修改時,cjs 獲取不到被修改後的值,esm 可以獲取到被修改後的值。

cjs 例子

// cjs_module1.js
var count = 1;
function incCount() {
    count += 1;
}

module.exports = {
    count: count,
    incCount: incCount,
}

// cjs_demo.js
var { count, incCount } = require('./cjs_module1.js');

console.log(count); // 1
incCount();
console.log(count); // 1

esm 例子

// esm_module1.js
let count = 1;
function incCount() {
    count += 1;
}

export {
    count,
    incCount,
}

// esm_demo.js
import { count, incCount } from './esm_module1.js';

console.log(count); // 1
incCount();
console.log(count); // 2

從實現原理上來看,cjs 的 module.exports是一個對象,在運行期注入模塊。在導出語句module.exports.count = count執行時,是給這個對象分配一個count的鍵,並賦值為1。 這之後模塊中的count變量再怎麼變化,都不會干擾到module.exports.count

esm 中的export { count }是導出了count變量的一個只讀引用,等於説使用者讀取count時,值的指向還是模塊中count變量的值。

可以看阮一峯的這篇文章:ES6入門教程

在 html 中使用 esm
使用script標籤引入 esm 文件,同時設置type=module,標識這個模塊為頂級模塊。瀏覽器將 esm 文件視為模塊文件,識別模塊的import語句並加載。

<script src="./esm_main.js" type="module"></script>

如果不設置type=module,瀏覽器認為該文件為普通腳本。檢查到文件中存在import語句時,會報如下錯誤:
image.png

esm的加載機制
esm 標準沒有規定模塊的加載細節,將這些留給具體環境實現。大致上分為下面四個步驟:

解析:實現讀取模塊的源代碼並檢查語法錯誤;

加載:遞歸加載所有import的模塊;

鏈接:對每個加載的模塊,都生成一個模塊作用域,該模塊下的所有全局聲明都綁定到該作用域上,包括從其他模塊導入的內容;

運行時:完成所有import的加載和鏈接,腳本運行每個已經加載的模塊中的語句。當運行到全局聲明時,什麼也不會做(在鏈接階段已經將聲明綁定到模塊作用域)。

可以看下 mdn 上的這篇深入 esm 的文章:ES6 In Depth: Modules

動態加載模塊
esm 的一個重要特性是編譯時加載,這有利於引擎的靜態分析。加載的過程會先於代碼的執行。卻也導致import導入語句不能在函數或者if語句中執行:

// 報語法錯誤
if (true) {
    import add from './esm_module1.js';
}

es2020 提案引入import()函數,用來動態加載模塊,並且可以用在函數和if語句中。

import('./esm_module1.js')
  .then(module => {
    console.log(module);
  })

import()函數接受要加載的模塊相對路徑,返回一個Promise對象,內容是要加載的模塊對象。

使用import()函數還可以實現根據變量動態加載模塊

async function getTemplate(templateName) {
    let template = await import(`./templates/${templateName}`);
    console.log(template);
}

getTemplate("foo");
getTemplate("bar");
getTemplate("baz");
user avatar alibabawenyujishu 頭像 littlelyon 頭像 longlong688 頭像 huajianketang 頭像 huichangkudelingdai 頭像 febobo 頭像 guixiangyyds 頭像 ccVue 頭像 yixiyidong 頭像 libubai 頭像 Asp1rant 頭像 zhaodawan 頭像
點贊 112 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.