Sự khác nhau giữa exports và module.exports

Module là một khái niệm mà có lẽ bất kì ai code nodeJS đều đã sử dụng và sử dụng cực kỳ thường xuyên. Vậy bạn đã bao giờ thắc mắc ông tác giả sinh ra module.exports sao còn phải đẻ thêm thằng exports cho dài dòng?

  • Bắt đầu bằng một vài ví dụ mà bạn thường hay dùng, đầu tiên là module.exports:
// math.js
const sum = (a, b) => {
  return a + b;
};

const subtract = (a, b) => {
  return a - b;
};

module.exports.sum = sum;
module.exports.subtract = subtract;

//index.js
const math = require("./math");

console.log(math.sum(2, 3)); // 5
console.log(math.subtract(2, 3)); // -1
  • và exports:
// math.js
const sum = (a, b) => {
  return a + b;
};

const subtract = (a, b) => {
  return a - b;
};

exports.sum = sum;
exports.subtract = subtract;

//index.js
const math = require("./math");

console.log(math.sum(2, 3)); // 5
console.log(math.subtract(2, 3)); // -1
  • Mọi thứ đều giống nhau 🥹
  • OK vậy nếu như mình export bằng cách gán cho biến module.exports thì sao?
// math.js
const sum = (a, b) => {
  return a + b;
};

const subtract = (a, b) => {
  return a - b;
};

module.exports = {
  sum,
  subtract,
};

//index.js
const math = require("./math");

console.log(math.sum(2, 3)); // 5
console.log(math.subtract(2, 3)); // -1
  • OK em vẫn ổn, còn exports?
// math.js
const sum = (a, b) => {
  return a + b;
};

const subtract = (a, b) => {
  return a - b;
};

exports = {
  sum,
  subtract,
};

//index.js
const math = require("./math");

console.log(math.sum(2, 3)); // TypeError: math.add is not a function
console.log(math.subtract(2, 3)); // TypeError: math.subtract is not a function
console.log(math); // {}
  • Ném lỗi vào mặt liền, tại sao lại như vậy?

Object references

Các bạn đã biết object trong javascript là một kiểu dữ liệu dạng tham thiếu. Khi bạn gán một biến a là kiểu object cho một biến b, bản chất cả hai biến a và b đều đang trỏ đến cùng một địa chỉ vùng nhớ trong heap.

const hero1 = {
  name: "Superman",
};

const hero2 = hero1; // Create a reference to hero1

hero2.name = "Batman"; // Modify hero2 name

console.log(hero1); // { name: 'Batman' } hero1 name is updated

Như đoạn code phía trên vì cả hai biến cùng tham chiếu vào cùng một địa chỉ, nên khi biến hero2 thay đổi thuộc tính, thì biến hero1 cũng sẽ bị thay đổi theo.

Có một vài cách để break ra khỏi tham chiếu chung đó, bạn có thể search thêm về: shallow/deep copy, spread operator

Quay lại với module.exports và exports. Vậy sự khác nhau giữa chúng là gì?

var module = { exports: {} };

var exports = module.exports;

// Assigning exports to module.exports via object reference...

exports.sum = sum; // ...results in module.exports.sum = sum
exports.subtract = subtract; // ...results in module.exports.subtract = subtract

// The module.exports object contains both add and subtract properties

return module.exports;

Bạn đã dần hiểu ra vấn đề rồi đúng không, bản chất module chính là một object có chứa thuộc tính exports, và biến exports dòng thứ hai là một alias tham chiếu đến thuộc tính exports của biến module.

Khi import một module bằng từ khóa require(''") thì chỉ có thằng module.exports được trả về nên nếu bạn gán cho biến exports = { add } thì có nghĩa là bạn đang gán một object mới cho biến exports lúc này tham chiếu của biến exports đến module.exports sẽ bị break.

Hồi kết

Bạn có thể dùng thằng nào cũng được, nhưng luôn nhớ exports chỉ là alias của module.exports nên không sử dụng phép gán đối với exports là được. See you!!

Tham khảo