content
- Overview
- module object
- module.exports property
- exports variable
- Compatibility of AMD specs with CommonJS specs
- require command
- Basic usage
- load rules
- directory loading rules
- module cache
- Environment variable NODE_PATH
- Cyclic loading of modules
- require.main
- Module loading mechanism
- The internal processing flow of require
- Reference link
Overview
Node applications are composed of modules, using the CommonJS module specification.
Each file is a module and has its own scope. Variables, functions, and classes defined in a file are private and invisible to other files.
// example.js
var x = 5;
var addX = function (value) {
return value + x;
};
In the above code, variables x
and functions addX
are private to the current file example.js
and are not visible to other files.
If you want to share a variable in multiple files, it must be defined as global
a property of the object.
global.warning = true;
The variables of the above code warning
can be read by all files. Of course, this way of writing is not recommended.
The CommonJS specification stipulates that within each module, module
variables represent the current module. This variable is an object whose exports
properties (ie module.exports
) are the external interface. Loading a module is actually loading the module.exports
properties of the module.
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
The above code passes module.exports
output variables x
and functions addX
.
require
method is used to load modules.
var example = require('./example.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
require
For a detailed explanation of the method, see the "Require Command" section.
The features of CommonJS modules are as follows.
- All code runs in the module scope and does not pollute the global scope.
- The module can be loaded multiple times, but it will only be run once when it is loaded for the first time, and then the running result will be cached, and when it is loaded later, the cached result will be read directly. For the module to work again, the cache must be cleared.
- The order in which modules are loaded, in the order they appear in the code.
module object
Node provides a Module
constructor function internally. All modules are Module
instances of .
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
// ...
Inside each module, there is an module
object representing the current module. It has the following properties.
module.id
An identifier for the module, usually the module filename with an absolute path.module.filename
The filename of the module, with an absolute path.module.loaded
Returns a boolean indicating whether the module has finished loading.module.parent
Returns an object representing the module that called this module.module.children
Returns an array of other modules used by this module.module.exports
Indicates the value output by the module to the outside.
Below is an example file, the last line outputs the module variable.
// example.js
var jquery = require('jquery');
exports.$ = jquery;
console.log(module);
Executing this file, the command line will output the following information.
{ id: '.',
exports: { '$': [Function] },
parent: null,
filename: '/path/to/example.js',
loaded: false,
children:
[ { id: '/path/to/node_modules/jquery/dist/jquery.js',
exports: [Function],
parent: [Circular],
filename: '/path/to/node_modules/jquery/dist/jquery.js',
loaded: true,
children: [],
paths: [Object] } ],
paths:
[ '/home/user/deleted/node_modules',
'/home/user/node_modules',
'/home/node_modules',
'/node_modules' ]
}
If a module is invoked on the command line, say node something.js
, then module.parent
yes null
. If it's called from within a script, for example require('./something.js')
, then module.parent
it's the module that called it. Using this, you can determine whether the current module is an entry script.
if (!module.parent) {
// ran with `node something.js`
app.listen(8088, function() {
console.log('app listening on port 8088');
})
} else {
// used with `require('/.something.js')`
module.exports = app;
}
module.exports property
module.exports
Attributes represent the external output interface of the current module. When other files load the module, they actually read module.exports
variables.
var EventEmitter = require('events').EventEmitter;
module.exports = new EventEmitter();
setTimeout(function() {
module.exports.emit('ready');
}, 1000);
The above module will emit a ready event 1 second after loading. Other files listen to this event, which can be written as follows.
var a = require('./a');
a.on('ready', function() {
console.log('module a is ready');
});
exports variable
For convenience, Node provides an exports variable for each module, pointing to module.exports. This is equivalent to having a line like this in the header of each module.
var exports = module.exports;
As a result, when exporting the module interface externally, you can add methods to the exports object.
exports.area = function (r) {
return Math.PI * r * r;
};
exports.circumference = function (r) {
return 2 * Math.PI * r;
};
Note that you can't point the exports variable directly to a value, because that would cut off the exports
connection module.exports
with.
exports = function(x) {console.log(x)};
The above way of writing is invalid, because it exports
no longer points module.exports
.
The following spelling is also invalid.
exports.hello = function() {
return 'hello';
};
module.exports = 'Hello world';
In the above code, the hello
function cannot be output externally because it module.exports
has been reassigned.
This means that if the external interface of a module is a single value, the output cannot be used exports
, only the module.exports
output can be used.
module.exports = function (x){ console.log(x);};
If you find it hard to tell the exports
difference module.exports
between and, an easy way to deal with it is to give up exports
and just use module.exports
.
Compatibility of AMD specs with CommonJS specs
The CommonJS specification loads modules synchronously, that is, only after the loading is complete, the subsequent operations can be performed. The AMD specification is to load modules asynchronously, allowing callback functions to be specified. Since Node.js is mainly used for server programming, the module files generally already exist on the local hard disk, so the loading is faster, and the asynchronous loading method does not need to be considered, so the CommonJS specification is more applicable. However, if it is a browser environment, to load modules from the server side, the asynchronous mode must be used at this time, so the browser side generally adopts the AMD specification.
The AMD specification uses the define method to define modules, here is an example:
define(['package/lib'], function(lib){
function foo(){
lib.log('hello world!');
}
return {
foo: foo
};
});
The AMD specification allows the output module to be compatible with the CommonJS specification. In this case, the define
method needs to be written as follows:
define(function (require, exports, module){
var someModule = require("someModule");
var anotherModule = require("anotherModule");
someModule.doTehAwesome();
anotherModule.doMoarAwesome();
exports.asplode = function (){
someModule.doTehAwesome();
anotherModule.doMoarAwesome();
};
});
require command
Basic usage
Node uses the CommonJS module specification, a built-in require
command for loading module files.
require
The basic function of the command is to read and execute a JavaScript file, and then return the exports object of the module. If the specified module is not found, an error will be reported.
// example.js
var invisible = function () {
console.log("invisible");
}
exports.message = "hi";
exports.say = function () {
console.log(message);
}
Run the following command to output the exports object.
var example = require('./example.js');
example
// {
// message: "hi",
// say: [Function]
// }
If the module outputs a function, it cannot be defined on the exports object, but on a module.exports
variable.
module.exports = function () {
console.log("hello world")
}
require('./example2.js')()
In the above code, the require command calls itself, which is equivalent to execution module.exports
, so it will output hello world.
load rules
require
The command is used to load the file, the default suffix is .js
.
var foo = require('foo');
// 等同于
var foo = require('foo.js');
According to the different formats of the parameters, the require
command goes to different paths to find the module file.
(1) If the parameter string starts with "/", it means that a module file located in an absolute path is loaded. For example, require('/home/marco/foo.js')
will load /home/marco/foo.js
.
(2) If the parameter string starts with "./", it means that a module file located in a relative path (compared to the location of the currently executed script) is loaded. For example, require('./circle')
the same directory as the current script will be loaded circle.js
.
(3) If the parameter string does not start with "./" or "/", it means that a core module provided by default (located in Node's system installation directory), or an installed core module located in the node_modules directory at all levels is loaded. Modules (globally or locally).
For example, the script /home/user/projects/foo.js
executes the require('bar.js')
command and Node searches for the following files in turn.
- /usr/local/lib/node/bar.js
- /home/user/projects/node_modules/bar.js
- /home/user/node_modules/bar.js
- /home/node_modules/bar.js
- /node_modules/bar.js
The purpose of this design is to enable different modules to localize the modules they depend on.
(4) If the parameter string does not start with "./" or "/", and is a path, for example require('example-module/path/to/file')
, the location will be found first example-module
, and then use it as a parameter to find the subsequent path.
(5) If the specified module file is not found, Node will try to add .js
, .json
, and to the file name, .node
and then search. .js
The file is parsed as a JavaScript script file in text format, the file .json
is parsed as a text file in JSON format, and the file .node
is parsed as a compiled binary file.
(6) If you want to get require
the exact file name loaded by the command, use the require.resolve()
method.
directory loading rules
Usually, we will put related files in a directory for easy organization. At this time, it is best to set an entry file for the directory, so that the require
method can load the entire directory through this entry file.
Place a package.json
file in the directory and write the entry file to the main
field. Below is an example.
// package.json
{ "name" : "some-library",
"main" : "./lib/some-library.js" }
require
After finding that the parameter string points to a directory, it will automatically view the package.json
files in the directory, and then load main
the entry file specified by the field. If the package.json
file has no main
fields, or if there is no file at all, the file or files package.json
under that directory are loaded .index.js
index.node
module cache
The first time a module is loaded, Node caches the module. module.exports
When the module is loaded later, the properties of the module are directly retrieved from the cache .
require('./example.js');
require('./example.js').message = "hello";
require('./example.js').message
// "hello"
In the above code, the require
command is used three times in a row to load the same module. On the second load, a message
property is added to the output object. But when it is loaded for the third time, the message attribute still exists, which proves that require
the command does not reload the module file, but outputs the cache.
If you want to execute a module multiple times, you can let the module output a function, and then require
re-execute the output function every time the module is executed.
All cached modules are stored in require.cache
it. If you want to delete the module's cache, you can write as follows.
// 删除指定模块的缓存
delete require.cache[moduleName];
// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})
Note that the cache identifies modules based on absolute paths. If the same module name is stored in a different path, the require
command will still reload the module.
Environment variable NODE_PATH
When Node executes a script, it first looks at environment variables NODE_PATH
. It is a set of absolute paths separated by colons. When the specified module cannot be found elsewhere, Node will go to these paths to find it.
NODE_PATH can be added to .bashrc
.
export NODE_PATH="/usr/local/lib/node"
So, if you encounter a complex relative path, such as the following.
var myModule = require('../../../../lib/myModule');
There are two solutions, one is to add the file to the node_modules
directory, the other is to modify the NODE_PATH
environment variable, the package.json
file can be written in the following way.
{
"name": "node_path",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "NODE_PATH=lib node index.js"
},
"author": "",
"license": "ISC"
}
NODE_PATH
is a path solution left over from history and should generally not be used, the node_modules
directory mechanism should be used instead.
Cyclic loading of modules
If a circular loading of modules occurs, i.e. A loads B which in turn loads A, then B will load an incomplete version of A.
// a.js
exports.x = 'a1';
console.log('a.js ', require('./b.js').x);
exports.x = 'a2';
// b.js
exports.x = 'b1';
console.log('b.js ', require('./a.js').x);
exports.x = 'b2';
// main.js
console.log('main.js ', require('./a.js').x);
console.log('main.js ', require('./b.js').x);
The above code is three JavaScript files. Among them, a.js loads b.js, which in turn loads a.js. At this time, Node returns an incomplete version of a.js, so the execution result is as follows.
$ node main.js
b.js a1
a.js b2
main.js a2
main.js b2
Modify main.js and load a.js and b.js again.
// main.js
console.log('main.js ', require('./a.js').x);
console.log('main.js ', require('./b.js').x);
console.log('main.js ', require('./a.js').x);
console.log('main.js ', require('./b.js').x);
Execute the above code, the result is as follows.
$ node main.js
b.js a1
a.js b2
main.js a2
main.js b2
main.js a2
main.js b2
In the above code, when a.js and b.js are loaded for the second time, the exports property will be directly read from the cache, so the console.log statements inside a.js and b.js will not be executed.
require.main
require
The method has an main
attribute that can be used to determine whether the module executes directly or is called to execute.
When executed directly ( node module.js
), the require.main
attribute points to the module itself.
require.main === module
// true
When the call is executed (by require
loading the script), the above expression returns false.
Module loading mechanism
The loading mechanism of CommonJS modules is that the input is a copy of the value that is output. That is, once a value is output, changes within the module cannot affect this value. See the example below.
Below is a module file lib.js
.
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
The above code outputs the internal variable counter
and the internal method that overwrites this variable incCounter
.
Then, load the above module.
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter); // 3
incCounter();
console.log(counter); // 3
The above code shows that counter
after output, lib.js
changes inside the module will not be affected counter
.
The internal processing flow of require
require
Commands are the commands in the CommonJS specification to load other modules. It's not actually a global command, but a command that points to the current module module.require
, which in turn calls Node's internal commands Module._load
.
Module._load = function(request, parent, isMain) {
// 1. 检查 Module._cache,是否缓存之中有指定模块
// 2. 如果缓存之中没有,就创建一个新的Module实例
// 3. 将它保存到缓存
// 4. 使用 module.load() 加载指定的模块文件,
// 读取文件内容之后,使用 module.compile() 执行文件代码
// 5. 如果加载/解析过程报错,就从缓存删除该模块
// 6. 返回该模块的 module.exports
};
In step 4 above, module.compile()
the script that executes the specified module is used, and the logic is as follows.
Module.prototype._compile = function(content, filename) {
// 1. 生成一个require函数,指向module.require
// 2. 加载其他辅助方法到require
// 3. 将文件内容放到一个函数之中,该函数可调用 require
// 4. 执行该函数
};
The above steps 1 and 2, require
functions and their auxiliary methods are mainly as follows.
require()
: load external modulesrequire.resolve()
: resolve the module name to an absolute pathrequire.main
: points to the main modulerequire.cache
: points to all cached modulesrequire.extensions
: Call different execution functions according to the suffix name of the file
Once the require
function is ready, the entire script content to be loaded is put into a new function, which can avoid polluting the global environment. The parameters of this function include require
, module
, exports
, and a few others.
(function (exports, require, module, __filename, __dirname) {
// YOUR CODE INJECTED HERE!
});
Module._compile
The method is executed synchronously, so Module._load
it will not return module.exports
the value to the user until it finishes executing.
Reference link
- Addy Osmani, Writing Modular JavaScript With AMD, CommonJS & ES Harmony
- Pony Foo, A Gentle Browserify Walkthrough
- Nico Reed, What is require?
- Fred K. Schott, The Node.js Way - How require() Actually Works