前言

本人记忆力一般,为了让自己理解《深入浅出Node.js-朴灵》一书,会在博客里记录一些关键知识,以后忘了也可以在这里找到,快速回想起来

Node通过require、exports、module实现CommonJS模块规范的

路径分析

require('http') //如http、fs、path,速度仅次于缓存加载,它在node源代码编译过程中已经被编译成二进制代码,其加载速度最快

require('./a.txt') //以.或者..开始的相对路径模块

require('/a.txt') //以/开始的绝对路径模块
//以上两种都当做文件模块来处理,在分析路径模块时require()方法会将路径转化为真实路径,并以真实路径为索引,将编译执行后的结果放到缓存中,以使二次加载更快。
//因为路径模块给了确切文件位置,所以在查找过程中可以节约大量时间,其加载慢于核心模块。 require(*) //非路径形式的文件模块,如自定义的connect模块//自己没太理解这块,因为可能没实现过自己的自定义模块所以举不出例子
//特殊的文件模块,可能是一个文件或者包的形式。这类模块查找最费时,也是所有方式中最慢的。原因是和js原型链一样要一层层node_modules找

文件定位

从缓存加载的优化策略使得二次引入不需要分析路径分析、文件定位和编译执行的过程,大大提高了再次加载时的效率

  • 文件扩展名分析

require() 允许参数不带后缀,在这种情况下,node会按照.js、.json、.node次序补足扩展名依次尝试,在尝试过程中需要调用fs模块同步阻塞式判断文件是否存在。

所以在后两种引入方式时推荐加上后缀名

  • 目录分析和包

require() 通过分析文件扩展名之后,可能没有查找到对应文件,但却得到一个目录,这在引入自定义模块和逐个模块路径进行查找时经常会出现,此时node会将目录当做一个包来处理。

在这个过程中,node对commonjs包规范进行了一定程度的支持。首先,node在当前目录下查找package.json,通过json.parse解析出包描述对象从中取出Main属性指定的文件名进行定位。

如果文件缺少扩展名,将会进入扩展名分析的步骤。

而如果main属性指定的文件名错误或者压根没有package.json文件,node将会将index当做默认文件名,依次添加扩展名查找

如果在目录分析的过程中没有定位成功任务文件则自定义模块进入下一个模块路径进行查找。如果模块路径数组都被遍历完毕,依然没有查找到目标文件,则会抛出查找失败异常。

编译执行

在node中每个模块就是一个对象它的定义如下:

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会新建一个模块对象,然后根据路径载入并编译。对于不同扩展名,其载入方式不一样如下:

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

  • .node 这是通过c/c++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件

  • .json 通过fs模块同步读取后,用JSON.parse解析返回结果

// 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;
}
};
  • 其余拓展名文件 都被当做js文件载入

每一个编译成功的模块都会将其文件路径作为索引缓存在Modules._cache对象上,以提高二次加载速度

实践

我们有个area.js文件看看node的commonjs规范流程

  • 暴露文件给node并会给下面js包装
 var math = require('math');
exports.area = function (radius) {
return Math.PI * radius * radius;
};

变为

(function (exports, require, module, __filename, __dirname) {
var math = require('math');
exports.area = function (radius) {
return Math.PI * radius * radius;
};
});

这样每个模块文件之间都进行了作用域隔离。包装后的代码会通过vm原生模块的runInThisContext方法执行(类似eval,只是有明确的上下文,不污染全局),返回一个具体function对象。

最后将当前模块对象的exports属性、require方法、module(模块自身)以及在文件定位中得到的完整文件路径和文件目录作为参数传递给这个function执行,执行后模块的exports属性返回给了调用方,

其他变量方法无法被调用。

  • 外部文件引用
require('./area');

node通过上述的(路径分析)来查这个'./area',通过fs找到area.js文件(文件定位)并读取编译执行(编译执行)

AMD与它的区别

AMD需要用define明确定义一个模块,而在node实现中是隐形包装的,目的是作用于隔离,仅在需要时被引入,避免掉过去那种全局变量或者命名空间的方式,防止被污染,另一个区别是内容需要通过返回的方式实现导出。

define(id?, dependencies?, factory);

define(function() {
var exports = {}; exports.sayHello = function() {
alert('Hello from module: ' + module.id); };
return exports; });

CMD与它的区别

AMD需要在声明模块的时候定义所有依赖,通过形参传递到模块内容中,CMD支持动态引入依赖,require、exports、module通过形参传递给模块,在需要依赖模块时,随时调用require引入即可

define(['dep1', 'dep2'], function (dep1, dep2) { return function () {};
});
define(function(require, exports, module) {
});

NodeJs的CommonJS模块规范的更多相关文章

  1. CommonJs模块规范

    1.什么是模块化 文件作用域 通信规则 加载 require 导出 exports 2.CommonJs模块规范 在Node中的Javascript还有一个很重要的概念:模块概念 模块作用域 使用re ...

  2. "浏览器端" 使用 commonjs 模块规范开发网页应用,像开发 node 那样开发网页应用

    Containjs 1.0 Containjs 是什么? Containjs 是一个基于 Commonjs 模块管理规范的 浏览器端 的 JavaScript 模块加载器(目前为非标准的,代码会持续迭 ...

  3. commonJS模块规范 和 es6模块规范 区别

    ES6 模块与 CommonJS 模块的差异 CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口. Commo ...

  4. CommonJS 模块规范 1.1.1

    本规范致力于描述一类可以同时适用于客户端和服务器端的模块系统.该系统中的模块拥有自己的作用域,可以从其他模块导入单例对象,或者对外提供 API. Require require 是一个函数对象. re ...

  5. CommonJs模块化(nodejs模块规范)

    1.概述: Node应用由模块组成,采用CommonJS模块规范. 根据这个规范,每个文件就是一个模块,有自己的作用域.在一个文件里面定义的变量.函数.类,都是私有的,对其他文件不可见. 如果想在多个 ...

  6. node (02 CommonJs 和 Nodejs 中自定义模块)顺便讲讲module.exports和exports的区别 dependencies 与 devDependencies 之间的区别

    CommonJS 规范的提出,主要是为了弥补当前 JavaScript 没有标准的缺陷.它的终极目标就是:提供一个类似 Python,Ruby 和 Java 语言的标准库,而不只是停留在小脚本程序的阶 ...

  7. Node.js学习笔记(二) --- CommonJs和Nodejs 中自定义模块

    一. 什么是 CommonJs? JavaScript 是一个强大面向对象语言,它有很多快速高效的解释器. 然而, JavaScript标准定义的 API 是为了构建基于浏览器的应用程序.并没有制定一 ...

  8. 理解CommonJS ,AMD ,CMD, 模块规范

    参考 : https://blog.csdn.net/xcymorningsun/article/details/52709608 1.CommonJS 模块规范 (同步加载模块): var math ...

  9. Node基础-CommonJS模块化规范

    1.在本地项目中基于NPM/YARN安装第三方模块 第一步:在本地项目中创建一个"package.json"的文件 作用:把当前项目所有依赖的第三方模块信息(包含:模块名称以及版本 ...

随机推荐

  1. Flutter-ListTile

    ListTile 通常用于在 Flutter 中填充 ListView.在这篇文章中,我将用可视化的例子来说明所有的参数. title title 参数可以接受任何小部件,但通常是文本小部件 List ...

  2. springboot框架中的各种 注解

    使用注解的优势: 1.采用纯java代码,不在需要配置繁杂的xml文件 2.在配置中也可享受面向对象带来的好处 3.类型安全对重构可以提供良好的支持 4.减少复杂配置文件的同时亦能享受到springI ...

  3. Linux启动redis提示 /var/run/redis_6379.pid exists, process is already running or crashed

    执行启动命令:service redisd start 提示信息:/var/run/redis_6379.pid exists, process is already running or crash ...

  4. kwargs - Key words arguments in python function

    This is a tutorial of how to use *args and **kwargs For defining the default value of arguments that ...

  5. Python3解leetcode Min Cost Climbing Stairs

    问题描述: On a staircase, the i-th step has some non-negative cost cost[i]assigned (0 indexed). Once you ...

  6. 31 October

    https://www.cnblogs.com/RabbitHu/p/51nod1353.html 树形 DP 求所有联通块 \(\ge K\) 的方案数. 切断:\(\forall i\in\lef ...

  7. [CSP-S模拟测试96]题解

    以后不能再借没改完题的理由不写题解了…… A.求和 求$\sum \sum i+j-1$ 柿子就不化了吧……这年头pj都不考这么弱智的公式化简了…… 坑点1:模数不定,可能没有2的逆元,那么只要先把乘 ...

  8. GCD 和 NSOperationQueue 的差别

    http://stackoverflow.com/questions/10373331/nsoperation-vs-grand-central-dispatch http://www.cocoach ...

  9. .NET Core:目录

    ylbtech-.NET Core:目录 1.返回顶部 1. https://dotnet.microsoft.com/ 2. 2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部   ...

  10. node后台fetch请求数据-Hostname/IP doesn't match certificate's altnames解决方法

    一.问题背景 基于express框架,node后台fetch请求数据,报错Hostname/IP doesn't match certificate's altnames..... require(' ...