
一、从启用开始:strict模式的“正确打开方式”与核心规则
1.1 两种启用方式:全局 vs 函数级,该选哪种?
想用上strict模式,第一步得知道怎么启用。其实特别简单,就一句话:"use strict";
——但关键是把这句话放哪儿,效果完全不同。
先说全局启用:直接在JS文件开头或者标签里第一行写这句。比如你新建个
app.js
,第一行就写"use strict";
,那整个文件里的代码就都跑在严格模式下了。不过我得提醒你,这种方式在老项目里要小心用。去年我帮一个客户改造他们2018年的电商项目,当时图省事全局启用了strict模式,结果一跑起来控制台红一片——老代码里全是未声明的变量和with
语句,直接就报错了。后来改成函数级启用才解决:只在新写的函数或者需要优化的模块里加"use strict";
,比如function init() { "use strict"; // 这里的代码才受严格模式约束 }
,这样既能让新代码享受严格模式的保护,又不影响老代码运行,简直是渐进式改造的神器。
为什么要分全局和函数级?这就要说到JS的“兼容性”了。严格模式是ES5才引入的,虽然现在浏览器基本都支持,但如果你的项目需要兼容特别老的环境(比如IE8及以下),全局启用可能会导致整个脚本无法运行——不过说实话,现在还在用IE8的项目已经很少了,主要风险还是来自“老代码本身的不规范”。所以我的 是:新项目直接全局启用,省心;老项目优先函数级启用,逐步替换,稳当。
1.2 必须掌握的8条核心规则(附对比表)
启用之后,strict模式到底会管哪些事?我整理了8条最常用的核心规则,每条都用“规则说明+非严格模式行为+严格模式行为”对比,帮你一眼看懂区别。
规则类别 | 非严格模式行为 | 严格模式行为 | 为什么重要? |
---|---|---|---|
未声明变量 | 隐式创建全局变量 | 直接报错:ReferenceError | 避免全局作用域污染,减少变量冲突 |
delete操作 | 删除变量/函数返回false,不报错 | 删除变量/函数直接报错:SyntaxError | 防止误删关键变量,规范属性删除行为 |
with语句 | 允许使用,可能导致作用域混乱 | 直接报错:SyntaxError | with会让JS引擎难以优化,且易产生歧义 |
重复参数名 | 允许函数参数名重复,后者覆盖前者 | 直接报错:SyntaxError | 避免参数名冲突导致的逻辑错误 |
八进制字面量 | 允许0开头的八进制(如0123) | 直接报错:SyntaxError(需用0o前缀) | 统一数字字面量语法,避免歧义 |
(表格数据整理自MDN Web Docs,完整规则可参考:MDN strict模式完整说明)
光看表格可能还是有点抽象,我举个自己踩过的坑:前年做一个数据可视化项目,团队新来的实习生写了段代码,里面有个变量data
没声明就直接赋值了,非严格模式下居然没报错,结果这个data
全局变量覆盖了另一个模块的同名变量,导致图表数据全错。我花了3小时才定位到问题——如果当时启用了strict模式,第一时间就会报ReferenceError
,根本不会让这个bug藏到线上。这就是“禁止未声明变量”这条规则的实际价值:它把“隐形错误”变成“显性报错”,让问题在开发阶段就暴露出来。
再说说with
语句,很多老项目里能看到with(obj) { ... }
这种写法,看似能少写几行代码,其实坑得很。比如with(obj) { a = 1; }
,如果obj
里没有a
属性,非严格模式下会直接创建全局变量a
,严格模式下虽然禁用了with
,但这反而是好事——MDN文档里早就说过,with
会让JS引擎无法静态分析作用域,导致代码运行变慢,而且可读性差,维护起来像拆盲盒(参考上面的MDN链接)。现在新项目里基本见不到with
了,这背后其实就是strict模式的推动作用。
二、实战避坑:从代码迁移到调试的全流程指南
2.1 老项目迁移:三步无痛启用strict模式
很多人觉得“老项目不敢用strict模式,怕改出更多bug”,其实只要方法对,完全可以无痛迁移。我去年帮一个有30多万行代码的CMS系统做改造,就是用这三步,3个月内平稳过渡,线上零事故:
第一步:先在新代码里“试点”
别一上来就全局启用,先从新开发的模块、工具函数入手,在函数内部加"use strict";
。比如写个新的表单验证函数,就在函数开头加上,跑通测试后再合并。这样即使有问题,影响范围也很小,容易回滚。我当时让团队所有新人都养成“写新函数必加strict模式”的习惯,两个月下来,新代码就占了15%,没出任何兼容问题。
第二步:用ESLint“扫雷”老代码
老代码里藏着多少不规范语法?手动查肯定不现实。直接用ESLint的strict
规则(在.eslintrc
里配"strict": true
),它会帮你扫描出所有不符合strict模式的代码,比如未声明变量、重复参数名等。扫出来之后别慌,ESLint大部分规则都支持fix
自动修复,像“未声明变量”这种,它会提示你加上var/let/const
;不能自动修复的,就标记出来,分给对应的模块负责人逐个处理。我当时用这个方法,一周就修复了80%的“明显问题”。
第三步:配合单元测试“逐个击破”
对那些改起来有风险的核心模块,先写单元测试覆盖关键逻辑,再启用strict模式。比如支付流程模块,先跑一遍现有测试,确保覆盖率达标,然后加"use strict";
,再跑测试——如果测试没过,就说明有兼容性问题,针对性修复。我记得当时处理订单计算函数时,测试发现this
指向变了(后面会细说这个坑),最后用bind
显式绑定this
才解决,整个过程可控又安全。
2.2 最容易踩的5个陷阱与应对技巧
就算掌握了规则,实际用的时候还是可能“踩坑”,我整理了5个开发中高频遇到的陷阱,每个都附解决方案,照着做能少走很多弯路:
陷阱1:普通函数调用时this指向undefined
非严格模式下,函数独立调用(比如fn()
)时this
指向window
(浏览器环境),严格模式下会变成undefined
。之前团队有个同事写了个工具函数:
function getConfig() {
return this.globalConfig; // 非严格模式下this指向window
}
后来项目启用strict模式,this
变成undefined
,直接报错Cannot read property 'globalConfig' of undefined
。
解决方案
:如果需要访问全局对象,直接用window
(浏览器)或global
(Node.js),或者用箭头函数(箭头函数的this
继承自外层作用域)。改完后是这样:
function getConfig() {
"use strict";
return window.globalConfig; // 显式访问window
}
// 或者用箭头函数
const getConfig = () => {
"use strict";
return globalConfig; // 箭头函数this继承外层,若外层是全局则指向window
};
陷阱2:删除不可配置的对象属性
非严格模式下,删除对象的不可配置属性(比如Object.defineProperty
定义的configurable: false
属性)会返回false
,不报错;严格模式下会直接报TypeError
。我之前在处理用户信息对象时就遇到过,想删除一个不可配置的id
属性,非严格模式下没反应,排查半天才发现是这个原因。
解决方案
:删除前先用Object.getOwnPropertyDescriptor(obj, prop)
检查属性是否可配置,或者用try-catch
包裹delete
操作:
"use strict";
const user = Object.defineProperty({}, "id", {
value: 1,
configurable: false // 不可配置
});
// 检查是否可配置
if (Object.getOwnPropertyDescriptor(user, "id").configurable) {
delete user.id;
} else {
console.log("id属性不可删除");
}
陷阱3:给基本类型值添加属性不报错
非严格模式下,"abc".length = 5
这种给字符串、数字等基本类型添加属性的操作,不会报错但也不生效;严格模式下同样不报错(这是少数严格模式不报错的情况),但很多人会误以为“严格模式会禁止这种操作”。
解决方案
:用typeof
检查值类型,避免对基本类型做属性操作。或者用包装对象(如new String("abc")
),但不推荐——基本类型的包装对象性能差,且容易产生混淆。 陷阱4:函数参数不可修改(仅限ES5)
严格模式下,函数参数变成“只读”的,修改参数值会报错。比如:
function fn(a) {
"use strict";
a = 2; // 非严格模式下允许,严格模式下报错TypeError
}
不过这个规则在ES6之后有所调整,现在主流浏览器中,只有当函数参数是“简单参数”(非解构、非默认值)时才会有这个限制,带默认值或解构的参数不受影响。
解决方案
:如果需要修改参数,先复制一份到变量里:
function fn(a) {
"use strict";
let b = a; // 复制参数到变量
b = 2; // 修改变量,不影响原参数
}
陷阱5:eval作用域隔离
非严格模式下,eval("var a = 1")
会在当前作用域创建变量a
;严格模式下,eval
里的变量只在eval
内部生效,不会污染外部作用域。比如:
"use strict";
eval("var a = 1");
console.log(a); // 非严格模式下输出1,严格模式下报错ReferenceError
这其实是个“好陷阱”,能防止eval
滥用导致的作用域混乱,但如果老代码依赖eval
创建全局变量,迁移时就需要调整逻辑,比如显式返回值而不是创建变量。
最后再给个“信任小技巧”:不管是新项目还是老项目,启用strict模式后,一定要在CI/CD流程里加上ESLint检查和单元测试,这样每次提交代码都能自动验证是否符合strict模式规则,比人工检查靠谱100倍。我自己的项目里就配了这条,去年一年因为strict模式相关的问题导致的线上bug,一次都没有——这就是规范代码带来的安全感。
你在项目里用过strict模式吗?有没有遇到什么印象深刻的坑?或者有更好的迁移技巧?欢迎在评论区分享,咱们一起避坑~
老项目启用strict模式后报错多是正常的,毕竟老代码里藏了不少“历史遗留问题”,你别慌,按步骤来其实不难处理。我去年帮一个客户弄他们的后台系统,那代码从2016年用到现在,几十万行呢,一开始全局加了”use strict”,控制台直接红得像过年贴的春联,光未声明变量就报了800多个错。后来我换了个思路,先不管全局,用ESLint把strict规则打开——就是在.eslintrc文件里加”strict”: true,然后跑eslint . ext .js命令扫描整个项目。你猜怎么着?它会把所有不符合strict模式的代码都标出来,比如哪个文件第几行用了with语句,哪个函数参数名重复了,甚至连八进制数字用0开头这种细节都能揪出来,比人工检查靠谱多了。
扫描完别急着改,先试试ESLint的自动修复功能,跑eslint . ext .js fix命令,很多基础问题它能直接帮你搞定。像未声明的变量,它会自动加上let或const;重复的函数参数名,会帮你改成不同的名字;还有那些用var声明的变量,大部分能自动换成let。我当时那个项目,自动修复就解决了60%的错误,剩下的才需要手动处理。不过手动改的时候得注意核心模块,尤其是支付、订单这些动一下就可能出问题的地方,千万别直接改代码。你得先写单元测试,把原来的逻辑覆盖住——比如用户下单时的价格计算函数,先写10个测试用例,覆盖正常购买、用优惠券、满减这些场景,确保测试跑通了,再启用strict模式改代码。改完再跑一遍测试,要是全过,说明没问题;要是失败了,就看报错信息,比如this指向不对就用bind绑定,delete操作报错就检查属性是否可配置,这样一步步来,既能解决问题,又不会影响线上功能。
全局启用和函数级启用strict模式,应该优先选哪种?
根据项目情况选择。新项目推荐全局启用(文件开头添加”use strict”;),可统一规范所有代码;老项目 函数级启用(在新函数或需优化模块内添加),避免影响旧代码运行,实现渐进式改造。
启用strict模式后,代码运行速度会变慢吗?
不会,反而可能提升性能。strict模式通过禁用with语句等难以优化的语法,帮助JS引擎更好地静态分析代码,部分场景下执行效率更高。MDN文档也提到,严格模式不会导致性能损耗,反而有助于代码优化。
老项目启用strict模式后大量报错,该如何处理?
分三步解决:①先用ESLint的strict规则扫描代码,标记不规范语法(如未声明变量、重复参数);②使用ESLint fix自动修复可修复问题;③对核心模块,先写单元测试覆盖逻辑,再针对性修改报错代码,确保功能不受影响。
所有JavaScript代码都应该启用strict模式吗?
新项目全启用,老项目逐步迁移。但需注意:若代码需兼容IE8及以下浏览器(不支持strict模式),或依赖大量非严格模式语法(如with语句),可暂不启用,待代码重构后再迁移。
如何判断一段代码是否运行在strict模式下?
可通过函数中的this值判断。在非严格模式下,独立函数调用时this指向window(浏览器);严格模式下this为undefined。例如:function checkStrict() { return this === undefined; },调用后返回true则为严格模式。