在前端开发中使用ES6的模块化和node模块化总会有所混淆,今天深度解析下它们之间的区别
node模块化
-
node模块中的exports对象,用来创建对象
// rocker.js exports.name = function() { console.log('My name is Lemmy Kilmister'); };
-
引用rocker.js
var rocker = require('./rocker.js'); rocker.name(); // 'My name is Lemmy Kilmister'
-
exports 与 module.exports 测试
// 修改rocker.js如下: module.exports = 'ROCK IT!'; exports.name = function() { console.log('My name is Lemmy Kilmister'); };
// 再次引用执行rocker.js var rocker = require('./rocker.js'); rocker.name(); // TypeError: Object ROCK IT! has no method 'name'
-
上面报错,实际上Module.exports才是真正的接口,exports只不过是它的一个辅助工具。最终返回给调用的是Module.exports而不是exports。所有的exports收集到的属性和方法,都赋值给了Module.exports。当然,这有个前提,就是Module.exports本身不具备任何属性和方法。如果,Module.exports已经具备一些属性和方法,那么exports收集来的信息将被忽略。
-
exports 与 module.exports实质
- 模块内部大概是这样:
exports = module.exports = {};
-
exports是module.exports的一个引用
-
require引用模块后,返回给调用者的是module.exports而不是exports
-
exports.xxx,相当于在导出对象上挂属性,该属性对调用模块直接可见
-
exports =相当于给exports对象重新赋值,调用模块不能访问exports对象及其属性
-
扩展
- Node.js在模块编译的过程中会对模块进行包装,最终会返回类似下面的代码:
(function (exports, require, module, __filename, __dirname) { // module code... }); // 其中,module就是这个模块本身,require是对Node.js实现查找模块的模块Module._load实例的引用,__filename和__dirname是Node.js在查找该模块后找到的模块名称和模块绝对路径,这就是官方API里头这两个全局变量的来历。
-
总结:
- 对于要导出的属性,可以简单直接挂到exports对象上
- 对于类,为了直接使导出的内容作为类的构造器可以让调用者使用new操作符创建实例对象,应该把构造函数挂到module.exports对象上, 不要和导出属性值混在一起
ES6模块
-
export 命令
- 出口,用于规定模块的对外接口(这就意味着数据必须被包装成对象的格式)。
// 输出变量 // profile.js var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export {firstName, lastName, year}; // 输出函数 function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion };
- export 输出时,只有三种固定格式,其他均会报错:
// 写法一 export var m = 1; // 声明时输出 // 写法二 var m = 1; export {m}; // 包装成数据对象输出 // 写法三 var n = 1; export {n as m}; // 使用别名输出,基本上相当于第二种
-
import 命令
- 入口,加载 export 的模块中的变量、方法等。
// main.js import {firstName, lastName, year} from './profile.js'; function setName(element) { element.textContent = firstName + ' ' + lastName; } // 当然,引入的变量也可以修改变量名: import { lastName as surname } from './profile.js'; // 如果 export 时未采用 {xxx,xxx} 方式,那么在 import 时,想要引入所有值,可以使用 * 来代替: // main.js import { area, circumference } from './circle'; console.log('圆面积:' + area(4)); console.log('圆周长:' + circumference(14)); // 可以简写成 import * as circle from './circle'; console.log('圆面积:' + circle.area(4)); console.log('圆周长:' + circle.circumference(14));
-
export default 命令
- 用于不知道需要加载模块的变量名和函数名
// export-default.js export default function () { console.log('foo'); } // import-default.js import customName from './export-default'; customName(); // 'foo' // 通过 export default 出来的通常在 import 时候不需要 {},且 import 时可以任意命名。
-
总结
- export与export default均可用于导出常量、函数、文件、模块等
- 在一个文件或模块中,export、import可以有多个,export default仅有一个
- 通过export方式导出,在导入时要加{ },export default则不需要如果此模块是一个类,则等同于node模块中给module.exports赋值,这样调用者就是一个类构造器,可以直接new实例
ES6模块化与commonjs、amd区别:
- ES6 模块的设计思想是尽量的静态化,使得**编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。