CommonJS ??楣娣?/h4>
??橐?/strong>
require() 方法,引入一个??榈?API 到当前上下文中
const math = require('math')
??槎ㄒ?/strong>
exports 对象用于导出当前??榈姆椒ɑ蛘弑淞?/p>
module 对象代表??樽陨?,而 exports 是 module 的属性
// math.js
exports.add = function() {
let sum = 0,
i = 0,
args = arguments,
l = args.length
while(i < l) {
sum += args[i++]
}
return sum
}
??楸晔?/strong>
传递给 require() 方法的参数,它必须是符合小驼峰命名的字符串,或相对路径/绝对路径
Node 的模块实现
在 Node 中引入模块, 需要经历如下3个步骤
路径分析
文件定位
编译执行
在 Node 中, 模块分为两类:一类是 Node 提供的???, 称为核心???;另一类是用户编写的???,称为文件模块
核心??椴糠衷?Node 源代码的编译过程中,编译进了二进制执行文件。在 Node 进程启动时,部分核心??榫捅恢苯蛹釉亟诖嬷?,所以这部分核心??橐胧?,文件定位和编译执行这两个步骤可以省略掉,并且路径分析中优先判断,所以它的加载速度是最快的
文件模块则是在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程,速度比核心模块慢
优先从缓存加载
Node 对引入过的??槎蓟峤谢捍妫约跎俣我胧钡目?。与浏览器仅仅缓存文件不同,它缓存的是编译和执行之后的对象
路径分析和文件定位
- ??楸晔斗治?/li>
核心??椋河畔燃督龃斡诨捍婕釉?/p>
路径形式的文件??椋航肪蹲媸德肪?,并以真实路径作为索引,将编译执行后的结果存放到缓存中,以使二次加载时更快,其加载速度慢于核心???/p>
自定义模块:非核心??椋膊皇锹肪缎问降谋晔斗?。它是一种特殊的文件???,可能是一个文件或者包的形式。这类??榈牟檎沂亲罘咽钡模彩撬蟹绞街凶盥囊恢?/p>
- 文件定位
-
文件扩展名分析:CommonJS ??楣娣对市碓诒晔斗胁话ㄎ募┱姑庵智榭鱿?,Node 会按 .js、.json、.node 的次序补足扩展名,依次尝试
在尝试的过程中,需要调用 fs ??橥阶枞降嘏卸衔募欠翊嬖?。因为 Node 是单线程的,所以这里是一个会引起性能问题的地方。 小诀窍是:如果是 .node 和 .json 文件,在传递给 require() 的标识符中带上扩展名,会加快一点速度。另一个诀窍是:同步配合缓存,可以大幅度缓解 Node 单线程中阻塞式调用的缺陷
-
目录分析和包
Node 在当前目录下查找 package.json,通过 JSON.parse() 解析出包描述对象,从中取出 main 属性指定的文件名进行定位。如果文件名缺失扩展名,将会进入扩展名分析的步骤。
而如果 main 属性指定的文件名错误,或者压根没有 package.json 文件,Node 会将 index 当做默认文件名,然后依次查找 index.js、index.json、index.node
如果在目录分析的过程中没有定位成功任何文件,则自定义??榻胂乱桓瞿?槁肪督胁檎?。如果??槁肪妒槎急槐槔瓯希廊幻挥胁檎业侥柯嘉募?,则会抛出查找失败的异常。
??楸嘁?/strong>
-
JavaScript ??榈谋嘁?/p>
在编译的过程中,Node 对获取的 JavaScript 文件内容进行了头尾包装。在头部添加了
(function(exports, require, module, ____filename, ____dirname)) {\n 在尾部添加了 \n}
为何存在 exports 情况下,还存在 module.exports,其原因在于,exports 对象是通过形参的方式传入的,直接赋值形参会改变形参的引用,如果要达到 require 引入一个类的效果,请赋值给 module.exports 对象。这个迂回的方案不改变形参的引用。
-
C/C++ ??榈谋嘁?/p>
Node 调用 process.dlopen() 方法进行加载和执行
-
JSON 文件的编译
Node 利用 fs 模块同步读取 JSON 文件的内容之后,调用 JSON.parse() 方法得到对象,然后将它赋给??槎韵蟮?exports,以供外部调用。JSON 文件在用做项目的配置文件时比较有用。如果你定义了一个 JSON 文件作为配置,那就不必调用 fs 模块去异步读取和解析,直接调用 require() 引入即可。此外,你还可以享受到??榛捍娴谋槔⑶叶我胧币趁挥行阅苡跋?。
核心???/h4>
核心??槠涫捣治?C/C++ 编写和 JavaScript 编写的两部分,其中 C/C++ 文件存放在 Node 项目的 src 目录下
JavaScript 核心模块的编译过程
转存为 C/C++ 代码
编译 JavaScript 核心??椋阂簿肺舶暗墓蹋缓蟛胖葱泻偷莱隽?exports 对象。不同于文件??槭?,获取源代码的方式(核心??槭谴幽诖嬷屑釉氐模┮约盎捍嬷葱薪峁奈恢?/p>
C/C++ 核心??榈谋嘁牍?/strong>
在核心模块中,有些??槿坑?C/C++ 编写,有些??樵蛴?C/C++ 完成核心部分,其他部分则由 JavaScript 实现包装或向外导出,以满足性能需求。
内建??榈淖橹问?/p>
-
内建模块的导出
文件??榭赡芑嵋览岛诵哪??,核心模块可能会依赖内建???/p>
通常,不推荐文件??橹苯拥饔媚诮??。如需调用,直接调用核心模块即可,因为核心??橹谢径挤庾傲四诮??。
核心??榈囊肓鞒?/strong>
编写核心???/strong>
编写头文件和编写 C/C++ 文件
C/C++ 扩展???/h4>
JavaScript 的一个典型弱点就是位运算。JavaScript 的位运算参照 Java 的位运算实现,但是 Java 位运算是在 int 型数字的基础上进行的, 而 JavaScript 中只有 double 型的数据类型,在进行位运算的过程中,需要将 double 型转换为 int 型,然后再进行。所以,在 JavaScript 层面上作位运算的效率不高。
JavaScript 的一个典型弱点就是位运算。JavaScript 的位运算参照 Java 的位运算实现,但是 Java 位运算是在 int 型数字的基础上进行的, 而 JavaScript 中只有 double 型的数据类型,在进行位运算的过程中,需要将 double 型转换为 int 型,然后再进行。所以,在 JavaScript 层面上作位运算的效率不高。
在应用中,会频繁出现效率低的操作(如上面提到的位运算),如果通过 JavaScript 来实现,CPU 资源将会耗费好多,这时编写 C/C++ 扩展模块来提升性能的机会来了。
前提条件
GYP 项目生成工具
V8引擎 C++ 库
libuv 库
Node 内部库
其他库
C/C++ 扩展??榈谋嘈?/strong>
普通的扩展模块与内建??榈那鹪谟谖扌虢创氡嘁虢?Node,而是通过 dlopen() 方法动态加载。所以在编写普通??槭保扌虢创胄唇?node 命名空间,也不需要提供头文件。
C/C++ 扩展??榈谋嘁?/strong>
写好 .gyp 项目文件,node-gyp 约定 .gyp 文件为 binding.gyp
C/C++扩展??榈募釉?/strong>
require() 方法通过间隙标识符、路径分析、文件定位,然后加载执行即可
C/C++ 扩展??橛?JavaScript ??榈那鹪谟诩釉刂蟛恍枰嘁?,直接执行之后就可以被外部调用了,其加载速度比 JavaScript ??槁钥?/p>
使用 C/C++ 扩展??榈囊桓龊么υ谟诳梢愿榛詈投丶釉厮?,保持 Node ??樽陨砑虻バ缘耐?,给予 Node 无线的可扩展性
模块调用栈
包与 NPM
CommonJS 的包规范的定义其实十分简单,它由包结构和包描述文件两个部分组成,前者用于组织包中的各种文件,后者则用于描述包的相关信息,以供外部读取分析
包结构
完全符合 CommonJS 规范的包目录应该包含如下这些文件
package.json:包描述文件
bin:用于存放可执行二进制文件的目录
lib:用于存放 JavaScript 代码的目录
doc:用于存放文档的目录
test:用于存放单元测试用例的代码
包描述文件与 NPM
包描述文件用于表达非代码相关的信息,它是一个 JSON 格式的文件——package.json, 位于包的根目录下,是包的重要组成部分
NPM 常用功能
- 查看帮助
$ npm help <command>
- 安装依赖包
# 全局模式安装
$ npm install <package> -g
# 从本地安装
$ npm install <tarball file>
$ npm install <tarball url>
$ npm install <folder>
# 从非官方源安装
$ npm install underscore --registry=http://registry.url
# 指定默认源
$ npm config set registry http://registry.url
- npm 钩子命令
"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 指向的脚本
-
发布包
编写模块
初始化包描述文件
注册包仓库账号
上传包
安装包
管理包权限
分析包
局域 NPM
搭建自己的 NPM 仓库
NPM 潜在问题
潜在问题在于,鉴于开发者水平不一,上面的包质量也良莠不齐。另一个问题则是,Node 代码可以运行在服务器端,需要考虑安全问题
前后端公用???/h4>
??榈牟嘀氐?/strong>
客户端的瓶颈在于带宽,服务端的瓶颈在于 CPU 和内存等资源
鉴于网络的原因,CommonJS 为后端 JavaScript 指定的规范并不完全是个前端的应用场景
AMD 规范
CMD 规范
兼容多种??楣娣?/strong>
;(function (name, definition) {
// 检测上下文环境是否为AMD或CMD
var hasDefine = typeof define === 'function',
// 检查上下文环境是否为Node
hasExports = typeof module !== 'undefined' && module.exports;
if (hasDefine) {
// AMD环境或CMD环境
define(definition);
} else if (hasExports) {
// 定义为普通Node模块
module.exports = definition();
} else {
// 将??榈闹葱薪峁以趙indow变量中,在浏览器中this指向window对象
this[name] = definition();
}
})('hello', function () {
var hello = function () {};
return hello;
});