Node:模块机制学习笔记

本文详细探讨了Node.js的模块机制,包括CommonJS规范、Node.js的模块实现、路径分析、文件定位、模块编译及缓存加载。还介绍了AMD和CMD规范在前端场景的应用,以及Node.js中包与NPM的管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模块机制

1. CommonJS

1. 模块规范

a. 模块引用

  • 在CommonJS中,存在require()方法,接收模块标识,以此引入一个模块的API到当前模块中。

b. 模块定义

  • 对应引入功能,上下文提供了exports对象用于导出当前模块的方法或者属性,并且是唯一的出口。
  • 在模块中,module对象代表模块自身,而exports是module的属性。
  • 在Node中,一个文件就是一个模块,将方法挂载在exports对象上作为属性即可定义导出的方式。

c. 模块标识

  • 模块标识其实就是传递给require()方法的参数,它必须是小驼峰命名法的字符串,可以没有后缀.js。

CommonJS构建的这套模块导出和引入机制使得用户无需考虑全局污染的问题。

2. Node的模块实现
  • Node在引入模块时需要经历如下三个步骤:

    1. 路径分析

    2. 文件定位

    3. 编译执行

  • Node的模块分为两类:核心模块和用户自定义的文件模块。

  • 核心模块在Node源代码编译过程中已经被编译进了二进制的执行文件。在Node进程启动时,部分核心模块就直接被加载进内存中,所以这部分核心模块引入时可以省略前两个步骤,加载速度是最快的。

  • 文件模块实在运行时动态加载,需要完整地经历上面三个步骤,速度比加载核心模块慢。

2.1 优先从缓存加载
  • Node对引入过的模块都会进行缓存,以减少第二次引入时的开销。
  • Node缓存的时编译和执行后的对象。
  • 不论是核心模块还是文件模块,require方法对相同模块的二次加载都采用缓存优先的方式,这是第一优先级的。且核心模块的缓存检查优先于文件模块。
2.2 路径分析
  • 核心模块

    1. 核心模块的优先级仅次于缓存加载,在Node的源代码编译过程中已经编译进了二进制代码,加载过程最快。
    2. 如果文件模块的标识符与核心模块相同,如果试图加载之是不会成功的。解决方法可以采用路径引入。
  • 路径形式的文件模块

    1. 在分析文件模块时,require方法会将路径转化为真实路径,以真实路径为索引,将编译执行后的结果存放到缓存中,以使第二次加载更加迅速。

    2. 由于指定了路径,查找过程中可以节约大量的时间。

  • 自定义模块

    1. 自定义模块指的是非核心模块,也不是路径形式的标识符。
    2. 这是一种最费时间的查找方式。
    3. 它的加载方式与原型链或者作用域链的方式十分相似。在加载过程中,Node会从当前文件目录开始,往外一层一层查找,直到找到目标文件为止。
    4. 这种方式是最耗时间的,是自定义模块加载速度最慢的原因。
2.3 文件定位
  • 文件扩展名分析

    1. 当标识符不包含文件扩展名时,Node会按照.js、.json、.node的顺序补全扩展名依次尝试。

    2. 在尝试的过程中,会调用fs模块以同步阻塞的形式判断文件是否存在。

    3. 如果是.node或者.json文件,在引入时加上扩展名,会提高速度。

    4. 同步配合缓存,可以大幅度环节Node单线程中阻塞式调用的缺陷。

  • 目录分析和包

    1. 当Node在查找时没有找到对应模块,但是得到了一个目录,此时Node就会把他当成包来处理。
    2. 首先,Node在当前目录下查找package.json文件,通过JSON.parse方法解析出包的描述对象,取出main属性指定的文件名进行扩展名分析定位。
    3. 如果main属性指定的文件名错误,或者没有package.json,则额Node会将index作为默认的文件名,进行扩展名分析定位。
    4. 如果目录分析过程失败了,则自定义模块就会进入下一个模块路径进行查找。如果所有路径都遍历却没有找到,就抛出查找失败异常。
2.4 模块编译
  • 编译和执行式引入文件模块后的最后一步。

  • 定位到文件后,Node会新建一个模块对象,然后根据路径载入编译。

  • 不同的拓展名有着不同的载入方法:

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

    json:通过fs模块同步读取文件后,用JSON.parse()方法解析返回的结果。

    node:这是C/C++编写的扩展文件,通过dlopen()方法加载编译后生成的文件。

    其他:当作js文件导入。

  • 每个编译成功后的模块会将其路径作为索引缓存在Module._cache对象上,提高二次引用的性能。

  • JavaScript模块编译

    1. 在编译过程中,Node对获取的JavaScript文件进行头尾包装。将整个JS模块用function(exports,require,module,__filename,__dirname){}进行包围;
    2. 这样做,每个模块文件之间都进行了作用域隔离;
    3. 包装后的代码会通过vm原生模块的runInThisContext方法执行,返回一个具体的function对象;
    4. 最后,将当前对象的exports属性、require方法、module和文件定位中得到的完整文件路径和文件目录作为参数传入。

    这就是为什么模块里没有定义却有exports、require等。执行后,模块的exports属性被返回给了调用方,exports属性上的任何方法和属性都可以被外部调用到,但模块里的其余变量却不能直接调用。

  • C/C++模块编译

    1. Node调用process.dlopen()方法进行加载执行。
    2. .node模块不需要编译,因为他是编写C/C++模块之后生成的,所以只需要加载和执行。
    3. 执行过程中,模块的exports对象与.node模块产生联系,然后返回给调用者
  • JSON文件的编译

    1. Node利用fs模块同步读取JSON文件里的内容后,调用JSON.parse方法得到对象,然后将他赋值给模块对象的exports,供外部调用。
    2. JSON文件适合作项目的配置文件。当你用JSON文件作为配置文件时,无需再调用fs模块去异步读取和解析,直接进行require既可。这么做可以享受到模块缓存带来的优势。
2.5 包与NPM
  • CommonJS的包规范有包结构和包描述文件两个部分组成,前者用于组织包内的各种文件,后者用于描述包的相关信息。

  • 包结构

    包是一个存档文件,即将一个目录直接打包成.zip或者tar.gz文件,安装后解压文件至目录。

    完整的包目录需要包含以下文件

    package.json:包描述文件

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

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

    doc:用于存放文档的目录

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

  • 包描述文件

    包描述文件用于表达非代码相关的信息——package.json。

    包的规范定义可以帮助Node解决依赖包安装的问题,而NPM正是基于此规范进行了实现。

    在包描述文件中,NPM需要的字段有:name、version、description、keywords、repositories、author、bin、main、scripts、engines、dependencies、devDependencies。

3. AMD规范
  • AMD规范是CommonJS模块规范的一个延申,适用于前端场景。

  • 它的模块id和依赖实可选的,与Node模块相似的地方在于factory内容就是实际代码的内容。

    define(id?,dependencies?,factory);
    // 举个例子
    define(function(){
        var exports = {};
        exports.sayHello = function() {
            return 'hello';
        };
        return exports;
    });
    
  • 与Node不一样的是,AMD模块需要用define来明确定义一个模块,而Node是隐式包装的,目的是进行作用域隔离;

  • 还有一个区别就是AMD模块需要显示地返回exports对象。

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

  • AMD规范需要在声明的时候指定所有的依赖:

    define(['dep1','dep2'],function(dep1,dep2){
        return function(){};
    })
    
  • 而CMD规范支持动态引入依赖,可以随时调用require进行引入。

    define(function(require,exports,module){
        // 模块代码
    })
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值