前言
// a.js
var name = 'ywq'
var age = 12
// b.js
var name = 'yyy'
// index.js
console.log(`我的名字是${name},我今年${age}了`) // 我的名字是yyy,我今年12了
// index.html
<script src="./a.js"></script>
<script src="./b.js"></script>
<script src="./index.js"></script>
以前我们在开发时,经常会出现这样的情况,代码被覆盖掉了. 在多人开发时,是非常烦人的.因此有下面的一种解决方案.
// a.js
var moduleA = (function(){
var name = 'ywq'
var age = 12
return {
name,
age
}
})()
// b.js
var moduleB = (function() {
var name = 'yyy'
})()
// index.js
var moduleIndex = (function() {
console.log(`我的名字是${moduleA.name},我今年${moduleA.age}了`) // 我的名字是ywq,我今年12了
})()
// index.html
<script src="./a.js"></script>
<script src="./b.js"></script>
<script src="./index.js"></script>
- 防止代码被污染,因此js文件需要有自己的作用域,而函数拥有自己的作用域
- 使用立即执行函数,解决了函数命名及调用问题
- 通过函数返回值,对外暴露
上面的解决方式,确实带来了极大的便利.但是全局的moduleA,moduleB... 随着代码量的增多,我们也保证不了会不会也被覆盖啊... 因此,社区推出了CommonJS规范,而node实现了这一规范
一、CommonJS
// a.js
const name = 'ywq'
const age = 18
module.exports = {
name,
age
}
// b.js
var name = 'yyy'
module.exports = {
name
}
// index.js
const moduleA = require('./a.js')
const moduleB = require('./b.js')
console.log(`我的名字是${moduleA.name},我今年${moduleA.age}了`)
// 我的名字是ywq,我今年18了
上面是一段简单的符合CommonJS规范的代码.
- 每个js文件都是一个单独模块(有自己的作用域)
- 模块可通过module.exports、exports对外导出,require对内导入
下面我们来介绍一下CommonJS的使用吧
1.导入导出
1.1 导出
- module.exports = { 对外暴露的变量 } ---- module是对象,exports也是对象
const name = 'ywq'
const age = 12
// 导出
module.exports = {
name,
age
}
// 或者这样导出
// module.exports.name = name
// module.exports.age = age
- exports.对外暴露的变量 = 'xxx'
const name = 'ywq'
const age = 12
exports.name = name
exports.age = age
1.2 导入
- require(X)
const moduleA = require('./a.js')
1.3 module.exports 和 exports 有什么关系
// 第一段代码
// a.js
exports.aaa = '111'
exports.bbb = '222'
module.exports = { ccc: '333', ddd: '444' }
// b.js
const m = require('./a.js')
console.log(m) // 结果: { ccc: '333', ddd: '444' }
// ---------------
// 第二段代码
// a.js
module.exports = { ccc: '333', ddd: '444' }
exports.aaa = '111'
exports.bbb = '222'
// b.js
const m = require('./a.js')
console.log(m) // 结果: { ccc: '333', ddd: '444' }
上面代码我们有发现什么呢?
// 伪代码
module.exports = {}
exports = module.exports
- module.exports和exports都可以对外暴露,当两者同时出现时,总是以module.exports进行导出
- exports是符合CommonJS规范的,module.exports是不符合CommonJS规范的.node为了符合CommonJS规范,特意添加了exports
- exports内部实现都是通过module.exports进行导出的
2.内部原理
下面我们来看一段代码
// a.js
let obj = {
name: "小狗",
color: "黄色"
}
setTimeout(() => {
obj.color = "黑色";
}, 1000);
setTimeout(() => {
console.log(obj. color);
}, 3000);
module.exports = obj
// b.js
let moduleA = require('./a.js');
console.log(moduleA.color);
setTimeout(() => {
console.log(moduleA.color);
moduleA.color = "红色";
}, 2000);
// 打印结果依次为: 黄色 黑色 红色
上面代码,我们发现了什么? 我们将obj暴露出去,外部引入文件后,可以对引入的数据进行更改,并且导出文件里面对应的数据也随之进行了修改. 这是为什么嘞???
// 伪代码
// a.js
module.exports = {}
// b.js
const m = function require(x) {
return module.exports
}
console.log(module.exports === m) // true
- module.exports是一个对象(引用数据类型),有其对应的内存空间
- module.exports导出的数据,就是require导入的数据
- module.exports和require指向的是同一个指针
3.require的查找规则 -> require(X)
- 情况一: X是node的核心模块
const fs = require('fs')
-
情况二: X是路径 ./ ../ /
-
将X当作文件在当前文件的目录下查找
-
文件带后缀名,根据后缀名查找
-
文件不带后缀名
2.1. 按照 .js/.json/.node 后缀名依次查找
-
-
将X当作目录 (X作为文件没有找到的情况下)
- 查找目录下的X/index.js文件
- 查找目录下的X/index.json文件
- 查找目录下的X/index.node文件
-
-
情况三: 既不是node核心模块,也不是一个路径 (通常是第三方库)
console.log(module)
require会一层一层根据paths查找node_modules目录下面是否有对应目录,如果没有,最后会报错
4.模块加载流程
// a.js
console.log('a文件开始执行')
module.exports = { name: 'a' }
// b.js
console.log('b文件开始执行')
require('./a.js')
require('./a.js')
require('./a.js')
console.log('b文件后续代码')
我们又发现了什么?
-
commonjs代码是同步执行的,文件加载并执行完后才继续执行后续代码
-
多次导入同一个文件,该文件中的代码只会执行一次
原因: node中有loaded属性记载是否文件被加载过
5.模块加载顺序
// a.js
console.log('aaa')
require('./b.js')
require('./c.js')
// b.js
console.log('bbb')
require('./d.js')
// c.js
console.log('ccc')
require('./e.js')
// d.js
console.log('ddd')
require('./e.js')
// e.js
console.log('eee')
二、AMD
// index.html
<script src="./require.js" data-main="./index.js"></script>
// index.js
require.config({
baseUrl: './src',
foo: './foo',
bar: './bar'
})
console.log(111)
require(['foo', 'bar'], function(module) {
console.log('index:', module)
})
console.log(222)
// src/foo.js
define(function() {
const a = 10
const b = 3
return {
a,
b
}
})
// src/bar.js
define(function() {
console.log(333)
require(['foo'], function(module) {
console.log('bar:', module)
})
console.log(444)
})
- data-main: 保证require.js文件加载后,首先执行index.js文件
- require(): 保证代码是异步执行
- define(): 相当于拥有自己的作用域
三、CMD
// index.html
<script src="./sea.js"></script>
<script>seajs.use('./index.js')</script>
// index.js
define(function(require, exports, module) {
console.log(111)
const foo = require('./src/foo.js')
console.log(foo.sum(3, 2))
console.log(222)
})
// src/foo.js
define(function(require, exports, module) {
const sum = (num1, num2) => {
return num1 + num2
}
const name = 'ywq'
module.exports = {
name,
sum
}
})
四、ESModule
// index.html
<script src="./index.js" type="module"></script>
// index.js
import { name, age } from './foo.js'
console.log(name, age) // ywq 18
// foo.js
const name = 'ywq'
const age = 18
export {
name,
age
}
上面是一段使用ESModule的代码.
- type是为了使index.js以ESModule的方式进行解析,避免当作普通的js文件
- import: 对内引入 export: 对外暴露
- 采用编译器静态分析,也可以动态导入
- 自动采用严格模式
1.导入导出
1.1 导出
- export 声明语句
// a.js
export const a = 1
export const b = 2
- export { 要暴露的变量 }
// foo.js
function sum(num1, num2) {
return num1 + num2
}
export {
// sum // 命名导出
sum as bbb // 起别名
}
- default 默认导出
// foo.js
function sum(num1, num2) {
return num1 + num2
}
// 第一种:
export {
sum as default
}
// 第二种:
export default sum
1.2 导入
- 普通的导入
import { a, b } from './a.js'
import { bbb } from './foo.js'
- 起别名
import { name as sname } from './a.js'
- 导入所有
import * as my from './a.js'
- default 默认导入
import my from './a.js'
注意:
// index.js
import { a, b } from './main.js'
export { a, b}
// 在一个文件里面,导入和导出的一样,可使用下面的语法糖
export { a, b } from './main.js'
2.import动态导入
// a.js
export const name = 'ywq'
// b.js
console.log(111)
if(3 > 2) {
import('./a.js').then(res => {
console.log(res.name)
})
}
console.log(222)
import动态导入时,返回值是一个Promise
3.原理
原理可参考hacks.mozilla.org/2018/03/es-…
结论: 暴露的变量,只能在导出文件进行修改; 在导入文件修改会报错