TypeScript 和 JavaScript 的 'use strict' 有啥不同

205 阅读13分钟

都叫严格模式,但它们解决的问题完全不在一个层次上

前言

写完 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 团队真得背)

这篇文章就来聊聊,同样是"严格",它们到底严在哪里,又有什么本质区别。


目录


一个真实的困惑:我到底该开哪个?

先看一个常见场景。你在写 TypeScript 项目,tsconfig.json 里配了:

{
  "compilerOptions": {
    "strict": true
  }
}

然后在代码里写:

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 引擎的行为。

'use strict';  // 告诉 JS 引擎:"用严格模式跑这段代码"
​
x = 10;  // ReferenceError: x is not defined(运行时报错)

它解决什么?

JavaScript 早期设计的语言层面的问题

  • 运行时错误:把静默失败变成抛出异常
  • 危险语法:禁止容易出错的语法(比如 with、八进制字面量)
  • 意外行为:修正反直觉的行为(比如自动创建全局变量)

关键特征

mindmap
  root((JavaScript<br/>严格模式))
    运行时生效
      代码执行时检查
      依赖 JS 引擎
      无法在编译时发现问题
    语言层面
      修改语言行为
      禁止危险语法
      修正历史问题
    向后兼容
      老代码不受影响
      需要主动开启
      只影响声明的作用域

TypeScript 严格模式:编译时的守护者

TypeScript 的 strict: true 是另一个完全不同的东西。

它是什么?

一个编译选项集合,在代码编译(转换为 JS)之前进行类型检查

// tsconfig.json
{
  "compilerOptions": {
    "strict": true  // 这是个"总开关"
  }
}

当你开启 strict: true,实际上是同时开启了这 7 个编译选项:

{
  "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 引起的运行时错误
  • 函数安全:确保函数调用的类型正确性

关键特征

mindmap
  root((TypeScript<br/>严格模式))
    编译时生效
      转译前检查
      IDE 实时提示
      运行前发现问题
    类型系统层面
      强制类型明确
      空值安全检查
      函数类型检查
    配置灵活
      总开关
      可单独开关每个选项
          逐步迁移友好

核心差异:编译时 vs 运行时

现在重点来了,这两者的本质区别

1. 生效时机不同

flowchart LR
    A[编写代码] --> B[TypeScript 编译]
    B --> C[生成 JavaScript]
    C --> D[浏览器/Node.js 执行]

    B -.->|TypeScript strict| E[编译时检查]
    D -.->|JavaScript 'use strict'| F[运行时检查]

    style E fill:#e1f5ff
    style F fill:#fff4e1

TypeScript strict: true

  • ✅ 在编译阶段检查(你还在写代码的时候)
  • ✅ IDE 实时提示,根本不让你编译通过
  • ✅ 问题在开发阶段就被发现

JavaScript 'use strict'

  • ✅ 在运行阶段检查(代码已经在跑了)
  • ✅ 只有执行到那行代码才会报错
  • ✅ 问题可能在生产环境才暴露

2. 检查内容不同

检查项TypeScript strictJavaScript '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' 能捕获

'use strict';
​
function test() {
  myVar = 10;  // ❌ ReferenceError: myVar is not defined(运行时)
}
​
test();

TypeScript strict 不检查这个

// tsconfig.json: { "strict": true }function test() {
  myVar = 10;  // ⚠️ TypeScript: Cannot find name 'myVar'
               // 但这是因为 TypeScript 要求先声明变量
               // 不是因为 strict 模式
}

TypeScript 编译后:

"use strict";  // 👈 注意这里!因为 alwaysStrict: truefunction test() {
  myVar = 10;  // 运行时还是会被 'use strict' 捕获
}

结论

  • TS 的 strict 本身不处理未声明变量
  • strict 包含 alwaysStrict,会自动加 'use strict'
  • 最终还是靠 JS 的严格模式在运行时捕获

案例 2:隐式 any 类型

TypeScript strict 能捕获

// strict: truefunction greet(name) {
  // ❌ 编译错误:Parameter 'name' implicitly has an 'any' type
  console.log('Hello ' + name);
}

JavaScript 'use strict' 完全不管

'use strict';
​
function greet(name) {
  // ✅ 没问题,JS 本来就是动态类型
  console.log('Hello ' + name);
}

结论

  • TS 的 strict 强制你明确类型
  • JS 的 'use strict' 对类型无能为力(因为 JS 没有静态类型)

案例 3:空值安全

TypeScript strict 的强项

// 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' 无能为力

'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 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 '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'thisundefined,在运行时才报错

实战案例:看看它们如何配合工作

完整示例:两者互补

// tsconfig.json
{
  "compilerOptions": {
    "strict": true  // 包含 alwaysStrict: true
  }
}
// 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:

// 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' 捕获)
}

双重保险

  1. 第一层(编译时) :TypeScript 的 strict 检查类型、空值、函数签名
  2. 第二层(运行时) :JavaScript 的 'use strict' 检查语言层面的问题

深入理解:为什么需要两者?

JavaScript 严格模式的局限

'use strict' 再严格,也只是让错误暴露得早一点,但:

  • ❌ 不能阻止类型错误(比如把字符串传给期望数字的函数)
  • ❌ 不能保证空值安全(比如访问 null 的属性)
  • ❌ 不能检查函数签名(比如参数数量、类型)

TypeScript 严格模式的局限

strict: true 再强大,也只在编译时有效,但:

  • ❌ 不能处理动态引入的第三方库(没有类型定义的)
  • ❌ 不能检查运行时的值(比如从 API 返回的数据)
  • ❌ 如果用了 any 或类型断言,类型检查就被绕过了

两者互补

graph LR
    A[开发阶段] --> B[TypeScript strict<br/>类型检查]
    B --> C[编译]
    C --> D[运行阶段]
    D --> E[JavaScript 'use strict'<br/>语言规则检查]

    B -.-> F[捕获类型错误<br/>空值引用<br/>函数签名问题]
    E -.-> G[捕获未声明变量<br/>静默失败<br/>危险语法]

    style B fill:#e1f5ff
    style E fill:#fff4e1

最佳组合

  • TypeScript strict: true:在开发时就把大部分问题拦住
  • JavaScript 'use strict' (自动加上):作为最后一道防线,拦住 TypeScript 也管不了的运行时问题

✅ 最佳实践:该怎么配置?

1. 新 TypeScript 项目:两个都要

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,  // 👈 已经包含 alwaysStrict: true
    "target": "ES2020",
    "module": "ESNext"
  }
}

这样配置后:

  • ✅ TypeScript 会做编译时检查
  • ✅ 自动为每个文件加上 'use strict'
  • 你不需要手写 'use strict'

2. 老 TypeScript 项目:逐步迁移

如果直接开 strict: true 会导致满屏报错,可以单独开启

{
  "compilerOptions": {
    "strict": false,  // 先不开总开关
​
    // 逐步开启单个选项
    "noImplicitAny": true,            // 第一步:禁止隐式 any
    "alwaysStrict": true,             // 第二步:加 'use strict'
    "strictNullChecks": true,         // 第三步:空值检查
    // ... 逐步开启其他选项
  }
}

3. 纯 JavaScript 项目:只能用 'use strict'

如果你不用 TypeScript,那就只能用 JavaScript 的严格模式:

// 方式 1:全局开启(文件顶部)
'use strict';
​
// 你的代码...
// 方式 2:函数级别开启
function myFunction() {
  'use strict';
  // 只在这个函数内严格
}

推荐:配合 ESLint 强制添加:

// .eslintrc.js
module.exports = {
  rules: {
    'strict': ['error', 'global']
  }
};

4. 配合 ESLint/Prettier

TypeScript 的 strict 模式专注类型检查,但代码质量还需要 ESLint:

// .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: trueJavaScript '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' 再严格,也不能做类型检查。比如:

'use strict';
​
function add(a, b) {
  return a + b;
}
​
add('1', 2);  // ✅ 运行通过,结果是 '12'(字符串拼接)

TypeScript 会在编译时就发现类型问题:

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,不用手写

使用建议

  1. TypeScript 项目:开启 strict: true(已包含 alwaysStrict
  2. 纯 JavaScript 项目:手动加 'use strict',配合 ESLint 强制
  3. 不要因为名字相似就混淆它们:一个管编译时类型,一个管运行时语言规则

下次有人问你"TypeScript 的 strict 和 JavaScript 的 'use strict' 有啥区别",你可以自信地说:

一个在编译时保护你的类型安全,一个在运行时保护你的代码行为。名字像,但完全不是一回事!

TypeScript 官方文档

  1. Compiler Options: strict - TypeScript 严格模式官方说明
  2. TSConfig Reference - 完整的编译选项参考

JavaScript 官方规范

  1. ECMAScript Strict Mode - 严格模式的官方定义
  2. MDN - Strict mode - 最全面的严格模式文档

相关文档

  1. TypeScript Deep Dive: Strict - 深入理解 TypeScript 严格性
  2. JavaScript: The Good Parts - Douglas Crockford 讲解严格模式的设计哲学