zoukankan      html  css  js  c++  java
  • Node: 模块

    我们知道,Node.js 选用 JavaScript 语言来编写代码。JavaScript 这门语言呢,之前主要用于前端应用,并没有相应的模块管理功能,而是以 script 标签为单位,直接引入即可运行。Node.js 主要运行在后端,这怎么办呢?好在它借鉴了 CommonJS 中的 Modules 规范,实现了一套易用的模块系统。今天,我们就来介绍一下 Node.js 中的模块。

    举个栗子

    下面我们写一段代码,封装一个模块,这个模块包含一个方法,对指定的人返回一句问候语:

    // greeting.js
    
    // 问候一声
    exports.greet = (name) => {
      return `hello ${name}`;
    };
    

    看上去挺简单的,直接在 exports 上面定义一个 greet 方法,即可将该方法暴露出去。

    然后,我们在主文件中引入该模块,并调用它的 greet 方法:

    // main.js
    
    const greeting = require('./greeting');
    
    console.log(greeting.greet('Scott'));
    

    在引入该模块时,我们使用了相对路径 ./greeting,Node.js 运行时会根据这个路径,找到我们自定义模块文件,然后编译执行。

    最后,在命令行中运行 main.js,程序会输出运行结果:

    $ node main.js
    hello Scott
    

    一探究竟

    下面,我们就来分析一下 Node.js 中的模块系统。

    Node.js 中的模块分为 内置模块自定义模块 两大类,而引用一个模块时一般会经历 路径分析文件定位编译执行 三个步骤。

    需要注意的是,如果我们引入内置模块,只需要 路径分析 这一个步骤,这是因为,内置模块在 Node.js 进程启动时,就已经被加载进内存了,可以直接引用,并且优先进行路径分析,所以不再需要 文件定位编译执行 了。

    引入内置模块时,只需指定模块名即可:

    const http = require('http');
    

    如果是引入自定义模块,则包含以下几种形式:

    • 使用 ... 起始的相对路径
    • 使用 / 起始的绝对路径
    • 使用第三方模块名

    如果使用了 相对路径绝对路径require(module) 会根据模块路径读取相应的文件,然后编译执行,并将结果放入缓存,下次引入时直接从缓存中取结果。

    如果我们引入一个第三方模块,例如 connect 模块,这时候,我们的引入方式和内置核心模块是类似的:

    const connect = require('connect');
    

    首先,Node.js 会先按内置模块查找,但发现它并不在内置模块名单中,所以接下来,要在 模块路径 中查找该模块对应的包。

    这里提到了一个概念:模块路径。它是 Node.js 定位文件模块的一种查找策略,表现形式是包含多个路径的一个数组。

    为了验证这个数据结构,我们在代码中添加下面一行:

    console.log(module.paths);
    

    运行代码后,我们会得到下面输出内容:

    [ '/Users/Scott/learning/node_modules',
      '/Users/Scott/node_modules',
      '/Users/node_modules',
      '/node_modules' ]
    

    可以看到,模块路径列举了这些目录:当前目录下的 node_modules 目录、父级目录下的 node_modules 目录、祖先目录下的 node_modules 目录、根目录下的 node_modules 目录。

    在加载过程中,Node.js 会按照这个路径数组,逐个进行尝试,直到找到目标模块文件或包为止。

    这里需要提醒一下,在使用 require(module) 时,如果参数不包含后缀名,Node.js 会按照 .js.node.json 次序补足后缀名,然后逐个尝试以单线程同步形式加载,所以,对于非 .js 后缀名的文件,引入时最好加上后缀名,以提高加载的速度。

    回到我们最开始的一个小例子,这个程序中的 greeting.js 模块,通过 exports.greet 的形式导出一个方法,为什么能这样写呢,exports 是从哪里来的呢?

    原来,Node.js 在加载一个模块时,会首先对它进行编译,在这个过程中,进行了一次头尾的包装,我们上面例子中的模块,经过包装之后是这个样子的:

    (function (exports, require, module, __filename, __dirname) {
      exports.greet = (name) => {
        return `hello ${name}`;
      };
    });
    

    前面我们直接拿来用的 exportsrequire 以及 module,原来是这么来的,还有 __filename__dirname,它们并不是全局变量,而是由包装函数传递进来的,表示当前模块文件的路径和目录。

    经过上面的包装,我们的每个模块都有了自己的作用域,包装之后的代码会由 vm 原生模块的 runInThisContext() 方法执行,同时将包装函数所需的参数传递进去。

    默认情况下,exportsmodule.exports 都指向一个空对象引用,即:

    module.exports = exports = {};
    

    所以上面我们给 exports 添加了一个方法,同时也会改变 module.exports

    当然,我们也可以使用下面这种方式,改变导出的引用对象:

    module.exports = {
      greet: (name) => {
        return `hello ${name}`;
      }
    };
    

    但注意,不要在模块内使用下面这种方式:

    // 无效的导出方式
    exports = {
      greet: (name) => {
        return `hello ${name}`;
      }
    };
    

    原因在于它更改的只是形参的引用,却对实参引用没有做任何更改,所以是无效的导出方式。

  • 相关阅读:
    C语言 选择排序算法原理和实现 从数组中 找出最小的元素然后交换位置
    C语言十六进制转换成十进制:要从右到左用二进制的每个数去乘以16的相应次方
    pycharm的注册码,所有版本
    无法链接glew的解决办法-编译开源库出现: error LNK2001: 无法解析的外部符号
    删除文件是遇到“拒绝访问”的解决方法
    基类的析构函数写成virtual虚析构函数
    C++语言定义的标准转换
    VC中C++数值范围的确定
    SCI投稿过程总结、投稿状态解析、拒稿后对策及接受后期相关问答
    STL其他--<tuple>用法【C11】
  • 原文地址:https://www.cnblogs.com/liuhe688/p/11402931.html
Copyright © 2011-2022 走看看