资讯动态

JavaScript严格模式全解:从ES5到ES2020的规则、坑与最佳实践

发布时间:2026/10/17 7:35:59 来源:尧图企业网站定制
JavaScript的严格模式我在项目里用了五年多今天想踏踏实实把这件事讲透。很多同学一听到严格模式就觉得是ES5时代的老古董跟ES2020有什么关系实际上如果你现在写的是ES Module、Class或现代构建工具链产出的代码你很可能已经默默运行在严格模式下了只是没人告诉你。严格模式不是一个独立的新语法也不是一个版本分支它是一组在解析和执行阶段生效的规则约束。ES2020时期新增的可选链、空值合并、BigInt这些特性在严格模式下的行为边界也值得重新梳理一遍。这篇文章不打算从教科书定义出发而是按照我自己在真实项目中理解和排查严格模式问题的顺序来写先讲清它为什么存在再讲怎么开启然后把每条规则用代码案例拆开揉碎最后集中列出我在团队项目里遇到过的报错和排查方法。无论你是刚接触JavaScript的初学者还是已经在业务里写过大量代码的工程师只要还在跟JavaScript打交道这篇文章应该都能给你提供一些可直接落地的参考。1. 严格模式的前世今生从ES5到ES2020为什么这个特性依然值得学1.1 严格模式到底解决了什么问题严格模式最初是ECMAScript 5规范引入的但它不是为了让JavaScript多几个新API而是给这门语言立了一套更严格的运行规则。早期JavaScript在变量声明、this绑定、函数参数处理这些方面预留了很多历史包袱比如给未声明变量赋值不会报错而是静默创建全局属性调用一个普通函数时内部this默认指向全局对象使用delete操作符删除不可删除的属性时会静默失败。这些设计在浏览器脚本刚兴起时可能无伤大雅但随着前端工程规模越来越大它们就成了bug温床。我举个特别常见的例子团队协作时两个文件拼接执行某个文件里定义了全局变量另一个文件里同名变量被意外覆盖排查起来非常痛苦。严格模式下给未声明变量赋值会直接抛出ReferenceError问题在运行的第一行就暴露而不是等到线上用户反馈数据被篡改。严格模式做的不是追求把所有错误都变成报错而是把那些永远不该被容忍的错误从静默状态变成显式异常。1.2 与ES2020及后续版本的协同关系有人会问既然严格模式从ES5就有了为什么标题还要强调与ES2020同步因为JavaScript模块体系正式完善并普及正好是ES2020前后这个阶段。ES Module规范规定模块顶层默认启用严格模式这意味着当你在项目里使用import和export无论有没有手动加use strict代码都已经运行在严格模式下。Class定义和方法内部同样是严格模式。ES2020把动态import()标准化让import()加载的模块继续遵守模块默认严格模式。从这个角度看ES2020不是修改了严格模式的规则清单而是扩大了严格模式的覆盖范围。同时ES2020推出的可选链、空值合并、BigInt这些新语法也明确兼容了严格模式下的解析约束。我在生产项目里用可选链时发现某些构建工具在压缩环节会做属性访问的转换如果目标运行环境比较老转译后的代码配合严格模式偶尔会改变this的绑定关系。这种复杂交互要在真实场景里才会意识到。提示判断当前代码是否处于严格模式最简单的方法是在函数内部console.log(this)严格模式下普通函数调用的this是undefined非严格模式下会指向全局对象。2. 开启严格模式的三种方式与生效时机2.1 全局严格模式用法与代价在脚本文件第一行写上单独的use strict;整个文件都会进入严格模式。这条指令必须放在文件或作用域的开头前面不能有任何有效语句否则会被当成普通字符串字面量忽略。虽然看起来只是加了一行字符串但要注意如果项目里同时存在多个脚本文件只有写了的文件是严格模式其他文件不受影响。全局严格模式有一个实际隐患如果页面里同时加载多个第三方脚本有些旧脚本不是严格模式安全的一旦它们被放入严格模式上下文可能因为this指向变化而行为异常。所以在老项目里我通常不推荐直接给整个文件加全局严格模式而是把严格模式控制到函数或模块级别。现在构建工具普遍用ES Module模块本身就是严格模式全局指令反而用得少了。2.2 函数级严格模式最灵活的粒度在函数体开头写use strict;只有这个函数内部按严格模式执行函数外面的代码不受影响。这在旧项目迁移时非常有用可以逐个函数去验证而不是一次性引爆整个文件。函数级严格模式必须放在函数体的第一条语句位置前面不能有任何表达式语句。有一个细节值得注意函数的参数列表在严格模式下有特殊规则比如不允许存在重名参数。在ES2015之前function f(a, a) {}这种写法合法但在严格模式下会直接抛语法错误。即使你只想在函数内部开启严格模式use strict写在函数体内参数检查也会执行严格模式规则。我用这个特性的时候踩过坑旧代码迁移到一个加了严格模式的函数后重名参数问题不是出现在运行期而是在解析阶段就失败了。2.3 模块与类中的自动严格模式ES Module中的import和export顶层代码自带严格模式不需要也无法在模块内部使用use strict;来关闭。这就解释了为什么很多现代项目里你从未见过严格模式指令但代码仍然符合严格模式的行为。ES2020前后前端的代码组织方式普遍从全局脚本切到模块体系严格模式也就从可选配置变成了默认环境。类是另一个自动严格模式的场景。类声明和类表达式内部的所有代码包括构造函数、方法、getter、setter都默认严格模式。值得注意的是类内部没有设置严格模式开关的选项。如果你在类方法中试图给未声明的变量赋值会拿到ReferenceError。这一点对初学者来说尤其容易困惑明明没有写use strict为什么报错规则这么严格原因就是类语法本身强制了严格模式。3. 严格模式的核心规则逐个拆解3.1 变量必须先声明再使用严格模式下给未声明的变量赋值会抛出ReferenceError这是最常见也最好理解的一条规则。非严格模式下count 1;会自动在全局对象上创建属性虽然写起来方便但会造成两个问题一是变量意外泄漏到全局二是严格模式下无法给只读全局属性赋值。我在排查线上问题时就遇到过代码里少写一个let变量直接挂到了window上团队另一段代码恰好用了同名变量结果两边互相覆盖。开启严格模式后第一次执行到漏声明的地方就会报错并能快速定位到具体行号。对于开发者来说这条规则的收益是最大的它直接把静默的全局污染变成了即时的、可定位的异常。3.2 delete操作符、eval与arguments的限制严格模式对delete操作符做了收紧如果操作数是一个变量、函数参数或函数名称直接抛语法错误如果操作数是对象的不可配置属性非严格模式下返回false并静默失败严格模式下会抛出TypeError。我自己在清理第三方库属性时曾被这个差异坑过所以我习惯在严格模式下先检查Object.getOwnPropertyDescriptor里的configurable字段。eval在严格模式下不会向外部作用域泄漏变量。非严格模式中eval(var x 1)会影响到当前作用域严格模式下eval拥有自己的词法作用域这符合现代编程语言对eval的隔离期待。此外eval内部声明的变量在调用结束后就被销毁不会污染外部命名空间。arguments对象在严格模式下的行为也变了它不再和命名参数保持同步关系。非严格模式下函数体内修改arguments[0]函数第一个参数也会跟着变严格模式下两者完全独立。这在实现函数重载和参数装饰时能避免很多隐蔽的bug。同时严格模式禁止在函数体内通过赋值重绑arguments本身比如arguments []会抛错。3.3 this 绑定规则的变化严格模式最影响老代码迁移的规则之一就是普通函数调用时this不再指向全局对象。非严格模式下一个独立的函数调用fn()内部this指向全局对象严格模式下this是undefined。这条规则改变了大量老代码的默认行为。尤其是在事件处理和回调函数里如果回调内部没有做this绑定直接使用this访问全局严格模式下一旦运行就会报错。这条规则和ES2020的很多新特性配合会变得特别微妙。比如把对象方法解构出来单独调用const fn obj.method; fn();非严格模式下this可能指向全局严格模式下this是undefined。如果你在方法里依赖this去访问对象状态这种调用方式就会出问题。我通常建议在组件或服务类里把所有会单独调用的方法用箭头函数定义或者在调用时显式fn.call(obj)。3.4 函数参数、重复属性与保留字限制严格模式对函数声明和参数有额外约束不允许重名参数不允许在块级作用域中声明函数部分引擎在ES6之后放宽为允许但仍建议使用函数表达式或箭头函数。对象的属性名在严格模式下也不允许重名例如{a: 1, a: 2}会抛语法错误。非严格模式下后一个属性覆盖前一个虽然逻辑上说得通但重复键往往是合并或生成代码时产生的笔误严格模式把它显式化了。保留字方面严格模式禁止使用implements、interface、package、private、protected、public、static、yield等作为变量名或函数名。这些词在ES2020时代大多已经被用作class的关键字或未来可能扩展的关键字所以这条规则更多是面向未来的兼容性防护。我在定义工具函数时特意避开这些词也是为了避免ES未来升级时产生解析冲突。3.5 八进制字面量与原始字符串老版本JavaScript允许写010表示八进制的8严格模式下这种八进制字面量被禁止会抛语法错误。如果你处理的是配置文件或历史数据转换数字时要注意在严格模式下用Number(010)得到的是10而老解析逻辑可能得到8。我在处理从旧系统迁移的配置项时遇到过这种解析差异导致的数值错位。ES2020引入了BigInt后二进制、八进制和十六进制的BigInt字面量都使用前缀写法例如0o10n这种新写法在严格模式下是合法的。也就是说严格模式不是排斥新的字面量语法而是淘汰了旧的、有歧义的八进制表示方式。理解这一点比单纯记忆严格模式禁八进制要更准确它禁止的是无前缀的八进制表示不是八进制这个概念本身。4. 与ES2020新特性一起用会踩哪些坑4.1 可选链、空值合并与严格模式ES2020给JavaScript带来了可选链?.和空值合并??。这两个语法本身与严格模式并不冲突但在使用它们访问某些底层属性时仍然会受到严格模式规则的约束。比如对一个只读属性赋值可选链不会帮助你屏蔽TypeError。像obj?.readonlyValue 1这种写法如果readonlyValue是不可写属性严格模式下依然抛错。还有一个和转译相关的坑现代构建工具在输出目标为旧版本环境时会把可选链转译成条件判断和临时变量。转译后的代码如果被包进一个严格模式函数检查逻辑里对临时变量赋值的操作也必须遵守严格模式规则。我在一次排查构建产物问题时发现某个压缩插件生成的一段辅助函数悄悄把未声明变量写到了全局配合严格模式直接报错。解决办法是锁定压缩插件的配置确保所有辅助函数都使用局部变量。空值合并??在严格模式下没有特殊限制但要注意它与逻辑或||的区别。如果业务逻辑里希望区分0、空字符串和null、undefined用??更精确。严格模式下变量声明规则不变所以??左侧的未声明变量依然会在访问时抛ReferenceError而不是安静地返回右侧值。这一点容易被忽略unDeclaredVar ?? 1在严格模式下会报错不是在运行时返回1。4.2 BigInt、动态import与严格模式BigInt是ES2020新增的数字类型它与严格模式的关系更多体现在语法解析层面。严格模式下禁止旧式八进制字面量但0o前缀的八进制BigInt如0o10n是允许的。在严格模式过度稀疏的老代码里如果刚好有010这种字面量转成BigInt时要手动改写成0o10n否则语法错误。我自己在迁移历史代码时写了一个小的正则检查专门扫描无前缀八进制字面量。动态import()返回Promise它在严格模式下没有额外的语法冲突但要注意模块内的作用域隔离。通过动态import()加载的模块无论调用方是否处于严格模式模块内的顶层代码都按严格模式执行。因此如果你要动态加载一个不是严格模式安全的旧脚本直接使用import()可能比你预期更严格导致旧脚本内的this绑定逻辑出错。我在项目中让动态加载的第三方库尽量用函数封装避免依赖全局this。4.3 现代构建工具下的严格模式开关现在很多项目用打包工具的use strict注入机制。有些配置会在每个模块顶部自动插入严格模式指令这能保证模块之间不会因执行顺序产生严格模式不一致。但也有副作用某些第三方依赖本身没有为严格模式做好准备在非严格模式下工作正常打包后因为被注入严格模式而行为异常。遇到这种情况我通常查看具体报错定位是哪个模块验证它在strict模式下的表现再用构建工具的module或exclude配置排除注入。ES2020前后前端工具链对严格模式的态度越来越统一默认开启不做兼容。因此如果你还在维护一个运行node_modules版本较老的旧项目升级依赖时务必先做严格模式行为测试尤其是涉及外部参数对象和this绑定的逻辑。我当初升级一个内部库时就因为没有做严格的差异测试导致线上出现了一类this为undefined的报错排查了很久才定位到是严格模式导致的回调this变化。5. 进阶排查与团队协作严格模式报错速查与编码规范5.1 常见报错信息表格速查我整理了一份在严格模式下最常见到的报错信息、出现原因和解决方向方便你遇到报错时快速对号入座。报错类型典型触发场景处理建议ReferenceError: x is not defined未声明变量赋值或读取确保变量已用let/const声明检查是否有漏写关键字SyntaxError: Duplicate parameter name not allowed函数重名参数合并或重写参数避免同名SyntaxError: Octal literals are not allowed出现010这类字面量改写成0o10或十进制TypeError: Cannot delete property对不可配置属性执行delete先检查configurable或用Map/Set管理数据TypeError: Cannot assign to read only property对只读属性或冻结对象赋值查找赋值源头确认是否需要改数据模型ReferenceError: eval is not allowed to contain...eval内声明与外部作用域冲突改用Function构造器或重构SyntaxError: Unexpected strict mode reserved word使用保留字作为变量名替换为语义明确的命名这张表不是替代你亲手调试而是提供一个起点。严格模式的报错几乎都是显式的比非严格模式的静默失败好排查得多。我通常在团队里保留一份类似的速查表出现这类报错时先对照表格做初步判断然后去代码里看具体的上下文。5.2 团队项目中启用严格模式的落地步骤如果你想把一个老项目逐步切换到严格模式我的建议不是一次性全局开启而是按模块分批迁移。先盘点项目的脚本加载方式如果还是用常规script标签加载且没有模块体系优先选择函数级严格模式如果有ES Module构建流程直接依赖模块自带严格模式不需要额外加指令。迁移时先把所有函数入口加上use strict;跑一遍完整测试用例记录所有失败点。失败点大多数集中在三类未声明变量、this依赖、arguments与参数的同步修改。逐项修复后再决定是否把严格模式提到文件级或依赖模块级。我在迁移一个中型前端项目时走了类似的渐进策略整个过程耗时约两个迭代但线上回归问题几乎没有比一夜全量切换稳妥得多。5.3 避免滥用严格模式的场景严格模式不是万能的也不是所有代码都适合无脑开启。如果你在Node.js里写的是一个非常小的CLI脚本且没有涉及this绑定和全局变量污染手动加严格模式收益有限。但也要小心Node.js的CommonJS模块默认不是严格模式ES Module默认是严格模式。在package.json里设置type: module后所有.js文件按ES Module解析会悄悄进入严格模式这点容易被人忽略。另外某些模板字符串或动态生成的代码可能在严格模式下语法报错。比如使用new Function动态构造函数时函数体内的代码同样受严格模式约束。如果这些代码来自外部输入最好在构造前做语法校验或者在沙箱化的函数体里显式关闭严格模式如果确实需要关闭的话。不过我的建议始终是不要依赖关闭严格模式来规避问题而是修正被严格模式揭露的糟粕写法。6. 我实际用下来的几条心得这篇文章写到这里我最后想分享几条个人经验不算是总结更像是踩过几次坑之后养成的习惯。在开发新项目时直接用ES Module和Class而不去纠结use strict写不写。依靠模块自带严格模式代码更干净也更容易被团队伙伴理解。在维护老项目时我坚持给每个待改造函数加函数级严格模式并配单测而不是选择全局开关因为风险和排查成本可控。有一个细节我一直很在意严格模式下的this变化会让某些内部库的行为变得难以预测。所以在设计公共方法时我倾向于用箭头函数保存this或者显式透传上下文参数避免回调调用时丢this。这个习惯帮我少花了很多debug时间。最后一个经验是把严格模式当作一个早期报警器而不是限制器。它不会阻止你写糟糕的代码但会在糟糕代码引发问题前提醒你。多留意那些被它拦下的报错往往能发现代码里真正需要改进的角落。如果你还没认真对待过严格模式建议下一次新代码就尝试不依赖全局、不写保留字变量、不搞arguments和参数的隐性联动看看代码是不是反而更清晰了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑