Node解析之----模块机制篇

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: Node解析之----模块机制篇

开篇前,我们先来看张图, 看node与W3C组织、CommonJS组织、ECMAScript之间的关系。

image.png

   Node借鉴来CommonJS的Modules规范实现了一套非常易用的模块系统,NPM对Packages规范

的完好支持使得Node应用在开发过程中事半功倍。


一、CommonJS 的模块规范

CommonJS中的大部分规范涵盖了模块、二进制、Buffer、字符集编码、I/O流、进程环境、文件系统、套接字、单元测试、Web服务器网关接口、包管理等。

 

1.1 模块引用

模块示例代码如下:

var math = require('math');

在CommonJS规范中,存在require()方法,这个方法接受模块标识,以此引入一个模块的API到当前上下文中。


1.2模块定义

在模块中,上下文提供require()方法引入外部模块。对应引入的功能,上下文提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一导出的出口。

例如:

// math.js
exports.add = function () {
 var sum = 0,
 i = 0,
 args = arguments,
 l = args.length;
 while (i < l) {
sum += args[i++];
 }
 return sum;
};
// program.js
var math = require('math');
exports.increment = function (val) {
 return math.add(val, 1);
};


1.3模块标识

   简单理解就是传递给require()方法的参数,必须使用小驼峰命名的字符串,或者使用相对路径,亦可使用绝对路径。

Tip:可以没有.js文件名后缀。

image.png

 

CommonJS导出和引入机制,我们不需要考虑变量污染、命名空间等。

 

二、Node的模块实现

在Node中引入模块,要经历3个步骤:

(1) 路径引入

(2) 文件定位

(3) 编译执行

Node模块分两种,一种是Node提供的模块,称为核心模块;另外一种是用户编写的模块,称为文件模块。

1、核心模块在Node源代码的编译过程中,编译进了二进制执行文件。在Node进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中优先判断,所以它的加载速度是最快的。

2、文件模块在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程,速度比核心模块慢。


2.1优先从缓存加载


2.2路径分析和文件定位

Tip: 同步配合缓存,缓解Node单线程中阻塞式调用的缺陷

1)模块标识符分析

   require()方法接受一个标识符作为参数,在Node实现中,正是基于这样一个标识符进行模块查找的。

2)自定义模块

   它的生成方式与Javascript的原型链的查找方式十分类型。在加载过程中,Node会逐个尝试模块路径中的路径,知道找到目标文件为止。可以看出,当前文件的路径越深,模块查找耗时会越多,这是自定义模块加载速递最慢的原因。


2.3模块编译

每个文件模块都是一个对象,例如:

function Module(id, parent) { this.id = id;
this.exports = {}; this.parent = parent;
       if (parent && parent.children) {
         parent.children.push(this);
}
this.filename = null; this.loaded = false; this.children = [];
}

编译和执行是引入文件模块的最后一个阶段。定位到具体的文件后,Node会新建一个模块对象,然后根据路径载入并编译。对于不同的文件拓展名,其载入方法也有所不同,例如:

1).js文件:通过fs模块同步读取文件后编译执行

2).node文件:这是用C/C++编写的拓展文件,通过dlopen()方法加载最后编译生成的文件

3).json文件:通过fs模块同步读取文件后,用JSON.parse()解析返回结果

4)其余拓展名文件:它们都被当作.js文件载入

Tip: 每一个编译成功的模块都会将文件路径作为索引缓存在Module._cache对象上,为了提高二次引入的性能。

根据不同的文件拓展名,Node会调用不同的读取方式,例如.json文件调用:

// Native extension for .json
Module._extensions['.json'] = function(module, filename) {
var content = NativeModule.require('fs').readFileSync(filename, 'utf8'); try {
module.exports = JSON.parse(stripBOM(content));
} catch (err) {
err.message = filename + ': ' + err.message; throw err;
} };

Module._extensions会被赋值给require()的extensions属性

console.log(require.extensions);
//{ '.js': [Function], '.json': [Function], '.node': [Function]


2.4 Javascript核心模块的编译过程

1)编译JavaScript核心模块

   lib目录下的所有模块文件也没有定义require、module、export这些变量。在引入javascript核心模块的过程中,也经历了头尾包装的过程,然后才执行和导出exports对象。与文件模块有区别的地方在于:获取源代码的方式(核心模块是从内存中加载的)以及缓存执行结果的位置。

function NativeModule(id) { this.filename = id + '.js'; this.id = id;
this.exports = {}; this.loaded = false;
}
NativeModule._source = process.binding('natives'); NativeModule._cache = {};

 

Javascript核心模块源文件通过process.binding('natives')取出,编译成功的模块缓存到NativeModule._cache对象上,文件模块则缓存到Module._cache对象上。


3.包与NPM

   具体讲内容前,我们先看一张图:

image.png

 

CommonJS的包规范定义很简单,由包结构和包描述组成。

1)包结构:用于组织包中的各种文件

2)包描述:用于描述包的相关信息,供外部读取分析

 

3.1包结构

   包实际上是一个存档文件,一个目录直接打包为.zip或tar.gz格式的文件,安装解压还原为目录。

符合CommonJS规范的包目录应该包含以下文件:

1)package.json:包描述文件

2)bin: 用于存放可执行二进制文件的目录

3)lib:用于存放JavaScript代码的目录

4)doc:用于存放文档的目录

5)test:用于存放单元测试用例的代码

Tip: 当一个包完成后,用户看到单元测试和文档时,会有一种可靠的感觉。


3.2.包描述文件于NPM

   包描述文件用于表达非代码相关的信息,它是一个JSON格式的文件----package.json,位于包的根目录下,是包的重要组成部分。NPM的所有行为都与包描述文件的字段息息相关。


3.3NPM常用功能

    CommonJS包规范是理论,NPM是其中的一种实践。NPM帮助第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模块之间形成来很好的一个生态系统。

   借助NPM,我们可以快速安装和管理依赖包。除此之外,NPM还有一些巧妙的用法。

// 查看当前NPM版本
$ npm -v
1.2.32
// 查看帮助
$ npm
// 查看具体命令
npm help <command

3.4安装依赖包

  npm install 安装依赖包是NPM最常见的用法,执行这个命令后,NPM会在当前目录下创建node_modules目录,然后在node_modules目录下创建express目录,接着将包解压到这个目录下。

   安装完成后,在代码里面使用require()来使用就可以了。

   安装有几种模式,全局模式安装、本地安装、非官方源安装。


3.5NPm钩子命令

   package.json中script字段就是让包在安装或者卸载中提供钩子机制,例如:

"scripts": {
  "preinstall": "preinstall.js",
  "install": "install.js",
  "uninstall":
  "uninstall.js",
  "test": "test.js"
 }

   当我们执行npm install <package>时,preinstall指向的脚本将会被加载执行,然后install指向的脚本会被执行。在执行npm uninstall <package> 时,uninstall指向的脚本也许会做一些清理工作等。

   当我们在某一个具体的包目录下执行npm test时,将会运行test指向的脚本。

 

总结

   Node通过模块规范,形成了自身的原生模块。NPM通过对包规范的支持,形成了第三方模块,让我们在开发项目依赖得到很好的解决,并且提供了分享和传播的平台。借助第三方的开源力量,Node第三方模块的发展速递可谓是一步千里。

 

参考资源:

http://www.commonjs.org

http://npmjs.org/doc/README.html

http://www.infoq.com/cn/articles/msh-using-npm-manage-node.js-dependence

http://nodejs.org/docs/latest/api/modules.html

http://addyosmani.com/writing-modular-js/

http://seajs.org/docs/

http://zh.wikipedia.org/zh/JavaScript

http://zh.wikipedia.org/wiki/ECMAScript

http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf

http://www.w3.org/TR/html5/

http://arstechnica.com/web/news/2009/12/commonjs-effort-sets-javascript-on-path-for-world-d

omination.ars

相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
73 2
|
2月前
|
存储 缓存 算法
分布式锁服务深度解析:以Apache Flink的Checkpointing机制为例
【10月更文挑战第7天】在分布式系统中,多个进程或节点可能需要同时访问和操作共享资源。为了确保数据的一致性和系统的稳定性,我们需要一种机制来协调这些进程或节点的访问,避免并发冲突和竞态条件。分布式锁服务正是为此而生的一种解决方案。它通过在网络环境中实现锁机制,确保同一时间只有一个进程或节点能够访问和操作共享资源。
89 3
|
19天前
|
PHP 开发者 UED
PHP中的异常处理机制解析####
本文深入探讨了PHP中的异常处理机制,通过实例解析try-catch语句的用法,并对比传统错误处理方式,揭示其在提升代码健壮性与可维护性方面的优势。文章还简要介绍了自定义异常类的创建及其应用场景,为开发者提供实用的技术参考。 ####
|
23天前
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
23天前
|
缓存 NoSQL Java
千万级电商线上无阻塞双buffer缓冲优化ID生成机制深度解析
【11月更文挑战第30天】在千万级电商系统中,ID生成机制是核心基础设施之一。一个高效、可靠的ID生成系统对于保障系统的稳定性和性能至关重要。本文将深入探讨一种在千万级电商线上广泛应用的ID生成机制——无阻塞双buffer缓冲优化方案。本文从概述、功能点、背景、业务点、底层原理等多个维度进行解析,并通过Java语言实现多个示例,指出各自实践的优缺点。希望给需要的同学提供一些参考。
42 7
|
22天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
27天前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
53 8
|
1月前
|
Java 测试技术 API
Java 反射机制:深入解析与应用实践
《Java反射机制:深入解析与应用实践》全面解析Java反射API,探讨其内部运作原理、应用场景及最佳实践,帮助开发者掌握利用反射增强程序灵活性与可扩展性的技巧。
89 4
|
1月前
|
存储 消息中间件 算法
深入探索操作系统的心脏——内核机制解析
本文旨在揭示操作系统核心——内核的工作原理,通过剖析其关键组件与机制,为读者提供一个清晰的内核结构图景。不同于常规摘要的概述性内容,本文摘要将直接聚焦于内核的核心概念、主要功能以及其在系统管理中扮演的角色,旨在激发读者对操作系统深层次运作原理的兴趣与理解。
|
1月前
|
JavaScript 安全 中间件
深入浅出Node.js中间件机制
【10月更文挑战第36天】在探索Node.js的奥秘之旅中,中间件的概念如同魔法一般,它让复杂的请求处理变得优雅而高效。本文将带你领略这一机制的魅力,从概念到实践,一步步揭示如何利用中间件简化和增强你的应用。

推荐镜像

更多