先记住这个答案
ES 模块(<script type="module"> 或 .mjs 文件)的顶层 this 是 undefined。普通脚本(<script> 不加 type="module")的顶层 this 是全局对象。原因是模块有自己独立的作用域,模块里的代码不会挂到全局对象上,this 自然没有理由指向全局。如果确实需要访问全局对象,用 globalThis。
- 模块顶层
this是undefined - 普通脚本顶层
this是globalThis - 要访问全局对象,用
globalThis - 模块顶层
this不指向全局对象
脚本和模块的对比
在浏览器里,普通 <script> 标签执行时,顶层 this 就是 window,用 var 声明的变量也会变成 window 的属性。模块则完全独立:既不会往 window 上挂东西,顶层 this 也不指向 window。
<script>
console.log(this === window) // true:经典脚本顶层
</script>
<script type="module">
console.log(this === undefined) // true:模块顶层
</script>Node.js 里也一样:.mjs 文件或 package.json 设了 "type": "module" 的 .js 文件里,顶层 this 是 undefined。CommonJS 的 .cjs 文件比较特殊,顶层 this 指向 module.exports,不是 undefined 也不是 globalThis,这是 Node.js 把文件包在函数里执行导致的。
为什么模块要这样设计
普通脚本的全局 this 是 JavaScript 早期的遗留设计。那时候没有模块系统,所有代码共享全局作用域,this 指向全局对象是自然的选择。
模块系统的目标是让每个文件有自己的作用域,互不污染。如果模块顶层的 this 还是全局对象,就等于给了一条绕过模块化的路——你可以用 this.x = 1 往全局上挂东西,这和模块化的初衷矛盾。所以规范直接让模块顶层的 this 为 undefined,彻底切断这条路。
这也意味着 ES 模块默认就是严格模式。和函数里 this 为 undefined 的道理相通:不做自动绑定,减少意外行为。
老库迁移时的常见问题
有些老库会在顶层用 this 来判断运行环境或注册全局变量。比如写类似 (this || window).MyLib = { ... } 的代码,指望 this 在浏览器里是 window。当你用 <script type="module"> 加载这个库时,this 变成 undefined,整个表达式就变成了 (undefined || window).MyLib。如果在 Node.js 或 Web Worker 这类没有 window 的环境里,代码就直接报错了。
globalThis 统一访问全局对象JavaScript// 正确的跨环境写法
var global = globalThis
console.log(typeof global)查看输出与解释
objectglobalThis 是 ES2020 引入的标准属性,在浏览器、Node.js、Web Worker 里都能拿到全局对象。迁移老代码时把 this 或 self 替换成 globalThis 就行。
容易答错的地方
- 把模块顶层
this和函数里this混为一谈 - 严格模式函数里
this为undefined是因为没有调用者,这是this绑定规则的一部分。模块顶层this为undefined是规范直接规定的,和调用方式无关。两者结果一样,但原因不同。面试时要分开说。 - 以为 CommonJS 模块的
this也是undefined - CommonJS 的
.cjs文件里,顶层this指向module.exports,不是undefined也不是globalThis。这是 Node.js 把文件包在(function(exports, require, module, __filename, __dirname) { ... })里执行导致的特殊行为。
面试官还会怎么问?
globalThis 出来之前怎么跨环境拿全局对象?
常见写法是 typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : self。这种三元判断又长又容易漏场景,globalThis 的出现一劳永逸解决了这个问题。
为什么 ES 模块默认是严格模式?
模块化的目标是隔离和可预测。严格模式禁止了静默错误、全局 this 绑定、with 语句等容易引起混乱的特性,和模块化的目标一致,所以规范直接把两者捆绑在一起。
<script type="module"> 和普通 <script> 还有哪些区别?
模块默认 defer(不阻塞页面渲染),有独立作用域(var 不挂全局),支持 import 和 export,且同一模块只执行一次(多次引用不重复执行)。普通脚本加载就执行,引入几次就执行几次。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。