前言:本文着重介绍ES5和ES6的模块化加载,至于AMD和CMD因为已经过时了,仅仅只是提了下。早些年的前端都仅仅只是静态页面,还没有模块化这个概念,但随着时间的推移,前端代码愈发庞大,那么自然而然会暴露很多问题:
- 命名冲突
- 文件依赖
那个时候都是通过匿名自执行函数来解决命名冲突,文件依赖只能手动保证引入的顺序正确,直到后来某国外大神造出来Require.js,从此前端模块化的概念就出现了;再后来淘宝前端大神玉伯根据require.js的思想造出了sas.js,至此,两大模块加载器在前端领域独领风骚一段时间。
1. AMD
AMDRequireJS 在推广过程中对模块定义的规范化产出,是浏览器端的模块化解决方案。
2. CMD
CMD是 SeaJS 在推广过程中对模块定义的规范化产出,是浏览器端的模块化解决方案。
3. AMD与CMD的差异
- AMD是提前执行(RequireJS2.0开始支持延迟执行,不过只是支持写法,实际上还是会提前执行),CMD是延迟执行
- AMD推荐依赖前置,CMD推荐依赖就近
4. CommonJS
CommonJS是NodeJs服务器端模块的规范,ES Module是在ES5中推出的,基于CommonJS规范,而export和export default是ES6中的模块化加载语法。
在这之前,必须了解 ES6 模块与 CommonJS 模块完全不同。它们有两个重大差异:
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。具体可参考阮一峰大神的ES6文章Module加载语法。
重点来了,下面着重介绍ES5中和ES6中的模块化加载方案:
ES5中exports和module.exports的区别
module.exports才是真正的接口,exports只不过是它的一个辅助工具。nodejs只会导出module.exports的指向,最终返回给调用者的是module.exports而不是exports。
所有的exports收集到的属性和方法,都赋值给了Module.exports。当然,这有个前提,就是module.exports本身不具备任何属性和方法。
如果,module.exports已经具备一些属性和方法,那么exports收集来的信息将被忽略。
挂载在exports上的方式和属性,都会传递给module.exports。二者没区别。
exports.fun = function() {
console.log('Hello world')
}
exports.messgae = 'Nice to meet you'
var isEq = (exports === module.exports);
console.log(exports);
console.log(module.exports);
console.log(isEq);
// output:
// { fun: [Function], message: 'Nice to meet you' }
// { fun: [Function], message: 'Nice to meet you' }
// true
支持用import
和require
的具名和匿名引入
let mod = require('./mod.js') // mod: // { fun: [Function], message: 'Nice to meet you' }
import { fun, messgae } from './mod.js' // fun: [Function], message: Nice to meet you
直接把变量赋值给exports,那么exports指向变了,那就仅仅是exports不再指向module.exports
exports.fun = function() {
console.log('Hello world')
}
exports = 'Nice to meet you'
var isEq = (exports === module.exports);
console.log(exports);
console.log(module.exports);
console.log(isEq);
// output
// Nice to meet you
// { fun: [Function] }
// false
let mod = require('./mod.js') // mod: // { fun: [Function] }
直接把变量赋值给module.exports,那就说明module.exports和exports的引用关系断开了,二者不相等了。
exports.fun = function() {
console.log('Hello world')
}
module.exports = 'Nice to meet you'
var isEq = (exports === module.exports);
console.log(exports);
console.log(module.exports);
console.log(isEq);
// output:
// { fun: [Function] }
// Nice to meet you
// false
let mod = require('./exports.js') // mod: Nice to meet you
⚠️建议NodeJS开发者注意一下两点:
- 最好别分别定义module.exports和exports
- 导出对象用module.exports,导出多个方法和变量用exports
导出多个属性或方法:
exports.fun = function() {
console.log('Hello world')
}
exports.message = 'Nice to meet you'
// 匿名引入
let mod = require('./mod.js') // { fun: [Function], message: 'Nice to meet you' }
// or 具名引入
let { fun, message } = require('./mod.js') // [Function], Nice to meet you
导出一个对象:
let fun = function() {
console.log('Hello world')
}
let message = 'Nice to meet you'
module.exports = {
fun,
message
}
// 匿名引入
let mod = require('./mod.js') // { fun: [Function], message: 'Nice to meet you' }
// or 具名引入
let { fun, message } = require('./mod.js') // [Function], Nice to meet you
⚠️这里必须用module.exports,而不能用exports,因为nodejs只会导出module.exports的指向
ES6中export 和 export default区别
- export与export default均可用于导出常量、函数、文件、模块等
- 在一个文件或模块中,export、import可以有多个,export default仅有一个
- export方式只能具名导入,在导入时要加{ };export default则只能匿名导入
- export能直接导出变量表达式,export default不行。
export
const Programmer = {name: 'UncleFirefly',age:25}
export let message = 'hello'
export { Programmer }
console.log(module.exports) // { message: 'hello', Programmer: { name: 'UncleFirefly', age: 25 } }
import
引入时必须具名导入,也就是需要声明式指定对象里的key来导入你需要的
import { Programmer } from './mod.js' // Programmer: {name: 'UncleFirefly',age:25}
如果使用require
引入的话,则可以直接匿名引入
let mod = require('./mod.js') // mod: { message: 'hello', Programmer: { name: 'UncleFirefly', age: 25 } }
// or require也支持具名导入
let { Programmer } = require('./export.js') // Programmer: {name: 'UncleFirefly',age:25}
export default
const message = 'hello'
const Programmer = {name: 'UncleFirefly',age:25}
export default Programmer
// export default message // 报错:export default只能用一次
console.log(module.exports) // { default: { name: 'UncleFirefly', age: 25 } }
只能匿名导入
import mod from './mod.js' // mod: { name: 'UncleFirefly', age: 25 }
如果使用require
引入的话,必须通过default
属性拿到实际导出的变量:
let mod = require('./mod.js') // mod: { default: { name: 'UncleFirefly', age: 25 } }
export与import混合使用
具名接口改为默认接口的写法如下:
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
同样地,默认接口也可以改名为具名接口:
export { default as es6 } from './someModule';