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/

相关文章
相关标签/搜索