美文网首页
模块机制

模块机制

作者: 茂茂爱吃鱼 | 来源:发表于2019-05-07 10:40 被阅读0次

CommonJS 模块规范

模块引用

require() 方法,引入一个模块的 API 到当前上下文中

const math = require('math')

模块定义

exports 对象用于导出当前模块的方法或者变量

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
}

模块标识

传递给 require() 方法的参数,它必须是符合小驼峰命名的字符串,或相对路径/绝对路径

Node 的模块实现

在 Node 中引入模块, 需要经历如下3个步骤

  1. 路径分析

  2. 文件定位

  3. 编译执行

在 Node 中, 模块分为两类:一类是 Node 提供的模块, 称为核心模块;另一类是用户编写的模块,称为文件模块

  • 核心模块部分在 Node 源代码的编译过程中,编译进了二进制执行文件。在 Node 进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略掉,并且路径分析中优先判断,所以它的加载速度是最快的

  • 文件模块则是在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程,速度比核心模块慢

优先从缓存加载

Node 对引入过的模块都会进行缓存,以减少二次引入时的开销。与浏览器仅仅缓存文件不同,它缓存的是编译和执行之后的对象

路径分析和文件定位

  1. 模块标识符分析
  • 核心模块:优先级仅次于缓存加载

  • 路径形式的文件模块:将路径转为真实路径,并以真实路径作为索引,将编译执行后的结果存放到缓存中,以使二次加载时更快,其加载速度慢于核心模块

  • 自定义模块:非核心模块,也不是路径形式的标识符。它是一种特殊的文件模块,可能是一个文件或者包的形式。这类模块的查找是最费时的,也是所有方式中最慢的一种

  1. 文件定位
  • 文件扩展名分析: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

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

模块编译

  1. JavaScript 模块的编译

    在编译的过程中,Node 对获取的 JavaScript 文件内容进行了头尾包装。在头部添加了 (function(exports, require, module, ____filename, ____dirname)) {\n 在尾部添加了 \n}

    为何存在 exports 情况下,还存在 module.exports,其原因在于,exports 对象是通过形参的方式传入的,直接赋值形参会改变形参的引用,如果要达到 require 引入一个类的效果,请赋值给 module.exports 对象。这个迂回的方案不改变形参的引用。

  2. C/C++ 模块的编译

    Node 调用 process.dlopen() 方法进行加载和执行

  3. JSON 文件的编译

    Node 利用 fs 模块同步读取 JSON 文件的内容之后,调用 JSON.parse() 方法得到对象,然后将它赋给模块对象的 exports,以供外部调用。JSON 文件在用做项目的配置文件时比较有用。如果你定义了一个 JSON 文件作为配置,那就不必调用 fs 模块去异步读取和解析,直接调用 require() 引入即可。此外,你还可以享受到模块缓存的遍历,并且二次引入时页没有性能影响。

    核心模块

    核心模块其实分为 C/C++ 编写和 JavaScript 编写的两部分,其中 C/C++ 文件存放在 Node 项目的 src 目录下

    JavaScript 核心模块的编译过程

    1. 转存为 C/C++ 代码

    2. 编译 JavaScript 核心模块:也经历头尾包装的过程,然后才执行和道出了 exports 对象。不同于文件模块是,获取源代码的方式(核心模块是从内存中加载的)以及缓存执行结果的位置

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

    在核心模块中,有些模块全部由 C/C++ 编写,有些模块则由 C/C++ 完成核心部分,其他部分则由 JavaScript 实现包装或向外导出,以满足性能需求。

    1. 内建模块的组织形式

    2. 内建模块的导出

      文件模块可能会依赖核心模块,核心模块可能会依赖内建模块

依赖层级关系.png

通常,不推荐文件模块直接调用内建模块。如需调用,直接调用核心模块即可,因为核心模块中基本都封装了内建模块。

核心模块的引入流程

os原生模块的引入流程.png
编写核心模块

编写头文件和编写 C/C++ 文件

C/C++ 扩展模块

JavaScript 的一个典型弱点就是位运算。JavaScript 的位运算参照 Java 的位运算实现,但是 Java 位运算是在 int 型数字的基础上进行的, 而 JavaScript 中只有 double 型的数据类型,在进行位运算的过程中,需要将 double 型转换为 int 型,然后再进行。所以,在 JavaScript 层面上作位运算的效率不高。

在应用中,会频繁出现效率低的操作(如上面提到的位运算),如果通过 JavaScript 来实现,CPU 资源将会耗费好多,这时编写 C/C++ 扩展模块来提升性能的机会来了。

扩展模块不同平台上的编译和加载过程.png

前提条件

  • GYP 项目生成工具

  • V8引擎 C++ 库

  • libuv 库

  • Node 内部库

  • 其他库

C/C++ 扩展模块的编写

普通的扩展模块与内建模块的区别在于无须将源代码编译进 Node,而是通过 dlopen() 方法动态加载。所以在编写普通模块时,无须将源代码写进 node 命名空间,也不需要提供头文件。

C/C++ 扩展模块的编译

写好 .gyp 项目文件,node-gyp 约定 .gyp 文件为 binding.gyp

C/C++扩展模块的加载

require() 方法通过间隙标识符、路径分析、文件定位,然后加载执行即可

require()引入.node文件的过程.png

C/C++ 扩展模块与 JavaScript 模块的区别在于加载之后不需要编译,直接执行之后就可以被外部调用了,其加载速度比 JavaScript 模块略快

使用 C/C++ 扩展模块的一个好处在于可以更灵活和动态地加载他们,保持 Node 模块自身简单性的同时,给予 Node 无线的可扩展性

模块调用栈

模块之间的调用关系.png

包与 NPM

CommonJS 的包规范的定义其实十分简单,它由包结构和包描述文件两个部分组成,前者用于组织包中的各种文件,后者则用于描述包的相关信息,以供外部读取分析

包结构

完全符合 CommonJS 规范的包目录应该包含如下这些文件

  • package.json:包描述文件

  • bin:用于存放可执行二进制文件的目录

  • lib:用于存放 JavaScript 代码的目录

  • doc:用于存放文档的目录

  • test:用于存放单元测试用例的代码

包描述文件与 NPM

包描述文件用于表达非代码相关的信息,它是一个 JSON 格式的文件——package.json, 位于包的根目录下,是包的重要组成部分

NPM 常用功能

  1. 查看帮助
$ npm help <command>
  1. 安装依赖包
# 全局模式安装
$ 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
  1. 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 指向的脚本

  1. 发布包

    • 编写模块

    • 初始化包描述文件

    • 注册包仓库账号

    • 上传包

    • 安装包

    • 管理包权限

    • 分析包

局域 NPM

搭建自己的 NPM 仓库

NPM 潜在问题

潜在问题在于,鉴于开发者水平不一,上面的包质量也良莠不齐。另一个问题则是,Node 代码可以运行在服务器端,需要考虑安全问题

前后端公用模块

模块的侧重点

客户端的瓶颈在于带宽,服务端的瓶颈在于 CPU 和内存等资源

鉴于网络的原因,CommonJS 为后端 JavaScript 指定的规范并不完全是个前端的应用场景

AMD 规范

CMD 规范

兼容多种模块规范

;(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 {
 // 将模块的执行结果挂在window变量中,在浏览器中this指向window对象
 this[name] = definition();
 }
})('hello', function () {
 var hello = function () {};
 return hello;
}); 

相关文章

  • Vue.js 学习之路(二)

    npm install npm install 模块安装机制简介 关于 npm install 模块安装机制的简介...

  • Node.js 模块机制

    Node.js 模块机制 Node.js 模块机制采用了 Commonjs 规范,弥补了当前 JavaScript...

  • 模块加载机制有哪些?es6中的module和其他机制有什么区别?

    模块加载机制有哪些?es6中的module和其他机制有什么区别? 答案: 模块机制有amd,cmd和commonJ...

  • 模块机制

    nodejs模块采用Commonjs规范,弥补Javascript开发大型应用没有标准的缺陷,类似java中的类,...

  • 模块机制

    CommonJS 模块规范 模块引用 require() 方法,引入一个模块的 API 到当前上下文中 模块定义 ...

  • angular中使用第三方库

    一、TypeScript 模块机制 javascript从es5之前都缺少一种模块机制,无法通过js引入文件,于是...

  • Nodejs2 模块机制

    模块 nodejs采用的是commonjs的模块机制,commonjs模块的定义很简单,主要分为模块引用requi...

  • Node模块机制

    Q&A 1.node模块机制有什么用?答:node模块机制使你可以选择要暴露给引用这个模块的程序的变量、函数或对...

  • 那些年成为node攻城狮的路(二)

    模块加载机制浅析 node中模块分为核心模块和文件模块两大类,核心模块诸如fs、http、util...,文件模块...

  • 饿了吗大前端阅读(二)

    模块 模块机制 模块加载机制官方文档已经说的很明白的,基本的流程是 如果加载为第三方或者系统依赖(就是在node_...

网友评论

      本文标题:模块机制

      本文链接:https://www.haomeiwen.com/subject/lmosoqtx.html