任何一门编程语言,当代码越写越多时,人们就会考虑如何将代码进行分类组织。通常情况下,都会讲代码按照功能进行拆分并保存在不同目录及文件中,通过相互引用来达到代码复用。那么这个时候不可避免的就会遇到全局变量可能会被污染的问题。通常情况下,很多语言都会提供一个叫做命名空间的概念,从而形成了模块这个概念。然而JS天生并没有提供命名空间这样一个机制。于是有热心的开发者提供了CommonJS和AMD,CMD等多种规范。其中NodeJS就是遵循CommonJS规范的。而现在ES6也已经推出,它提供的模块机制是综合了各家的优势后设立的另一套规范。
现在我们来细细聊NodeJS中的模块机制。在NodeJS中,Node模块允许开发者从被引入的文件中选择要暴露给外界的函数或变量。机制是通过exports对象的属性或module.exports属性进行模块暴露。NodeJS模块系统正是通过这样一种机制来避免了全局作用域的污染,从而避免了命名冲突。接下来从几个方面来讲解模块。
一、创建模块
NodeJS中的模块可以是一个文件,也可以是一个包含多文件的目录。如果模块是一个目录,那么Node就会在该目录下查找一个叫index.js的文件作为模块的入口。当然这个文件名index.js也是可以通过package.json文件进行配置的。通过在exports对象上设置属性的方式来创建模块。
var canadianDollar = 0.91; function roundTwoDecimals(amount){ return Math.round(amount*100)/100; } exports.canadianToUS = function(canadian){ return roundTwoDecimals(canadian * canadianDollar); } exports.USToCanadian = function(us){ return roundTwoDecimals(us / canadianDollar); }
通过require(path)的方式来加载模块。注意如果是自定义文件模块并且处在同一目录下,那么一定要记住请使用 " ./ " 这个相对路径标识。
var currency = require('./currency'); console.log("50 Canadian dollars equals this amount of US dollars:"); console.log(currency.canadianToUS(50)); console.log("30 US dollar equals this amount of Canadian dollars:"); console.log(currency.USToCanadian(30));
也可以通过module.exports来创建模块
function Currency(canadianDollar){ this.canadianDollar = canadianDollar; } Currency.prototype.roundTwoDecimals = function(amount){ return Math.round(amount*100)/100; }; Currency.prototype.canadianToUS = function(canadian){ return this.roundTwoDecimals(canadian * this.canadianDollar); }; Currency.prototype.USToCanadian = function(us){ return this.roundTwoDecimals(us / this.canadianDollar); }; module.exports = Currency;
这样通过require方法引入的模块就是一个构造函数
var Currency = require("./currency"); var canadianDollar = 0.91; var currency = new Currency(canadianDollar); console.log(currency.canadianToUS(50));
模块最终导出的是module.exports对象,而模块内部隐藏了一句代码exports = module.exports 所以不要轻易给exports重新设置引用关系。
module对象在所有的Node程序中都存在,但是每个module对象都仅仅存在于它当前的模块内。也就是说只存在于模块作用域内。
二、安装与加载模块
从npm安装第三方模块使用 npm install [package] 命令
通过require() 来加载模块
通过npm search [package] 命令来查找模块
同理,require对象也不是全局变量,而是每个模块内部的一个对象,作用于模块内。
require对象的方法
1. require.cache
当模块被引入时,会被缓存到这个对象中,通过 delete 可以从该对象中删除键值,这样下次调用 require()时就会重新加载模块。不适用于核心模块,常用于第三方模块。
2. require.resolve()
使用内部的require()机制来查找模块的位置,但是不会加载模块,只是返回解析后的模块文件绝对路径( __filename )。这个绝对路径也是module对象中的Id属性。
模块加载的内部原理
通常在package.json文件中会通过main属性来指定模块入口文件,这样加载模块时会考虑这个默认文件。