模块化
模块化
JavaScript 最初设计这门语言时只是将它定位成一个小型的脚本语言而并没有制定模块化的规范。因此 JavaScript 在很长一段是假也没有模块化的概念。
早期的页面开发引入脚本是通过 script标签引入不同的 JS 文件来进行拆分代码。但当代码规模逐渐扩大,此开发模式愈显弊端:
- 所有模块都在全局作用域中,全局环境受到污染,命名冲突。
- 模块之间的引用关系不明确,必须人为按需求调整加载顺序。
- 所有模块文件同步加载,造成页面阻塞。
在这个时期,使用了命名空间和封闭的匿名函数的手段,虽然很大的缓冲了命名冲突和执行顺序的问题,但由于都借助于挂载全局变量,是从代码逻辑的角度处理,实际并没根本解决该问题。
CommonJs
现在提 CommonJs 通常联想到 node.js 实现的模块化标准。事实上两者并不完全一样, nodejs 实现了 CommonJs 的一部分标准。
CommonJs 规定了每个文件为单独的模块,每个模块有自己独有的作用域,作用域内的变量只能模块自身访问。
CommonJs 通过 modules.exports = { ...} 导出,或者 exports.xxx = xxx 以添加属性的方式, 但不能对 exports 直接赋值,可以简单的理解为每个模块开头都有一段代码:
var module = {
exports: {},
};
var exports = module.exports;
CommonJs 通过 require() 加载模块,当模块第一次被加载时,模块代码将被执行,然后导出其结果。当模块再次被加载时则不执行模块代码,直接到处上次执行的结果。
AMD
全称为:Asynchronous Module Definition ,专注于浏览器的异步模块定义规范。
代表库 Require.js: define() 定义模块,require() 加载模块。
CMD
全称为:Common Module Definition,与 AMD 相同都是专注于浏览器的模块化规范,区别在于 CMD 是同步阻塞式的加载方式。
代表库: Sea.js ,使用上基本与 Require.js 相同,且 Require.js 通过配置也可以实现同步加载。
UMD
全称为: Universal Module Definition,严格讲并不是一个规范,而是一种兼容 CommonJs 、AMD 、 CMD 的一套具体实现:
((root, factory) => {
if (typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
//CommonJS
var $ = requie('jquery');
module.exports = factory($);
} else {
root.testModule = factory(root.jQuery);
}
})(this, ($) => {});
ES Modules
ES Modules 是 ES6 发布后, JavaScript 这语言才真正具备的模块化特性。import 与 export 作为保留关键字,对模块进行导入与导出。(CommonJs 的 module 并不是关键字)
与 CommonJs 相同, ES Modules 也是一个文件为一个模块,模块享有单独的作用域。
在高级浏览器的 script 标签中添加 type="module" 即可支持 ES Modules。
ES Modules 默认打开严格模式。
// calculate.js 导出
export { add };
// calculate.js 导入
import { add } from './calculate.js';
default 与 as 用法相当于变量名。
// calculate.js 导出
const add = function() {};
export default add;
// calculate.js 导入
import def, { default as add } from './calculate.js';
//ps: 此处的 def 与 add 效果等价,但 def 必须写在前面,否则会有异常
复合写法:
export { add } from './calculate.js';
//等价于
import { add } from './calculate.js';
export { add };
ES Modules执行可支持异步,通过 import() 将返回一个 Promise 对象。
import('./calculate.js').then((add) => {
add();
});
ES Modules 与 CommonJs 的区别
ES Modules 与 CommonJs (指 node.js 实现的版本) 为现在前端模块化最常用的两种模式。
-
静态加载与动态加载
CommonJs是在运行阶段动态加载,这使得他可以出现在if这一类条件语句中运行。
而ES Modules是在编译阶段的静态加载.这要求他必须在顶层作用域中声明,并且加载路径不可以使用表达式。
由于ES Modules加载的是可确定的静态资源,这将有利于代码的检查,排错和优化。 -
值的拷贝与动态映射
CommonJs的取值是依赖所导出值的拷贝,是一个全新的数据。// calculate.js 导出 var count = 0; module.exports = { count: count, add: function() { count++; }, }; // calculate.js 导入 var count = require('./calculate.js').count; var add = require('./calculate.js').add; console.log(count); //0 add(); console.log(count); //0 由于是拷贝值所以对当前模块的 count 与 calculate模块的 count 并影响 count++; console.log(count); //1ES Modules导入的是依赖模块原有值的映射:// calculate.js 导出 var count = 0; var add = function() { count++; }; export { count, add }; // calculate.js 导入 import { count, add } from './calculate.js'; console.log(count); //0 add(); console.log(count); //1 count++; //不可改变, SyntaxError: "count" is read-only -
循环依赖
在工程项目中,开发人员应尽量避免循环依赖的加载,但由于工程规模的庞大,此类问题则是有可能出现。
由于两种规范的加载机制不同,导致了循环引用依赖带来的导出值将不一致。
回顾下 CommonJs 加载机制的特点:- 只在首次加载执行代码,再次加载则返回上次执行的结果。
- 导出结果为值的拷贝。
module.exports默认情况下为空对象。
示例:
//foo.js const bar = require('./bar.js'); console.log('value of bar is : ' + bar); module.exports = 'foo'; //bar.js const foo = require('./foo.js'); console.log('value of foo is : ' + foo); module.exports = 'bar'; // 入口文件 index.js require('./foo.js');结果为:
value of foo is : {} value of bar is : bar解析其执行顺序:
index.js执行导入foo.js,主动权交于foo.js。- 执行
foo.js文件脚本。 foo.js执行导入bar.js,主动权交于bar.js。- 执行
bar.js文件脚本。 bar.js执行导入foo.js,此时出现了循环引用,主动权不转交,直接将原先foo.js的导出值拷贝 。由于foo.js并没有向下执行完毕,默认导出的为空对象。bar.js接收空对象执行打印语句value of foo is : {}bar.js导出结果'bar',主动权回到foo.js。foo.js接收'bar',向下执行语句。
同样逻辑的代码使用
ES Modules结果为:value of foo is : undefined value of bar is : bar解析其执行顺序:
index.js执行导入foo.js,主动权交于foo.js,不变。foo.js静态分析导入bar.js,主动权交于bar.js。bar.js静态分析导入foo.js,此时出现了循环引用,主动权不转交,直接将指向foo.js的导出 。由于foo.js并没有向下执行完毕,没有执行导出动作。bar.js执行脚本,此时foo变量为undefined。bar.js执行完毕后,主动权回到foo.js,执行脚本命令,此时bar的变量取值为'bar'。
ES Harmony
还在建议阶段
module staff{
export var baker = {
bake: function( item ){
console.log( "Woo! I just baked " + item );
}
}
}
module skills{
export var specialty = "baking";
export var experience = "5 years";
}
module cakeFactory{
import baker from staff;
import * from skills;
export var oven = {
makeCupcake: function( toppings ){
baker.bake( "cupcake", toppings );
},
makeMuffin: function( mSize ){
baker.bake( "muffin", size );
}
}
}

浙公网安备 33010602011771号