當前端應用越來越復雜時,我們想要將代碼分割成不同的模塊,便于復用、按需加載等。
require 和 import 分別是不同模塊化規(guī)范下引入模塊的語句,下文將介紹這兩種方式的不同之處。
1. 出現(xiàn)的時間、地點不同
| 年份 | 出處 | |
|---|---|---|
| require/exports | 2009 | CommonJS |
| import/export | 2015 | ECMAScript2015(ES6) |
2. 不同端(客戶端/服務器)的使用限制
| require/exports | import/export | |
|---|---|---|
| Node.js | 所有版本 | Node 9.0+(啟動需加上 flag --experimental-modules) Node 13.2+(直接啟動) 如何使用? |
| Chrome | 不支持 | 61+ |
| Firefox | 不支持 | 60+ |
| Safari | 不支持 | 10.1+ |
| Edge | 不支持 | 16+ |
CommonJS 模塊化方案 require/exports 是為服務器端開發(fā)設計的。服務器模塊系統(tǒng)同步讀取模塊文件內容,編譯執(zhí)行后得到模塊接口。(Node.js 是 CommonJS 規(guī)范的實現(xiàn))。
在瀏覽器端,因為其異步加載腳本文件的特性,CommonJS 規(guī)范無法正常加載。所以出現(xiàn)了 RequireJS、SeaJS 等(兼容 CommonJS )為瀏覽器設計的模塊化方案。
兩種方案各有各的限制,需要注意以下幾點:
-
原生瀏覽器不支持 require/imports,可使用支持 CommonJS 模塊規(guī)范的
Browsersify、webpack等打包工具,它們會將 require/imports 轉換成能在瀏覽器使用的代碼。 -
import/export 在瀏覽器中無法直接使用,我們需要在引入模塊的
<script\>元素上添加type="module屬性。 - 即使
Node.js13.2+ 已經(jīng)支持import/export,官方不建議在正式環(huán)境使用,目前可以使用 babel 將ES6的模塊系統(tǒng)編譯成CommonJS規(guī)范(注意:語法一樣,但具體實現(xiàn)還是require/exports)。
3. require/exports 是運行時動態(tài)加載,import/export 是靜態(tài)編譯
CommonJS加載的是一個對象(即 module.exports 屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會生成。- 阮一峰
4. require/exports 輸出的是一個值的拷貝,import/export 模塊輸出的是值的引用
require/exports 輸出的是值的拷貝。也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。
import/export 模塊輸出的是值的引用。JS 引擎對腳本靜態(tài)分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執(zhí)行時,再根據(jù)這個只讀引用,到被加載的那個模塊里面去取值。
若文件引用的模塊值改變,require 引入的模塊值不會改變,而 import 引入的模塊值會改變。
5. 用法不一致
(1). require/exports 的用法
const fs = require('fs')
exports.fs = fs
module.exports = fs
exports 是對 module.exports 的引用,相當于
exports = module.exports = {};
在不改變 exports 指向的情況下,使用 exports 和 module.exports 沒有區(qū)別;如果將 exports 指向了其他對象,exports 改變不會改變模塊輸出值。示例如下:
//utils.js
let a = 100;
exports.a = 200;
console.log(module.exports) //{a : 200}
exports = {a:300}; //exports 指向其他內存區(qū)
//test.js
var a = require('./utils');
console.log(a) // 打印為 {a : 200}
(2). import/export 的寫法
import fs from 'fs'
import {readFile} from 'fs' //從 fs 導入 readFile 模塊
import {default as fs} from 'fs' //從 fs 中導入使用 export default 導出的模塊
import * as fileSystem from 'fs' //從 fs 所有導出模塊,引用對象名為 fileSystem
import {readFile as read} from 'fs' //從 fs 導入 readFile 模塊,引用對象名為 read
import('/modules/my-module.js') //動態(tài)導入
.then((module) => {
// Do something with the module.
});
export default fs
export const fs
export function readFile
export {readFile, read}
export * from 'fs'
建議1:建議明確列出我們要引用的內容。,使用 * 雖然很方便,但是不利于現(xiàn)代的構建工具檢測未被使用的函數(shù),影響代碼優(yōu)化。
建議2: 請不要濫用動態(tài)導入 import()(只有在必要情況下采用)。靜態(tài)框架能更好的初始化依賴,而且更有利于靜態(tài)分析工具和 tree shaking 發(fā)揮作用
同時需要注意
- 引入
export default導出的模塊不用加 {},引入非export default導出的模塊需要加 {}。
import fileSystem, {readFile} from 'fs'
一個文件只能導出一個
default模塊。在驗證代碼的時候遇到如下報錯
access to script from origin 'null' has been blocked by CORS policy
前面提到過,瀏覽器引入模塊的 <script> 元素要添加 type="module 屬性,但 module 不支持 file:// 文件協(xié)議,只支持 HTTP 協(xié)議,所以本地需要使用 http-server 等本地網(wǎng)絡服務器打開網(wǎng)頁文件。
(3). import/export 不能對引入模塊重新賦值/定義
當我嘗試給 import 的模塊重新賦值時
import {e1} from './webUtils.js';
e1=234;
瀏覽器顯示
Uncaught TypeError: Assignment to constant variable.
當我重新定義引用的模塊
import {e1} from './webUtils.js';
var e1=1;
瀏覽器顯示
(index):17 Uncaught SyntaxError: Identifier 'e1' has already been declared
(4). ES6 模塊可以在 import 引用語句前使用模塊,CommonJS 則需要先引用后使用
ES6 模塊
//webUtils.js
export var e='export';
console.log(e) //export
import {e} from './webUtils.js';
console.log(e) //export
CommonJS
//utils.js
exports.e = 'export';
console.log(a)
a = require('./utils');
console.log(a)
程序報錯
ReferenceError: a is not defined
(5). import/export 只能在模塊頂層使用,不能在函數(shù)、判斷語句等代碼塊之中引用;require/exports 可以。
import fs from './webUtils.js';
function a(){
import {e1} from './webUtils.js';
console.log(e1)
}
a();
console.log(fs())
程序報錯
Uncaught SyntaxError: Unexpected token '{'
前面提到過 import/export 在代碼靜態(tài)解析階段就會生成,不會去分析代碼塊里面的 import/export,所以程序報語法錯誤,而不是運行時錯誤。
這里需要注意,動態(tài)導入 import() 任何地方使用,因為他是運行時加載
6. 是否采用嚴格模式
嚴格模式是采用具有限制性JavaScript變體的一種方式
import/export 導出的模塊默認調用嚴格模式。
var fun=()=>{
mistypedVaraible = 17; //報錯,mistypedVaraible is not defined
};
export default fun;
require/exports 默認不使用嚴格模式,可以自定義是否使用嚴格模式。
例如
exports.fun = ()=>{
mistypedVaraible = 17; //沒有調用嚴格模式,不會報錯
};