前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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模块与语言机制

给 import 进来的变量重新赋值会怎样?

给 import 进来的变量重新赋值会报 TypeError。ESM 的导入绑定是只读的,不管源模块用 const 还是 let 声明,导入方都不能赋值。但是如果导入的是一个对象,修改对象的属性是可以的——限制的是绑定本身,不是绑定指向的值。

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

导入绑定的只读规则

  1. 只读绑定导入方拿到的是对源模块变量的引用,不能重新赋值
  2. 属性修改导入的对象可以修改属性,和 const obj 修改属性是同一个道理
  3. 修改权归属只有源模块自己能修改导出的变量值

这个限制在编译期就能检测出来。大多数打包工具和 TypeScript 在静态分析阶段就会报错,不用等到运行时。

核心回答

先记住这个答案

直接赋值会报 TypeError。在 ESM 里,import { count } from './counter' 建立的是一个指向源模块 count 变量的只读引用。导入方试图写 count = 10 时,引擎会拒绝这个操作,抛出 TypeError: Assignment to constant variable 或类似的错误。这个限制和源模块用 const 还是 let 无关——即使源模块用 let 声明了 count,导入方依然不能赋值。

  • 给导入绑定赋值报 TypeError,不管源端是 const 还是 let
  • 导入绑定是对源模块变量的只读引用
  • 修改导入对象的属性不会报错,限制的是绑定不是值
  • 和 const 的行为类似,但机制不同

看一个最简单的例子

源模块导出一个用 let 声明的变量,导入方尝试直接赋值。

导入绑定赋值报错JavaScript
// counter.mjs
export let count = 0
export function increment() {
  count++ // 源模块自己可以修改
}

// main.mjs
// import { count, increment } from './counter.mjs'
// console.log(count) // 0
// count = 10         // TypeError: Assignment to constant variable
// increment()        // 正常,源模块自己改
// console.log(count) // 1 —— 导入方能读到源模块的修改

导入方能读到源模块的变化,但自己不能写入。这是 ESM 的设计约束——数据流是单向的,从导出方流向导入方。

为什么导入绑定必须是只读的

如果导入方也能修改,多个模块同时导入同一个变量时,任何一方都能改它的值,代码的可预测性会非常差。你不知道 count 什么时候被谁改了,调试起来是噩梦。

ESM 的设计选择是:谁声明谁负责修改。源模块声明了 count,只有源模块有权修改。其他模块想改的话,只能通过源模块暴露的函数(比如上面的 increment())间接操作。这和面向对象里的封装思想是一样的——把可变状态和修改它的方法放在一起,外部只能通过接口来操作。

这个限制在很多工具链里是编译期就能发现的。TypeScript 和大多数打包工具在静态分析阶段就会标红 import 绑定的赋值操作,不用等到运行时才报错。

修改导入对象的属性为什么不报错

只读限制的是绑定本身,不是绑定指向的值。

对象属性可以修改JavaScript
// config.mjs
export const options = { debug: false }

// main.mjs
// import { options } from './config.mjs'
// options = {}          // TypeError —— 不能重新赋值
// options.debug = true  // 正常 —— 修改的是对象属性,不是绑定

这和 const obj = {}; obj.x = 1 的道理一模一样。const 阻止你把 obj 指向别的东西,但不阻止你修改 obj 内部的内容。导入绑定的只读约束也是同理。 虽然技术上可以这样做,但在实际项目里直接修改导入对象的属性不是好实践。其他导入同一模块的文件也会受到影响,而且这种修改很难追踪。如果确实需要修改,应该通过源模块暴露的函数来操作。

回答前,多想一步

容易答错的地方

以为报的是 SyntaxError
有人说"编译期就报错所以是 SyntaxError"。实际上规范定义的是运行时错误 TypeError。打包工具和 TypeScript 可能在编译期就拦截了,但那是工具层面的行为,不是语言规范的要求。如果在原生 ESM 环境里运行,报的是 TypeError。
以为和 const 的限制来自同一个机制
const 的不可赋值是变量声明层面的约束,import 绑定的只读是模块系统层面的约束。两者效果类似,但来源不同。即使源模块用 let 声明(本身是可赋值的),导入绑定也是只读的——这不是 const 在起作用,而是 ESM 的模块绑定规则。
试着用自己的话回答

面试官还会怎么问?

import * as ns 的命名空间对象可以修改属性吗?

不可以。命名空间对象是不可扩展的(non-extensible),所有属性都是不可配置、不可写的。ns.count = 10 会被静默忽略(非严格模式)或报 TypeError(严格模式,ESM 默认就是严格模式)。

解构导入后赋值会报错吗?

import { count } 的 count 本身就是导入绑定,赋值会报错。但如果你先用局部变量接住它——比如 let local = count——那 local 是普通变量,可以随便赋值。不过这样就断开了和源模块的实时绑定,local 不会跟着源模块变化了。

CommonJS 的 require 导入也是只读的吗?

不是。const { count } = require('./counter') 里的 count 是用 const 声明的局部变量,它是只读的是因为 const,不是因为模块系统。如果换成 let { count } = require('./counter'),count 就可以被重新赋值了——不过改了也只是改了自己的局部变量,和源模块没有关系。

从一道题,走向一组知识

把知识连起来

模块与语言机制

export { a } 和 export const a 同时写会怎样?怎么避免命名冲突?

了解导出列表和声明导出的区别后,能更好地理解为什么不同的导出形式都会产生只读的导入绑定。

模块与语言机制

原生 ESM 怎么导入 JSON?导入语句后面的 with 属性起什么作用?

导入 JSON 模块后拿到的对象也是只读绑定,修改属性的行为和导入普通模块时不同。

参考资料

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

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

本题目录
  1. 先记住这个答案
  2. 看一个最简单的例子
  3. 为什么导入绑定必须是只读的
  4. 修改导入对象的属性为什么不报错
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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