Q8: exports 和 module.exports 有什么区别?

19 阅读2分钟

Node.js 面试题详细答案 - Q8

Q8: exports 和 module.exports 有什么区别?

基本概念

exports
  • exportsmodule.exports 的引用
  • 默认指向同一个对象
  • 用于添加属性到模块导出
module.exports
  • 模块的真正导出对象
  • 可以完全替换导出内容
  • 优先级高于 exports

基本区别

使用 exports 添加属性
// 使用 exports 添加属性
exports.name = 'MyModule'
exports.version = '1.0.0'
exports.sayHello = function () {
  return 'Hello from MyModule'
}

// 等价于:
module.exports.name = 'MyModule'
module.exports.version = '1.0.0'
module.exports.sayHello = function () {
  return 'Hello from MyModule'
}
使用 module.exports 替换导出
// 使用 module.exports 替换整个导出
module.exports = {
  name: 'MyModule',
  version: '1.0.0',
  sayHello: function () {
    return 'Hello from MyModule'
  },
}

// 此时 exports 仍然指向原来的对象
// 但模块导出的是新的对象

详细示例

示例 1:exports 添加属性
// module1.js
exports.name = 'Module1'
exports.getValue = function () {
  return 42
}

// main.js
const module1 = require('./module1')
console.log(module1.name) // 'Module1'
console.log(module1.getValue()) // 42
示例 2:module.exports 替换导出
// module2.js
module.exports = {
  name: 'Module2',
  getValue: function () {
    return 100
  },
}

// main.js
const module2 = require('./module2')
console.log(module2.name) // 'Module2'
console.log(module2.getValue()) // 100
示例 3:混合使用的问题
// module3.js
exports.name = 'Module3' // 添加属性

// 后面替换了导出
module.exports = {
  version: '2.0.0',
}

// main.js
const module3 = require('./module3')
console.log(module3.name) // undefined (被覆盖了)
console.log(module3.version) // '2.0.0'

引用关系

初始状态
// 模块加载时的初始状态
console.log('exports === module.exports:', exports === module.exports) // true
console.log('exports:', exports)
console.log('module.exports:', module.exports)
修改后的状态
// 修改 exports 后
exports.name = 'Test'
console.log('exports === module.exports:', exports === module.exports) // true
console.log('exports:', exports)
console.log('module.exports:', module.exports)

// 替换 module.exports 后
module.exports = { version: '1.0.0' }
console.log('exports === module.exports:', exports === module.exports) // false
console.log('exports:', exports)
console.log('module.exports:', module.exports)

实际应用场景

场景 1:导出多个函数
// utils.js
exports.add = function (a, b) {
  return a + b
}

exports.subtract = function (a, b) {
  return a - b
}

exports.multiply = function (a, b) {
  return a * b
}

// 使用
const utils = require('./utils')
console.log(utils.add(5, 3)) // 8
console.log(utils.subtract(5, 3)) // 2
console.log(utils.multiply(5, 3)) // 15
场景 2:导出单个函数
// logger.js
module.exports = function (message) {
  console.log(`[${new Date().toISOString()}] ${message}`)
}

// 使用
const logger = require('./logger')
logger('Hello World')
场景 3:导出类
// User.js
class User {
  constructor(name, email) {
    this.name = name
    this.email = email
  }

  getName() {
    return this.name
  }

  getEmail() {
    return this.email
  }
}

module.exports = User

// 使用
const User = require('./User')
const user = new User('John', 'john@example.com')
console.log(user.getName()) // 'John'
场景 4:导出配置对象
// config.js
module.exports = {
  database: {
    host: 'localhost',
    port: 5432,
    name: 'myapp',
  },
  server: {
    port: 3000,
    host: '0.0.0.0',
  },
  api: {
    version: 'v1',
    baseUrl: '/api',
  },
}

// 使用
const config = require('./config')
console.log(config.database.host) // 'localhost'
console.log(config.server.port) // 3000

常见错误

错误 1:直接赋值给 exports
// 错误做法
exports = {
  name: 'MyModule',
  version: '1.0.0',
}

// 正确做法
module.exports = {
  name: 'MyModule',
  version: '1.0.0',
}
错误 2:混合使用导致混乱
// 混乱的做法
exports.name = 'MyModule'
module.exports = { version: '1.0.0' }
exports.description = 'A test module' // 这行不会生效

// 清晰的做法
module.exports = {
  name: 'MyModule',
  version: '1.0.0',
  description: 'A test module',
}

最佳实践

1. 统一使用 module.exports
// 推荐:统一使用 module.exports
module.exports = {
  name: 'MyModule',
  version: '1.0.0',
  methods: {
    method1: function () {
      return 'method1'
    },
    method2: function () {
      return 'method2'
    },
  },
}
2. 使用 exports 添加属性
// 推荐:使用 exports 添加属性
exports.name = 'MyModule'
exports.version = '1.0.0'
exports.method1 = function () {
  return 'method1'
}
exports.method2 = function () {
  return 'method2'
}
3. 导出函数或类
// 推荐:导出函数
module.exports = function createUser(name, email) {
  return { name, email }
}

// 推荐:导出类
module.exports = class User {
  constructor(name, email) {
    this.name = name
    this.email = email
  }
}

总结

  • exportsmodule.exports 的引用,用于添加属性
  • module.exports:真正的导出对象,可以完全替换
  • 优先级module.exports 优先级更高
  • 最佳实践:统一使用 module.exportsexports,避免混合使用
  • 常见错误:直接赋值给 exports 不会生效
  • 适用场景:根据导出内容选择合适的导出方式