JavaScript模块化总结(二)

130 阅读4分钟

「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。

在上篇文章中,我们介绍了为什么要使用模块化JavaScript模块化总结(一)

我们知道,模块化有很多规范,包括AMD,CMD,CommonJs,以及ES6新增的模块化等等。如今,常用的是CommonJS和ES6新增的模块化。本篇文章主要介绍commonJS规范。

CommonJS规范和node关系

CommonJS是一种规范,它在服务器端比较有代表性的实现是node。node中使用的js是符合CommonJS规范的。可以说,node是CommonJS规范的一种实现。

CommonJS最初提出来是在浏览器以外的地方使用的,并且那时候被命名为ServerJS。后来它被使用在其他地方,为了体现它的广泛性,修改为CommonJS。

我们知道,node中是支持CommonJS的,可以让我们很方便地进行模块化开发。在node中,每一个js文件都是一个模块。我们可以使用exportsmodule.exportsrequire进行模块化开发。

模块化的核心是导入导出。node中对其进行了实现:exports和module.exports可以对模块中的内容进行导出。require函数可以导入其他模块(自定义模块、系统模块、第三方库模块)中的内容。

CommonJS的基本使用

我们在haha.js文件中定义了一些变量。

const name = 'haha'
const age = 18
function sum(num1, num2) {
  return num1 + num2
}

然后,想要在main.js其他文件中使用这些变量,我们可以直接使用吗?

console.log(name);

我们可以看到报错了。这是因为每个js文件是一个模块,每个模块有自己的独立空间。其他文件是访问不到我自己的独立空间的。

image.png 那么要使用其他模块的变量该怎么办呢?我们可以通过CommonJS规范使用module.exports对内容进行导出,在其他文件中使用require对其他文件导出的内容进行导入。

module.exports是个对象。module是模块本身的对象,exports也是个对象。

在haha.js中,我们可以给module.exports添加属性,该属性为要导出的内容。也可以给module.exports重新赋值为一个新的对象,对象中包含着要导出的内容。

// module.exports.name = name
module.exports = {
  name,
  age,
  sum
}

在main.js中,通过require(path)来接收haha.js中导出的对象

const haha = require('./haha')
console.log(haha.name);
console.log(haha.age);

我们也可以使用对象的解构。

const {name, age} = require('./haha')
console.log(name);
console.log(age);

需要注意的是,module.exports导出的对象和require接收的对象是同一个,在一个文件中进行修改,那么另一个文件中的内容也会相应跟着修改。

// haha.js文件
const info = {
  name: 'haha',
  age: 18,
  foo: function () {
    console.log('foo函数');
  }
}

setTimeout(() => {
  // info.name = 'xixi'
  console.log(info.name);
}, 2000)

module.exports = info
// main.js文件
const haha = require('./haha')
setTimeout(() => {
  // console.log(haha.name);
  haha.name = 'xixi'
},1000)

我们可以看到在main.js中修改导入的对象的属性,在haha.js中导出的对象的属性也被修改了。

exports导出

每个模块都有exports对象,这个exports默认指向的是空对象,可以在这个对象中添加需要导出的属性。

image.png bar和exports是同一个对象,bar对象是exports对象的浅拷贝。浅拷贝的本质是引用赋值。exports对象中的修改会导致main中bar对象也会被修改。

// haha.js文件
const name = 'haha'
const age = 18
function sum(num1, num2) {
  return num1 + num2
}

exports.name = name
exports.age = age
exports.sum = sum
// main.js文件
const haha = require('./haha')
console.log(haha.name);
console.log(haha.age);
console.log(haha.sum(20, 30));

在node中,我们还可以通过module.exports进行导出。它们之间有什么关系呢?

  • commonjs中是没有module.exports的概念的
  • 但是为了实现模块的导出,node使用的是Moodule的类,每一个模块都是Module的一个实例,也就是module。 new Module() => 一个文件就是一个module实例。这个module实例就是js文件另外的一个特殊全局对象module。在所有文件里面都有一个module对象。
  • 所以在node中真正用于导出根本不是exports,而是module.exports
  • 因为module才是导出的真正实现者

image.png

  • 在三者引用的情况下,修改了exports中的name属性,module.exports和main中的bar对象的name属性也会改变
  • 在三者引用的情况下,修改main中bar的name属性,另外两个地方也会改变
  • 如果module.exports不再引用exports对象, 那么修改exports没有意义。 需要了解的是,node中真正导出的是module.exports,而不是exports。那为什么exports也能导出呢?

那是因为module对象的exports属性是exports对象的一个引用。在例子中,也就是module.exports = exports = main中的haha。