commonJS 包会系列

675 阅读4分钟

commonJS核心

导出

commonJS 里我们可以使用exports导出,也可以通过module.exports导出,那么它们到底什么关系,曾经我一直一脸懵b,现在终于是搞明白了~~ 嗝

其实在commonJS规范里是没有module.exports这个导出规则的,只有通过exports进行导出。module的本质是node的一个构造函数,我们在node环境下创建一个js文件的本质就是实例化一个module,别不相信,事实就是这样。

node里之所以可以使用module.exports导出,是因为node做了这样的处理: exports = module.exports = {},没错,就是引用赋值。所以我们在使用module.exports本质还是使用exports
注意:node的模块里最终导出的是module.exports 而非exports

如:

    
    // 模块:bar.js
    let name = 'jack'
    let age = 12
    function getSum() {
      console.log('getSum')
    }  
    
     // 导出方式1
    exports.name = 'jack'  
    exports.age = 12  
    exports.getSum = function(){}
    
    // 导出方式2
    module.exports = {
      name,
      age,
      getSum
    }


导入

commonJS里通过require()函数导入
如上面bar.js模块导出的内容可以这样导入:

// 模块:index.js  

// 方式1:导入的是一个对象
let bar = require('./bar.js')
console.log(bar)  // { name: 'jack', age: 12, getSum: [Function: getSum] }

// 方式2: 将导入的对象直接解构导入

let {name, age, getSum } = require('./bar.js')

console.log(name, age, getSum) // jack 12 [Function: getSum]

练习1:index.js里输出的值是什么

// 模块1 : bar.js
let name = 'jack'
let age = 12

exports.name = name
exports.age = age

exports = {}
// 模块2 : index.js
let bar = require('./bar.js')

console.log(bar)  



答案是: // {name: 'jack', age: 12}

分析:我们可以想象在模块的第一行有一行代码: exports = module.exports = {},相当于module.exports 和exports指向同一个引用地址,此时为一个空对象

执行完

let name = 'jack' 
let age = 12 
exports.name = name
exports.age = age

时,这个空对象有了name和age属性及值,即{name: 'jack', age: 12}

执行完exports = {} 时,exports不再指向上面说的那个对象,而是指向一个空对象,即此时只有module.export 指向上面所述的对象。

而我们说过node的模块里最终导出的是module.exports 而非exports。所以这个模块最终导出的是{name: 'jack', age: 12} 而非{}
这里涉及到js的按引用赋值知识;


练习2: index.js里输出的值是什么?

// 模块:bar.js

  let name = 'jack'
  let age = 12
  function getSum() {
    console.log('getSum')
  }
  exports = {
    name,
    age,
    getSum
}

// 模块:index.js  

let {name, age, getSum } = require('./bar.js')

console.log(name, age, getSum)




答案是  // undefined undefined undefined  原理同上一题

commonJS导入模块查找规则:

    1. 情况1:内置模块

let url = require('url')
如果导入的模块为node内置模块,则会直接导入;因为url是node的内置模块,直接导入;

  • 情况2:自定义模块(以./ 或者 ../ 或者 / 开头)

let url = require('./home')

(1)会在对应得目录找是否有home文件,如果有,则导入;

(2)如果没有home文件,则会在对应目录下依次查找home.js,home.json,home.node这三个文件,如果查找到其中一个文件,则导入。(这也是为什么我们在使用commonJS时,导入模块不写后缀,却依然正常导入的原因);

(3)如果没有home.js,home.json,home.node文件,则会将home当做文件夹进行查找,如果有home文件夹,则依次查找home文件夹里的index.js,index.json,index.json,如果查找到这三个文件里的其中一个,则导入(这也是为什么我们在导入index.js文件时,有时候并不会精确到index.js,而是只写到index.js所在的目录的原因);

如果上面三种情况都没有查到对应文件,则报错;

  • 情况3:第三方模块

如导入第三方模块axios: let server = require('axios')

因为axios不是node的内置模块,不符合情况一和情况二的查找规则,所以按照第三方模块进行查找

(1)在当前文件所在的目录下查看是否有node_modules文件夹,如果有,则在其中查找axios模块,查到,则导入;

(2)如果当前文件所在的目录下没有node_modules文件夹,则在当前文件的上一级目录查找node_modules文件夹,如果有,则在其中查找是否有axios模块,查到,则导入;

(3)如果上一级目录还没有node_modules 文件夹,则继续查找上一级......以此类推,直到查到根目录为止,如果最终没查到则报错,查到,则导入;

这也是为什么我们的项目依赖(node_modules)总是放在项目的根目录下的原因,因为这样,项目里的所有文件都可以导入node_modules里的依赖

小技巧:我们可以在文件里执行 console.log(module.paths) 代码,输出结果就是当前模块加载第三方模块的目录组成的数组,会依次按照这个数组进行查找