前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库模块 namespace 对象是否冻结
JavaScript模块与语言机制

import * as ns 拿到的对象是冻结的吗?能改它的属性吗?

不能改:给 ns.x 赋值、新增属性、删除属性都会抛 TypeError。但它不是冻结对象,Object.isFrozen(ns) 是 false,因为属性要跟着导出方实时变化,描述符上标的是可写。

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

命名空间对象是一个特殊对象

  1. 不可扩展不能加新属性,原型是 null,也不能改原型
  2. 属性只能读赋值抛 TypeError,删除也抛 TypeError
  3. 实时绑定读 ns.count 时实时去导出模块取当前值

描述符里 writable 显示为 true,这不是说你能写,而是告诉引擎「这个值会变」。如果标成只读,引擎和工具就可能以为值固定不变,而导出方其实随时会改它。

核心回答

先记住这个答案

import * as ns from "./mod.js" 拿到的叫模块命名空间对象。它的属性不能赋值、不能新增、不能删除,这些操作在模块里(严格模式)都会抛 TypeError。但严格来说它不是冻结对象:Object.isSealed(ns) 是 true,Object.isFrozen(ns) 却是 false。原因是它的属性值必须跟着导出模块实时变化,描述符上只能标成可写。另外它只管住命名空间这一层,导出的对象内部仍然可以修改。

  • 赋值、增加、删除属性都抛 TypeError
  • 模块命名空间已密封但未冻结
  • 属性值会随导出方实时变化,所以不能是真正冻结
  • 导出的对象内部照样能改,限制的只是命名空间这一层

逐个试一下能不能改

在 Node.js 里导入一个模块,它导出了 let count = 0、一个对象 obj 和一个函数 inc。

各种修改操作的结果JavaScript
import * as ns from './counter.mjs'

ns.count = 1 // TypeError: Cannot assign to read only property 'count'
ns.newProp = 1 // TypeError: Cannot add property newProp, object is not extensible
delete ns.count // TypeError: Cannot delete property 'count'
Object.freeze(ns) // TypeError: Cannot redefine property: count

console.log(Object.isSealed(ns), Object.isFrozen(ns)) // true false
console.log(Object.getPrototypeOf(ns)) // null
console.log(Object.getOwnPropertyDescriptor(ns, 'count'))
// { value: 0, writable: true, enumerable: true, configurable: false }

ns.obj.n = 100 // 可以,改的是导出对象的内部

前三行说明这一层完全改不了。第四行更有意思,连 Object.freeze 都会失败,因为冻结需要把属性改成不可写,而命名空间对象拒绝任何属性重定义。 最后一行能成功。ns.obj 读出来是一个普通对象,改它的属性和命名空间对象无关,所有导入这个模块的地方都会看到 n 变成了 100。

为什么要设计成这样

命名空间对象不是一个存值的普通对象,更像一扇窗户,每次读属性都透过它去看导出模块里那个变量现在的值。导出方执行 count++,下一次读 ns.count 就是新值。

为什么不直接冻结
如果真的冻结,属性描述符会标成不可写,这等于对外宣称「这个值永远不变」,但导出方明明会改它。所以规范让描述符显示 writable: true,同时让赋值操作失败。这是一个行为上只读、描述上可写的特殊对象,规范里叫 exotic object。 不允许导入方修改,则是为了保证一个模块的状态只能由它自己改。否则任何导入者都能替换别人的导出,模块之间的边界就没有意义了。
回答前,多想一步

容易答错的地方

说「命名空间对象是冻结的」
面试里直接这么答不够准确。更好的说法是:它不可扩展、属性不可配置、赋值会失败,效果上像冻结,但 Object.isFrozen 返回 false,因为属性值是实时绑定的。
以为导出的对象也不能改
限制只在命名空间这一层。ns.config.debug = true 完全合法,而且会影响所有导入方。想让导出对象不能被改,要在导出模块里自己 Object.freeze。
以为在非严格模式下赋值只是静默失败
ES 模块代码永远是严格模式,所以在模块里给命名空间属性赋值一定会抛错。只有在普通脚本里通过动态 import() 拿到命名空间,并且在非严格代码里赋值时,才会静默失败。
试着用自己的话回答

面试官还会怎么问?

能给命名空间对象的属性打补丁做 mock 吗?

不能直接改。测试里想替换某个导出,要用测试框架提供的模块 mock 能力,比如 Jest 的 jest.mock 或 Vitest 的 vi.mock,它们在模块加载阶段就替换掉整个模块。直接赋值 ns.fn = mockFn 会抛 TypeError。

命名空间对象里有 default 吗?

如果模块有默认导出,命名空间对象上就有一个名为 default 的属性,值就是默认导出。它还有一个 Symbol.toStringTag 属性,值是 "Module",所以在控制台里会显示成 [Module: null prototype]。

用 import * as 会不会影响 tree shaking?

只要是 ns.foo 这种静态属性访问,打包器通常能分析出只用了哪些导出。如果把 ns 整个传给别的函数、或者用 ns[key] 动态访问,打包器就只能保留全部导出。

从一道题,走向一组知识

把知识连起来

模块与语言机制

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

命名空间这一层改不了,但导出的对象内部可以改,而且所有导入方都能看到。

模块与语言机制

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

多数情况下拿到命名空间对象的第一步是导入一个包名,而浏览器默认不认包名。

参考资料

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

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

本题目录
  1. 先记住这个答案
  2. 逐个试一下能不能改
  3. 为什么要设计成这样
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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