博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入理解es module
阅读量:7208 次
发布时间:2019-06-29

本文共 3732 字,大约阅读时间需要 12 分钟。

模块系统的作用

传统script标签的代码加载容易导致全局作用域污染,而且要维系一系列script的书写顺序,项目一大,维护起来越来越困难。模块系统通过声明式的暴露和引用模块使得各个模块之间的依赖变得明显。

es module如何工作的

这部分推荐去看,原文里有图,以下的内容是个人理解整理。

分三步:

  1. 构造,寻找并且下载所有的文件并且解析成模块记录(Module Records)(包含当前模块代码的抽象语法树,当前模块的依赖模块的信息)。
  2. 实例化,将模块记录实例化将各个模块之间的import,export部分对应的都在内存中指向到一起(linking)
  3. 执行,将import, export内存里指向的地址填上实际的值。

构造阶段(Construction)

构造阶段要做三件事情:

解释(interpret)import后的模块指示符(module specifier)成实际url或者文件地址

不同平台根据自己平台的模块解析算法(Module Resolution Algorithm)解释模块指示符,浏览器端目前只接受url做为指示符。不过浏览器将来会同样支持内置模块比如。

模块指示符里的变量

模块指示符里不能有变量但是node中commonJS是可以有的,因为在commonJS的模块代码里,require声明前的代码是会先执行的,es module是最后一步再去执行,这一步才知道各个变量的具体值是多少。所以可以在node中有如下写法:

require(`${path}/sum.js`);复制代码

不过es module里有另一种写法动态引入import()可以支持在代码执行时动态引入模块,可以在指示符里携带变量

import(`${path}/sum.js`);复制代码

浏览器根据url下载文件或者node根据文件地址去加载文件

将文件解析成模块记录

浏览器解析常规js文件时会解析完后再执行。和模块的解析策略不一样,这里要告诉浏览器解析的是个模块。在html中:

复制代码

ps: 在node中因为没有浏览器这种类似打tag的形式,有种方案是模块文件是.mjs后缀结尾的方案,不过目前尚未敲定。

解析模块文件为模块记录,找到依赖的模块再去下载模块然后解析成模块记录,直到所有的模块都解析成模块记录为止。模块记录会存在当前全局的一个模块映射里(Module Map),可以理解成一个缓存,下次再有相同url的模块请求就直接从模块映射里拿出模块记录即可。

实例化阶段

将上面得到的模块记录类实例化。 首先在内存中指定位置给各个模块的export导出的变量或者函数,接着将模块中对应的import部分同样指向对应的export的内存地址。 举个?

// main.jsimport {obj} from "./obj.js"// obj.jsconst obj = {
a: 123};export {obj}复制代码

obj.js文件里导出的objmain.js文件里引用的obj是指向同一个内存地址的,这中方法就是动态绑定(live binding)。

复制代码
let obj = {        a: 123    };    setTimeout(() => {        obj = { b: 233 };    }, 1000);    export { obj };复制代码

下面我们看下node中同样的代码的效果。

// test1.js    var obj = require("./test2.js");    console.dir(obj); // {a: 123}    setTimeout(() => {        console.dir(obj); // {a: 123}    }, 2000);// test2.js    let obj = { a: 123 };    setTimeout(() => {    obj = { b: 233 };    }, 1000);    module.exports = obj;复制代码

在commonJS中require一个对象是在内存中复制一份导出模块的对象。动态绑定主要解决的问题就是循环引用的问题,循环引用在下面的执行阶段进行解释。 注意: es module中可以在模块导出的部分更改导出值如上面代码所示,但是不能在引入部分更改。

import {obj} from "./sum.js"    obj = '233'  // Uncaught TypeError: Assignment to constant variable.复制代码

如上报错会提示不能给常量赋值,不过如果是对象的话可以更改内部的key,由于动态绑定的原因,导出部分也会发生改变

// main.js    import {obj} from "./obj.js"    setTimeout(() => {        obj.a = '嘻嘻'    }, 1000);// obj.js    let obj = { a: 123 };    console.log(obj); // {a: 123}    setTimeout(() => {        console.log(obj); // {a: "嘻嘻"}    }, 2000);    export { obj };复制代码

执行阶段(evaluate)

原文中是evaluate,我这里理解成了执行,如有不对欢迎指出。引擎开始执行模块了,每个模块只会被执行一次。在上面提到过的module map里的模块记录里会存有当前模块的状态是实例化中还是实例完成还是执行完成等。可以避免同一个模块文件被多次执行。

循环引用问题

如下在node中,两个模块互相引用。

// test1.js    var b = require("./test2").b;    console.dir("test1: " + b);  // 'test1: test2' ?    var a = "test1";    exports.a = a;// test2.js    var a = require("./test1").a;    console.log("test2: " + a);  // test2: undefined ?    var b = "test2";    setTimeout(() => {        console.log("test2: " + a); // test2: undefined ?    }, 1000);    exports.b = b;    node test1.js // 启动复制代码

ps: emoji里表示打印顺序 node执行某个模块时会将当前模块的代码放入函数中,向这个函数传递module, module.exports, __dirname等参数。初始的module就是一个空对象。 test1.js执行遇到require('./test2)时会进入test2模块开始执行,这个时候又碰到引用test1模块的东西;因为test1模块没有执行完成,它的module.exports还是空对象,所以这个时候test2里的aundefined。因为commonJS不是动态绑定的,so等到test1模块执行完a变量里还是undefined es module

// es1     import { b } from "./es2.js";    console.log("es1: " + b); // es1: es2 ?    var a = "es1";    export { a };// es2    import { a } from "./es1.js";    console.log("es2: " + a); // es2: undefined ?    var b = "es2";    setTimeout(() => {        console.log("es2: " + a); // es2: es1 ?    }, 1000);    export { b };复制代码

以上代码入口是es1文件。根据打印顺序来看先是执行的es2模块,之后es1里的a填充了实际值,由于是动态绑定es2中的a中的值也在之后能取到值了。

es module的好处

  1. 动态绑定解决了循环调用的问题(见上文)
  2. 静态分析(statically analysis) 因为在代码未执行阶段就已经知道当前模块导入了什么,导出了什么,所以有些工具就可以进行静态分析。比如vscode中引入模块代码时会提示当前模块里导出的内容。

es module的坏处

  1. 兼容性
  2. 尚未有针对node的解决方案

参考资料

转载于:https://juejin.im/post/5ccc06dc51882541cc70c51c

你可能感兴趣的文章
DevExpress Crack
查看>>
在Centos7服务器上搭建网关服务
查看>>
使用Guid做主键和int做主键性能比较
查看>>
Java调用solrj5.5.3接口,查询数据
查看>>
[Android Pro] Android TypedValue.applyDimension()的用法
查看>>
MySql笔记
查看>>
Odoo 二次开发教程(三)-第一个Model及Form、Tree视图
查看>>
Entity Framework Core 1.1 升级通告
查看>>
MySQL:procedure, function, cursor,handler
查看>>
委托的多种写法
查看>>
关于优酷视频代码播放的若干事情……
查看>>
异步与并行~List<T>是线程安全的吗?
查看>>
CentOS7安装mysql提示“No package mysql-server available.”
查看>>
linux下截取给定路径中的目录部分
查看>>
MVC学习随笔----如何在页面中添加JS和CSS文件
查看>>
gulp同步执行任务
查看>>
各种变换滤波和噪声的类型和用途总结
查看>>
rocketmq生产者部署的机器注意事项
查看>>
exp函数
查看>>
MySql的一些用法
查看>>