js模块化——commonJS
总结于知乎《前端科普系列-CommonJS:不是前端却革命了前端》,作者:无名之辈
What
为了解决模块化问题,而制定的一种模块化规范
规范核心部分:
- Node.js 应用由模块组成,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
// a.js
var name = 'morrain'
var age = 18
上面代码中,a.js 是 Node.js 应用中的一个模块,里面申明的变量 name 和 age 是 a.js 私有的,其它文件都访问不到。
2. 每个模块内部有两个变量可以使用,require 和 module。
require 用来加载某个模块
module 代表当前模块,是一个对象,保存了当前模块的信息。exports 是 module 上的一个属性,保存了当前模块要导出的接口或者变量,使用 require 加载的某个模块获取到的值就是那个模块使用 exports 导出的值
// a.js
var name = 'morrain'
var age = 18
module.exports.name = name
module.exports.getAge = function(){
return age
}
//b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
console.log(a.getAge())// 18
HOW
- 关于export
为了方便,Node.js 在实现 CommonJS 规范时,为每个模块提供一个 exports的私有变量,指向 module.exports。你可以理解为 Node.js 在每个模块开始的地方,添加了如下这行代码。
var exports = module.exports
于是上面的代码也可以这样写:
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
return age
}
如果一个模块的对外接口,就是一个单一的值,可以使用 module.exports 导出
// a.js
var name = 'morrain'
var age = 18
module.exports = name
- 关于require
require 命令的基本功能是,读入并执行一个 js 文件,然后返回该模块的 exports 对象的拷贝。如果没有发现指定模块,会报错。
2.1 NodeJs 会缓存一个module
如图所示,第二次require的时候,并没有重新执行并加载module A,而是直接返回了第一次 require 时的结果,也就是模块A的 module.exports。
2.2 require 的是被导出的值的拷贝。也就是说,一旦导出一个值,模块内部的变化就影响不到这个值 。
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
age = a
}
// b.js
var a = require('a.js')
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 18
CommonJS 的实现
有了commonjs的规范,我们可以将它在前端/后端进行实现:
commonjs在前端的实现:
Browserify 、Webpack就是将各模块代码进行打包,进而转化为浏览器能够运行的js代码。commonjs规范无非是三个东西,module、require和exports,webpack等打包工具将其实现后,就可以进行转化了:
// bundle.js
(function (modules) {
// 模块管理的实现
})({
'a.js': function (module, exports, require) {
// a.js 文件内容
},
'b.js': function (module, exports, require) {
// b.js 文件内容
},
'index.js': function (module, exports, require) {
// index.js 文件内容
}
})
** 但是前端有一个异步加载的问题,因为各个模块不一定同时下载完成 **
为了解决这个问题,后面发展起来了众多的前端模块化规范,包括 CommonJS 大致有如下几种:
现在大都使用的是ES6 Module了,内容也不用多说,就是import,export, export default, import()等
ES6 Module 和 CommonJS 的区别
- 执行时加载和编译时加载
CommonJS 只能在运行时确定导出的接口,实际导出的就是一个对象。而 ES6 Module 的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及导入和导出的变量,也就是所谓的"编译时加载"。
正因为如此,import 命令具有提升效果,会提升到整个模块的头部,首先执行。下面的代码是合法的,因为 import 的执行早于 getAge 的调用。
// a.js
export const name = 'morrain'
const age = 18
export function getAge () {
return age
}
// b.js
const age = getAge()
console.log(age) // 18
import { getAge } from 'a.js'
- 没有拷贝或者cache
由于编译过后代码已经被拼接在一起,所以不会有对被require文件的缓存。所以对被引模块进行内部方法修改变量后,能够反映到后续程序执行中。
// a.js
var name = 'morrain'
var age = 18
const setAge = a => age = a
export {
name,
age,
setAge
}
// b.js
import * as a from 'a.js'
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 19