ES模塊的基本用法常見使用問題


本文作者:高峰,360奇舞團前端工程師,W3C WoT工作組成員。

ES6中引入了模塊(Modules)的概念,相信大家都已經挺熟悉的了,在日常的工作中應該也都有使用。

本文會簡單介紹一下ES模塊的優點、基本用法以及常見問題。

着重介紹3個使用ES模塊的常見問題:

  1. 如何在瀏覽器中下快速使用export/import?

  2. 如何在Node下快速使用export/import?

  3. 當心,不要修改export輸出的對象,盡管你能改

一、ES模塊的優點

ES模塊的引入主要有以下幾個優點:

  1. 可以將代碼分割成功能獨立的更小的文件。

  2. 有助於消除命名沖突。

  3. 不再需要對象作為命名空間(比如Math對象),不會污染全局變量。

  4. ES6 模塊在編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量,從而可以進行靜態優化。

二、ES模塊的基本用法

模塊功能中主要有以下幾個關鍵詞:export、import、as、default、*。

  • export用於規定輸出模塊的對外接口

  • import用於輸入模塊提供的接口

  • as用於重命名輸出和輸入接口

  • default用於指定模塊輸出的默認接口

  • *表示輸入模塊的所有接口。

2.1 export

2.1.1 常規用法

export輸出規定模塊的對外接口,有四種常規用法:

// 用法1:直接輸出一個變量聲明、函數聲明或者類聲明

export var m = 1;

export function m() {};

export class M {};

 

// 用法2:輸出內容為大括號包裹的一組變量,

// 注意不要被迷惑,export不能直接輸出常規的對象,下面會給出錯誤示例。

var m1 = 1;

var m2 = 2;

export {m1, m2};

 

// 用法3:輸出指定變量,並重命名,則外部引入時得到的是as后的名稱。

var n = 1;

export {n as m};

 

// 用法4:使用default輸出默認接口,default后可跟值或變量

export default 1;

var m = 1

export default m;

2.1.2 錯誤用法

需要注意的是,在使用export時會經常出現以下錯誤用法。如下代碼所示:

// 用法1

export 1;

export {m: '1'};

 

// 用法2

var m = 1;

export m;

 

// 用法3

function foo() {

  export default 'bar' // SyntaxError

}

其中錯誤用法1和用法2相同,export必須輸出一個接口,不能輸出一個值(哪怕是對象也不行)或者一個已賦值的變量,已賦值的變量對應的也是一個值。上述常規用法中,export default后之所以可以直接跟值是因為default為輸出的接口。

錯誤用法3是因為export只能出現在模塊的頂層作用域,不能存在塊級作用域中。如果出現在塊級作用域的話,就沒法做靜態優化了,這違背ES6中模塊的設計初衷了。

2.2 import

import命令用於引入模塊提供的接口,有以下幾種常見用法:

// 用法1:僅執行 my_module 模塊,不輸入任何值(可能沒啥用,但是是合法的)

import 'my_module';

 

// 用法2:輸入 my_module 的默認接口, 默認接口重命名為 m

import m from 'my_module';

 

// 用法3:輸入 my_module 的 m 接口

import { m } from 'my_module';

 

// 用法4:輸入 my_module 的 m 接口,使用as重命名m接口

import { m as my_m} from 'my_module';

 

// 用法5:導入所有接口

import * as all from 'my_module';

需要注意的是,如果多次重復執行同一句import語句,那么只會執行一次,而不會執行多次。如下兩種均不會多次執行。

// 用法1:重復引入 my_module,只執行一次

import 'my_module';

import 'my_module';

 

// 用法2:多次引入不同的接口,只執行一次

import { m1 } from 'my_module';

import { m2 } from 'my_module';

此外,import命令輸入的變量都是只讀的,加載后不能修改接口。

import { m } from 'my_module';

m = 1; // SyntaxError: "m" is read-only

如果m是一個對象,改寫m的屬性是可以的。但是筆者不建議這么做,具體內容第三部分會詳細說。

錯誤用法

需要注意的是,import也必須在頂級作用域內,並且其中不能使用表達式和變量。其常見的錯誤用法示例如下:

// 用法1:不能使用表達式

import { 'm' + '1' } from 'my_module';

 

// 用法2:不能使用變量

let module = 'my_module';

import { m } from module;

 

// 用法3:不能用於條件表達式

if (x === 1) {

  import { m } from 'module1';

} else {

  import { m } from 'module2';

}

三、常見的使用問題

3.1 如何在瀏覽器中下快速使用import?

各大瀏覽器已經開始逐步支持ES模塊了,如果我們想在瀏覽器中使用模塊,可以在script標簽上添加一個type="module"的屬性來表示這個文件是以module的方式來運行的。如下:

// myModule.js

export default {

  name: 'my-module'

}

 

// script腳本引入

<script type="module">

  import myModule from './myModule.js'

 

  console.log(myModule.name) // my-module

</script>

不過,由於ES的模塊功能還沒有完全支持,在不支持的瀏覽器下,我們需要一些回退方案,可以通過nomodule屬性來指定某腳本為回退方案。如下,在支持的瀏覽器中進行提示。

<script type="module">

  import myModule from './myModule.js'

</script>

 

<script nomodule>

  alert('你的瀏覽器不支持ES模塊,請先升級!')

</script>

如上,當瀏覽器支持type=module時,會忽略帶有nomodule的script;如果不支持,則忽略帶有type=module的腳本,執行帶有nomodule的腳本。

在使用type=module引入模塊時還有一點需要注意的,module的文件默認為defer,也就是說該文件不會阻塞頁面的渲染,會在頁面加載完成后按順序執行。

3.2 如何在Node下快速使用export/import?

相信大家都遇到過如下錯誤:

 

 

當我們直接在node下執行包含ES模塊的的代碼時,就會看到如上報錯,這是因為Node還沒有原生支持ES模塊。但有的時候我們又想在Node下使用,那么該如何做呢?

下面介紹兩種快捷的方法,一種是Node原生支持的,一種需要借助Babel進行編譯。

3.2.1 Node原生支持

Node從9.0版本開始支持ES模塊,可以在flag模式下使用ES模塊,不過這還處於試驗階段(Stability: 1 - Experimental)。其用法也比較簡單,執行腳本或者啟動時加上--experimental-modules即可。不過這一用法要求import/export的文件后綴名必須為*.mjs。

node --experimental-modules test-my-module.mjs

 

// test-my-module.mjs

import myModule from './myModule.mjs'

 

console.log(myModule.name) // my-module

這是Node原生支持的方法,但是對文件的后綴名有限制,但是現階段,我們在項目中的代碼應該還是以.js為后綴居多,所以大多數情況下我們還是會通過編譯使用ES模塊。

下面我們就介紹下如何快速編譯並使用ES模塊。

3.2.2 借助babel-node執行包含ES模塊代碼的文件

平時我們可能會借助構建工具對ES模塊,可能是借助Webpack/Rollup等構建工具進行編譯,這些工具配置起來都相對繁瑣。

有時,我們只想簡單的執行某些代碼,而其中又包含ES模塊代碼,就會發生問題,因為node默認不支持。這時候如果進行一堆配置來使其支持的話,又太過麻煩。

下面我給大家介紹一種看起來更加快捷的方法。

  1. 安裝babel-cli和babel-preset-env,並將其保存為開發依賴。

  2. 在根目錄創建.babelrc文件,在其中添加如下配置。

    {

         "presets": ["env"]

     }

     

  3. 通過./node_modules/.bin/babel-node index.js或npx babel-node index.js執行腳本。其中babel-node為babel-cli自帶。

怎么樣,是不是相當快捷了,而且近乎於0配置。

3.3 當心,不要修改export輸出的對象

前面有提到如果export輸出的接口是一個對象,那么是可以修改這個對象的屬性的。

而我的建議是,盡管你能改,也不要修改。

大家可能都會有這樣一個常規的用法,即在編寫某個組件時,可能會存在包含基礎配置的代碼,我們姑且稱其為options.js,其輸出一堆配置文件。

// options.js

export default {

  // 默認樣式

  style: {

    color: 'green',

    fontSize: 14,

  }

}

如果你沒有類似需求,你可以想象下,你現在要把EChart的某個圖表抽象成自己代碼庫里的組件,那么這時候應該就有一大堆基礎配置文件了。

既然稱其為基礎配置,那么言外之意就是,根據組件的用法不同,會一定程度上對配置進行修改。比如我們會在引入后將顏色改為紅色。

// use-options.js

import options from "./options.js";

 

console.log(options); // { style: { color: 'green', fontSize: 14 } }

 

options.style.color = "red";

這時候就需要格外注意了,如果我們直接對輸入的默認配置對象進行修改,就可能會導致一些bug。

因為export輸出的值是動態綁定的,如果我們修改了其中的值,就會導致其他地方再次引入該值時會發生變化,此時的默認配置就不是我們所設想的默認配置了。如上例,我們再次引入基礎配置后,就會發現顏色的默認值已經變成紅色了。

// use-options-again.js

import useOptions from "./use-options.js

import options from "./options.js";

 

console.log(options); // { style: { color: 'red', fontSize: 14 } }

所以,筆者建議,當我們有需求對輸入的對象接口進行改變時,可以先對其進行深度復制,然后在進行修改,這樣就不會導致上述所說的問題了。如下所示:

// use-options.js

import _ from "./lodash.js";

import options from "./options.js";

 

const myOptions = _.cloneDeep(options);

console.log(myOptions); // { style: { color: 'green', fontSize: 14 } }

myOptions.style.color = "red";

四、總結

本文只是簡單點的介紹了下ES模塊的基本用法,還有一些用法,如import和export的結合使用等,這些大家可以結合MDN或者其他網站進行了解。本文主要是介紹了以下筆者及身邊的同事在使用ES模塊時會存在的一些疑問,希望對大家有一點幫助。

參考內容

  1. Export | MDN

  2. Import | MDN

  3. 7 Different Ways to Use ES Modules Today!

  4. Import, Export, Babel, and Node


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM