func-package

函数库, 面向复杂业务场景的 js 类库

require加载原理

require 模块引入与处理

CommonJS 模块同步加载并执行模块文件,CommonJS 模块在执行阶段分析模块依赖,采用深度优先遍历(depth-first traversal),执行顺序是父 -> 子 -> 父;

eg:

a.js文件

const getMes = require('./b')
console.log('我是 a 文件')
exports.say = function(){
const message = getMes()
console.log(message)
}

b.js文件

const say = require('./a')
const object = {
name:'《React进阶实践指南》',
author:'我不是外星人'
}
console.log('我是 b 文件')
module.exports = function(){
return object
}

主文件main.js

const a = require('./a')
const b = require('./b')
console.log('node 入口文件')

接下来终端输入 node main.js 运行 main.js,效果如下:

从上面的运行结果可以得出以下结论:

main.js 和 a.js 模块都引用了 b.js 模块,但是 b.js 模块只执行了一次。 a.js 模块 和 b.js 模块互相引用,但是没有造成循环引用的情况。 执行顺序是父 -> 子 -> 父;

require 加载原理

** 首先为了弄清楚上述两个问题。我们要明白两个感念,那就是 module 和 Module。 module :在 Node 中每一个 js 文件都是一个 module ,module 上保存了 exports 等信息之外,还有一个 loaded 表示该模块是否被加载。

为 false 表示还没有加载; 为 true 表示已经加载

Module :以 nodejs 为例,整个系统运行之后,会用 Module 缓存每一个模块加载的信息。**

require 的源码大致长如下的样子:

// id 为路径标识符
function require(id) {
/* 查找 Module 上有没有已经加载的 js 对象*/
const cachedModule = Module._cache[id]
/* 如果已经加载了那么直接取走缓存的 exports 对象 */
if(cachedModule){
return cachedModule.exports
}
/* 创建当前模块的 module */
const module = { exports: {} ,loaded: false , ...}
/* 将 module 缓存到 Module 的缓存属性中,路径标识符作为 id */
Module._cache[id] = module
/* 加载文件 */
runInThisContext(wrapper('module.exports = "123"'))(module.exports, require, module, __filename, __dirname)
/* 加载完成 *//
module.loaded = true
/* 返回值 */
return module.exports
}

从上面我们总结出一次 require 大致流程是这样的;

require 会接收一个参数——文件标识符,然后分析定位文件,分析过程我们上述已经讲到了,加下来会从 Module 上查找有没有缓存,如果有缓存,那么直接返回缓存的内容。

如果没有缓存,会创建一个 module 对象,缓存到 Module 上,然后执行文件,加载完文件,将 loaded 属性设置为 true ,然后返回 module.exports 对象。借此完成模块加载流程。

模块导出就是 return 这个变量的其实跟 a = b 赋值一样, 基本类型导出的是值, 引用类型导出的是引用地址。

exports 和 module.exports 持有相同引用,因为最后导出的是 module.exports, 所以对 exports 进行赋值会导致 exports 操作的不再是 module.exports 的引用。

require 避免重复加载

从上面我们可以直接得出,require 如何避免重复加载的,首先加载之后的文件的 module 会被缓存到 Module 上,比如一个模块已经 require 引入了 a 模块,如果另外一个模块再次引用 a ,那么会直接读取缓存值 module ,所以无需再次执行模块。 对应 demo 片段中,首先 main.js 引用了 a.js ,a.js 中 require 了 b.js 此时 b.js 的 module 放入缓存 Module 中,接下来 main.js 再次引用 b.js ,那么直接走的缓存逻辑。所以 b.js 只会执行一次,也就是在 a.js 引入的时候。