本文主要是介绍《深入浅出node.js》第二章 模块机制,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1、CommonJS
模块规范
- 模块引用
- 模块定义
- 模块标识
- 参数:小驼峰命名 or 相对路径 or 绝对路径,可以没有后缀
- 传给require的参数
2、Node
核心模块:node提供
文件模块:用户编写
2.1 优先从缓存加载
减少二次引入的开销
2.2 路径分析和文件定位
2.2.1 模块标识符
- 核心模块
- 文件模块
- 相对路径
- 绝对路径
- 非路径形式
- 又称自定义模块,可能是一个文件或者包的形式
- 查找费时,node会逐个尝试模块路径中的路径,文件路径越深越慢
2.2.2 文件扩展名
若不含扩展名,node会按照.js , .json , .node 的顺序补充尝试,若是后两者,带上扩展名会加快速度。
2.2.3 目录分析和包
路径查询得到一个目录,则会当做包来处理
- 查找package.json,若缺少扩展名则自动补充尝试
- 解析json取出main属性指定的文件名进行定位
- 若main的文件路径错误或无package.json,则查找index(无则补充扩展名)
- 都没有定位成功,进入下一模块路径重复操作
- 全部遍历完了都无,则抛出异常
2.3 模块编译
根据文件扩展名的不同,载入方式也有区别
- **js:**通过fs模块同步读取文件后编译执行
- node:(这是C/C++编写的扩展文件)通过dlopen()方法加载最后编译生成的文件
- **json:**fs后JSON.parse()解析返回结果
- **其他:**当做js文件载入
编译成功后,会将其文件路径作为索引,缓存在Module._cache对象上,提高二次引入的可能性
2.3.1 js模块编译
在编译过程中,Node对获取的JavaScript文件内容进行了头尾包装。
(function (exports, require, module, __filename, __dirname) {
var math = require('math');
exports.area = function (radius) {
return Math.PI * radius * radius;
};
});
所以,每个模块之间都进行了作用域隔离。
2.3.2 C/C++模块编译
.node其实不需要编译,这里只是加载和执行。
执行效率高,但编写门槛高
2.3.3 JSON文件编译
最简单,fs后Math.parse()得到对象,直接赋值
3、核心模块
核心模块 = C/C++文件 + js文件
下图为个人看书理解后画的图,不保证绝对正确,如有错误请指正
3.1 js核心模块编译
- 转存C/C++代码
- js代码以字符串的形式春初在node命名空间,是不可直接执行的。
- 启动node进程时,js代码直接加载进内存中
- 加载过程中,js核心模块经历标识符分析后直接定位到内存中,比普通文件在磁盘中一个个查找快
- 编译js核心模块
- 与文件模块一样未定义require等变量,需要引入过程中包装
- 不同在于:获取源码的方式(从内存中加载),缓存执行结果的位置
- 源文件过process.binding(‘natives’)取出,编译成功的模块缓存到NativeModule._cache对象上,文件模块测缓存到Module. _cache对象上
3.2 C/C++核心模块的编译过程
有些全部由C/C++编写(内建模块)
有些由C/C++完成核心部分,js包装或向外导出。
具体过程见3.1上面的图。
3.3 编写核心模块
-
编写头文件 .h
-
编写C/C++文件 .cc
以下步骤为让Node认为它是内建模块
-
更改src/node_extensions.h
- 在 NODE_EXT_LIST_END 前添加 NODE_EXT_LIST_ITEM(node_name) ,以将模块加进node_module_list数组中
-
编写两份代码编译进执行文件,更改Node项目生成文件node.gyp;在
‘target_name’: 'node’节点的sources中添加上新编写的两个文件
详细自己翻书
4、C/C++扩展模块
js的位运算参照java;js中只有double的数据类型,所以进行位运算时需要转为int型再运算,所以js位运算的效率不高。
而应用中会频繁出现位运算(转码、编码等),所以可以使用C/C++扩展模块来提升性能。
分为以下四个步骤:
4.1 编写
- 编写hello.cc放到src下
- 与内建一样,挂在在target上,通过NODE_MODULE声明
通过dlopen()来动态加载,然后导出
4.2 编译
GYP工具使编译无需为每个平台编写不同的项目编译文件
编译过程会根据平台,分别通过make或者vcbuild进行编译
完成后的.node 文件会生成在build/Release目录下
4.3 加载
require()方法通过解析标识符、路径分析、文件定位,然后加载执行
.node文件,Node会调用process.dlopen()方法去加载文件
libuv函数的调用表现了node的跨平台方式
C/C++扩展模块加载后不需要编译,所以比js模块略快
4.4 导出
5、模块调用栈
6、包与npm
6.1 包结构
- package.json:包的描述文件
- bin:可执行二进制文件的目录
- lib:js代码的目录
- doc:文档的目录
- test:单元测试用例的代码
6.2 包描述文件与NPM
package.json必需字段:
- name:包名
- description:包简介
- version:版本号
- keywords:关键词数组
- maintainers:包维护者列表。每位由name、email、web组成
NPM通过该属性进行权限认证
- contributors:贡献者列表
- bugs:可以反馈bug的网页地址or邮箱地址
- licenses:所使用的许可证列表
- repositories:托管源代码位置列表
- dependencies:需要使用的依赖包列表
- homepage:网站地址
- os:操作系统支持列表
- cpu:CPU架构支持列表
- engine:支持的js引擎列表
- builtin:当前包是否是内建在底层系统的标准组件
- directories:包目录说明
- implements:实现规范的列表
- scripts:脚本说明对象
包描述文件规范,NPM实际需要的字段主要有:name、version、description、keywords、 repositories、author、bin、main、scripts、engines、dependencies、devDependencies。
- author:包作者
- bin:一些包作者希望包可以作为命令行根据使用
- main:模块引入方法require()在引入包时,会优先检查的字段,并将其作为包中其余模块的入口
- dependencies:一些模块只在开发时需要依赖
6.3 NPM常用功能
- npm -v 查看版本
- npm 查看帮助
- npm install express 安装依赖包
- -g 全局模式安装
- npm install <tarball file / tarball url / folder> 本地安装
- npm install underscore --registry=http://registry.url 非官方源安装
- npm config set registry http://registry.url 指定默认源
- npm ls 分析包
发布包
- 编写模块
- 初始化包描述信息(npm init)
- 注册包仓库账号(npm adduser)
- 上传包(npm publish <folder>)
- 安装包 (npm install <package_name>)
- 管理包权限(npm owner ls <package_name> 、 npm owner add <user> <package name> 、npm owner rm <user> <package_name>)
6.4 局域NPM
6.5 潜在问题
任何人都可以开发,所以良莠不齐
使用前先自己筛选
7、前后端共用模块
- AMD:异步模块定义。需要用define来明确定义一个模块。node实现中是隐式包装的。内容需要通过返回的方式实现导出。
- CMD:与AMD的区别在于,需要在声明模块的时候指定所有的依赖,通过形参传递依赖到模块内容中
这篇关于《深入浅出node.js》第二章 模块机制的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!