前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库ESM 导入对象属性修改是否可见
JavaScript模块与语言机制

导出方改了导出对象的属性,导入方能看到吗?

能看到。模块只执行一次,所有导入方拿到的是同一个对象,谁改了属性大家都能看到,这一点 ESM 和 CommonJS 一样。两者的区别在于导出方把变量整个换成一个新对象时:ESM 的导入方能看到新对象,CommonJS 解构出来的变量还指着旧对象。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#模块与语言机制
先看核心答案读代码示例
理解线索

两种「变化」要分开看

  1. 改对象内部obj.n = 1、arr.push(x),引用没变,谁都能看到
  2. 重新赋值绑定obj = {},只有 ESM 的实时绑定能反映出来
  3. 导入方的修改不能给导入名赋值,但能改它指向对象的属性

导出一个可变对象,等于把它的写权限也交给了所有导入方。任何一个模块随手改一下,其它模块读到的数据就变了,而且很难查是谁改的。需要保护时,导出前 Object.freeze,或者只导出读取和修改的函数。

核心回答

先记住这个答案

能看到。模块的顶层代码只执行一次,导出的对象在内存里只有一份,所有导入方拿到的都是它的引用。导出方后来改属性、往数组里 push,导入方下次读就是新内容。这靠的是对象引用共享,和普通函数之间传对象是一样的道理。要和它区分开的是「导出方把变量整个赋值成一个新对象」,这时只有 ESM 的实时绑定能让导入方看到新对象。

  • 导出对象只有一份,改属性所有导入方都可见
  • 这是对象引用共享,和实时绑定是两回事
  • 整个重新赋值时,ESM 跟着变,CommonJS 解构的不变
  • 导入方也能改对象内部,这往往是 bug 的来源

改属性和整体替换

store.js 导出一个对象,并提供两个函数,一个改属性,一个直接换成新对象。

ESM 里的两种修改JavaScript
// store.js
export let state = { list: [] }
export function add(item) {
  state.list.push(item) // 改对象内部
}
export function reset() {
  state = { list: [] } // 整个换成新对象
}

// main.js
import { state, add, reset } from './store.js'
const saved = state // 存一份当前引用

add('a')
console.log(state.list, saved.list) // [ 'a' ] [ 'a' ],同一个对象
reset()
console.log(state.list, saved.list) // [] [ 'a' ],state 指向了新对象

add 之后,state 和 saved 指着同一个对象,都能看到 'a'。reset 之后,导出方的 state 变量指向了新对象,导入方的 state 是实时绑定,也跟着指过去了;saved 只是之前存的一个引用,还指着旧对象。

换成 CommonJS 会怎样

CommonJS 导出的是 module.exports 这个对象,导入方拿到的是它本身,常见写法是 const { state, add, reset } = require("./store")。

改属性一样,整体替换不一样
改属性的情况和 ESM 完全一样,因为解构出来的 state 和导出方指向同一个对象。 整体替换就不一样了。解构发生在 require 那一刻,state 被复制成一个普通局部变量。导出方后来即使执行了 module.exports.state = { list: [] },这边的 state 还是旧对象。想拿到最新值,只能不解构,每次写 store.state 去读。 所以面试里常说的「ESM 是值的引用,CommonJS 是值的拷贝」,准确意思是:CommonJS 导入时把导出对象上的属性值取了出来,对象类型的值拷贝的是引用,所以改属性依然共享;区别只在重新赋值这一种情况。
回答前,多想一步

容易答错的地方

以为 CommonJS 导出对象就完全隔离
很多人把「CommonJS 是拷贝」理解成深拷贝,以为导出方改了对象属性导入方看不到。实际拷贝的只是引用,对象内部的修改照样共享。
以为导入方改不了导出对象
导入绑定只读,指的是不能写 state = {}。但 state.list.push("x") 完全合法,其它模块都会受影响。这是模块间共享可变状态最常见的坑:某个组件为了方便直接改了导入的配置对象,另一个页面莫名其妙出了问题。
试着用自己的话回答

面试官还会怎么问?

怎么防止导入方修改导出的对象?

最直接的是导出前冻结,export const config = Object.freeze({ ... }),注意 Object.freeze 是浅冻结,嵌套对象要递归处理。更好的做法是不直接导出状态,只导出 getState() 返回副本、setXxx() 负责修改,把写操作收口到模块内部。

Vue、React 里导出一个普通对象当全局状态可以吗?

数据能共享,但界面不会自动更新,因为框架感知不到普通对象的属性变化。Vue 里要用 reactive 包一层再导出,React 里要通过状态管理库或 useSyncExternalStore 订阅变化。模块共享解决的是「同一份数据」,响应式解决的是「数据变了通知谁」。

在导出方用 setTimeout 改属性,导入方同步代码能看到吗?

看不到,要等到定时器回调执行之后再读才是新值。能不能看到取决于读的时机,不取决于模块系统。导入方在事件回调、渲染函数这些之后才执行的代码里读,拿到的就是最新内容。

从一道题,走向一组知识

把知识连起来

模块与语言机制

为什么 Node.js 能 import 'lodash',浏览器里这么写就报错?怎么解决?

同一个包如果被加载成两份,各自有一个导出对象,这里说的共享就不成立了,包怎么被解析就很关键。

模块与语言机制

Node.js 怎么判断一个 .js 文件是 CommonJS 还是 ESM?type 字段管什么?

本题 ESM 和 CommonJS 表现不同,Node.js 按文件扩展名和 type 字段决定用哪一套规则。

参考资料

  • JavaScript:模块与语言机制参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 改属性和整体替换
  3. 换成 CommonJS 会怎样
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑