zoukankan      html  css  js  c++  java
  • 《深入浅出Node.js》第2章 模块机制

    @by Ruth92(转载请注明出处)

    第2章 模块机制

    JavaScript 的变迁

    JavaScript 先天缺乏的功能:模块。

    一、CommonJS 规范:

    JavaScript 规范的缺陷:1)没有模块系统;2)标准库较少;3)没有标准接口;4)缺乏包管理系统。

    CommonJS 规范的提出,主要是为了弥补当前 JavaScript 没有标准的缺陷,使其具备开发大型应用的基础能力。

    Node与浏览器以及W3C组织、CommonJS组织、ECMAScript之间的关系

    Node 借鉴 CommonJS 的 Modules 规范实现了一套非常易用的模块系统,NPM 对 Packages 规范的完好支持使得 Node 应用在开发过程中事半功倍。

    ☛ 【CommonJS 对模块的定义】:

    1. 模块引用:

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

       var math = require('math');
      
    2. 模块定义:

      module 对象:代表模块自身。

      exports 对象:module 的属性,用于导出当前模块的方法或者变量,且它是唯一导出的出口。

      在 Node 中,一个文件就是一个模块,将方法挂载在 exports 对象上作为属性即可定义导出的方式:

       // math.js
       exports.add = function() {};
      
    3. 模块标识:传递给 require() 方法的参数

      模块定义

    ♫ 【优点】:CommonJS 构建的这套模块导出和引入机制使得用户完全不必考虑变量污染,命名空间等方案与之相比相形见绌。

    二、Node 的模块实现

    在 Node 中引入模块,需要经历:(1)路径分析;(2)文件定位;(3)编译执行。

    Node 中的模块分为两类:

    (1)核心模块:在 Node 源代码编译过程中,编译进了二进制执行文件,所以在引入时,可以省略文件定位和编译执行,且在路径分析中优先判断,加载速度最快。

    (2)文件模块:在运行时动态加载,需要完整的3步过程,速度较慢。

    ☛ 【详细的模块加载过程】:

    1. 优先从缓存加载(第一优先级)

      ♫ 提高性能的方式比较:

      • 前端浏览器:缓存静态脚本文件,仅缓存文件。
      • Node:对引入过的模块进行缓存,缓存的是编译和执行之后的对象。

      无论是核心模块还是文件模块,require() 方法对相同模块的二次加载都一律采用缓存优先的方式。

      核心模块的缓存检查先于文件模块。

    2. 路径分析与文件定位

      ① 模块标识符分析:require() 方法的参数

      • 核心模块:如 http、fs、path 等。(最快)
      • 路径形式的文件模块:... 开始的相对路径文件模块 和 以 / 开始的绝对路径文件模块。
      • 自定义模块:特殊的文件模块,可能是一个文件或者包的形式。(最慢)
        • 模块路径:Node 在定位文件模块的具体文件时指定的查找策略,具体表现为一个路径组成的数组
        • 当前文件的路径越深,模块查找越耗时,因此自定义模块加载速度最慢。

      ② 文件定位

      • 文件扩展名分析
        • require() 在分析标识符的过程中,会出现标识符不包含扩展名的情况;
        • Node 会按 .js.json.node 的次序依次补足扩展名,调用 fs 模块同步阻塞式地依次尝试。
        • 缓解 Node 单线程中阻塞式调用的诀窍:(1) 带上扩展名;(2) 同步配合缓存。
      • 目录分析和包
        • 在分析标识符时,require() 通过分析文件扩展名之后,可能没有查找到对应文件,但却得到一个目录(经常出现在引入自定义模块和逐个模块路径进行查找时),此时 Node 会将目录当做一个包来处理。
    3. 模块编译

      在 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() 解析返回结果;
        • 其余扩展名文件:它们都被当做 .js 文件载入。
      • 每一次编译成功的模块都会将其文件路径作为索引缓存在 Module._cache 对象上,以提高二次引入的性能。

      • exports 只能改变形参的引用,但不能改变作用域外的值;要达到 require 引入一个类的效果,请赋值给 module.exports 对象。

    三、核心模块

    核心模块分为两部分: c/C++ 编写的和 JavaScript 编写的。

    1. JavaScript 核心模块的编译过程

      ① 转存为 C/C++ 代码

      ② 编译 JavaScript 核心模块

      与文件模块的区别:

      • 获取源代码的方式(核心模块是从内存中加载的);
      • 缓存执行结果的位置:编译成功的模块缓存到 NativeModule._cache 对象上,文件模块则缓存到 Module._cache 对象上。
    2. C/C++ 核心模块的编译过程

      内建模块:由纯 C/C++ 编写的部分,Node 的 buffercryptoevalsfsos 等。

      内建模块的优势:(1)性能上优于脚本语言;(2)在进行文件编译时,被编译进二进制文件,一旦 Node 开始执行,它们被直接加载进内存中,无须再做标识符定位、文件定位、编译等过程,直接就可执行。

      ➹ 【内建模块的导出】:

      • 在 Node 的所有模块类型中,存在着一种依赖层级关系:

        依赖层级关系

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

      • Node 在启动时,会生成一个全局变量 process(),并提供 Binding() 方法来协助加载内建模块。

    3. 核心模块的引入流程

      os原生模块的引入流程

    4. 编写核心模块

      • 核心模块中的 JavaScript 部分几乎与文件模块的开发相同,遵循 CommonJS 规范,上下文中除了拥有 requiremoduleexports 外,还可以调用 Node 中的一些全局变量;
      • 内建模块的编写通常分成两步:编写头文件和编写 C/C++ 文件。

    四、C/C++ 扩展模块

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

    • C/C++ 扩展模块与 JavaScript 模块的区别在于加载之后不需要编译,直接执行之后就可以被外部调用了,其加载速度比 JavaScript 模块略快。
    • 使用 C/C++ 扩展模块的好处:可更灵活和动态地加载它们,保持 Node 模块自身简单性的同时,给予 Node 无限的可扩展性。

    五、模块调用栈

    模块之间的调用关系

    • C/C++ 内建模块属于最底层的模块,它属于核心模块,主要提供 API 给 JavaScript 核心模块和第三方JavaScript 文件模块调用。
    • javaScript 核心模块属于的两类职责:1)作为C/C++内建模块的封装层和桥接层,供文件模块调用;2)纯粹的功能模块,它不需要跟底层打交道,但是又十分重要。

    六、包与 NPM

    在模块之外,包和 NPM 是将模块联系起来的一种机制。

    包组织模块示意图

    • CommonJS 的包规范由两部分组成:包结构、包描述文件。
    • CommonJS 包规范是理论,NPM 是其中的一种实践。

    NPM 常用功能

    七、前后端共用模块

    1. 模块的侧重点

      • 浏览器端的 JavaScript:需要经历从同一个服务器端分发到多个客户端执行,瓶颈在于带宽,需要通过网络加载代码;

      • 服务器端的 javaScript:相同的代码需要多次执行,瓶颈在于 CPU 和内存等资源,从磁盘加载。

    2. AMD 规范

      • 产生原因:鉴于网络的原因,CommonJS 为后端 JavaScript 制定的规范并不完全适合前端的应用场景。

      • AMD:Asynchronous Module Definition,异步模块定义,是 CommonJS 规范的一个延伸,适用于前端应用场景。

      • AMD 模块定义(id 和依赖是可选的):

          // 定义
          define(id?, dependencies?, factory);
        
          // 实现
          define(function() {
              var exports = {};
              exports.sayHello = function() {
                  alert('Hello from module: ' + module.id);
              };
              return exports;
          })
        
      • 与 Node 的区别:1)AMD模块需要用 define 来明确定义模块,而在 Node 实现中是隐式包装的,目的都是进行作用域隔离;2)内容需要通过返回的方式实现导出。

    3. CMD 规范

      • 由国内的玉伯提出。

      • 与 AMD 规范的主要区别:在于定义模块和依赖引入的部分。

          /**
           * AMD 模块定义:
           * 在声明模块的时候需要制定所有的依赖,通过形参传递到模块内容中
           */
          define(['dep1', 'dep2'], function(dep1, dep2) {
              return function () {};
          });
          
          /**
           * CMD 模块定义:
           * 更加接近于 Node 对 CommonJS 规范的定义
           */
          define(factory);
          
          // 在依赖部分,CMD 支持动态引入
          // require、exports 和 module 通过形参传递给模块
          // 在需要依赖模块时,随时调用 require() 引入即可
          define(function(require, exports, module) {
              // The module code goes here
          });
        
    4. 兼容多种模块规范

      • 为了让同一个模块运行在前后端提出的解决方案;
      • 将类库代码包装在一个闭包内,兼容 Node、AMD、CMD 以及常见的浏览器环境。

    Scoop It and Enjoy the Ride!
  • 相关阅读:
    SQL Server, Timeout expired.all pooled connections were in use and max pool size was reached
    javascript 事件调用顺序
    Best Practices for Speeding Up Your Web Site
    C语言程序设计 使用VC6绿色版
    破解SQL Prompt 3.9的几步操作
    Master page Path (MasterPage 路径)
    几个小型数据库的比较
    CSS+DIV 完美实现垂直居中的方法
    由Response.Redirect引发的"Thread was being aborted. "异常的处理方法
    Adsutil.vbs 在脚本攻击中的妙用
  • 原文地址:https://www.cnblogs.com/Ruth92/p/5827651.html
Copyright © 2011-2022 走看看