Javascript

《深入浅出node.js》第二章 模块机制

本文主要是介绍《深入浅出node.js》第二章 模块机制,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

image-20210713104100587

1、CommonJS

模块规范

  1. 模块引用
    • require()引入
  2. 模块定义
    • module的属性exports导出
  3. 模块标识
    • 参数:小驼峰命名 or 相对路径 or 绝对路径,可以没有后缀
    • 传给require的参数

2、Node

核心模块:node提供

文件模块:用户编写

2.1 优先从缓存加载

减少二次引入的开销

2.2 路径分析和文件定位

2.2.1 模块标识符

  1. 核心模块
  2. 文件模块
    1. 相对路径
    2. 绝对路径
    3. 非路径形式
      • 又称自定义模块,可能是一个文件或者包的形式
      • 查找费时,node会逐个尝试模块路径中的路径,文件路径越深越慢

2.2.2 文件扩展名

若不含扩展名,node会按照.js , .json , .node 的顺序补充尝试,若是后两者,带上扩展名会加快速度。

2.2.3 目录分析和包

路径查询得到一个目录,则会当做包来处理

  1. 查找package.json,若缺少扩展名则自动补充尝试
  2. 解析json取出main属性指定的文件名进行定位
  3. 若main的文件路径错误或无package.json,则查找index(无则补充扩展名)
  4. 都没有定位成功,进入下一模块路径重复操作
  5. 全部遍历完了都无,则抛出异常

2.3 模块编译

根据文件扩展名的不同,载入方式也有区别

  1. **js:**通过fs模块同步读取文件后编译执行
  2. node:(这是C/C++编写的扩展文件)通过dlopen()方法加载最后编译生成的文件
  3. **json:**fs后JSON.parse()解析返回结果
  4. **其他:**当做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文件

下图为个人看书理解后画的图,不保证绝对正确,如有错误请指正

Untitled Diagram

3.1 js核心模块编译

  1. 转存C/C++代码
    • js代码以字符串的形式春初在node命名空间,是不可直接执行的。
    • 启动node进程时,js代码直接加载进内存中
    • 加载过程中,js核心模块经历标识符分析后直接定位到内存中,比普通文件在磁盘中一个个查找快
  2. 编译js核心模块
    • 与文件模块一样未定义require等变量,需要引入过程中包装
    • 不同在于:获取源码的方式(从内存中加载),缓存执行结果的位置
    • 源文件过process.binding(‘natives’)取出,编译成功的模块缓存到NativeModule._cache对象上,文件模块测缓存到Module. _cache对象上

3.2 C/C++核心模块的编译过程

有些全部由C/C++编写(内建模块)

有些由C/C++完成核心部分,js包装或向外导出。

具体过程见3.1上面的图。

3.3 编写核心模块

  1. 编写头文件 .h

  2. 编写C/C++文件 .cc

    以下步骤为让Node认为它是内建模块

  3. 更改src/node_extensions.h

    • 在 NODE_EXT_LIST_END 前添加 NODE_EXT_LIST_ITEM(node_name) ,以将模块加进node_module_list数组中
  4. 编写两份代码编译进执行文件,更改Node项目生成文件node.gyp;在

‘target_name’: 'node’节点的sources中添加上新编写的两个文件

详细自己翻书

4、C/C++扩展模块

js的位运算参照java;js中只有double的数据类型,所以进行位运算时需要转为int型再运算,所以js位运算的效率不高。

而应用中会频繁出现位运算(转码、编码等),所以可以使用C/C++扩展模块来提升性能。

分为以下四个步骤:

4.1 编写

  1. 编写hello.cc放到src下
  2. 与内建一样,挂在在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 分析包

发布包

  1. 编写模块
  2. 初始化包描述信息(npm init)
  3. 注册包仓库账号(npm adduser)
  4. 上传包(npm publish <folder>)
  5. 安装包 (npm install <package_name>)
  6. 管理包权限(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》第二章 模块机制的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!