模块化开发方便代码的管理,提高代码复用性,降低代码耦合,每个模块都会有自己的作用域。当前流行的模块化规范有 CommonJS,AMD,CMD,ES6 的 import/export
- CommonJS 的主要实践者就是 nodejs, 一般对模块输出用 module.exports 去输出,用 require 去引入模块,CommonJS 一般采用同步加载【require / module.exports / exports】
- AMD 遵从 RequireJs 规范,推崇依赖前置(提前执行)【require / defined】
- CMD 遵从 SeaJs 规范,推崇依赖就近(延迟执行)【require / defined】
- ES6 可静态分析,提前编译,不是在运行时确认【import / export】
发展历程
简单封装 -> 命名空间/modules -> script loader/modules loader -> 同步加载 CommonJS/AMD/CMD -> ES6 import export/export default -> 模块打包工具 browserify/webpack
简单封装
用法:把不同的函数简单地放在一起,看作一个模块
缺点:
- “ 污染 ” 了全局变量,无法保证不与其他模块发生变量名冲突;
- 模块成员之间看不出直接关系
对象(命名空间)
用法:把功能代码放入对象中,当作对象的属性
优点:减少了全局上的变量数目,避免变量全局污染
缺点:本质是对象,而这个对象会暴露所有模块成员,内部状态可以被外部改写
立即执行函数(IIFE)
- 声明一个匿名函数
- 马上调用这个匿名函数
作用:创建一个独立的作用域。数据是私有的, 外部只能通过暴露的方法操作
这个作用域里面的变量,外面访问不到(即避免「变量污染」)
CommonJS
导入导出:require & exports/module.exports
主要实践者:NodeJS
模块导入
语法:require(module)
eg:
var math = require('math');
math.add(2, 3);
如果模块输出的是一个函数,那就不能定义在 exports 对象上面,而要定义在 module.exports 变量上面。
// example2.js
module.exports = function () {console.log("hello world")
}
// main.js
require('./example2.js')()
AMD(Asynchronous Module Definition)
提倡依赖前置,在定义模块的时候就要声明其依赖的模块。在 requireJs 推广过程中产生的规范
模块定义define(id?, dependencies?, factory)
id:字符串,模块名称(可选)
dependencies:数组,是我们要载入的依赖模块(可选),使用相对路径
factory:工厂方法,返回一个模块函数
模块导入require([module], callback)
module:是一个数组,里面的成员就是要加载的模块
callback:则是加载成功之后的回调函数
requireJS 优点
- 实现 js 文件的异步加载,避免网页失去响应;
- 管理模块之间的依赖性,按照依赖关系加载,便于代码的编写和维护。
采用异步方式加载模块,通过 define 来定义一个模块,通过 require 来引入模块,模块的加载不影响后面语句的执行,所有依赖于这些模块的语句都写在一个回调函数中,加载完毕后,这个回调函数才运行
CMD(Common Module Definition)
提倡就近依赖(按需加载),在用到某个模块的时候再去 require 进来。在 Sea.js 推广过程中产生的规范
模块定义 define(id?, dependencies?, factory)
与 AMD 类似
// eg
define('hello', ['jquery'], function(require, exports, module) {
// 模块代码
// return 模块对象
});
模块导入导出
与 AMD 类似
ES6
导入导出:import/export/export default
export defalut
默认输出是一个函数/变量
其他模块加载该模块时,import 命令可以为该匿名函数指定任意名字
需要注意的是,这时 import 命令后面,不使用大括号
// export-fn.js
export default function () {console.log('foo');
}
import foo from 'export-fn.js'
export default 命令也可以用在非匿名函数前,视同匿名函数加载
export default 命令用于指定模块的默认输出。一个模块只能有一个默认输出,因此 export default 命令只能使用一次。所以,import 命令后面才不用加大括号,因为只可能唯一对应 export default 命令。
本质上,export default 就是输出一个叫做 default 的变量或方法,然后系统允许你为它取任意名字
export & export default 区别
使用 export default 时,对应的 import 语句不需要使用大括号,默认输出
使用 export 时,对应的 import 语句需要使用大括号
// 第一组 export default
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组 export
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
ES6、AMD 和 CommonJS 区别
ES6 模块使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
CommonJS 和 AMD 模块,都只能在运行时确定这些东西;
CommonJS 模块就是对象,输入时必须查找对象属性
-
CommonJS 模块
let {stat, exists, readFile} = require('fs')
整体加载 fs 模块(即加载 fs 的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”
-
ES6 模块
import {stat, exists, readFile} from 'fs'
从 fs 模块加载 3 个方法,其他方法不加载,这种加载称为“编译时加载”或者静态加载
-
CommonJS 模块
const path = './' + fileName const myModual = require(path)
动态加载,require 到底加载哪一个模块,只有运行时才知道
import()
动态加载 ,正在提案阶段,import() 返回一个 Promise 对象
import() 加载模块成功以后,这个模块会作为一个对象,当作 then 方法的参数
import() 类似于 Node 的 require 方法,区别主要是前者是异步加载,后者是同步加载。
适用场景
(1)按需加载
(2)条件加载
(3)动态的模块路径
注:关于 ES6 模块化,详细见 阮一峰的 es6 入门 module 模块
es6 与 commonJS/AMD 模块化区别
- 模块化的规范:CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。
- 而 ES6 中提供了简单的模块系统,完全可以取代现有的 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
- ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。
require 与 import 的区别(commonJS 与 ES6 模块化区别)
require/exports 是 CommonJS 的一部分;import/export 是 ES6 的新规范
require 支持 动态导入,import 不支持,正在提案 (babel 下可支持)
require 是 同步 导入,import 属于 异步 导入
require 是 值拷贝,导出值变化不会影响导入值;import 是值引用,指向 内存地址,导入值会随导出值而变化