前言

本人记忆力一般,为了让自己理解《深入浅出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. MYSQL学习笔记——sql语句优化之索引

    上一篇博客讲了可以使用慢查询日志定位耗时sql,使用explain命令查看mysql的执行计划,以及使用profiling工具查看语句执行真正耗时的地方,当定位了耗时之后怎样优化呢?这篇博客会介绍my ...

  2. MYSQL学习笔记——常用语句

    1.检索数据 1.1.检索单个列:SELECT prod_name FROM products; 1.2.检索多个列:SELECT prod_id, prod_name, prod_price FRO ...

  3. 使用国外 DNS 造成国内网站访问慢的解决方法

    本文原载于 wzyboy's blog,转载请注明本文地址: https://wzyboy.im/post/874.html ,谢谢合作. 为什么要用国外 DNS 由于众所周知的问题,国内 DNS 服 ...

  4. python笔记(1)--基础知识

    一.注释 单行注释 #打印“hello world” print("hello.world!") 另外一种单行注释 print("hello,world!") ...

  5. B1001. 害死人不偿命的(3n + 1)猜想

    题目描述 卡拉兹(Callatz)猜想: 对任何一个自然数n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把(3n + 1)砍掉一半.这样一直反复砍下去,最后一定在某一步得到n=1.卡拉兹在19 ...

  6. linux运维、架构之路-linux磁盘管理

    一.企业中磁盘选购: 1.线上的业务,用SAS磁盘 2.线下的业务,用SATA磁盘,磁带库 3.线上高并发.小容量(多人浏览力图片)的业务,SSD磁盘 4.根据数据的访问热度,智能分析分层存储,SAT ...

  7. CSS中的背景用法详解

    background 属性是CSS中用于设置元素背景的属性,最简单的background属 性名,是针对背景若干设定的合并简写,最早的CSS只能使用单一背景图片,而在现在却可以设置多个背景图片.而不用 ...

  8. KMP字符串匹配模板

    题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next. (如果你不知道这是什么意思也不要问,去百度 ...

  9. [luogu]P1016 旅行家的预算[贪心]

    [luogu]P1016 旅行家的预算 题目描述 一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的).给定两个城市之间的距离D1.汽车油箱的容量C(以升为单位).每升汽油能 ...

  10. 再探容斥好题——ROOK

    这个时候考过:安师大附中集训 Day2 当时看shadowice1984的做法,但是没有亲自写,,, 雅礼集训考试的时候鼓捣半天,被卡常到80pts,要跑9s 卡不动. 正解实际是: 3重容斥 1.随 ...