都叫严格模式,但它们解决的问题完全不在一个层次上
前言
写完 JavaScript 严格模式的文章,突然想到一个问题:"TypeScript 不也有个 strict: true
吗?这俩是一回事吗?开了 TS 的 strict 还要写 'use strict'
吗?"
说实话,我刚学 TypeScript 时也搞混过。看着 tsconfig.json
里的 strict: true
,心想这应该和 JS 的 'use strict'
差不多吧,结果配完发现代码里还是满屏标红。
后来花了个周末把 TypeScript 编译选项挨个试了一遍,才明白:这俩虽然名字像,但压根不是一个维度的东西------一个管编译时的类型检查,一个管运行时的语言行为。
先抛几个问题,看看你是不是也有同样的困惑:
- TypeScript 的
strict
和 JavaScript 的'use strict'
到底啥区别? - 开了 TS 的 strict 模式,还需要写
'use strict'
吗? - 它们检查的东西一样吗?(答案是完全不一样)
- 为啥名字这么像,却是两个东西?(这锅 TypeScript 团队真得背)
这篇文章就来聊聊,同样是"严格",它们到底严在哪里,又有什么本质区别。
目录
- 一个真实的困惑:我到底该开哪个?
- [JavaScript 严格模式回顾:运行时的守护者](#JavaScript 严格模式回顾:运行时的守护者 "#javascript-%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F%E5%9B%9E%E9%A1%BE%E8%BF%90%E8%A1%8C%E6%97%B6%E7%9A%84%E5%AE%88%E6%8A%A4%E8%80%85")
- [TypeScript 严格模式:编译时的守护者](#TypeScript 严格模式:编译时的守护者 "#typescript-%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F%E7%BC%96%E8%AF%91%E6%97%B6%E7%9A%84%E5%AE%88%E6%8A%A4%E8%80%85")
- [核心差异:编译时 vs 运行时](#核心差异:编译时 vs 运行时 "#%E6%A0%B8%E5%BF%83%E5%B7%AE%E5%BC%82%E7%BC%96%E8%AF%91%E6%97%B6-vs-%E8%BF%90%E8%A1%8C%E6%97%B6")
- 深入对比:它们分别解决什么问题?
- 实战案例:看看它们如何配合工作
- 最佳实践:该怎么配置?
一个真实的困惑:我到底该开哪个?
先看一个常见场景。你在写 TypeScript 项目,tsconfig.json
里配了:
json
{
"compilerOptions": {
"strict": true
}
}
然后在代码里写:
lua
function greet(name) { // TS 报错:Parameter 'name' implicitly has an 'any' type
console.log('Hello ' + name);
}
TypeScript 立马给你标红了。你想:行,TypeScript 的严格模式生效了。
但是,这时候你在文件顶部加不加 'use strict'
,会有区别吗?
或者反过来,如果你只写了 'use strict'
,没开 TypeScript 的 strict: true
,又会怎样?
这就是今天要搞清楚的问题。
JavaScript 严格模式回顾:运行时的守护者
先快速回顾一下 JavaScript 的严格模式(详细内容可以看上一篇文章)
它是什么?
一个运行时开关,在代码执行时改变 JavaScript 引擎的行为。
javascript
'use strict'; // 告诉 JS 引擎:"用严格模式跑这段代码"
x = 10; // ReferenceError: x is not defined(运行时报错)
它解决什么?
JavaScript 早期设计的语言层面的问题:
- 运行时错误:把静默失败变成抛出异常
- 危险语法 :禁止容易出错的语法(比如
with
、八进制字面量) - 意外行为:修正反直觉的行为(比如自动创建全局变量)
关键特征
严格模式)) 运行时生效 代码执行时检查 依赖 JS 引擎 无法在编译时发现问题 语言层面 修改语言行为 禁止危险语法 修正历史问题 向后兼容 老代码不受影响 需要主动开启 只影响声明的作用域
TypeScript 严格模式:编译时的守护者
TypeScript 的 strict: true
是另一个完全不同的东西。
它是什么?
一个编译选项集合 ,在代码编译(转换为 JS)之前进行类型检查。
json
// tsconfig.json
{
"compilerOptions": {
"strict": true // 这是个"总开关"
}
}
当你开启 strict: true
,实际上是同时开启了这 7 个编译选项:
json
{
"compilerOptions": {
"strict": true, // 👆 等价于下面 👇
"noImplicitAny": true, // 禁止隐式 any 类型
"noImplicitThis": true, // 禁止 this 有隐式 any 类型
"strictNullChecks": true, // 严格的 null/undefined 检查
"strictFunctionTypes": true, // 严格的函数类型检查
"strictBindCallApply": true, // 严格检查 bind/call/apply
"strictPropertyInitialization": true,// 严格的类属性初始化检查
"alwaysStrict": true, // 始终以严格模式解析(会加 'use strict')
"useUnknownInCatchVariables": true // catch 变量默认为 unknown 类型
}
}
等等,看到 alwaysStrict
了吗?这就是联系的地方!
它解决什么?
TypeScript 的严格模式解决的是类型安全问题:
- 编译时错误:在代码运行前就发现类型错误
- 类型推断 :强制明确类型,避免隐式
any
- 空值安全 :防止
null
/undefined
引起的运行时错误 - 函数安全:确保函数调用的类型正确性
关键特征
严格模式)) 编译时生效 转译前检查 IDE 实时提示 运行前发现问题 类型系统层面 强制类型明确 空值安全检查 函数类型检查 配置灵活 总开关 可单独开关每个选项 逐步迁移友好
核心差异:编译时 vs 运行时
现在重点来了,这两者的本质区别:
1. 生效时机不同
TypeScript strict: true
:
- ✅ 在编译阶段检查(你还在写代码的时候)
- ✅ IDE 实时提示,根本不让你编译通过
- ✅ 问题在开发阶段就被发现
JavaScript 'use strict'
:
- ✅ 在运行阶段检查(代码已经在跑了)
- ✅ 只有执行到那行代码才会报错
- ✅ 问题可能在生产环境才暴露
2. 检查内容不同
检查项 | TypeScript strict |
JavaScript 'use strict' |
---|---|---|
未声明变量 | ❌ 不检查(这是 JS 运行时的事) | ✅ 运行时报错 |
隐式 any 类型 | ✅ 编译错误 | ❌ 不检查(JS 没有类型) |
null/undefined 安全 | ✅ 编译错误 | ❌ 不检查(运行时才知道是否为 null) |
函数参数类型 | ✅ 编译错误 | ❌ 不检查 |
只读属性赋值 | ✅ 编译错误(如果用了 readonly ) |
✅ 运行时报错 |
重复参数名 | ✅ 编译错误 | ✅ 运行时报错 |
八进制字面量 | ✅ 编译错误 | ✅ 运行时报错 |
with 语句 | ✅ 编译错误 | ✅ 运行时报错 |
this 为 undefined | ✅ 类型检查会提示 | ✅ 运行时行为改变 |
3. 适用范围不同
TypeScript strict
:
- 只在
.ts
或.tsx
文件中生效 - 需要 TypeScript 编译器
- 编译后的 JS 文件里没有类型信息
JavaScript 'use strict'
:
- 在所有 JS 文件中都能用(
.js
、.ts
编译后的文件) - 不需要任何工具,浏览器原生支持
- 直接影响 JS 引擎的行为
深入对比:它们分别解决什么问题?
案例 1:未声明的变量
JavaScript 'use strict'
能捕获:
javascript
'use strict';
function test() {
myVar = 10; // ❌ ReferenceError: myVar is not defined(运行时)
}
test();
TypeScript strict
不检查这个:
csharp
// tsconfig.json: { "strict": true }
function test() {
myVar = 10; // ⚠️ TypeScript: Cannot find name 'myVar'
// 但这是因为 TypeScript 要求先声明变量
// 不是因为 strict 模式
}
TypeScript 编译后:
javascript
"use strict"; // 👈 注意这里!因为 alwaysStrict: true
function test() {
myVar = 10; // 运行时还是会被 'use strict' 捕获
}
结论:
- TS 的
strict
本身不处理未声明变量 - 但
strict
包含alwaysStrict
,会自动加'use strict'
- 最终还是靠 JS 的严格模式在运行时捕获
案例 2:隐式 any 类型
TypeScript strict
能捕获:
lua
// strict: true
function greet(name) {
// ❌ 编译错误:Parameter 'name' implicitly has an 'any' type
console.log('Hello ' + name);
}
JavaScript 'use strict'
完全不管:
javascript
'use strict';
function greet(name) {
// ✅ 没问题,JS 本来就是动态类型
console.log('Hello ' + name);
}
结论:
- TS 的
strict
强制你明确类型 - JS 的
'use strict'
对类型无能为力(因为 JS 没有静态类型)
案例 3:空值安全
TypeScript strict
的强项:
php
// strict: true(包含 strictNullChecks)
function getLength(str: string) {
return str.length;
}
const maybeStr: string | null = getSomeString();
getLength(maybeStr);
// 编译错误:Argument of type 'string | null' is not assignable to parameter of type 'string'
JavaScript 'use strict'
无能为力:
javascript
'use strict';
function getLength(str) {
return str.length;
}
const maybeStr = getSomeString();
getLength(maybeStr);
// 编译通过
// 运行时如果 maybeStr 是 null,会报错:Cannot read property 'length' of null
结论:
- TS 的
strict
在编译时就发现了潜在的 null 引用问题 - JS 的
'use strict'
只能等到运行时才崩溃
案例 4:函数 this 类型
两者都有帮助,但方式不同:
typescript
// TypeScript strict
interface User {
name: string;
greet(this: User): void; // 明确 this 类型
}
const user: User = {
name: 'Alice',
greet() {
console.log(this.name);
}
};
const greetFn = user.greet;
greetFn();
// ❌ TS 编译错误:The 'this' context of type 'void' is not assignable to method's 'this' of type 'User'
javascript
// JavaScript 'use strict'
'use strict';
const user = {
name: 'Alice',
greet() {
console.log(this.name); // this 是 undefined
}
};
const greetFn = user.greet;
greetFn();
// ✅ 编译通过
// 运行时报错:Cannot read property 'name' of undefined
结论:
- TS 的
strict
通过类型系统在编译时就警告你 - JS 的
'use strict'
让this
为undefined
,在运行时才报错
实战案例:看看它们如何配合工作
完整示例:两者互补
json
// tsconfig.json
{
"compilerOptions": {
"strict": true // 包含 alwaysStrict: true
}
}
typescript
// user.ts
// 1️⃣ TypeScript 的 strict 检查类型
function calculateTotal(price: number, quantity: number): number {
// 2️⃣ TypeScript 确保参数类型正确
if (price < 0) {
// 3️⃣ strictNullChecks 确保不返回 undefined
throw new Error('Price cannot be negative');
}
return price * quantity;
}
// 4️⃣ 编译时就发现类型错误
// calculateTotal('100', 5); // ❌ 编译错误
// 5️⃣ 如果不小心写了未声明变量
function buggyCode() {
totol = 100; // ❌ TS: Cannot find name 'totol'
}
编译后的 JavaScript:
javascript
// user.js
"use strict"; // 👈 自动加上!来自 alwaysStrict: true
// TypeScript 的类型检查已经完成,这里只剩运行时代码
function calculateTotal(price, quantity) {
if (price < 0) {
throw new Error('Price cannot be negative');
}
return price * quantity;
}
// 如果 TypeScript 没拦住(比如用了 any),运行时会拦住
function buggyCode() {
totol = 100; // 💥 ReferenceError(被 'use strict' 捕获)
}
双重保险:
- 第一层(编译时) :TypeScript 的
strict
检查类型、空值、函数签名 - 第二层(运行时) :JavaScript 的
'use strict'
检查语言层面的问题
深入理解:为什么需要两者?
JavaScript 严格模式的局限
'use strict'
再严格,也只是让错误暴露得早一点,但:
- ❌ 不能阻止类型错误(比如把字符串传给期望数字的函数)
- ❌ 不能保证空值安全(比如访问
null
的属性) - ❌ 不能检查函数签名(比如参数数量、类型)
TypeScript 严格模式的局限
strict: true
再强大,也只在编译时有效,但:
- ❌ 不能处理动态引入的第三方库(没有类型定义的)
- ❌ 不能检查运行时的值(比如从 API 返回的数据)
- ❌ 如果用了
any
或类型断言,类型检查就被绕过了
两者互补
类型检查] B --> C[编译] C --> D[运行阶段] D --> E[JavaScript 'use strict'
语言规则检查] B -.-> F[捕获类型错误
空值引用
函数签名问题] E -.-> G[捕获未声明变量
静默失败
危险语法] style B fill:#e1f5ff style E fill:#fff4e1
最佳组合:
- TypeScript
strict: true
:在开发时就把大部分问题拦住 - JavaScript
'use strict'
(自动加上):作为最后一道防线,拦住 TypeScript 也管不了的运行时问题
✅ 最佳实践:该怎么配置?
1. 新 TypeScript 项目:两个都要
json
// tsconfig.json
{
"compilerOptions": {
"strict": true, // 👈 已经包含 alwaysStrict: true
"target": "ES2020",
"module": "ESNext"
}
}
这样配置后:
- ✅ TypeScript 会做编译时检查
- ✅ 自动为每个文件加上
'use strict'
- ✅ 你不需要手写
'use strict'
2. 老 TypeScript 项目:逐步迁移
如果直接开 strict: true
会导致满屏报错,可以单独开启:
json
{
"compilerOptions": {
"strict": false, // 先不开总开关
// 逐步开启单个选项
"noImplicitAny": true, // 第一步:禁止隐式 any
"alwaysStrict": true, // 第二步:加 'use strict'
"strictNullChecks": true, // 第三步:空值检查
// ... 逐步开启其他选项
}
}
3. 纯 JavaScript 项目:只能用 'use strict'
如果你不用 TypeScript,那就只能用 JavaScript 的严格模式:
javascript
// 方式 1:全局开启(文件顶部)
'use strict';
// 你的代码...
csharp
// 方式 2:函数级别开启
function myFunction() {
'use strict';
// 只在这个函数内严格
}
推荐:配合 ESLint 强制添加:
java
// .eslintrc.js
module.exports = {
rules: {
'strict': ['error', 'global']
}
};
4. 配合 ESLint/Prettier
TypeScript 的 strict
模式专注类型检查,但代码质量还需要 ESLint:
perl
// .eslintrc.json
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"parserOptions": {
"project": "./tsconfig.json"
}
}
这样你会得到:
- TypeScript
strict
:类型安全 - ESLint:代码质量、最佳实践
- Prettier:代码格式
对比总结表
维度 | TypeScript strict: true |
JavaScript 'use strict' |
---|---|---|
本质 | 编译选项集合 | 运行时指令 |
生效时机 | 编译时(写代码时) | 运行时(代码执行时) |
检查内容 | 类型、空值、函数签名 | 语言规则、危险语法 |
错误提示 | IDE 实时提示、编译失败 | 运行时抛出异常 |
依赖 | TypeScript 编译器 | JavaScript 引擎 |
适用文件 | .ts 、.tsx |
所有 .js 文件 |
性能影响 | 无(编译时) | 微小(运行时) |
向后兼容 | 需要 TypeScript | 所有现代浏览器 |
配置方式 | tsconfig.json |
代码中写 'use strict' |
关联关系 | alwaysStrict 会自动加 'use strict' |
无关 TypeScript |
最佳实践 | 新项目必开 | TS 项目自动加上,JS 项目手动加 |
常见误区澄清
误区 1:"开了 TypeScript strict 就不需要 'use strict' 了"
❌ 错误
虽然 strict: true
包含 alwaysStrict: true
(会自动加 'use strict'
),但:
- TypeScript 只检查编译时的类型问题
'use strict'
检查运行时的语言问题
正确理解 :开了 strict: true
后,编译出的 JS 会自动带 'use strict'
,所以你不用手写。
误区 2:"'use strict' 能替代 TypeScript"
❌ 错误
'use strict'
再严格,也不能做类型检查。比如:
javascript
'use strict';
function add(a, b) {
return a + b;
}
add('1', 2); // ✅ 运行通过,结果是 '12'(字符串拼接)
TypeScript 会在编译时就发现类型问题:
lua
function add(a: number, b: number) {
return a + b;
}
add('1', 2); // ❌ 编译错误:Argument of type 'string' is not assignable to parameter of type 'number'
误区 3:"strict: true 太严格了,影响开发效率"
❌ 错误(短期看似如此,长期受益)
刚开始确实会遇到很多类型错误,但:
- 这些错误本来就存在,只是以前被隐藏了
- 在编译时发现远比在生产环境崩溃要好
- 类型提示会让重构和协作更安全
建议 :新项目直接开 strict: true
,老项目逐步迁移。
写在最后
研究完这两个"严格模式",我的理解是:
它们的关系:
- TypeScript
strict
:编译时的守护者,拦截类型错误、空值引用、函数签名问题 - JavaScript
'use strict'
:运行时的守护者,拦截语言层面的危险语法和意外行为 - 它们不是替代关系,而是互补关系
为什么要两者都用?
- TypeScript 再强大,也只在编译时有效
- 编译后的 JS 代码,依然需要
'use strict'
在运行时提供保护 strict: true
里的alwaysStrict
会自动加上'use strict'
,所以你只需要配置 TypeScript,不用手写
使用建议:
- TypeScript 项目 :开启
strict: true
(已包含alwaysStrict
) - 纯 JavaScript 项目 :手动加
'use strict'
,配合 ESLint 强制 - 不要因为名字相似就混淆它们:一个管编译时类型,一个管运行时语言规则
下次有人问你"TypeScript 的 strict 和 JavaScript 的 'use strict' 有啥区别",你可以自信地说:
一个在编译时保护你的类型安全,一个在运行时保护你的代码行为。名字像,但完全不是一回事!
TypeScript 官方文档
- Compiler Options: strict - TypeScript 严格模式官方说明
- TSConfig Reference - 完整的编译选项参考
JavaScript 官方规范
- ECMAScript Strict Mode - 严格模式的官方定义
- MDN - Strict mode - 最全面的严格模式文档
相关文档
- TypeScript Deep Dive: Strict - 深入理解 TypeScript 严格性
- JavaScript: The Good Parts - Douglas Crockford 讲解严格模式的设计哲学