ES6+ 模块化扩展
1. 前言
要深入前端学习时绕不开的是 Node 的学习,而 Node 中自带了模块化系统,Node 中的模块化是基于 Commonjs 规范实现的。而 ES6 中的模块化与之还有很多的不同的地方。现阶段 Node 还依然使用的是 Commonjs 规范,而前端正在逐渐使用 ES6 module 规范。两个规定统一是一个漫长的过程,两者都存在历史遗留问题和兼容问题需要浏览器和 Node 核心的支持。有必要搞清楚两个规范的区别和注意事项,有助于我们深入地学习前端。
2. Commonjs 规范
在维基百科中是这样定义 Commonjs 的:
Commonjs 是一个项目,其目标是为 JavaScript 在网页浏览器之外创建模块约定。创建这个项目的主要原因是当时缺乏普遍可接受形式的 JavaScript 脚本模块单元,模块在与运行 JavaScript 脚本的常规网页浏览器所提供的不同的环境下可以重复使用
JavaScript 语言在很长一段时间是没有模块化的概念的,直到 Node.js 的诞生后,让 JavaScript 有能力编写服务端语言,对操作系统、网络、文件系统等等的复杂业务场景,使用模块化就是不可或缺。这样也把模块化的概念带到了前端,而这时的客户端的功能也很复杂,急需一种可以拆分代码模块方便管理代码的一种模式。最终在社区的推动下 ES6 给出了 JavaScript 模块化的规范。
Commonjs 规定每个模块内部,module
变量代表当前模块。这个变量是一个对象,它的 exports
属性(即 module.exports
)是对外的接口。加载某个模块,其实是加载该模块的 module.exports
属性。
2.1 导出模块
使用 module.exports
把需要暴露的内容导出,没有导出的在外面是访问不了的。
// a.js
module.exports.name = 'imooc';
module.exports.fn = function(){}
const age = ;
为了方便 module.exports
也可以省略 module 直接使用 exports 进行导出操作:
exports.a = 'hello'
使用 module.exports
时还可以整体导出,整体导出时不能简写 exports
。
module.exports = { name: 'imooc', fn:function(){} }
2.2 导入模块
const a = require('./a');
console.log(a); // { name: 'imooc', fn: [Function (anonymous)] }
2.3 Commonjs 模块的特点
3. 不同规范之间的加载
3.1 import 加载 Commonjs 模块
// a.js
module.exports = {
foo: 'hello',
bar: 'world'
}
// 在import引入时等同于
export default {
foo: 'hello',
bar: 'world'
}
import {readfile} from 'fs' //当'fs'为Commonjs模块时错误
// 整体输入
import * as express from 'express'
const app = express.default();
3.2 require 加载 ES6 模块
// es.js
let foo = {bar : 'my-default'};
exxport default foo;
foo = null;
// cjs.js
const es_namespace = require('./es')
console.log(es_namespace.default);// {bar:'my-default'}
4. 面试题
模块化在面试中经常会被问到,掌握其深层原理是回答这类问题的关键。下面是面试中参考的两道题,这里和大家分享一下,提供的答案仅供参考。
- commonjs 规范与 es module 规范的区别?
两个规范的区别可以从以下几个方面来回答:
- 模块的导出和导入:commonjs 使用的是 module.exports 和 require;es module 使用的是 export 和 import;
- 模块的引入方式:commonjs 是动态引用;esmodule 是静态分析,export 和 import 只能出现在代码的顶层,在编译时就可以确定引用;
- 模块的引用类型:commonjs 对基本类型传递值,esmodule 对基本类型是传递引用;
- Commonjs 的 this 是当前模块,ES6 Module 的 this 是 undefined;
- 对 webpack 来说,想要支持 tree shaking,包必须采用 es module 规范。
JS 在加载时分为两个阶段:编译和执行,而 ES6 模块是在 编译时进行加载(也可以叫:静态加载),这使得静态分析成为可能。es module 自动采用严格模式,不管你有没有在模块头部加上 "use strict";
。
- 题目:commonjs 规范的循环引用
//main.js
var a = require('./a')
console.log(a)
// a.js
module.exports.a =
var b = require('./b')
console.log(b)
module.exports.a =
// b.js
module.exports.b =
var a = require('./a')
console.log(a)
module.exports.b =
- 使用 node main.js 执行 main.js 文件内容;
- 执行
require('./a')
会将 a 模块加入缓存,然后执行 a 模块中的内容,执行权交到了 a 模块中,执行 a; - 执行第一行将缓存的 a 值赋值为 1,然后执行第二行
require('./b')
把 b 模块加入缓存,并把执行权交到 b 模块中; - b 模块中把 b 的值赋值为 11,在
require('./a')
时,是从缓存中取的值,这里就会在控制台打印{a: 1}
,最后把缓存中的 b 值修改为 22,执行权交给上一级; - 代码执行权回到 a 模块中,这时 b 从缓存中取的值是 22,控制台中打印
{ b: 22 }
,最后把缓存中的 a 值修改为 2,执行权交给上一级; - 代码执行回到 main 模块中,这时缓存中的 a 是 2,控制台中打印
{ a: 2 }
,然后代码执行完毕。