1. 程式人生 > 實用技巧 >ES模組基礎用法及常見使用問題

ES模組基礎用法及常見使用問題

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.1export

2.1.1 常規用法

export輸出規定模組的對外介面,有4種常規用法:

// 用法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.2import

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"]
    }
    

    廣州vi設計公司http://www.maiqicn.com 辦公資源網站大全 https://www.wode007.com

  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模組時會存在的一些疑問,希望對大家有一點幫助。