乐趣区

关于node.js:深入浅出Nodejs一-模块机制

模块分类

Node.js 有两种模块

  1. 外围模块
    局部外围模块曾经被间接加载进内存中,路径分析 编译执行的步骤能够省略 并且在路径分析中优先被判断,所以加载速度最快
  2. 文件模块
    运行时 动静加载 ,所以须要 残缺的路径分析 文件定位和 编译执行 过程,所以速度比外围模块慢

实现“模块”性能的奥秘就在于 JavaScript 是一种函数式编程语言,它反对闭包。如果咱们把一段 JavaScript 代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数外部的局部变量。

var s = 'Hello';
var name = 'world';

console.log(s + '' + name +'!');
(function() {
    var s = 'Hello';
    var name = 'world';
    
    console.log(s + '' + name +'!');
})()

这样一来,原来的全局变量 s 当初变成了匿名函数外部的局部变量。如果 Node.js 持续加载其余模块,这些模块中定义的“全局”变量 s 也互不烦扰。

所以,Node 利用 JavaScript 的 函数式编程的个性,轻而易举地实现了模块的隔离。

模块缓存机制

Nodejs 对加载过的模块 会进行缓存,以缩小二次引入时的开销,引入模块时会优先从缓存中查找,Node 缓存的是编译和执行之后的对象

缓存模式:key-value 的模式,以实在门路作为 key,以编译执行后的后果作为 value 放在缓存中(Module._cache 对象中)(二次加载速度更快)
打印rquire.cache 能够看到缓存的对象

模块的循环援用

先说论断,因为 Node.js 会缓存加载过的模块,所有模块的循环依赖并不会引起有限循环援用。举个例子:

a.js文件下

console.log('a starting');
exports.done = false

const b = require('./b.js')
console.log('in a, b done = %j', b.done);

exports.done = true
console.log('a done');

b.js文件下

console.log('b starting');
exports.done = false

// 这里导入的是 a 未执行完的正本
const a = require('./a.js')
console.log('in b, a done = %j', a.done);

exports.done = true
console.log('b done');

main.js文件下

console.log('main starting');
const a = require('./a')
const b = require('./b')

console.log('in main.js, a done = %j, b done = %j', a.done, b.done);

整个具体的过程剖析如下:

  1. node main.js
  2. require a.js,load a.js,输入“a starting“
  3. a: exports.done = false,require b.js,load b.js
  4. 输入”b starting“,b: exports.done = false
  5. require a.js,因为 a.js 没有执行完,将未实现的正本导出,所以 a = {done: false}
  6. 输入 in b, a.done = false
  7. b: exports.done = true,输入 b done,b.js 执行结束,返回 a.js 继续执行
  8. b = {done: true},输入 in a, b.done = true,输入 a done
  9. a.js 执行结束,a = {done: true},返回 main.js 继续执行,require b.js
  10. 因为 b.js 曾经被执行结束,缓存中拿值,当初 a = {done: true},b = {done: true}
  11. 输入 in main, a.done = true, b.done = true

由此可见,Node.js 对已加载过的模块进行缓存,解决了循环援用的问题,在二次加载时间接从缓存中取,进步了加载速度。

路径分析和文件定位

咱们须要理解,自定义模块是动静加载的(运行时加载),在首次加载时,要通过路径分析、文件定位、编译执行的过程。

在剖析门路模块时,require()办法会去查找实在的门路

  1. 如果没有扩展名,会依照剖析程序:.js > .node > .json 顺次进行匹配
  2. 如果没有查找到对应的文件,然而失去的是一个目录,那么会被当成一个包来解决
    优先查找 package.jsonmain属性指定的文件名进行定位 > index.js > index.node > index.json 顺次匹配

模块的编译

编译和执行是引入文件模块的最初一个阶段。这里只讲对 .js 文件的编译,通过 fs 模块同步读取文件后进行编译,每个编译胜利的模块都会以它的实在门路作为索引缓存在 Module._cache 对象上,以进步二次引入的性能。

编译过程中,Node 会对获取到的文件进行头尾包装

(function(module, exports, require, __filename, __dirname) {})

这样每个模块之间都进行了 作用域隔离 也解释了咱们没有在模块文件中定义module、exports、__filename、__dirname 这些变量却能够应用它们的起因。

module.exports 和 exports 的区别

Node.js 在执行一个 javascript 文件时,会生成一个 moduleexports对象, module还有一个 exports 属性,module.exportsexports 指向同一个援用
两者的基本区别是:
exports 返回的是模块函数,module.exports 返回的是模块对象自身
举个例子:
a.js文件下

let sayHello = function() {console.log('hello');
}
exports.sayHello = sayHello

b.js文件下

// 这样应用会报错
const sayHello = require('./a')
sayHi()

// 正确的形式
const func = require('./a')
func.sayHello() // hello

新建 c.js 文件

let sayHello = function() {console.log('hello');
}
// 1 形式导出
module.exports.sayHello = sayHello
// 2 形式导出
module.exports = sayHello

b.js 中引入

// 1 形式的
const func = require('./a')
func.sayHello() // hello

// 2 形式的
const sayHello = require('./a')
sayHello() // hello

能够看出,1 形式的导出跟 exports 的导出,在引入时的形式是统一的

module.exports.sayHello = sayHello
等同于
module.exports = {sayHello: sayHello}
也等同于
exports.sayHello = sayHello

还有一个留神的点是:执行 require() 办法时,引入的是 module.exports 导出的内容。

// d.js 文件下:exports = {a: 200}
module.exports = {a: 100}

// b.js 引入
const value = require('./d')
console.log('value', value); // {a: 100}

从下面能够看出,其实 require 导出的内容是 module.exports 的指向的内存块内容,并不是 exports 的。

// 如果 d.js 文件变成
exports = {a: 200}

// b.js 引入
const value = require('./d')
console.log('value', value); // {}

能够看到打印进去的值为 {} 那是因为exports 原本指向跟 module.exports 同一个援用,当初exports = {a: 200} exports 指向了另一个内存地址,将与 module.exports 脱离关系,默认module.eports={}

总结

  • exports 是 module.exports 的一个援用
  • module.exports 初始化是一个{},exports 也是这个{}
  • require 援用返回的是 module.exports,而不是 exports
  • exports.xxx = xxxx 相当于在导出对象上间接增加属性或者批改属性值,在调用模块间接可见
  • exports = xxx 为 exports 从新分配内存,将脱离 module.exports,两者无关联。调用模块将不能拜访。

参考:
Node 模块机制不齐全指北

退出移动版