Node的模块系统

505 阅读6分钟

1. 什么是模块

  • 一个具有特定功能的文件就是一个模块

  • 模块的优点: 有了模块,我们就可以非常方便地使用这些模块,因为模块总是完成了特定的功能,如果要修改模块中个功能,那么只需要修改这个自己的模块文件即可,模块独立于每一个文件中,不影响模块的代码。模块独立于每一个文件中,不影响模块的代码

  • 模块之间相互独立,如果一个模块引入另一模块,要使用里面的值,我们就需要在被引入的模块中暴露这些值 2. 模块化

  • 将一个复杂的程序依据一定的规则(规范)封装成几个模块(文件),并进行组合在一起,每个模块内部数据实现是私有的, 只是向外部暴露一些接口(方法)与外部其他模块进行通信

  • 按照模块化思考开发的项目, 也被称为模块化开发

  • 模块化的发展

    ① 全局开发模式

    // 最早期所有的js代码写在一个js文件中
    function foo(){}
    function bar(){}
    // 造成的问题,就是代码量过大以后,Global全局被污染,很容易导致命名冲突
    

    ② 命名空间

    对代码进行简单的封装,减少global变量的数量

    // 简单封装: Namespac 模式, 就是我们俗称的命名空间
      var Namespac = {
          foo: function(){},
          bar: function(){}
      }
    // 本质就是对象,不太安全
    

    ③ IIFE模式:是一个在定义时就会立即执行的JavaScript函数。

    var Module = (function(){
    var foo = function(){
        
    }
    
    return {
        foo: foo
    }
    })()
    
    Module.foo()
    

3. 为什么要模块化

  • 降低复杂度

  • 提高解耦性

  • 部署方便

4. 模块化的好处

  • 避免命名冲突(减少命名空间的污染)
  • 更好的分类,按需加载
  • 更高的复用性
  • 高可维护性

5. 模块的规范

  • commenJS规范,node.js采用的规范
  • AMD
  • CMD
  • ESModule(ES模块化)

6. Node.js模块化

  • 采用commonjs模块系统
  • 导入模块:require全局函数
  • 导出模块:module.exports
  • require方法导入本地的某个文件组件的话,一定要加上盘符前缀,即使在同一目录下
  • node内部提供一个Module构建函数,所有的模块都是Module的实例 7. module对象
  • module.id 模块的识别符,通常是带有绝对路径的模块文件名
  • module.filename 模块的文件名,带有绝对路径
  • module.loaded 返回一个布尔值,表示模块是否已经完成加载
  • module.parent 返回一个对象,表示调用该模块的模块
  • module.children 返回一个数组,表示该模块要用到的其他模块
  • module.exports 表示模块对外输出的值

8. exports变量

  • 为了方便,Node为每个模块提供了一个exports变量,指向module.exports,这等同于在每个模块头部,有一行这样的命令(只是等同于,并不是真的有该行代码)[参考指针作用]

    var exports = module.exports

  • 不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系

    	// 导出name变量
    exports.name="张三"
    
    // 导出对象
    let student = {
      name:'张三',
      age:18
    }
    exports.student = student;
    
    // 对外输出模块接口时,可以向exports对象添加方法。
    exports.eara = function(r){
      return Math.PI*r*r;
    }
    exports.circumference = function(r){
      return 2*Math.PI*r;
    }
    
    // 注意,不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系
    exports = function(x){
      console.log(x);
    }
    
        var aa = require('./common')
        console.log(aa.name);
    
        var bb = aa.student
        console.log(bb);
    
        var box = aa.eara(5)
        console.log(box);
        var box2 = aa.circumference(2)
        console.log(box2);
    
        var f = aa
        console.log(aa);
    

9. require指令

  • require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象,如果没有发现指定模块,就会得到一个空对象
  • require命令用于加载文件,后缀名默认为.js,根据参数的不同格式,require命令去不同路径寻找模块文件
  • 如果参数字符串以"/"开头,则表示加载的是一个位于绝对路径的模块文件 require('/user/cors/fun.js')
  • 如果参数字符以"./"开头,则表示加载的是一个位于相对路径(跟当前执行脚本的位置相比)的模块文件 require('./fun.js')
  • 如果参数字符串不以"./"或者"/"开头,则表示加载的是一个默认提供的核心模块(位于Node的系统安装目录中),或者一个位于各级node_modules目录的已安装模块(全局安装或局部安装)
  • 通常,我们会把相关的文件放在一个目录里面,便于组织,这时,最好为该目录设置一个入口文件,让require方法可以通过这个入口文件,加载整个目录。require发现参数字符串指向一个目录以后,会自动查看该目录的package.json文件,然后加载main字段指定的入口文件。

10. 模块的缓存

  • 第一次加载某个模块时,Node会缓存该模块,以后再加载该模块,就直接从缓存中取出该模块的module.exports属性 上面代码中,连续三次使用require命令,加载同一个模块,第二次加载的时候为输出的对象添加了一个message属性,但是第三次加载的时候,这个message属性依然存在,这就证明require命令并没有重新加载模块文件,而是输出了缓存。

  • 如果想要多次执行某个模块,可以让该模块输出一个函数,然后每次require这个模块的时候,重新执行一下这个输出的函数

  • 所有缓存的模块都保存在require.cache中,如果想要删除模块的缓存,可以用下面的写法

  • 注意,缓存时根据绝对路径来识别模块的,如果是同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块

11. 环境变量NODE_PATH

  • node执行一个脚本时,会先查看环境变量NODE_PATH,它是一组以冒号分隔的绝对路径,在其他位置找不到指定模块时,可以将NODE_PATH添加到.bashrc

12. 模块的循环加载

  • 如果发生模块的循环加载,即A既加载B,B又加载A,则B将加载A的不完整版本

  • require方法有一个main属性,可以用来判断模块是直接执行,还是被调用执行

    • 直接执行的时候,require.main属性指向模块本身(node module.js) require.main === module
    • 调用执行的时候,返回false(通过require加载执行) 13. 模块的运行机制
  • commonjs模块的加载机制

输入的是被输出的值的拷贝,也就是说,一旦输出一个值,模块内部的变化就是影响不到该值(仅仅是普通的值,如果是引用类型的值,还是会受影响的)

14. require的内部处理流程

  • require命令是commonjs规范中,用来加载其他模块的命令,它不是一个全局命令,而是指向当前模块的module.require命令,而后者又调用Node的内部命令Module._load

    第四步,采用module.compile()执行指定模块的脚本

    第一步和第二步,require函数执行如下:

    1. require():加载外部模块
    2. require.resolve():将模块名解析到一个绝对路径
    3. require.main:指向主模块
    4. require.cache:指向所有缓存的模块
    5. require.extensions:根据文件的后缀名,调用不同的执行函数