We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
前言: 随着前端js代码复杂度的提高,js模块化是必然趋势,不仅好维护,同时依赖很明确,不会全局污染,今天就整理一下模块化的几个规范。 模块化的发展情况如下: 无模块化-->CommonJS规范-->AMD规范-->CMD规范-->ES6模块化
script标签引入js文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:
<script src="jquery.js"></script> <script src="jquery_scroller.js"></script> <script src="main.js"></script> <script src="other1.js"></script> <script src="other2.js"></script> <script src="other3.js"></script>
即简单的将所有的js文件统统放在一起。但是这些文件的顺序还不能出错,比如jquery需要先引入,才能引入jquery插件,才能在其他的文件中使用jquery。缺点很明显:
这里插一个知识,如果script标签中什么都不写,是按顺序执行的,即使先加载完也按顺序执行。
<script type="text/javascript" src="https://developers.google.com/_static/c614b31167/js/script_foot_closure__zh_cn.js?hl=zh-cn"></script> <script type="text/javascript" src="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/static/protocol/https/jquery/jquery-1.10.2.min_65682a2.js"></script>
执行顺序
原始写法
function m1(){ //... } function m2(){ //... }
缺点:“污染”了全局变量,无法保证不与其他模块名冲突,模块间看不出关系。
对象写法
var module1 = new Object({ _count : 0, m1 : function (){ //... }, m2 : function (){ //... } });
使用的时候,调用这个对象的属性:
module1.m1();
这种写法的缺点是:会暴露所有模块成员,内部变量会被篡改。
module1._count=5;
立即执行函数 (Immediately-Invoked Function Expression,IIFE) 可以不暴露模块成员
var module1 = (function(){ var _count = 0; var m1 = function(){ //... }; var m2 = function(){ //... }; return { m1 : m1, m2 : m2 }; })();
这里的module1就是模块的基本写法。 如果一个模块很大,必须分成几部分,或者一个模块需要继承其他模块,就有必要采用“放大模式”:
var module1 = (function (mod){ mod.m3 = function () { //... }; return mod; })(module1);
上面的代码为module1添加了一个新方法m3.
在浏览器中,模块的各个部分通常都是从网上获取的,有时候无法知道哪个部分先下载,如果采用上面的写法,module1可能加载一个不存在的空对象,这时就要采用“宽放大模式”
var module1 = ( function (mod){ //... return mod; })(window.module1 || {});
它和“放大模式”相比就是参数可以是个空对象。
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。 为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
var module1 = (function ($, YAHOO) { //... })(jQuery, YAHOO);
参考地址:http://wiki.commonjs.org/wiki/Modules/1.1 node.js的模块系统,就是参照CommonJS规范实现的。 node对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性。 用法 在CommonJS中,用module.exports定义当前模块对外输出的接口(不推荐直接用exports)
function add(a,b){ return a+b; } module.exports = {add};
用require加载模块(同步)。假定有一个数学模块math.js,就可以像下面这样加载。
var math = require('math');
然后,就可以调用模块提供的方法:
var math = require('math’); math.add(2,3); // 5
特点:
疑问:CommonJS 暴露的模块到底是什么? CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。
模块的加载机制 CommonJS 模块的加载机制是,导入的是被输出的值的拷贝(浅拷贝)。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。这点与 ES6 模块化有重大差异(下文[CommonJs和ES6模块的区别]部分会介绍),请看下面这个例子:
// lib.js var counter = 3; function incCounter(){ counter++; } module.exports = { counter: counter, incCounter: incCounter }
代码输出了内部变量counter和改写counter的内部方法incCounter
// main.js var counter = require('./lib’).counter; var incCounter = require('./lib’).incCounter; console.log(counter); // 3 incCounter(); console.log(counter); // 3
上面代码说明,counter 输出以后,lib.js 模块内部的变化就影响不到 counter 了。这是因为 counter 是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。 那么如果是导出一个对象呢?结果是还会影响。所以是一个浅拷贝。
// lib.js var counterObj = { counter: 1, flag: true } function incCounter(){ counterObj.counter++; } module.exports = { counterObj, incCounter: incCounter, } // main.js var counterObj = require('./lib').counterObj; var incCounter = require('./lib').incCounter; console.log(counterObj.counter); // 1 incCounter(); console.log(counterObj.counter); // 2
但是,这种规范在浏览器上会有很大的问题,在上文例子中第二行代码math.add(2,3)执行必须等math.js加载完成,也就是说,如果加载时间很长,整个应用就会卡在那里等。 这个问题在服务器不存在,因为所有的模块都在本地硬盘上,可以同步加载完成,等待时间就是硬盘读取时间。但是浏览器模块都放在服务端,加载速度取决于网速,可能要等很长时间。 所以,浏览器端的模块,不能采用“同步加载”( synchronous),只能采用“异步加载”(asynchronous)。这就是AMD规范诞生的背景。 扩展:如果想将CommonJs用在浏览器端需要其他工具配合,例如browserify,提前编译打包处理。 https://www.cnblogs.com/xiaohuochai/p/6850977.html
三、AMD规范( Asynchronous[/ei'siŋkrənəs/] Module Definition) AMD规范则是非同步加载模块,允许指定回调函数,AMD是RequireJS 在推广过程中对模块定义的规范化产出。 AMD标准中,定义了下面三个API:
即通过define来定义一个模块,然后使用require来加载一个模块, 使用require.config()指定引用路径。 先到require.js官网下载最新版本,然后引入到页面,如下: <script data-main="./index.js" src="https://requirejs.org/docs/release/2.1.11/minified/require.js"></script> data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的index.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把index.js简写成index。 index.js里依赖别的文件。例如依赖alert.js,可以这么写
<script data-main="./index.js" src="https://requirejs.org/docs/release/2.1.11/minified/require.js"></script>
require(['alert'], function(alert){ alert.alertName('Maria'); alert.alertAge(15); });
被依赖的模块 alert.js,按照规范来定义模块
define(function(){ var alertName = function(str){ alert("I am"+str); } var alertAge = function(age){ alert('I am'+age+' years old'); } return { alertName: alertName, alertAge: alertAge } })
引用模块的时候,我们将模块名放在[]中作为reqiure()的第一参数;如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]中作为define()的第一参数。 在使用require.js的时候,我们必须要提前加载所有的依赖,然后才可以使用,而不是需要使用时再加载。 优点:适合在浏览器环境中异步加载模块、并行加载多个模块 缺点:不能按需加载、开发成本大
CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同。 AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。
在ES6中,我们可以使用 import 关键字引入模块,通过 export 关键字导出模块,功能较之于前几个方案更为强大,也是我们所推崇的,但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require[]。 (转换后的代码是遵循commonJS规范的,而这个规范是浏览器不能识别的,所以要借助webpack打包工具)
定义模块 math.js
var basicNum = 0; function add(a, b){ return a+b; } export { basicNum, add }
引用模块
import {basicNum, add} from './math'; var result = add(99, basicNum); console.log(result);
es6在导出的时候有一个默认导出,export default,使用它导出后,在import的时候,不需要加上{},模块名字可以随意起。该名字实际上就是个对象,包含导出模块里面的函数或者变量。 定义
export default { basicNum, add }
引用
import math from './math’; var result = math.add(99, basicNum);
但是一个模块只能有一个export default。
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.libName = factory()); }(this, (function () { 'use strict';})));
如果你在js文件头部看到这样的代码,那么这个文件使用的就是 UMD 规范 实际上就是 amd + commonjs + 全局变量 这三种风格的结合 这段代码就是对当前运行环境的判断,如果是 Node 环境 就是使用 CommonJs 规范, 如果不是就判断是否为 AMD 环境, 最后导出全局变量 有了 UMD 后我们的代码可同时运行在 Node 和 浏览器上 所以现在前端大多数的库最后打包都使用的是 UMD 规范
exports 是对 module.exports 的引用。比如我们可以认为在一个模块的顶部有这句代码: exports = module.exports所以,我们不能直接给exports赋值,比如number、function等。 注意:因为module.exports本身就是一个对象,所以,我们在导出时可以使用
module.exports = {foo: 'bar'} //true module.exports.foo = 'bar' //true
但是, exports 是 module.exports 的一个引用,或者理解为exports是一个指针,exports指向module.exports,这样,我们就只能使用 exports.foo = 'bar' 的方式 而不能使用exports = {foo: 'bar'} //error 这种方式是错误的,相当于重新定义了exports, 赋值之后exports失去了 对module.exports的引用,成为了一个模块内的局部变量
2、AMD和CMD的区别 最明显的区别就是在模块定义时对依赖的处理不同
3、CommonJs和ES6模块的区别
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。 ES6模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。 【详细解释:引用自阮一峰ECMAScript 6 入门】 CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。
// lib.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, };
上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。
// main.js var mod = require('./lib'); console.log(mod.counter); // 3 mod.incCounter(); console.log(mod.counter); // 3
上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
// lib.js var counter = 3; function incCounter() { counter++; } module.exports = { get counter() { return counter }, incCounter: incCounter, };
上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。
$ node main.js 3 4
ES6 模块的运行机制与 CommonJS 不一样。**JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。**原始值变了,import加载的值也会跟着变。因此,es6模块是动态引用,并不会缓存值,模块里面的变量绑定其所在的模块。 还是举上面的例子。
// lib.js export let counter = 3; export function incCounter() { counter++; } // main.js import { counter, incCounter } from './lib'; console.log(counter); // 3 incCounter(); console.log(counter); // 4
上面代码说明,ES6 模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。 再举一个例子。
// m1.js export var foo = 'bar'; setTimeout(() => foo = 'baz', 500); // m2.js import {foo} from './m1.js'; console.log(foo); setTimeout(() => console.log(foo), 500);
上面代码中,m1.js的变量foo,在刚加载时等于bar,过了 500 毫秒,又变为等于baz。 让我们看看,m2.js能否正确读取这个变化。
$ babel-node m2.js bar baz
上面代码表明,ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。 由于 ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。
// lib.js export let obj = {}; // main.js import { obj } from './lib'; obj.prop = 123; // OK obj = {}; // TypeError
上面代码中,main.js从lib.js输入变量obj,可以对obj添加属性,但是重新赋值就会报错。因为变量obj指向的地址是只读的,不能重新赋值,这就好比main.js创造了一个名为obj的const变量。 最后,export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
// mod.js function C() { this.sum = 0; this.add = function () { this.sum += 1; }; this.show = function () { console.log(this.sum); }; } export let c = new C();
上面的脚本mod.js,输出的是一个C的实例。不同的脚本加载这个模块,得到的都是同一个实例。
// x.js import {c} from './mod'; c.add(); // y.js import {c} from './mod'; c.show(); // main.js import './x'; import './y';
现在执行main.js,输出的是1。
$ babel-node main.js 1
这就证明了x.js和y.js加载的都是C的同一个实例。
// CommonJS模块 let { stat, exists, readFile } = require('fs'); // 等同于 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile;
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。 ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
// ES6模块 import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。 由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。 除了静态加载带来的各种好处,ES6 模块还有以下好处。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言:
随着前端js代码复杂度的提高,js模块化是必然趋势,不仅好维护,同时依赖很明确,不会全局污染,今天就整理一下模块化的几个规范。
模块化的发展情况如下:
无模块化-->CommonJS规范-->AMD规范-->CMD规范-->ES6模块化
一、无模块化时期
script标签引入js文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:
即简单的将所有的js文件统统放在一起。但是这些文件的顺序还不能出错,比如jquery需要先引入,才能引入jquery插件,才能在其他的文件中使用jquery。缺点很明显:
这里插一个知识,如果script标签中什么都不写,是按顺序执行的,即使先加载完也按顺序执行。
执行顺序
二、模块化探索-模块的写法
原始写法
缺点:“污染”了全局变量,无法保证不与其他模块名冲突,模块间看不出关系。
对象写法
使用的时候,调用这个对象的属性:
这种写法的缺点是:会暴露所有模块成员,内部变量会被篡改。
立即执行函数 (Immediately-Invoked Function Expression,IIFE)
可以不暴露模块成员
这里的module1就是模块的基本写法。
如果一个模块很大,必须分成几部分,或者一个模块需要继承其他模块,就有必要采用“放大模式”:
上面的代码为module1添加了一个新方法m3.
在浏览器中,模块的各个部分通常都是从网上获取的,有时候无法知道哪个部分先下载,如果采用上面的写法,module1可能加载一个不存在的空对象,这时就要采用“宽放大模式”
它和“放大模式”相比就是参数可以是个空对象。
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
三、CommonJS规范--同步
参考地址:http://wiki.commonjs.org/wiki/Modules/1.1
node.js的模块系统,就是参照CommonJS规范实现的。 node对模块规范进行了一定的取舍,同时也增加了少许自身需要的特性。
用法
在CommonJS中,用module.exports定义当前模块对外输出的接口(不推荐直接用exports)
用require加载模块(同步)。假定有一个数学模块math.js,就可以像下面这样加载。
然后,就可以调用模块提供的方法:
特点:
疑问:CommonJS 暴露的模块到底是什么?
CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。
模块的加载机制
CommonJS 模块的加载机制是,导入的是被输出的值的拷贝(浅拷贝)。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。这点与 ES6 模块化有重大差异(下文[CommonJs和ES6模块的区别]部分会介绍),请看下面这个例子:
代码输出了内部变量counter和改写counter的内部方法incCounter
上面代码说明,counter 输出以后,lib.js 模块内部的变化就影响不到 counter 了。这是因为 counter 是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
那么如果是导出一个对象呢?结果是还会影响。所以是一个浅拷贝。
但是,这种规范在浏览器上会有很大的问题,在上文例子中第二行代码math.add(2,3)执行必须等math.js加载完成,也就是说,如果加载时间很长,整个应用就会卡在那里等。
这个问题在服务器不存在,因为所有的模块都在本地硬盘上,可以同步加载完成,等待时间就是硬盘读取时间。但是浏览器模块都放在服务端,加载速度取决于网速,可能要等很长时间。
所以,浏览器端的模块,不能采用“同步加载”( synchronous),只能采用“异步加载”(asynchronous)。这就是AMD规范诞生的背景。
扩展:如果想将CommonJs用在浏览器端需要其他工具配合,例如browserify,提前编译打包处理。
https://www.cnblogs.com/xiaohuochai/p/6850977.html
三、AMD规范( Asynchronous[/ei'siŋkrənəs/] Module Definition)
AMD规范则是非同步加载模块,允许指定回调函数,AMD是RequireJS 在推广过程中对模块定义的规范化产出。
AMD标准中,定义了下面三个API:
即通过define来定义一个模块,然后使用require来加载一个模块, 使用require.config()指定引用路径。
先到require.js官网下载最新版本,然后引入到页面,如下:
<script data-main="./index.js" src="https://requirejs.org/docs/release/2.1.11/minified/require.js"></script>
data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的index.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把index.js简写成index。
index.js里依赖别的文件。例如依赖alert.js,可以这么写
被依赖的模块 alert.js,按照规范来定义模块
引用模块的时候,我们将模块名放在[]中作为reqiure()的第一参数;如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]中作为define()的第一参数。
在使用require.js的时候,我们必须要提前加载所有的依赖,然后才可以使用,而不是需要使用时再加载。
优点:适合在浏览器环境中异步加载模块、并行加载多个模块
缺点:不能按需加载、开发成本大
四、CMD规范(Common Module Definition)
CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同。
AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。
五、ES6模块化(ESM)
在ES6中,我们可以使用 import 关键字引入模块,通过 export 关键字导出模块,功能较之于前几个方案更为强大,也是我们所推崇的,但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require[]。 (转换后的代码是遵循commonJS规范的,而这个规范是浏览器不能识别的,所以要借助webpack打包工具)
定义模块 math.js
引用模块
es6在导出的时候有一个默认导出,export default,使用它导出后,在import的时候,不需要加上{},模块名字可以随意起。该名字实际上就是个对象,包含导出模块里面的函数或者变量。
定义
引用
但是一个模块只能有一个export default。
六、umd模块
如果你在js文件头部看到这样的代码,那么这个文件使用的就是 UMD 规范
实际上就是 amd + commonjs + 全局变量 这三种风格的结合
这段代码就是对当前运行环境的判断,如果是 Node 环境 就是使用 CommonJs 规范, 如果不是就判断是否为 AMD 环境, 最后导出全局变量
有了 UMD 后我们的代码可同时运行在 Node 和 浏览器上
所以现在前端大多数的库最后打包都使用的是 UMD 规范
七、扩展
1、CommonJs规范中的exports和module.exports的区别
exports 是对 module.exports 的引用。比如我们可以认为在一个模块的顶部有这句代码: exports = module.exports所以,我们不能直接给exports赋值,比如number、function等。
注意:因为module.exports本身就是一个对象,所以,我们在导出时可以使用
但是, exports 是 module.exports 的一个引用,或者理解为exports是一个指针,exports指向module.exports,这样,我们就只能使用 exports.foo = 'bar' 的方式
而不能使用exports = {foo: 'bar'} //error 这种方式是错误的,相当于重新定义了exports, 赋值之后exports失去了 对module.exports的引用,成为了一个模块内的局部变量
2、AMD和CMD的区别
最明显的区别就是在模块定义时对依赖的处理不同
这种区别各有优劣,只是语法上的差距,而且requireJS和SeaJS都支持对方的写法
AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同
3、CommonJs和ES6模块的区别
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
ES6模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
【详细解释:引用自阮一峰ECMAScript 6 入门】
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。
上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。
上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。
ES6 模块的运行机制与 CommonJS 不一样。**JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。**原始值变了,import加载的值也会跟着变。因此,es6模块是动态引用,并不会缓存值,模块里面的变量绑定其所在的模块。
还是举上面的例子。
上面代码说明,ES6 模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。
再举一个例子。
上面代码中,m1.js的变量foo,在刚加载时等于bar,过了 500 毫秒,又变为等于baz。
让我们看看,m2.js能否正确读取这个变化。
上面代码表明,ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。
由于 ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。
上面代码中,main.js从lib.js输入变量obj,可以对obj添加属性,但是重新赋值就会报错。因为变量obj指向的地址是只读的,不能重新赋值,这就好比main.js创造了一个名为obj的const变量。
最后,export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
上面的脚本mod.js,输出的是一个C的实例。不同的脚本加载这个模块,得到的都是同一个实例。
现在执行main.js,输出的是1。
这就证明了x.js和y.js加载的都是C的同一个实例。
运行时加载: CommonJS 加载的是一个对象(即module.exports属性);该对象只有在脚本运行完才会生成,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
详细解释:【引用自阮一峰ECMAScript 6 入门】
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
除了静态加载带来的各种好处,ES6 模块还有以下好处。
参考
The text was updated successfully, but these errors were encountered: