前置知識-幾種模塊化方案
require 屬於 commonJS 規範,想了解詳細的,戳我;
靜態 import,動態 import() 屬於 ES6 規範;
require 用法
require 可以引用 JS、json 等;
// test.js
module.exports = {
a: "china",
b: function() {
console.log("b");
}
};
//引用
var test = require("./test");
test.b();
ES6 import
前置知識點
export 用法
export 可以到處變量、對象、函數、類等
-
寫法 1:export + 完整定義變量或者函數的表達式
// 變量 export var a = 10; // 函數 export function name() {} // 這種寫法要求,必須要是完整定義;比如,變量要有 var、const、let字段,函數要有 function字段
錯誤寫法:
// 變量 表達式不完整 var a=10; export a; // 函數 表達式不完整 var name=function(){}; export name; // 對象 export { a:"name" } // 常量 export 1;
驗證網址,戳我,可以驗證上述 是否正確;
-
寫法 2:export {變量名}
針對方法 1 中的錯誤寫法,可以如下改正:// 變量名加花括號 var a = 10; export { a }; // 函數名加花括號 var name = function() {}; export { name }; // 多個變量時 export { a, name }; // 一般JS文件,比如utils 文件,在每個方法或者變量前加export,而不是 export {a,name}這種形式;這樣寫的好處,比較方便移植; export function name() {} export var a = "test";
-
寫法 3:
export default
語法糖使用
export default
寫法,爲模塊指定默認輸出,這樣就不需要知道所要加載模塊的變量名;export default
在一個 JS 文件中只能使用一次;// 變量 var a = 10; export default a; // 函數 export default function() {} // 等效於 function fun() {} export { fun as default }; // 導出default變量以及其他變量 export { fun as default, a, name }; // 針對 export { fun as default, a, name };在utils文件中還可以 export default fun; export var a = 0; export var name = "china";
使用 as 關鍵字
as
關鍵字用來取別名;加入引入多個文件,有相同的變量名,可以使用使用as
關鍵字避免衝突
// a.js
var a = function() {};
export { a as fun };
// b.js
import { fun as a } from "./a";
a();
靜態 import
-
引入文件不含有
export default
import { a, b, c } from "./a";
-
引入文件僅含有
export default
import a from "./d"; //等效於 import { default as a } from "./d";
-
引入文件既含有
export default
,還含有其他export
import $, { each, map } from "jquery";
動態 import-import()
import()出現的背景
import 命令會被 JS 引擎靜態分析(編譯階段),先於其他模塊執行;
// 報錯
if (x > 2) {
import a from "./a";
}
///repl: 'import' and 'export' may only appear at the top level (2:2)
上面代碼中,引擎處理 import 語句是在編譯時,這時不會去分析或執行 if 語句,所以 import 語句放在 if 代碼塊之中毫無意義,因此會報句法錯誤,而不是執行時錯誤。也就是說,import 和 export 命令只能在模塊的頂層,不能在代碼塊之中(比如,在 if 代碼塊之中,或在函數之中)。
這樣的設計,固然有利於編譯器提高效率,但也導致無法在運行時加載模塊。在語法上,條件加載就不可能實現。如果 import 命令要取代 Node 的 require 方法,這就形成了一個障礙。因爲 require 是運行時加載模塊,import 命令無法取代 require 的動態加載功能。
import()特點
特點 1:import() 返回 promise 對象
特點 2:相較於 import 命令,import()可以實現動態加載,其路徑可以根據情況改變
特點 3:import()適用在運行階段,而不是像 import 一樣在編譯階段,這意味着 import(),可以使用在 JS 代碼中,比如條件判斷,函數中等
import()用法
場景 1:按需加載
import()可以在需要的時候,再加載某個模塊。vue 中異步組件的使用,也用到了 import()方法;戳這裏
場景 2:條件加載
import()可以放在 if 代碼塊,根據不同的情況,加載不同的模塊。
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
場景 3:動態的模塊路徑
import()允許模塊路徑動態生成。
import(f()).then(...);
注意點:
import()加載模塊成功以後,這個模塊會作爲一個對象,當作 then 方法的參數。因此,可以使用對象解構賦值的語法,獲取輸出接口。
import("./myModule.js").then(({ export1, export2 }) => {
// ...·
});
上面代碼中,export1 和 export2 都是 myModule.js 的輸出接口,可以解構獲得。
如果模塊有 default 輸出接口,可以用參數直接獲得。
import("./myModule.js").then(myModule => {
console.log(myModule.default);
});
上面的代碼也可以使用具名輸入的形式。
import("./myModule.js").then(({ default: theDefault }) => {
console.log(theDefault);
});
如果想同時加載多個模塊,可以採用下面的寫法。
Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});
import()也可以用在 async 函數之中。
async function main() {
const myModule = await import("./myModule.js");
const { export1, export2 } = await import("./myModule.js");
const [module1, module2, module3] = await Promise.all([
import("./module1.js"),
import("./module2.js"),
import("./module3.js")
]);
}
main();
緩存
require
-
第一次加載某個文件時,Node 會緩存該模塊,以後再加載該模塊,就直接從緩存取出該模塊的 module.exports 屬性(不會再執行該模塊)
-
正常情況下(只有一個 module.exports),require 會緩存(即只執行一次,不會重複執行)
-
如果需要多次執行模塊中的代碼,一般可以讓模塊暴漏行爲(函數)
-
如果需要多次執行模塊中的代碼,還可以多次 module.exports
-
模塊的緩存可以通過 require.cache 拿到,同樣也可以刪除
module.exports 有兩種寫法:
-
module.exports.xxx=abc;// 作爲對象
-
module.exports=abc;// 重新賦值
針對上述結論以及 module.exports 兩種寫法,作了如下測試:
- demo1
// requie.js module.exports重新賦值 日期
console.log("I am require.js");
module.exports = new Date();
// index.js
const t = require("./require.js");
console.log(t.getTime());
setTimeout(() => {
const t = require("./require.js");
console.log(t.getTime());
}, 3000);
console.log("執行頁面");
// 結果:
// I am require.js
// 1576220112564
// 執行頁面
// 1576220112564
- demo2
// require.js module.exports重新賦值 對象;日期作爲對象屬性值
module.exports = {
t: new Date()
};
// index.js
const { t } = require("./require.js");
console.log(t.getTime());
setTimeout(() => {
const { t } = require("./require.js");
console.log(t.getTime());
}, 3000);
console.log("執行頁面");
// 結果:
// 1576219864761
// 1576219864761
- demo3
// require.js module.exports重新賦值 對象;日期賦值給變量,作爲函數導出值
let t = new Date();
module.exports = {
get: () => {
return t;
}
};
// index.js
const { get } = require("./require.js");
console.log(get().getTime());
setTimeout(() => {
const { get } = require("./require.js");
console.log(get().getTime());
}, 3000);
// 結果:
// 1576219588328
// 1576219588328
- demo4
// require.js module.exports作爲對象,日期賦值給t屬性
module.exports.t = new Date();
// index.js
const foo = require("./require.js");
console.log(foo.t.getTime());
setTimeout(() => {
const foo = require("./require.js");
console.log(foo.t.getTime());
}, 3000);
// 結果:
// 1576228870548
// 1576228870548
- demo5
// require.js module.exports作爲對象;日期作爲對象的值
module.exports.t = {
time: new Date()
};
// index.js
const foo = require("./require.js");
console.log(foo.t.time.getTime());
setTimeout(() => {
const foo = require("./require.js");
console.log(foo.t.time.getTime());
}, 3000);
// 結果:
// 1576229080434
// 1576229080434
- demo6
// require.js 重新賦值對象;日期在函數中運行(暴露函數)
module.exports = {
get: () => {
return new Date();
}
};
// index.js
const { get } = require("./require.js");
console.log(get().getTime());
setTimeout(() => {
const { get } = require("./require.js");
console.log(get().getTime());
}, 3000);
// 結果:
// 1576219323353
// 1576219326358
結論:上述情況總結下,require 要想不受緩存影響,把變量等定義在函數中,利用函數延遲執行的特點來解決;
--------兩次導出 module.exports------------
- demo1
// require.js
module.exports.t = new Date();
setTimeout(() => {
module.exports.t = new Date();
}, 500);
// index.js
const foo = require("./require.js");
console.log(foo.t.getTime());
setTimeout(() => {
const foo = require("./require.js");
console.log(foo.t.getTime());
}, 3000);
// 結果:
// 1576225753706
// 1576225754208
- demo2
// require.js
module.exports = new Date();
setTimeout(() => {
module.exports = new Date();
}, 500);
// index.js
const foo = require("./require.js");
console.log(foo.getTime());
setTimeout(() => {
const foo = require("./require.js");
console.log(foo.getTime());
}, 3000);
// 結果:
// 1576226052279
// 1576226052781
- demo3
// require.js
module.exports = {
t: new Date()
};
setTimeout(() => {
module.exports = {
t: new Date()
};
}, 500);
// index.js
const foo = require("./require.js");
console.log(foo.t.getTime());
setTimeout(() => {
const foo = require("./require.js");
console.log(foo.t.getTime());
}, 3000);
// 結果:
// 1576227838138
// 1576227838639
結論,只要是這種 module.exports 導出兩次的,都不會有緩存;
import
- 靜態 import 只執行一次,不重複執行,有緩存;
- 動態 import() 只執行一次,不重複執行,有緩存;
針對上述結論,作了如下測試:
- demo1
// import.js
console.log("import 執行");
export default new Date();
<script type="module">
import a from "./import/import.js";
import z from "./import/import.js";
console.log(a.getTime());
console.log(z.getTime());
// 結果:
// import 執行
// 1576478875409
// 1576478875409
</script>
- demo2
console.log("import 執行");
export default new Date();
<script type="module">
var b = import("./import/import.js");
b.then(({ default: time }) => {
console.log(time.getTime());
});
setTimeout(() => {
var c = import("./import/import.js");
c.then(({ default: time }) => {
console.log(time.getTime());
});
}, 3000);
// 結果:
// 調用文件執行
// import 執行
// 1576479834365
// 1576479834365
</script>
同異步
- require 是同步加載,先執行加載文件;
- import()是異步加載,先執行本地執行,然後執行加載文件;
require
// require.js
console.log("I am require.js");
module.exports.t = {
time: new Date()
};
// index.js
const foo = require("./require.js");
console.log(foo.t.time.getTime());
setTimeout(() => {
const foo = require("./require.js");
console.log(foo.t.time.getTime());
}, 3000);
console.log("執行頁面");
// 結果:
// I am require.js
// 1576483718781
// 執行頁面
// 1576483718781
import()
console.log("import 執行");
export default new Date();
<script type="module">
var b = import("./import/import.js");
b.then(({ default: time }) => {
console.log(time.getTime());
});
setTimeout(() => {
var c = import("./import/import.js");
c.then(({ default: time }) => {
console.log(time.getTime());
});
}, 3000);
// 結果:
// 調用文件執行
// import 執行
// 1576479834365
// 1576479834365
</script>
綜述
- require、動態 import() 運行在 JS 運行階段;靜態 import 運行在編譯階段;
- require 同步執行,動態 import()異步執行;靜態 import 運行在編譯階段,總是最先執行;
- require、靜態 import、動態 import()都是有緩存的;
自己總結這篇的時候,查了不少資料,自己做了一些測試,儘量保證所寫的結論,能夠得到實踐的支撐,以免貽誤他人;如果文中,有哪些代碼問題,或者邏輯錯誤,歡迎指正!
本人在日常學習中,也收集了一些有價值的資料,涉及到諸如源碼、設計模式等,歡迎一起交流學習!
參考資料
es6 import()函數
require、緩存
export MDN
NodeJS 中的 require 和 import
require,import 和 import()函數的區別
require 和 import 的區別是什麼?看這個你就懂了
模塊化
深入理解 ES6 模塊機制
萬歲,瀏覽器原生支持 ES6 export 和 import 模塊啦!