Node.js中的模块机制学习笔记


Posted in Javascript onNovember 04, 2014

Javascript自诞生以来,曾经没有人拿它当做一门编程语言。在Web 1.0时代,这种脚本语言主要被用来做表单验证和网页特效。直到Web 2.0时代,前端工程师利用它大大提升了网页上的用户体验,JS才被广泛重视起来。在JS逐渐流行的过程中,它大致经历了工具类库、组件库、前端框架、前端应用的变迁。Javascript先天就缺乏一项功能:模块,而CommonJS规范的出现则弥补了这一缺陷。本文将介绍CommonJS规范及Node的模块机制。

在其他高级语言中,Java有类文件,Python有import机制,PHP有include和require。而JS通过<script>标签引入代码的方式显得杂乱无章。过去人们不得不用命名空间等方式来人为地约束代码,直到CommonJS规范的出现,前后端的Javascript才得以实现大一统。Node借鉴了CommonJS的Modules规范实现了一套非常易用的模块系统。

1. CommonJS模块规范

CommonJS的模块规范分为3个部分:

1).模块引用:通过require()方法并传入一个模块标识来引入一个模块的API到当前上下文中,如var math = require('math');
2).模块定义:通过exports对象来导出当前模块的方法或变量。模块中还存在一个module对象,exports实际上是module的属性。在Node中,一个文件就是一个模块,模块内的“全局变量”对外都不可见,只有挂载在exports上的属性才是公开的,如exports.add = function() {}; exports.PI = 3.1415926;
3).模块标识:实际上就是传递给require()的参数,如上述的'math',它必须是符合camel命名法的字符串,或者是以“.”“..”开头的相对路径或绝对路径,它可以没有文件名后缀“.js”

2. Node模块实现过程

在Node中,模块分为两类:一类是Node本身提供的核心模块,另一类是用户自己编写的文件模块。核心模块有一部分在Node源代码的编译过程中,编译成了二进制文件,在Node启动时核心模块就被直接加载进内存中,所以它的加载速度是最快的。文件模块则是在运行时动态加载,需要经历三个步骤:路径分析,文件定位,编译执行。注意,Node对引入过的模块都会进行缓存,以减少二次引入时的开销,并对相同模块的二次加载都采用最优先从缓存加载的策略。

2.1 路径分析

路径分析主要分析上述提到的模块标识符,主要分为以下几类:

1)、核心模块,如http、fs、path等
2)、.或..开始的相对路径文件模块
3)、以/开始的绝对路径文件模块
4)、自定义文件模块,可能是一个文件或包的形式。Node会根据模块路径数组module.paths来逐个尝试查找目标文件,通常是沿着当前目录逐级向上直到根目录查找名为node_modules的目录,所以这是查找最费时的一种方式。

2.2 文件定位

在路径分析的基础上,文件定位需要注意如下细节:

1)、文件扩展名分析:由于CommonJS规范允许模块标识不填写扩展名,Node会按.js、.json、.node的次序不足扩展名,依次尝试
2)、目录分析和包:若通过上述文件扩展名分析后没有查找到对应文件,却得到一个目录,Node会把目录当做一个包来处理

2.3 编译执行

定位到具体文件后,Node会新建一个模块对象,根据路径载入并编译。对于不同的扩展名,载入方法有所不同:

1)、.js文件:通过fs模块同步读取文件并编译执行
2)、.node文件:这是用C/C++编写的扩展文件,通过dlopen()方法加载
3)、.json文件:通过fs模块同步读取文件,用JSON.parse()解析返回结果
4)、其余扩展名文件:都被当做.js文件载入

我们知道每个模块文件中默认都存在着require、exports、module这3个变量,甚至在Node的API文档中,我们知道每个模块还有filename、dirname这2个变量的存在,它们是从何而来的呢?Node的模块又是怎么做到声明的“全局变量”实际上是不会污染到其他模块的?事实上,Node在编译JS模块过程中会对文件内容进行头尾包装。下面是一个JS文件经过头尾包装的例子:

(function(exports, require, module, __filename, __dirname) {

    /* 中间是JS文件的实际内容 */

    var math = require('math');

    exports.area = function(radius) {

        return Math.PI * radius * radius;

    };

    /* JS文件的实际内容结束 */

});

这样每个模块文件之间都进行了作用域隔离,同时require、exports、module等变量也被注入到了模块的上下文当中。这就是Node对CommonJS模块规范的实现。关于C/C++模块及Node核心模块的编译过程较为复杂,不再赘述。

3. 模块调用栈

有必要明确一下Node中各种模块的调用关系,如下图所示:

Node.js中的模块机制学习笔记

C/C++内建模块是最底层的模块,属于核心模块,主要提供API给Javascript核心模块和第三方Javascript文件模块调用,实际中几乎不会接触到此类模块。Javascript核心模块主要职责有两种:一种是作为C/C++内建模块的封装层和桥接层供文件模块调用,另一种是纯粹的功能模块,不需要跟底层打交道。文件模块通常由第三方编写,包括普通Javascript模块和C/C++扩展模块。

4. 包与NPM

4.1 包结构

包本质上是一个存档文件(一般为.zip或.tar.gz),安装后解压还原为目录。CommonJS的包规范由包结构和包描述文件两部分组成。一个完全符合CommonJS规范的包结构应包含以下文件:

1).package.json:包描述文件
2).bin:存放可执行二进制文件的目录
3).lib:存放Javascript代码的目录
4).doc:存放文档的目录
5).test:存放单元测试用例的目录

4.2 包描述文件

包描述文件是一个JSON文件——package.json,位于包的根目录下,是包的重要组成部分,用于描述包的概况信息。后面要提到的NPM的所有行为都与这个文件的字段息息相关。下面将以知名Web框架express项目的package.json文件为例说明一些常用字段的含义。

1).name:包名
2).description:包简介
3).version:版本号,需遵照“语义化的版本控制”,参照http://semver.org/
4).dependencies:使用当前包所需要依赖的包列表。这个属性十分重要,NPM会通过这个属性自动加载依赖的包
5).repositories:托管源代码的位置列表

其余字段的用法可以参照NPM package.json说明

4.3 NPM常用功能

NPM(node package manager),通常称为node包管理器。它的主要功能就是管理node包,包括:安装、卸载、更新、查看、搜索、发布等。

4.3.1 NPM包安装

Node包的安装分两种:本地安装、全局安装。两者的区别如下:

1).本地安装npm install <package-name>:package会被下载到当前所在目录,也只能在当前目录下使用。
2).全局安装npm install -g <package-name>:package会被下载到到特定的系统目录下,安装的package能够在所有目录下使用。

4.3.2 NPM包管理

下面以grunt-cli(grunt命令行工具)为例,列出常用的包管理命令:

1).npm install:安装package.json文件的dependencies和devDependencies字段声明的所有包
2).npm install grunt-cli@0.1.9:安装特定版本的grunt-cli
3).npm install grunt-contrib-copy --save:安装grunt-contrib-copy,同时保存该依赖到package.json文件
4).npm uninstall grunt-cli:卸载包
5).npm list:查看安装了哪些包
6).npm publish <folder>:发布包

Javascript 相关文章推荐
基于jquery实现的鼠标滑过按钮改变背景图片
Jul 15 Javascript
javascript预加载图片、css、js的方法示例介绍
Oct 14 Javascript
JS 屏蔽按键效果与改变按键效果的示例代码
Dec 24 Javascript
JavaScript蒙板(model)功能的简单实现代码
Aug 04 Javascript
Vue.js组件tabs实现选项卡切换效果
Dec 01 Javascript
fullCalendar中文API官方文档
Feb 07 Javascript
JavaScript订单操作小程序完整版
Jun 23 Javascript
JavaScript动态加载重复绑定问题
Apr 01 Javascript
脚手架vue-cli工程webpack的作用和特点
Sep 29 Javascript
iview同时验证多个表单问题总结
Sep 29 Javascript
vue实现歌手列表字母排序下拉滚动条侧栏排序实时更新
May 14 Javascript
微信小程序 简易计算器实现代码实例
Sep 02 Javascript
Node.js异步I/O学习笔记
Nov 04 #Javascript
JavaScript中的ubound函数使用实例
Nov 04 #Javascript
JavaScript实现检查页面上的广告是否被AdBlock屏蔽了的方法
Nov 03 #Javascript
网页中表单按回车就自动提交的问题的解决方案
Nov 03 #Javascript
详解jquery中$.ajax方法提交表单
Nov 03 #Javascript
jquery处理json对象
Nov 03 #Javascript
js格式化时间小结
Nov 03 #Javascript
You might like
phpmyadmin操作流程
2006/10/09 PHP
phpfans留言版用到的install.php
2007/01/04 PHP
PHP5 面向对象程序设计
2008/02/13 PHP
Php图像处理类代码分享
2012/01/19 PHP
php parse_str() 函数的定义和用法
2016/05/23 PHP
JavaScript 选中文字并响应获取的实现代码
2011/08/28 Javascript
JQuery学习笔录 简单的JQuery
2012/04/09 Javascript
开发中可能会用到的jQuery小技巧
2014/03/07 Javascript
JQuery调用绑定click事件的3种写法
2015/03/28 Javascript
jQuery实现带滚动导航效果的全屏滚动相册实例
2015/06/19 Javascript
jQuery实现带玻璃流光质感的手风琴特效
2015/11/20 Javascript
jQuery基于正则表达式的表单验证功能示例
2017/01/21 Javascript
vue axios 给生产环境和发布环境配置不同的接口地址(推荐)
2018/05/08 Javascript
vue中Axios的封装与API接口的管理详解
2018/08/09 Javascript
js实现轮播图的完整代码
2020/10/26 Javascript
jQuery实现获取及设置CSS样式操作详解
2018/09/05 jQuery
微信小程序实现两个页面传值的方法分析
2018/12/11 Javascript
nodejs基础之多进程实例详解
2018/12/27 NodeJs
JavaScript原型对象原理与应用分析
2018/12/27 Javascript
使用npm命令提示: 'npm' 不是内部或外部命令,也不是可运行的程序的处理方法
2020/05/14 Javascript
[29:23]2014 DOTA2国际邀请赛中国区预选赛 LGD-GAMING VS CIS 第一场1
2014/05/23 DOTA
[51:07]VGJ.S vs Pain 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
Django1.7+python 2.78+pycharm配置mysql数据库教程
2014/11/18 Python
给Python IDLE加上自动补全和历史功能
2014/11/30 Python
为Python的web框架编写MVC配置来使其运行的教程
2015/04/30 Python
Python的Socket编程过程中实现UDP端口复用的实例分享
2016/03/19 Python
wxPython实现整点报时
2019/11/18 Python
Python tkinter界面实现历史天气查询的示例代码
2020/08/23 Python
css3+jq创作含苞待放的荷花
2014/02/20 HTML / CSS
英国排名第一的最新设计师品牌手表独立零售商:TIC Watches
2016/09/24 全球购物
贝玲妃英国官网:Benefit英国
2018/02/03 全球购物
日本最大的购物网站:日本乐天市场(Rakuten Ichiba)
2020/11/04 全球购物
介绍一下Java中的static关键字
2012/05/12 面试题
学习雷锋倡议书
2014/04/15 职场文书
开工典礼致辞
2015/07/29 职场文书
利用python做数据拟合详情
2021/11/17 Python