js实例教程-Node模块,CommonJS规范详解

发布时间:2018-12-02 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了js实例教程-Node模块,CommonJS规范详解脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
小宝典致力于为广大程序猿(媛)提供高品质的代码服务,请大家多多光顾小站,小宝典在此谢过。

在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Node环境中,一个.js文件就称之为一个模块(module),有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。Node 应用由模块组成,采用 CommonJS 模块规范。1、使用模块有什么好处?最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Node内置的模块和来自第三方的模块。使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。

 // example.js VAR x = 5; var addX = function (value) {   return value + x; };

上面代码中,变量x和函数addX,是当前文件example.js私有的,其他文件不可见。如果想在多个文件分享变量,必须定义为global对象的属性。

 global.warning = true;

2、模块的基本使用CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。'use strict';var s = 'Hello';function greet(name) { console.LOG(s + ', ' + name + '!');}module.exports = greet;函数greet()是我们在hello模块中定义的,然后通过module.exports接口把函数greet作为模块的输出暴露出去,这样其他模块就可以使用greet函数了。require方法用于加载模块。我们再编写一个main.js文件,调用hello模块的greet函数:'use strict';// 引入hello模块:var greet = require('./hello');var s = ';michael';greet(s); // Hello, Michael!注意到引入hello模块用Node提供的require函数:var greet = require('./hello');引入的模块作为变量保存在greet变量中,那greet变量到底是什么东西?其实变量greet就是在hello.js中我们用Module.exports = greet;输出的greet函数。所以,main.js就成功地引用了hello.js模块中定义的greet()函数,接下来就可以直接使用它了。3、module对象Node内部提供一个Module构建函数。所有模块都是Module的实例。

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

每个模块内部,都有一个module对象,代表当前模块。它有以下属性。

module.id模块的识别符,通常是带有绝对路径的模块文件名。

module.filename模块的文件名,带有绝对路径。

module.loaded返回一个布尔值,表示模块是否已经完成加载。

module.parent返回一个对象,表示调用该模块的模块。

module.children返回一个数组,表示该模块要用到的其他模块。

module.exports表示模块对外输出的值。例如:

 // example.js var jquery = require('jquery'); exports.$ = jquery; console.log(module);

输出的module:

 { 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' ] }

如果在命令行下调用某个模块,比如node something.js,那么module.parent就是null。如果是在脚本之中调用,比如require('./something.js'),那么module.parent就是调用它的模块。利用一点,可以判断当前模块是否为入口脚本。

 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属性module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。exports变量为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。

 var exports = module.exports;

所以,在对外输出模块接口时,也可以向exports对象添加方法。

 exports.area = function (r) {   return Math.PI * r * r; };  exports.circumference = function (r) {   return 2 * Math.PI * r; };

注意,不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。

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

上面这样的写法是无效的,因为exports不再指向module.exports了。下面的写法也是无效的。

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

上面代码中,hello函数是无法对外输出的,因为module.exports被重新赋值了。这意味着,如果一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports输出。如果你觉得,exports与module.exports之间的区别很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports。4、require命令4.1 加载规则(详见node加载模块顺序)4.2 模块的缓存第一次加载某个模块时,Node会缓存该模块。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个module对象,以后再加载该模块,就直接从缓存取出对应的module对象,然后获取module.exports属性。

 {  //module对象    id: '...',      exports: { ... },     loaded: true,  ... }

即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写。

 // 删除指定模块的缓存 delete require.cache[moduleName];  // 删除所有模块的缓存 Object.keys(require.cache).foreach(function(key) {   delete require.cache[key]; })

注意,缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。4.3 require.mainrequire方法有一个main属性,可以用来判断模块是直接执行,还是被调用执行。直接执行的时候(node module.js),require.main属性指向模块本身。

 require.main === module  // true

调用执行的时候(通过require加载该脚本执行),上面的表达式返回false。5、模块的加载机制CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

 // lib.js var counter = 3; function incCounter() {   counter++; } module.exports = {   counter: counter,   incCounter: incCounter, };
 // main.js var mod = require('./lib');  console.log(mod.counter);  // 3 mod.incCounter(); console.log(mod.counter); // 3

上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了(调用mod.incCounter()方法时,操作的是lib.js模块中的变量counter,而不是导出的变量counter)。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

 // lib.js var counter = 3; function incCounter() {   counter++; } module.exports = {   get counter() {     return counter   },   incCounter: incCounter, };

上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。

上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。

5.1 require的内部处理流程require命令是CommonJS规范之中,用来加载其他模块的命令。它其实不是一个全局命令,而是指向当前模块的module.require命令,而后者又调用Node的内部命令Module._load。

 Module._load = function(request, parent, isMain) {};

在这个函数中需要执行以下几步:1. 检查 Module._cache,是否缓存之中有指定模块2. 如果缓存之中没有,就创建一个新的Module实例3. 将它保存到缓存4. 使用 module.load() 加载指定的模块文件,读取文件内容之后,使用 module.COMpile() 执行文件代码5. 如果加载/解析过程报错,就从缓存删除该模块6. 返回该模块的 module.exports

采用module.compile()执行指定模块的脚本,逻辑如下。

 Module.PRototyPE._compile = function(content, filename) {};

在该函数中需要执行以下几步:1. 生成一个require函数,指向module.require;2. 加载其他辅助方法到require;3. 一旦require函数准备完毕,整个所要加载的脚本内容,就被放到一个新的函数之中,这样可以避免污染全局环境。该函数的参数包括require、module、exports,以及其他一些参数。

 (function (exports, require, module, __filename, __dirname) {   // YOUR CODE INJECTED HERE!  // 执行读取的hello.js代码:     function greet(name) {         console.log('Hello, ' + name + '!');     }     module.exports = greet;     // hello.js代码执行结束  });

4. 执行该函数上面的第1步和第2步,require函数及其辅助方法主要如下。

require(): 加载外部模块

require.resolve():将模块名解析到一个绝对路径

require.main:指向主模块

require.cache:指向所有缓存的模块

require.extensions:根据文件的后缀名,调用不同的执行函数Module._compile方法是同步执行的,所以Module._load要等它执行完成,才会向用户返回module.exports的值。5.2 模块的循环加载CommonJS 模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。我们来看一段代码:

 // main.js var a = require('./a.js'); var b = require('./b.js'); console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
 // a.js exports.done = false; var b = require('./b.js'); console.log('在 a.js 之中,b.done = %j', b.done); exports.done = true; console.log('a.js 执行完毕');
 //b.js exports.done = false; var a = require('./a.js'); console.log('在 b.js 之中,a.done = %j', a.done); exports.done = true; console.log('b.js 执行完毕');

当第一次require('a.js')时,执行a.js脚本。先输出一个done变量,然后加载另一个脚本文件b.js。注意,此时a.js代码就停在这里,等待b.js执行完毕,再往下执行。b.js执行到第二行,就会去加载a.js,这时,就发生了“循环加载”。系统会去a.js模块对应对象的exports属性取值,可是因为a.js还没有执行完,从exports属性只能取回已经执行的部分,而不是最后的值。a.js已经执行的部分,只有一行。exports.done = false;因此,对于b.js来说,它从a.js只输入一个变量done,值为false。然后,b.js接着往下执行,等到全部执行完毕,再把执行权交还给a.js。于是,a.js接着往下执行,直到执行完毕。

最后输出的结果:

 $ node main.js  在 b.js 之中,a.done = false b.js 执行完毕 在 a.js 之中,b.done = true a.js 执行完毕 在 main.js 之中, a.done=true, b.done=true

上面的代码证明了两件事。一是,在b.js之中,a.js没有执行完毕,只执行了第一行。二是,main.js执行到第二行时,不会再次执行b.js,而是输出缓存的b.js的执行结果,即它的第四行

 exports.done = true;

5.3 Node.js是如何实现模块Node实现“模块”功能的奥妙在于JavaScript是一种函数式编程语言,它支持闭包。如果我们把一段JavaScript代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。请注意我们编写的hello.js代码是这样的:

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

Node.js加载了hello.js后,它可以把代码包装一下,变成这样执行:

 (function () {        // 读取的hello.js代码:        var s = 'Hello';        var name = 'world';        console.log(s + ' ' + name + '!');        // hello.js代码结束 })();

这样一来,原来的全局变量s现在变成了匿名函数内部的局部变量。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量s也互不干扰。所以,Node利用JavaScript的函数式编程的特性,轻而易举地实现了模块的隔离。

在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Node环境中,一个.js文件就称之为一个模块(module),有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。Node 应用由模块组成,采用 CommonJS 模块规范。1、使用模块有什么好处?最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Node内置的模块和来自第三方的模块。使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。

 // example.js var x = 5; var addX = function (value) {   return value + x; };

上面代码中,变量x和函数addX,是当前文件example.js私有的,其他文件不可见。如果想在多个文件分享变量,必须定义为global对象的属性。

 global.warning = true;

2、模块的基本使用CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。'use strict';var s = 'Hello';function greet(name) { console.log(s + ', ' + name + '!');}module.exports = greet;函数greet()是我们在hello模块中定义的,然后通过module.exports接口把函数greet作为模块的输出暴露出去,这样其他模块就可以使用greet函数了。require方法用于加载模块。我们再编写一个main.js文件,调用hello模块的greet函数:'use strict';// 引入hello模块:var greet = require('./hello');var s = 'Michael';greet(s); // Hello, Michael!注意到引入hello模块用Node提供的require函数:var greet = require('./hello');引入的模块作为变量保存在greet变量中,那greet变量到底是什么东西?其实变量greet就是在hello.js中我们用module.exports = greet;输出的greet函数。所以,main.js就成功地引用了hello.js模块中定义的greet()函数,接下来就可以直接使用它了。3、module对象Node内部提供一个Module构建函数。所有模块都是Module的实例。

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

每个模块内部,都有一个module对象,代表当前模块。它有以下属性。

module.id模块的识别符,通常是带有绝对路径的模块文件名。

module.filename模块的文件名,带有绝对路径。

module.loaded返回一个布尔值,表示模块是否已经完成加载。

module.parent返回一个对象,表示调用该模块的模块。

module.children返回一个数组,表示该模块要用到的其他模块。

module.exports表示模块对外输出的值。例如:

 // example.js var jquery = require('jquery'); exports.$ = jquery; console.log(module);

输出的module:

 { 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' ] }

如果在命令行下调用某个模块,比如node something.js,那么module.parent就是null。如果是在脚本之中调用,比如require('./something.js'),那么module.parent就是调用它的模块。利用这一点,可以判断当前模块是否为入口脚本。

 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属性module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。exports变量为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。

 var exports = module.exports;

所以,在对外输出模块接口时,也可以向exports对象添加方法。

 exports.area = function (r) {   return Math.PI * r * r; };  exports.circumference = function (r) {   return 2 * Math.PI * r; };

注意,不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。

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

上面这样的写法是无效的,因为exports不再指向module.exports了。下面的写法也是无效的。

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

上面代码中,hello函数是无法对外输出的,因为module.exports被重新赋值了。这意味着,如果一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports输出。如果你觉得,exports与module.exports之间的区别很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports。4、require命令4.1 加载规则(详见node加载模块顺序)4.2 模块的缓存第一次加载某个模块时,Node会缓存该模块。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个module对象,以后再加载该模块,就直接从缓存取出对应的module对象,然后获取module.exports属性。

 {  //module对象    id: '...',      exports: { ... },     loaded: true,  ... }

即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写。

 // 删除指定模块的缓存 delete require.cache[moduleName];  // 删除所有模块的缓存 Object.keys(require.cache).forEach(function(key) {   delete require.cache[key]; })

注意,缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。4.3 require.mainrequire方法有一个main属性,可以用来判断模块是直接执行,还是被调用执行。直接执行的时候(node module.js),require.main属性指向模块本身。

 require.main === module  // true

调用执行的时候(通过require加载该脚本执行),上面的表达式返回false。5、模块的加载机制CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

 // lib.js var counter = 3; function incCounter() {   counter++; } module.exports = {   counter: counter,   incCounter: incCounter, };
 // main.js var mod = require('./lib');  console.log(mod.counter);  // 3 mod.incCounter(); console.log(mod.counter); // 3

上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了(调用mod.incCounter()方法时,操作的是lib.js模块中的变量counter,而不是导出的变量counter)。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

 // lib.js var counter = 3; function incCounter() {   counter++; } module.exports = {   get counter() {     return counter   },   incCounter: incCounter, };

上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。

上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。

5.1 require的内部处理流程require命令是CommonJS规范之中,用来加载其他模块的命令。它其实不是一个全局命令,而是指向当前模块的module.require命令,而后者又调用Node的内部命令Module._load。

 Module._load = function(request, parent, isMain) {};

在这个函数中需要执行以下几步:1. 检查 Module._cache,是否缓存之中有指定模块2. 如果缓存之中没有,就创建一个新的Module实例3. 将它保存到缓存4. 使用 module.load() 加载指定的模块文件,读取文件内容之后,使用 module.compile() 执行文件代码5. 如果加载/解析过程报错,就从缓存删除该模块6. 返回该模块的 module.exports

采用module.compile()执行指定模块的脚本,逻辑如下。

 Module.prototype._compile = function(content, filename) {};

在该函数中需要执行以下几步:1. 生成一个require函数,指向module.require;2. 加载其他辅助方法到require;3. 一旦require函数准备完毕,整个所要加载的脚本内容,就被放到一个新的函数之中,这样可以避免污染全局环境。该函数的参数包括require、module、exports,以及其他一些参数。

 (function (exports, require, module, __filename, __dirname) {   // YOUR CODE INJECTED HERE!  // 执行读取的hello.js代码:     function greet(name) {         console.log('Hello, ' + name + '!');     }     module.exports = greet;     // hello.js代码执行结束  });

4. 执行该函数上面的第1步和第2步,require函数及其辅助方法主要如下。

require(): 加载外部模块

require.resolve():将模块名解析到一个绝对路径

require.main:指向主模块

require.cache:指向所有缓存的模块

require.extensions:根据文件的后缀名,调用不同的执行函数Module._compile方法是同步执行的,所以Module._load要等它执行完成,才会向用户返回module.exports的值。5.2 模块的循环加载CommonJS 模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。我们来看一段代码:

 // main.js var a = require('./a.js'); var b = require('./b.js'); console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
 // a.js exports.done = false; var b = require('./b.js'); console.log('在 a.js 之中,b.done = %j', b.done); exports.done = true; console.log('a.js 执行完毕');
 //b.js exports.done = false; var a = require('./a.js'); console.log('在 b.js 之中,a.done = %j', a.done); exports.done = true; console.log('b.js 执行完毕');

当第一次require('a.js')时,执行a.js脚本。先输出一个done变量,然后加载另一个脚本文件b.js。注意,此时a.js代码就停在这里,等待b.js执行完毕,再往下执行。b.js执行到第二行,就会去加载a.js,这时,就发生了“循环加载”。系统会去a.js模块对应对象的exports属性取值,可是因为a.js还没有执行完,从exports属性只能取回已经执行的部分,而不是最后的值。a.js已经执行的部分,只有一行。exports.done = false;因此,对于b.js来说,它从a.js只输入一个变量done,值为false。然后,b.js接着往下执行,等到全部执行完毕,再把执行权交还给a.js。于是,a.js接着往下执行,直到执行完毕。

最后输出的结果:

 $ node main.js  在 b.js 之中,a.done = false b.js 执行完毕 在 a.js 之中,b.done = true a.js 执行完毕 在 main.js 之中, a.done=true, b.done=true

上面的代码证明了两件事。一是,在b.js之中,a.js没有执行完毕,只执行了第一行。二是,main.js执行到第二行时,不会再次执行b.js,而是输出缓存的b.js的执行结果,即它的第四行

 exports.done = true;

5.3 Node.js是如何实现模块Node实现“模块”功能的奥妙在于JavaScript是一种函数式编程语言,它支持闭包。如果我们把一段JavaScript代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。请注意我们编写的hello.js代码是这样的:

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

Node.js加载了hello.js后,它可以把代码包装一下,变成这样执行:

 (function () {        // 读取的hello.js代码:        var s = 'Hello';        var name = 'world';        console.log(s + ' ' + name + '!');        // hello.js代码结束 })();

这样一来,原来的全局变量s现在变成了匿名函数内部的局部变量。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量s也互不干扰。所以,Node利用JavaScript的函数式编程的特性,轻而易举地实现了模块的隔离。

觉得可用,就经常来吧!Javascript技巧 脚本宝典 欢迎评论哦! js技巧,巧夺天工,精雕玉琢。小宝典献丑了!

脚本宝典总结

以上是脚本宝典为你收集整理的js实例教程-Node模块,CommonJS规范详解全部内容,希望文章能够帮你解决js实例教程-Node模块,CommonJS规范详解所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。