Node.js Module

JS 是世界上使用频率最高的语言之一,JS 的核心是在 Netscape 公司大行其道的岁月里创建的,当时正处于浏览器白热化时期,JS 作为打击 Microsoft 的利器匆忙上马。它并没有发展得十分成熟就被发布,这意味着不可避免地带有一些糟糕的特性。虽然开发时间不长,但 JS 被赋予一些强大的特性。不过“脚本间共享全局命名空间”这一特性却不在其中。

一旦 JS 代码被载入页面,就会被添加进全局命名空间,全局命名空间是被所有已载入的脚本所共享的通用地址空间,这样会导致安全性问题、命名冲突以及一些难以跟踪和解决的一般性错误。

值得庆幸的是 Node 作为服务端的 JS 制定了一些规范,并实现了 CommonJS 模块标准。在这个标准中,每个模块都拥有一个上下文,将该模块和其他模块隔离开来,这意味着模块不会污染全局作用域,因为根本就没有全局作用域,并且也不会对其他模块造成干扰。

Node是如何加载模块的?

在 Node 可用文件路径也可用名称来引用模块,除非是核心模块,否则用名称引用的模块最终都会被映射为一个文件路径。Node 核心模块将一些核心函数暴露给程序员,它们在 Node 进程启动时会被预先载入。其他模块包括使用 NPM 安装的第三方模块,以及用户自己创建的本地模块。

不管什么类型的模块,在被导入当前脚本后,程序员都可以使用其对外暴露一组公共 API 。不管使用什么模块,你都可以使用 require() 载入它。

// 导入一个核心模块或由NPM安装的模块
// require() 会返回一个对象,该对象表示模块对外暴露的JS API。
// 根据模块不同,该对象可能是任意的JS值,可是一个函数,也可是一个具有若干属性的对象,属性可能是函数、数组或其它任何类型的JS对象。
var module = require('module_name');

导出模块

Node 中 CommonJS 模块系统是文件之间共享对象或函数的唯一方式,对于足够复杂的应用,应将一些类、对象、函数划分成定义良好的可重用模块,模块值对外暴露你指定的内容。

Node 中文件和模块是一一对应的,Node 可导出复杂的对象,module.exports 被初始化成一个空对象,可为空对象附加上任意想要导出的属性。

$ vim circle.js
function Circle(r){
    function square(){
        return Math.pow(r,2);
    }
    function area(){
        return Math.PI*square();
    }
    return {area:area};
}
// 定义模块导出的内容
// module是个变量表示当前模块自身
// module.exports 表示模块向需要它的脚本所导出的对象,可是任意对象。
// 导出Circle类的构造函数,用户可使用该函数来创建具备完整功能的Circle实例。
module.exports = Circle;

可设计模块让其导出一组函数或常量

$ vim module.js
function printA(){
    console.log('a');
}
function printB(){
    console.log('b');
}
function printC(){
    console.log('c');
}

module.exports.printA = printA;
module.exports.printB = printB;
module.exports.printC = printC;

module.expots.pi = Math.PI;

使用模块的客户端脚本

$ vim test.js
var module = require('./module');
module.printA();
module.printB();
console.log(module.pi);

加载模块

可使用 require() 加载模块,在代码中调用 require() 不会改变全局命名空间的状态,因为在 Node 中根本就没有全局命名空间这个概念。若模块存在并没有任何语法或初始化错误,调用 require() 就会返回这个模块对象,然后就可以将这个对象赋值给任意一个局部变量。

引用模块的方式会决定模块的类型

  • 核心模块
  • NPM 安装的第三方模块
  • 本地模块

加载核心模块

核心模块是指 NPM 中以二进制形式发布的模块,核心模块只能通过模块名引用,而不能通过文件路径引用。即使已经存在一个与其同名的第三方模块,也会优先加载核心模块。

// 加载http核心模块,返回http模块对象,该对象实现由Node API文件描述的 HTTP API.
var http = require('http');

加载文件模块

可通过提供绝对路径或相对路径从文件系统中加载非核心模块,可省略文件扩展名 .js 。若没找到此文件,Node会在文件名上添加 .js 扩展名再次查找路径。

// 以绝对路径从文件系统中加载非核心模块
var myModule = require('/home/user/modules/my_module');

// 基于当前文件的相对路径
var myModule = require('./lib/my_module');
var myModule = require('../modules/my_module');

// 模块扩展名可省略
var myModule = require('./my_module.js');

加载文件夹模块

// 可使用文件夹路径加载模块,Node会在指定文件夹下查找模块。
// Node假定该文件夹是一个包,并试图查找包定义。包定义包含在名为 package.json 文件中。
// 若文件夹中不存在包定义文件 package.json, 那么包入口点会假定为默认值 index.js。
// 若文件夹中存在 package.json,那么Node会尝试解析该文件并查找 main 属性,将 main 属性作为入口点的相对路径。
var myModule = require('./modules');// Node 将会在路径 ./modules/index.js 下查找文件

从 node_modules 文件夹加载

若一个模块名即不是相对路径也不是核心模块,Node 会尝试在当前目录下的 node_modules 文件夹中查找该模块。

// Node 会尝试查找文件 ./node_modules/module.js
// 若没有找到该文件,Node会继续在父文件夹 ../node_modules/module.js 中查找
// 若没有找到该文件,Node会继续查找上级父文件夹,这个过程一直持续到达根目录或找到所需的模块为止。
var myModule = require('module.js');

可利用此特性管理 node_modules 目录中的内容,不过最好还是让 NPM 来管理模块。本地模块 node_modules 是 NPM 安装模块的默认位置,此项功能将Node和NPM关联到一起。

缓存模块

模块在首次加载时会被缓存起来,这意味着若模块名能被解析为相同的文件名,那么每次调用 require('module') 都会确切地返回同一模块。

$ vim my_module.js
console.log('module my_module initializing...');

// 模块初始化过程只执行了一次,当构建自定义模块时,若模块在初始化时可能会产生副作用。
module.exports = function(){
    console.log('hi');
};

console.log('my_module initialized')

模块初始化过程只执行了一次

var m1 = require('./my_module.js');
var m2 = require('./my_module');

exports 和 module.exports有什么区别呢?

首先需明确的是,module.exports才是真正的接口,exports只不过是module.exports的一个辅助工具,exports是基于module.exports而实现的。实际上,全部由exports获取的属性和方法,最后都赋给了module.exports接口,不过有个前提是module.exports本身不具备任何属性和方法。换言之,若module.exports接口已具备属性和方法,exports获取的属性和方法将被忽略。

总结

Node取消了JS默认的全局命名空间,而用CommonJS模块系统取而代之,这样可更好地组织代码,也因此避免了了一些安全问题和错误。
使用 require() 从文件或文件夹加载核心模块、第三方模块、自定义模块。可使用相对或绝对路径加载非核心模块。若将模块放入 node_modules 文件夹或使用 NPM 安装模块,也可使用模块名加载。
可编写JS文件导出标识模块API的对象,以此创建自定义模块。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容