简介
因为今天要梳理的export我实在是区分不清楚,把它们的关系弄的很乱,今天就彻底梳理一下吧。
规范
想理清楚他们之间的关系,要先从规范开始看起。
CommonJS规范
1. 概述:
- 我们熟知的Node是由模块组成的,Node采用CommonJS模块规范。即每一个文件都是一个模块,有自己的作用域。自己文件里面定义的变量、函数、类都是私有的,其他文件不可见。
- 想在多个文件分享变量,必须定义为global对象的属性。
- CommonJS规范规定,每个模块内部,module变量代表当前的模块。这个变量是一个对象,他的exports属性(module.exports)是对外的接口。如果在一个文件中想使用另一个模块中的变量,即在该文件中使用
require
方法。(加载某个模块,其实就是加载该模块的module.exports属性)
// name.js 文件
const name = "Jack";
const sayName = function (value) {
return `hello ${name}`
}
module.exports.name = name
module.exports.sayName = sayName
// index.js 文件
const name = require("./name.js")
console.log(name.name)
console.log(name.sayName("Jone"))
- 这种方法不会污染全局作用域,所有代码都运行在模块内部。模块可以多次加载,但是只会在第一次加载时候运行一次,然后运行结果就被缓存了,以后再加载的时候,就会直接从缓存中读取。如果想再次运行模块,那么需要将缓存清除。
2. module对象
Node内部提供一个Module构建函数,所有模块都是Module的实例。
function Module(id, parent) {
this.id = id
this.exports = {}
this.parent = parent
}
每个模块内部,都有一个module对象,代表当前模块。
- module.id 是模块的识别符,通常是带有绝对路径的模块文件名。
- module.filename 模块的文件名,带有绝对路径
- module.loaded 返回一个布尔值 表示模块是否已经加载完成
- module.parent 返回一个对象,表示调用改模块的模块
- module.children 返回一个数组 表示该模块要用到的其他模块
- module.exports 表示模块对外输出的值
console.log(module)
可以试一试
3. module.exports属性
表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量
4. exports 变量
Node为每个模块提供一个exports
变量,指向module.exports
这等同在每个模块的头部,有var exports = module.exports
命令
console.log(exports === module.exports) // true
对外输出模块接口时,可以向exports对象添加方法
exports.sayName = function (name) {
return `hello ${name}`
}
不能直接将exports变量指向一个值,这样就切断了exports与module.exports的联系。(也就是指向另一个值,然后exports
就不再指向module.exports
)
exports = "change"
console.log(exports === module.exports) // false
所以这种写法是无效的。
···
name.js
exports.sayName = function(name) {
return "hello " + name
}
module.exports = "haha"
···
index.js
const name = require("./name")
console.log(name)
name.js
中的sayName是无法对外输出的,因为module.exports
被重新赋值了。如果一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports
输出。
5. require命令
用于加载模块文件(用于读入并执行一个Javascript文件),然后返回该模块的exports独享。如果没有发现指定模块,会报错。
name.js
exports.sayName = function(name) {
return "hello " + name
}
exports.name = "testing"
const name = require("./name")
console.log(name) // { sayName: [Function], name: 'testing' }
如果输出的是一个函数,就不能定义在exports
对象上面,而要定义在module.exports变量上面。
module.exports = function (name) {
console.log("hello " + name)
}
require('./name')('haha') // hello haha
require命令调用自身,等于是执行module.exports
,因此会输出hello haha
.(调用自己,顺便传了个参数,前一段时间做express的时候就把app传递给router,js了 ~)
6. 加载规则
require
命令用于加载文件,后缀名默认为.js
也就是在加载js文件的时候加不加后缀名都可以!根据参数的不同格式,require命令去不同路径寻找模块文件。
-
/
加载的是一个位于绝对路径的模块文件 - ‘./’ 加载的是一个位于相对路径 跟当前执行脚本的位置相比的模块文件
- 如果参数字符串不以上面两种开头目标是加载的是一个默认提供的核心模块(Node的系统安装目录中)或者一个位于各级
node_modules
目录的已安装的模块(全局或者局部安装的) 可以使得不同的模块可以将所依赖的模块本地化。 - 如果不以第一第二种开头,但是是一个文件路径
require('element-ui/css/index.css')
就是找到element-ui 模块,然后找到这个模块下的css路径下的index.css
文件。
- 如果指定的模块文件没有发现,Node会尝试为文件名添加
.js .json .node
后,再去搜索 分别是javascript脚本文件解析 json格式的文本文件解析 .node会以编译后的二进制文件解析。 - 如果想得到require命令加载的确切文件名,使用
require.resolve()
方法
const name = require.resolve("./name")
console.log(name) // D:\zamall\vueblog\server\name.js
7. 目录的加载规则
为目录设置一个入口文件,让require
方法可以通过这个入口文件,加载这个目录
在目录中设置package.json 文件 将入口文件写入main字段
- require发现参数字符串指向一个目录之后,会自动查看该目录的package.json 文件,然后加载main字段执行的入口文件。如果package.json 文件没有main字段 或者没有package.json 文件 ,则会加载该目录下的index.js 或者 index.node 文件
8.模块的缓存
上面讲过,Node在加载某个模块的时候,Node会缓存该模块。以后再次加载该模块,就直接从缓存中取出该模块的module.exports
属性。
name.js
index.js
require('./name.js')
require('./name.js').name = "hello"
console.log(require('./name.js').name) // hello
连续三次调用require
命令。第二次加载的时候,为输出对象添加一个name属性,第三次加载的时候,这个name属性依然存在,require命令并没有重新加载模块文件,而是输出了缓存。
所有缓存的模块保存在require.cache
之中,如果想删除模块的缓存。
// 删除指定模块的缓存 -moduleName 为要删除模块缓存的模块名称
delete require.cache[moduleName]
// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})
// tips:缓存是根据 绝对路径 识别模块的,所以相同的模块名,他们的路径不同,`require`命令还是会重新加载该模块
9.模块的循环加载
name1.js
exports.name = 'name11'
console.log('a.js ', require("./name2").name)
exports.name = 'name12'
name2.js
exports.name = 'name21'
console.log('a.js ', require("./name1").name)
exports.name = 'name22'
main.js
console.log('main.js ', require('./name1').name)
console.log('main.js ', require('./name2').name)
结果
node main
name2.js name11
name1.js name22
main.js name12
main.js name22
如果在main.js中再次打印一份到控制台
输出的结果还是上面main.js相同的两份。因为第一次加载就缓存起来了。所以再次调用,会直接取出缓存中的内容