CommonJS specification

content

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 xand functions addXare private to the current file example.jsand are not visible to other files.

If you want to share a variable in multiple files, it must be defined as globala property of the object.

global.warning = true;

The variables of the above code warningcan be read by all files. Of course, this way of writing is not recommended.

The CommonJS specification stipulates that within each module, modulevariables represent the current module. This variable is an object whose exportsproperties (ie module.exports) are the external interface. Loading a module is actually loading the module.exportsproperties 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.exportsoutput variables xand functions addX.

requiremethod is used to load modules.

var example = require('./example.js');

console.log(example.x); // 5
console.log(example.addX(1)); // 6

requireFor 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 Moduleconstructor function internally. All modules are Moduleinstances of .

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  // ...

Inside each module, there is an moduleobject 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.parentyes null. If it's called from within a script, for example require('./something.js'), then module.parentit'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.exportsAttributes represent the external output interface of the current module. When other files load the module, they actually read module.exportsvariables.

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 exportsconnection module.exportswith.

exports = function(x) {console.log(x)};

The above way of writing is invalid, because it exportsno longer points module.exports.

The following spelling is also invalid.

exports.hello = function() {
  return 'hello';
};

module.exports = 'Hello world';

In the above code, the hellofunction cannot be output externally because it module.exportshas 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.exportsoutput can be used.

module.exports = function (x){ console.log(x);};

If you find it hard to tell the exportsdifference module.exportsbetween and, an easy way to deal with it is to give up exportsand 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 definemethod 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 requirecommand for loading module files.

requireThe 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.exportsvariable.

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

requireThe 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 requirecommand 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.jsexecutes 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, .nodeand then search. .jsThe file is parsed as a JavaScript script file in text format, the file .jsonis parsed as a text file in JSON format, and the file .nodeis parsed as a compiled binary file.

(6) If you want to get requirethe 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 requiremethod can load the entire directory through this entry file.

Place a package.jsonfile in the directory and write the entry file to the mainfield. Below is an example.

// package.json
{ "name" : "some-library",
  "main" : "./lib/some-library.js" }

requireAfter finding that the parameter string points to a directory, it will automatically view the package.jsonfiles in the directory, and then load mainthe entry file specified by the field. If the package.jsonfile has no mainfields, or if there is no file at all, the file or files package.jsonunder that directory are loaded .index.jsindex.node

module cache

The first time a module is loaded, Node caches the module. module.exportsWhen 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 requirecommand is used three times in a row to load the same module. On the second load, a messageproperty is added to the output object. But when it is loaded for the third time, the message attribute still exists, which proves that requirethe 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 requirere-execute the output function every time the module is executed.

All cached modules are stored in require.cacheit. 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 requirecommand 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_modulesdirectory, the other is to modify the NODE_PATHenvironment variable, the package.jsonfile 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_PATHis a path solution left over from history and should generally not be used, the node_modulesdirectory 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

requireThe method has an mainattribute that can be used to determine whether the module executes directly or is called to execute.

When executed directly ( node module.js), the require.mainattribute points to the module itself.

require.main === module
// true

When the call is executed (by requireloading 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 counterand 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 counterafter output, lib.jschanges inside the module will not be affected counter.

The internal processing flow of require

requireCommands 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, requirefunctions and their auxiliary methods are mainly as follows.

  • require(): load external modules
  • require.resolve(): resolve the module name to an absolute path
  • require.main: points to the main module
  • require.cache: points to all cached modules
  • require.extensions: Call different execution functions according to the suffix name of the file

Once the requirefunction 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._compileThe method is executed synchronously, so Module._loadit will not return module.exportsthe value to the user until it finishes executing.

Reference link

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325260399&siteId=291194637