5

Module classification

Node.js has two kinds of modules

  1. Core module
    Part of the core modules have been directly loaded into the memory, the steps of path analysis and compilation can be omitted and they are judged first in the path analysis, so the loading speed is the fastest
  2. File module
    Runtime dynamically loaded , so it is necessary the full file path analysis to locate and compile and run process, so speed is slower than the Core Module

The secret of implementing the "module" function is that JavaScript is a functional programming language that supports closures. If we wrap a piece of JavaScript code with a function, all "global" variables of this code become local variables inside the function.

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

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

In this way, the original global variable s is now a local variable inside the anonymous function. If Node.js continues to load other modules, the "global" variables defined in these modules will not interfere with each other.

Therefore, Node uses JavaScript's functional programming feature to easily realize module isolation.

Module cache mechanism

Nodejs will cache the loaded modules to reduce the overhead during the second introduction. When the module is introduced, it will look up from the cache first. Node caches the objects after compilation and execution

Cache form: In the form of key-value, the real path is used as the key, and the result of the compilation and execution is used as the value and placed in the cache (in the Module._cache object) (the second load is faster)
Print rquire.cache to see the cached objects

Circular references of modules

Let me talk about the conclusion first, because Node.js caches loaded modules, the circular dependencies of all modules will not cause infinite circular references. for example:

a.js file

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 file

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 file

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);

The entire detailed process analysis is as follows:

  1. node main.js
  2. require a.js, load a.js, output "a starting"
  3. a: exports.done = false,require b.js,load b.js
  4. Output "b starting", b: exports.done = false
  5. require a.js, because a.js has not been executed, the unfinished copy to , so a = {done: false}
  6. Output in b, a.done = false
  7. b: exports.done = true, output b done, b.js is executed, return to a.js to continue execution
  8. b = {done: true }, output in a, b.done = true, output a done
  9. After a.js is executed, a = {done: true}, return to main.js to continue execution, require b.js
  10. Since b.js has been executed and the value is in the cache, now a = {done: true }, b = {done: true}
  11. Output in main, a.done = true, b.done = true

It can be seen that Node.js caches the loaded modules, which solves the problem of circular references, and directly fetches them from the cache during the second load, which improves the loading speed.

Path analysis and file location

We need to understand that custom modules are dynamically loaded (loaded at runtime). When they are loaded for the first time, they have to go through the process of path analysis, file positioning, and compilation and execution.

When analyzing the path module, the require() method will find the real path

  1. If there is no extension, it will be matched in sequence: .js> .node> .json
  2. If the corresponding file is not found, but a directory is obtained, it will be treated as a package
    First search package.json in main attribute specifies the file name to locate> index.js> index.node> index.json order matching

Compilation of modules

Compilation and execution is the last stage of introducing file modules. Here I only talk about the compilation .js fs module, the compilation is performed. Each successfully compiled module will be cached on the Module._cache object with its real path as an index to improve the performance of the second introduction.

During the compilation process, Node will wrap the obtained files head and tail

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

In this way, the scope is isolated also explains why we can use these variables module、exports、__filename、 __dirname

The difference between module.exports and exports

When Node.js executes a javascript file, it will generate a module and exports object, module also has a exports attribute, module.exports and exports point to the same reference
The fundamental difference between the two is:
exports returns the module function, module.exports returns the module object itself
for example:
a.js file

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

b.js file

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

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

Create a new file c.js

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

Introduced in b.js

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

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

It can be seen that the export of method 1 is the same as the export of exports in the same way at the time of introduction.

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

Another point to note is that when the require() method is executed, the content exported by module.exports

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

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

It can be seen from the above that, in fact, the content exported by require is the content of the memory block pointed to by module.exports, not exports.

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

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

You can see the printed value {} it is because exports original point with module.exports a reference to the same, now exports = {a: 200} Exports points to another memory address, to break off relations with module.exports, default module.eports={}

Summarize

  • exports is a reference to module.exports
  • The initialization of module.exports is {}, and exports are also {}
  • The require reference returns module.exports, not exports
  • exports.xxx = xxxx is equivalent to directly adding attributes or modifying attribute values on the exported object, which is directly visible in the calling module
  • exports = xxx Reallocate memory for exports, which will be separated from module.exports. The two are unrelated. The calling module will not be accessible.

refer to:
Node module mechanism does not fully refer to the north


高压郭
961 声望494 粉丝

从简单到难 一步一步