ES6入門十二:Module(模塊化)

  • webpack4打包配置babel7轉碼ES6
  • Module語法與API的使用
  • import()
  • Module加載實現原理
  • Commonjs規範的模塊與ES6模塊的差別
  • ES6模塊與Nodejs模塊相互加載
  • 模塊循環加載

 1、webpack4打包配置babel7轉碼ES6

1.webpack.config.jsjavascript

在webpack中配置babel轉碼其實就是在我以前的webpack配置解析基礎上添加babel的加載器,babel的具體轉碼配置在以前也有一篇詳細的博客作了解析:html

webpack安裝與核心概念java

ES6入門一:ES6簡介及Babel轉碼器node

在本來的webpack配置基礎上添加加載器(原配置詳細見:webpack安裝與核心插件):webpack

//下載babel加載器:babel-loader
npm install babel-loader --save-dev

而後關鍵的webpack.config.js配置代碼是下面這一段:git

 1 module.exports = {
 2     ...
 3     module:{
 4         rules:[
 5             {
 6                 test: /\.js$/,
 7                 loader:'babel-loader',
 8                 exclude: /node_modules/
 9             }
10         ]
11     }
12     ...
13 }

完整的webpack.config.js代碼在這裏:es6

 1 const path = require('path');
 2 const root = path.resolve(__dirname);
 3 const htmlWebpackPlugin = require('html-webpack-plugin');
 4 
 5 module.exports = {
 6     mode:'development',
 7     entry:'./src/index.js',
 8     output:{
 9         path: path.join(root, 'dist'),
10         filename: 'index.bundle.js'
11     },
12     module:{
13         rules:[
14             {
15                 test: /\.js$/,
16                 loader:'babel-loader',
17                 exclude: /node_modules/
18             }
19         ]
20     },
21     plugins:[
22         new htmlWebpackPlugin({
23             template:'./src/index.html',
24             filename:'index.html'
25         })
26     ]
27 }
View Code

2. .babelrc轉碼配置以及package.jsongithub

1 //關於這個配置的具體解析能夠到(ES6入門一:ES6簡介及Babel轉碼器)瞭解
2 {
3     "presets":["@babel/preset-env"],
4     "plugins": [
5         ["@babel/plugin-proposal-class-properties",{"loose":true}],
6         ["@babel/plugin-proposal-private-methods",{"loose":true}]
7     ]
8 }
.babelrc
 1 //
 2 {
 3   "name": "demo",
 4   "version": "1.0.0",
 5   "description": "",
 6   "main": "webpack.config.js",
 7   "scripts": {
 8     "test": "echo \"Error: no test specified\" && exit 1"
 9   },
10   "author": "",
11   "license": "ISC",
12   "devDependencies": {
13     "@babel/core": "^7.6.4",
14     "@babel/plugin-proposal-class-properties": "^7.5.5",
15     "@babel/plugin-proposal-private-methods": "^7.6.0",
16     "@babel/polyfill": "^7.6.0",
17     "@babel/preset-env": "^7.6.3",
18     "babel-loader": "^8.0.6",
19     "html-webpack-plugin": "^3.2.0",
20     "webpack": "^4.41.2"
21   }
22 }
package.json

3.工具區間及目錄解構:web

//工做區間
    src//文件夾
        a.js//index.js依賴的模塊
        index.js//入口文件
        index.html//解構文件
    .babelrc//babel轉碼配置
    package.json//
    webpack.config.js//webpack打包配置文件

src中的代碼文件:npm

 1 // 導出
 2 
 3 export let num = 10;
 4 export const str = "HELLO";
 5 export function show(){
 6 
 7 }
 8 
 9 export class Node {
10     constructor(value, node = null){
11         this.value = value;
12         this.node = node;
13     }
14 }
a.js
1 import {num, str, show, Node} from './a.js';
2 
3 console.log(num,str,show,Node);
index.js
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6     <meta http-equiv="X-UA-Compatible" content="ie=edge">
 7     <title>Document</title>
 8 </head>
 9 <body>
10     
11 </body>
12 </html>
index.html

4.下載打包及轉碼必備的包:

npm install//也能夠手動一個個下載npm install ... --save-dev

包所有下載完成之後,就能夠執行打包指令了,這裏我使用動態時時監聽自動打包(-w指令)

webpack -w --config ./webpack.config.js

5.這裏插入一條:在visual Studio Code編輯器中的控制檯在:View-Terminal(快捷鍵:Ctrl + ` ),這能夠免去在開啓系統的控制檯或者使用git的控制檯。

準備工做作完之後就開始Module的語法分析了,因爲前面已經解析了webpack和babel的使用,加上模塊發開發必然繞不開打包和轉碼這個環節,因此在這裏補充了webpack的babel轉碼打包配置,上面的配置是最簡單的入門版,若是想深刻了解webpack打包能夠考慮讀一讀Vue-cli的源碼,後面我也會有Vue-cli源碼解析的博客,可是可能會要等一段時間。

 2、Module語法與API的使用

在解析語法以前,強烈建議先閱讀這篇博客:js模塊化入門與commonjs解析與應用。一般做爲ES6模塊化的相關內容都會先闡述爲何要實現模塊,這的確很重要,可是在commonjs規範的模塊化在ES6以前就已經出現了,能夠說ES6模塊化是基於commonjs的模塊化思想實現的,可是commonjs也並不是js模塊化思想的始祖。在這以前咱們不少時候會使用閉包來實現一些功能,這些功能對外暴露一個API,好比jQuery就是在一個閉包內實現的,而後將jQuery賦給window的jQuery和$這兩個屬性,這種作法就是爲了保證jQuery庫實現時其不會對全局產生污染。另外閉包也在必定程度上能夠將程序分塊實現,可是閉包仍是在一個js文件中實現的分塊,並不能像java那樣實現獨立的文件快。

這些都是爲何要實現模塊化的緣由,在以前的「js模塊化入門與commonjs解析與應用」中有比較詳細的說明,順便也瞭解如下nodejs的模塊化的一些語法,這是有必要的。

1.ES6模塊化導出(export)導入(import)指令

在以前的webpack打包及babel轉碼示例基礎上繼續語法演示,a.js做爲index.js的依賴文件

 1 // a.js==>導出
 2 export let num = 10;
 3 export const str = "HELLO";
 4 export function show(){
 5 
 6 }
 7 export class Node {
 8     constructor(value, node = null){
 9         this.value = value;
10         this.node = node;
11     }
12 }

再在index.js中導入a.js導出的內容:

1 import {num, str, show, Node} from './a.js'; //導出語法:import {...} from ./a.js
2 console.log(num,str,show,Node);

對比nodejs的導出( module.exports )導入的和導出( require(url) )實現這很容易理解,由於nodejs是基於平臺API和工具方法實現,而ES6是基於語言的底層編譯指令實現。

若是前面你使用了動態監聽打包指令,這時候只須要使用瀏覽器打開./dist/index.html你會發現控制檯有了這樣的打印結果(若是關閉了就從新打包一次):

10 "HELLO" ƒ show() {} ƒ Node(value) {
  var node = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;

  _classCallCheck(this, Node);

  this.value = value;
  this.node = node;
}

2.關於ES6導出的對象寫法:

相信前面你已經發現了一個問題,寫這麼多export?ES6同樣能夠直接使用一個大括號導出「{}」,好比前面的a.js導出能夠這樣寫:

 1 // a.js==>導出
 2 let num = 10;
 3 const str = "HELLO";
 4 function show(){
 5 
 6 }
 7 class Node {
 8     constructor(value, node = null){
 9         this.value = value;
10         this.node = node;
11     }
12 }
13 export {num, str, show, Node} //導出

3.導出語法大括號中「{}」不是解構賦值語法,好比導出以後直接給導出的元素執行一個寫入操做:

1 import {num, str, show, Node} from './a.js';
2 
3 num = 2;//Uncaught ReferenceError: num is not defined

咱們看到了這並不是解構賦值,由於解構賦值是將一個變量的值賦給另外一個,執行了一個全新的變量聲明操做,可是import指令只負責導出對應模塊的內容接口,並不在當前模塊從新聲明變量,這也就意味着咱們只能直接讀取導出的內容而不能寫入,那若是有須要寫入的操做怎麼辦呢?

4.在導入模塊中給導出模塊的變量執行寫入操做:

 1 //a.js導出模塊
 2 let num = 10;
 3 function show(){
 4     num = 2;
 5 }
 6 export {num, show}
 7 //index.js導入模塊
 8 import {num, show} from './a.js';
 9 
10 console.log(num);//10
11 show();
12 console.log(num);//2

關於這種數據操做方式,在實際項目開發中咱們一般會在導出模塊中給變量定義get和set方法,而後將這兩個方法發用一個對象的方式導出。

5.導入重命名:

若是在導入模塊中出現了與導出模塊相同名稱,或者爲了更加方便區別導入的內容,一般會須要將導出內容名稱經過as指令修改。

1 import {num as num_aModule, str as as_aModule,show as show_aModule, Node as Node_aModule} from './a.js';
2 console.log(num_aModule);//10
3 show_aModule();
4 console.log(num_aModule);//2

6.默認導出:

默認導出只能一次導出一個元素,且只能使用一次默認導出。默認導出能夠導出模塊中的變量,甚至能夠直接導出一個值,這個值導出不須要使用as重命名,而是在默認導入時直接給他一個名稱就能夠了。

 1 // a.js==>導出
 2 let num = 10;
 3 const str = "HELLO";
 4 function show(){
 5     num = 2;
 6 }
 7 class Node {
 8     constructor(value, node = null){
 9         this.value = value;
10         this.node = node;
11     }
12 }
13 export default [1,2,3,4]; //默認導出
14 export {num, str, show, Node}
15 
16 //index.js==>導入模塊
17 import arr from "./a.js"; //導入默認元素
18 import {num as num_aModule, str as as_aModule,show as show_aModule, Node as Node_aModule} from './a.js';
19 console.log(num_aModule);//10
20 show_aModule();
21 console.log(num_aModule);//2
22 
23 console.log(arr);//[1, 2, 3, 4]

7.通配符(*)總體導出模塊內容:

import arr from "./a.js"; //導入默認元素
import * as a_Module from './a.js';
console.log(a_Module);//打印出一個導入模塊的內容對象,這個對象的元素只有get方法
console.log(a_Module.str);//HELLO

8.模塊繼承:

假設如今有一個模塊b.js,b繼承a.js模塊,而且index.js依賴b.js,直接在b.js模塊中使用export導出a模塊就能夠了。

1 //b.js
2 export * from './a.js'
3 
4 //index.js
5 import arr from "./a.js"; //導入默認元素,須要注意默認導出元素不會被繼承
6 import * as b_Module from './b.js';
7 console.log(b_Module);//打印出一個導入模塊的內容對象,這個對象的元素只有get方法
8 console.log(b_Module.str);//HELLO
9 console.log(arr)

若是出現了b模塊自身也存在須要導出的元素,這時候就只能先將a模塊中的元素經過import導入進來,在使用export將a和b自身導出元素一塊兒導出

 1 // a.js==>導出
 2 let num = 10;
 3 const str = "HELLO";
 4 function show(){
 5     num = 2;
 6 }
 7 class Node {
 8     constructor(value, node = null){
 9         this.value = value;
10         this.node = node;
11     }
12 }
13 export default [1,2,3,4]; //默認導出
14 export {num, str, show, Node}
15 
16 //b.js模塊
17 import {num, str, show, Node} from "./a.js";
18 let bstr = "biu";
19 export {num, str, show, Node, bstr}
20 
21 //index.js
22 import arr from "./a.js"; //導入默認元素
23 import * as b_Module from './b.js';
24 console.log(b_Module);//打印出一個導入模塊的內容對象,這個對象的元素只有get方法
25 console.log(b_Module.str);//HELLO
26 console.log(b_Module.bstr);//biu
27 console.log(arr)// [1, 2, 3, 4]

 3、import()

以前的內容一直沒有涉及一個問題,就是模塊是怎麼加載的?既然ES6出現了模塊的標準API那是否是意味着瀏覽器能夠自行加載解析模塊?它們又如何加載?

就目前來說其實咱們不多關注ES6的模塊加載問題,畢竟ES6的模塊在瀏覽器的兼容還須要一些時間,現階段咱們使用ES6的模塊都是經過轉碼打包,最後客戶端請求到的js文件都是基於編譯後的文件通篇加載,因此無論開發時基於多少模塊生成的js文件,都是在一個js文件中執行通篇加載同步執行,可是有時候咱們總有一些業務需求想作成按需加載執行。

在HTML5中提供了有一種js異步執行機制worker,詳細能夠了解HTML5之worker開啓JS多線程模式及window.postMessage跨域,這種機制嚴格來講是一種多線程的,在Promise那篇博客中我引用了《你不知道的js下篇》的相關內容闡述了異步與多線程的區別,有興趣能夠了解如下。

而import()則是嚴格意義上的js異步機制,由於它的底層是基於Promise實現的,這裏我就不詳細描述Promise的相關內容了,詳細能夠了解:ES6入門八:Promise異步編程與模擬實現源碼,若是你瞭解了Promise你就能夠瞬間明白import()的模塊異步導入原理,下面直接來看基於前面代碼index.js的異步導入b.js模塊的示例:

1 import arr from "./a.js"; //導入默認元素
2 import(`./b.js`).then((b_module) =>{
3     console.log(b_module);//導入的b模塊對象最後被打印
4 },(err) => {
5     console.log(err);
6 })
7 console.log("import是異步機制,因此我會先出現");//我第一個被打印
8 console.log(arr)// [1, 2, 3, 4],我第二個被打印

而對於import()的異步機制在webpack打包時並不會打包到生成到主index.js中,而是將import()中的`./b.js`打包成一個獨立的js文件,當import()被觸發時再發起一個請求獲取這個獨立js文件,因此這個js文件必然會是在主js文件執行完之後再執行。

關於import()方法有幾個值得注意的關鍵點:

  1.經過import()方法導入的模塊不會打包到主js文件中。

  2.只有import()方法被觸發時纔會加載這個導入的模塊。

  3.須要單獨發起一個網絡請求。

在這以前,若是咱們須要異步加載一個js的話通常都是採用建立一個script元素,而後經過script的Element對象的src屬性發起一個網絡請求,這種方法有幾個閉端:第1、須要於文檔對象模型進行通訊來建立元素對象,相比import()的性能確定有很大的差異;第2、須要使用回調來來接兩個js任務,這顯然代碼的閱讀比模塊化導入導出要差。

除了建立script元素對象,還有就是前面的worker機制,這種機制是一種多線程機制,有獨立線程,經過線程的事件機制通訊,這種操做相對模塊化也要複雜一些,但其兼容性是一個問題,不能像import()做爲純js雖然ES6也尚未特別好的兼容性,但import()能夠經過轉碼工具降級來解決兼容性問題。

 4、Module加載實現原理

1.瀏覽器加載機制:

爲何要了解瀏覽器的加載機制呢?咱們知道模塊自己就是一個獨立的js文件,雖然瀏覽器兼容性不是很好,可是在部分新版本瀏覽器中也是能夠經過script標籤來加載的。做爲模塊機制它們是相互依賴的關係,而模塊必然須要與主js文件有所區分,這就得要依靠script標籤的type屬性來標識它們的類型,而相互依賴的關係有必然影響到執行順序的問題,咱們知道網絡傳輸的因素會致使最後接收到的文件順序並不必定就是咱們的請求順序,因此這一切咱們都須要從瀏覽器的加載機制開始。

關於瀏覽器加載機制的詳細內容請移駕到這篇博客:瀏覽器加載解析渲染網頁原理,除了詳細的解析博客之外,這裏簡單的回顧一些相關的關鍵內容,若是對瀏覽器的加載解析機制有必定了解,經過這些相關的內容就能夠開始瞭解模塊的加載機制了。

 1.1.同步加載模式

1 //內嵌腳本--同步解析執行,阻塞頁面
2 <script type="text/javascript">
3     ...
4 </script>
5 //外部腳本--同步解析加載執行,阻塞頁面
6 <script type="text/javascript" src="path/to/index.js">
7     ...
8 </script>

1.2.異步加載模式

1 //defer異步加載--等到頁面正常渲染完成之後執行
2 <script type="text/javascript" src="path/to/index.js" defer></script>
3 
4 //async異步加載--加載完成之後就當即執行
5 <script type="text/javascript" src="path/to/index.js" async></script>

1.3.ES6模塊加載規則

//模塊加載(type值爲module)--異步加載:默認等同於defer
<script type="module" src="path/to/index.js"></script>

ES6模塊加載採用module的媒體類型,默認狀況下爲defer異步加載模式,等待頁面加載完成之後執行。

但模塊加載也能夠爲async異步加載模式,須要手動添加async屬性,設置async屬性之後模塊就會在加載完成之後當即執行。

//模塊async模式加載,手動添加async屬性
<script type="module" src="path/to/index.js" async></script>

2.import引入模塊及引入外部模塊的加載

ES6有了模塊化就必然有模塊引入,經過import引入的模塊與引入外部模塊腳本徹底一致。

1 <script type="module">
2     import utile from './utils.js'; //引入內部模塊
3     import outerUtile from 'https://example.com/js/utils.js' //引入外部模塊
4 </script>

引入的腳本必然是在引入指定所在的js腳本基礎上實現,因此引入腳本的加載也就再也不考慮阻塞頁面這個問題,這是由引入腳本的加載執行方式決定的,可是ES6引入腳本必須是同步模式,引入指令是確定會阻塞js的執行。這個很容易理解,由於引入其餘模塊就表明着當前模塊必然要依賴其餘模塊,若是依賴的模塊還沒加載執行就執行當前模塊,那當前模塊的依賴其餘模塊的功能必然不能正常執行。

引入模塊除了同步執行之外,還有一些須要注意的問題:

2.1.每一個模塊都有本身獨立的做用,模塊內部的變量外部不可見;(解決了命名衝突問題)

2.2.模塊默認自動採用嚴格模式;

2.3.加載外部模塊時不能省略‘.js’後綴;

2.4.模塊頂層對象指向undefined,而不是window;

2.5.同一個模塊加載屢次,只執行一次。

 5、Commonjs規範的模塊與ES6模塊的差別

ES6模塊在前面API的使用中展現了修改引入模塊的變量須要在依賴模塊中導出一個修改方法,這是由於ES6導出的變量是改變了的get方法,若是直接在引入模塊中作寫入操做會出現未聲明的錯誤。

可是Commonjs對於引入的數據直接進行寫操做並不會報錯,而且會寫入成功,可是這種寫入成功並非正真的成功,只是對當前模塊引入的變量而言,由於Commonjs模塊引入機制是對導出模塊將變量合成一個對象而後引入模塊複製這個對象,因此會出現如下狀況,來看下面這個nodejs的示例:

 1 //導出模塊 a.js
 2 let num = 5;
 3 function foo(){
 4     num++;
 5 }
 6 module.exports = { //導出
 7     num : num,
 8     foo : foo
 9 }
10 //引入模塊
11 let aModule = require('./a.js');
12 console.log(aModule.num);//5
13 aModule.foo();
14 console.log(aModule.num);//5

上面這種狀況這是由於Commonjs導出的是一個對象,而這時候執行的foo方法內num則是在引入模塊中尋找這個變量,而不是在引入的對象中尋找這個變量,出給foo這種寫入的是一個"this.num++"才能夠實現這個累加寫入的操做。

若是是在ES6中上面示例中的寫法就能夠實現正常的累加寫入和讀取,前面API解析中已經有相似的代碼片斷就再也不重複展現。

總結上面這種狀況ES6與Commonjs模塊的區別就是:

1.ES6導入的是對依賴模塊的數據只讀引用;

2.Commonjs導入的則是依賴模塊導出的一組數據鍵值對的複製操做。

 6、ES6模塊與Nodejs模塊相互加載

1.使用ES6模塊語法import導入Nodejs模塊:

在Nodejs模塊中導出的方式是module.exports = {...},若是這個模塊被ES6模塊語法import導入,就至關於Nodejs默認導出一個對象(export default {...})。

 1 //a.js
 2 module.exports = {
 3     foo:'hello',
 4     bar:'world'
 5 }
 6 //若是a.js被ES6的import導入,上面的導出就至關於下面這種形式
 7 //export default {
 8 //    foo:'hello',
 9 //    bar:'world'
10 //}
11 
12 //index.js採用ES6語法導入a.js
13 import baz from './a.js';//baz = {foo:'hello',bar:'world'}
14 import {default as baz} from './a.js';////baz = {foo:'hello',bar:'world'}

須要注意一種狀況是若是ES6模塊語法import使用了通配符導入nodejs模塊,這時候得到的模塊數據引用對象中會包括nodejs總體變量get方法:get default()、同時還分別包括每一個變量的get方法:get foo()、get bar()。例如上面的示例被通配符導出會是這樣的結果:、

1 import * as baz from './a.js';
2 //baz ={
3 //    get default () {return module.exports;},
4 //    get foo() {return thisl.default.foo}.bind(baz),
5 //    get bar() {return this.default.bar}.bind(baz)
6 //}

2.使用Nodejs的require方法加載ES6的模塊:

nodejs使用require()導入ES6模塊時,將ES6模塊導出的數據接口轉換成一個對象的屬性。(可是須要注意的是node並不直接支持ES6的模塊語法,因此若是須要在nodejs模塊中導入ES6的模塊,須要使用轉碼工具降級,降級操做前面的示例已經有展現,也能夠參考這篇博客:ES6入門一:ES6簡介及Babel轉碼器

 1 //es6模塊
 2 let num = 10;
 3 const str = "HELLO";
 4 let arr = [1,2,3,4];
 5 function show(){
 6     num = 2;
 7 }
 8 class Node {
 9     constructor(value, node = null){
10         this.value = value;
11         this.node = node;
12     }
13 }
14 export default arr; //默認導出
15 export {num, str, show, Node}
ES6模塊a.js
 1 {
 2   "name": "Node_Module",
 3   "version": "1.0.0",
 4   "description": "",
 5   "main": "index.js",
 6   "scripts": {
 7     "test": "echo \"Error: no test specified\" && exit 1"
 8   },
 9   "keywords": [],
10   "author": "",
11   "license": "ISC",
12   "devDependencies": {
13     "@babel/cli": "^7.6.4",
14     "@babel/core": "^7.6.4",
15     "@babel/plugin-proposal-class-properties": "^7.5.5",
16     "@babel/plugin-proposal-decorators": "^7.6.0",
17     "@babel/plugin-proposal-private-methods": "^7.6.0",
18     "@babel/polyfill": "^7.6.0",
19     "@babel/preset-env": "^7.6.3"
20   }
21 }
package.json
1 //關於這個配置的具體解析能夠到(ES6入門一:ES6簡介及Babel轉碼器)瞭解
2 {
3     "presets":["@babel/preset-env"],
4     "plugins": [
5         ["@babel/plugin-proposal-class-properties",{"loose":true}],
6         ["@babel/plugin-proposal-private-methods",{"loose":true}]
7     ]
8 }
./babelrc
npx babel a.js -o aa.js//將es6模塊語法降級生成aa.js文件

index.js:nodejs模塊

1 let aModule = require('./aa.js'); //導入降級後的es6模塊
2 console.log(aModule);

在nodejs環境下執行index.js文件

node index.js

打印結果:

{ show: [Function: show],
  default: [ 1, 2, 3, 4 ],
  num: 10,
  str: 'HELLO',
  Node: [Function: Node] }

 7、模塊循環加載

循環加載是指a模塊執行依賴b模塊,而b模塊又依賴a模塊。雖然這種極端的強耦合通常不存在,可是在複雜的項目中可能出現a依賴b,b依賴c,而後c依賴a的狀況。並且ES6模塊與Commonjs模塊的加載原理存在差別,二者是有區別的。在解析區別以前我想強調一點,不管是ES6模塊仍是Commonjs模塊都只加載一遍。

 1.commonjs模塊的加載模式

 1 //a.js
 2 exports.done = false;
 3 var b = require('./b.js');
 4 console.log("在a.js中導入b.js",b.done);
 5 exports.done = true;
 6 console.log('a.js執行完畢');
 7 
 8 //b.js
 9 exports.done = false;
10 var b = require('./a.js');
11 console.log("在b.js中導入a.js",b.done);
12 exports.done = true;
13 console.log('b.js執行完畢');

在node環境中執行a.js,打印如下結果:

在b.js中導入a.js false
b.js執行完畢
在a.js中導入b.js true
a.js執行完畢

從打印結果看到的差異就是a導入b時,b執行又導入a,這時候a導入給b的done是一個初始值,並非a模塊最終導出的結果。這是由於a沒有執行完,因此導出了一個初始值,可是a會等待b執行完再執行導入指令後面的程序。這裏有個很關鍵的注意點就是每一個模塊只執行一次,由於b導入a時,a已經執行就不會再次執行,而是基於a當前的執行結果導入a的數據。

2.ES6模塊的加載模式

 1 //a.js
 2 import {bar} from './b.js';
 3 console.log('./a.js');
 4 console.log(bar);
 5 export let foo = 'foo';
 6 
 7 //b.js
 8 import {foo} from './a.js';
 9 console.log('b.js');
10 console.log(foo);
11 export let bar = 'bar';

注意這裏若是直接使用babel轉碼的話須要將轉碼後的代碼中的引入文件名改爲對應的轉碼後的文件名稱,而後再在node環境中執行轉碼後生成的文件。固然你也可使用打包工具導出到一個html文件中測試。

//打印結果
b.js
undefined
./a.js
bar

ES6模塊的執行方式與Commonjs的執行方式有很大的區別,重點就是在於模塊沒有執行完成就不能獲取該模塊中的導出數據,在示例中當a執行時,就當即導入b,而後b執行。這時候b又導入了a。當b導入a時,a尚未執行完,因此打印除了undefined。

這裏你可能會有點迷惑,即便代碼改爲下面這種狀況也仍是一樣的打印結果:

 1 //a.js
 2 import {bar} from './b.js';
 3 export let foo = 'foo';
 4 console.log('./a.js');
 5 console.log(bar);
 6 
 7 //b.js
 8 import {foo} from './a.js';
 9 export let bar = 'bar';
10 console.log('b.js');
11 console.log(foo);

ES6的這種狀況是因爲加載模塊本質上是生成一個模塊引用給模塊導入的位置,而這個引用必須是文件所有執行完成之後纔會生成,因此在模塊執行完成之前引用ES6模塊中導出的數據都是undefined。

 

 注:ES6模塊標準並非由T39標準組織制定,而是由一個獨立的一個組織來制定的,相關標準和意見能夠到這個鏈接查看: https://whatwg.github.io/loader/

相關文章
相關標籤/搜索