Menu

TypeScript 和 JavaScript 的区别:对比与示例

TypeScript 就是 JavaScript 加上一套在代码运行前检查的静态类型系统。本文把两者放在一起对比:语法、类型检查器能发现什么问题、构建步骤、运行速度、学习曲线,以及如何迁移 JavaScript 项目。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

TypeScript 就是在 JavaScript 之上加了一套静态类型系统。每个 JavaScript 程序都是合法的 TypeScript 语法;TypeScript 增加了类型注解、一个在代码运行前检查这些注解的编译器,以及一个再把它们移除的构建步骤。运行时只有 JavaScript,所以区别完全在于你在发布之前能发现什么。

在 JavaScript 中,同一个函数就是这段代码去掉 type Product = ...、: Product[] 和 : number。类型为编译器和编辑器提供了信息,但不会改变程序的行为。

TypeScript 与 JavaScript 一览

JavaScriptTypeScript
类型系统动态:类型属于值,只有在运行时才知道静态:类型通过声明或推断得到,在编译期检查
类型错误何时出现那一行运行时(undefined、NaN、TypeError)在编辑器中边写边提示,以及编译时
运行环境浏览器、Node.js、Deno、Bun,直接运行同样的环境,在移除类型之后运行
构建步骤不需要tsc 或打包工具,或者自己去除类型的运行时
文件.js、.mjs、.cjs.ts、.mts、.cts、.tsx,以及 .d.ts 类型声明
运行速度基准完全相同:输出就是 JavaScript
编辑器支持基于推断类型和库类型定义的自动补全,可能不完整基于声明类型的自动补全、重命名和“查找所有引用”
学习曲线较低JavaScript 加上类型系统
标准ECMAScript,由 TC39 制定微软的开源项目,跟进 ECMAScript

同一段代码的两种写法

下面是一个 JavaScript 函数。其中没有任何地方说明 user 应该是什么样子:

function greeting(user) {
    return `Hello, ${user.firstName} ${user.lastName}`;
}

greeting({ firstname: "Ada", lastName: "Lovelace" });
// "Hello, undefined Lovelace", no error anywhere

TypeScript 版本只需声明一次结构,拼写错误就会在代码运行之前被报告出来:

interface User {
    firstName: string;
    lastName: string;
}

function greeting(user: User): string {
    return `Hello, ${user.firstName} ${user.lastName}`;
}

greeting({ firstname: "Ada", lastName: "Lovelace" });
// error TS2561: Object literal may only specify known properties,
// but 'firstname' does not exist in type 'User'. Did you mean to write 'firstName'?

语法上的区别就只有这些注解。TypeScript 也增加了一些自己的声明(interface、type、enum、Array<string> 这样的泛型、private 这样的访问修饰符),但语句、运算符和内置对象都是 JavaScript 的。

TypeScript 能发现而 JavaScript 发现不了的问题

JavaScript 会悄悄地转换类型。对于来自表单字段的值(它们总是字符串),这种 bug 很常见。运行下面的代码,看看编译器怎么说:

index.ts(7,17): error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'number[]'.
  Type 'string' is not assignable to type 'number'.

在 JavaScript 中这段代码能运行,并输出 010205,因为 0 + "10" 是字符串拼接。TypeScript 会拒绝编译,直到你把字符串转换成数字,例如使用 fromForm.map(Number)。

另一大类问题是可能缺失的值。Array.prototype.find 在没有匹配项时返回 undefined,TypeScript 会强制你处理这种情况:

index.ts(8,13): error TS18048: 'user' is possibly 'undefined'.

纯 JavaScript 版本会在运行时崩溃,报 TypeError: Cannot read properties of undefined (reading 'name')。TypeScript 中的修复方法,就是处理编译器指出的那种情况:

输出:

GRACE
no user with id 3

TypeScript 发现不了的问题有:逻辑错误(一个写错的公式类型也是对的),以及运行时进入程序的数据的任何问题。一个类型标注为 User 的 API 响应,只有在发送它的服务器正确时才是正确的,因为代码一运行,类型就不存在了。对这类数据,要用运行时代码来检查。

在 TypeScript 中使用 JavaScript 库

每个 npm 包都能在 TypeScript 中使用,因为输出本来就是 JavaScript。一个包的类型来自以下三处之一:

  • 包自带 .d.ts 文件。 大多数仍在积极维护的包都是这样,不需要额外安装任何东西。
  • 单独的 @types 包,来自社区项目 DefinitelyTyped:npm install --save-dev @types/lodash 为 lodash 添加类型。
  • 哪里都没有。 这时如果开启了 strict,导入语句本身就会报错:
error TS7016: Could not find a declaration file for module 'lodash'. '/project/node_modules/lodash/lodash.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';`

修复方法是安装对应的 @types 包(如果存在的话),或者自己在 .d.ts 文件中描述这个模块;声明文件页面 介绍了具体做法。

构建步骤

浏览器和 Node.js 不做类型检查,所以 TypeScript 需要在源码和实际运行的代码之间加一个步骤。常见的配置有三种:

  • 用 tsc 编译所有文件。 它做类型检查并写出 .js 文件,通常放在 dist 文件夹中。简单,也是库的标准做法。
  • 打包工具或开发服务器移除类型,tsc --noEmit 负责检查。 Vite 和 esbuild 只去除类型而不检查,这让热更新保持快速;类型检查交给编辑器和 CI 步骤。
  • 由运行时移除类型。 当前版本的 Node.js、Deno 和 Bun 都能直接运行 .ts 文件。它们在运行时都不做类型检查,所以仍然要靠 tsc --noEmit(或 deno check)来发现类型错误。

JavaScript 完全不需要这些,这是它在小脚本上最大的实际优势。TypeScript 这一步的代价主要是配置(一个 tsconfig.json 和一个 typescript 开发依赖)以及编译时间;TypeScript 7 的原生编译器在大型项目上把编译时间缩短到了大约十分之一。

学习曲线

你掌握的所有 JavaScript 知识都可以沿用,因为 TypeScript 的运行时就是 JavaScript。新的内容是类型系统,可以分层学习:

  1. 给变量、参数和返回值加注解(: string、: number[])。
  2. 用 interface 和 type 定义对象类型、可选属性,以及 string | number 这样的联合类型。
  3. 收窄:用 typeof、in 或 === 检查一个值,让编译器知道你处于哪种情况。
  4. 泛型、Partial<T> 和 Pick<T, K> 这类工具类型,以及供库作者使用的高级类型。

前两层就覆盖了大部分应用代码。很多类型都是推断出来的,所以大量 TypeScript 代码看起来就像 JavaScript,只在函数签名上有注解。

何时选择 TypeScript,何时选择 JavaScript

TypeScript 比 JavaScript 好吗?对于多人维护或要存在多年的代码,通常是的,整个行业也正朝这个方向发展:按 GitHub 每月贡献者数量统计,TypeScript 在 2025年8月超过了 JavaScript 和 Python,成为 GitHub 上使用最多的语言。对于小脚本,纯 JavaScript 往往是更合适的工具。

以下情况选择 TypeScript:

  • 代码由不止一个人维护,或者要维护几个月甚至几年。
  • 代码库大到你无法记住每个函数签名。
  • 你经常重构:重命名一个属性会更新所有用到它的地方,编译器还会列出遗漏之处。
  • 你要发布一个库:.d.ts 文件能为使用者提供自动补全和检查。
  • 框架要求使用它。Angular 应用用 TypeScript 编写,Next.js 和 Astro 默认用 TypeScript 创建新项目,Vite 的 React、Vue 和 Svelte 模板也都有 TypeScript 版本。

以下情况选择 JavaScript:

  • 程序是一个简短的脚本、一次性的实验,或者在浏览器控制台里运行的代码片段。
  • 你第一次学编程,希望一次接触的概念少一些。
  • 没有构建步骤,你也不想要构建步骤。即便如此,在普通 .js 文件中使用 // @ts-check 配合 JSDoc,也能得到一定的检查。

把 JavaScript 项目迁移到 TypeScript

迁移不必一次完成。编译器可以接受与 TypeScript 并存的 JavaScript:

{
    "compilerOptions": {
        "allowJs": true,
        "checkJs": false,
        "outDir": "dist",
        "rootDir": "src"
    },
    "include": ["src"]
}

开启 allowJs 后,.js 文件也会被编译,并且可以从 .ts 文件导入,反之亦然。然后逐步转换:

  1. 把一个文件从 .js 重命名为 .ts,修复编译器在其中报告的错误。
  2. 从依赖树的末端开始(导入较少的工具模块),再逐步向内推进。
  3. 开启 checkJs,或者在单个 .js 文件顶部加上 // @ts-check,对还没有重命名的文件做类型检查。

在开启检查的 JavaScript 文件中,类型由 JSDoc 注释提供:

// @ts-check

/**
 * @param {number} price
 * @param {number} qty
 * @returns {number}
 */
function lineTotal(price, qty) {
    return price * qty;
}

lineTotal("3", 2);
// error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

有些团队就停在这一步:JavaScript 文件配合 JSDoc 类型,由 tsc 检查,自己的代码不需要构建步骤。另一些团队则一路转换到 .ts。如果在已有项目中开启 strict,一开始会看到大量错误;严格模式页面 列出了每个标志检查的内容,方便你逐个开启。

常见问题

TypeScript 和 JavaScript 的主要区别是什么?

TypeScript 给 JavaScript 加上了静态类型。你描述每个值是什么(name: string、items: Item[]),TypeScript 编译器会在代码运行之前报告错误。JavaScript 不做任何提前检查:类型错误只有在那一行执行时才会暴露,通常表现为 undefined 或 TypeError。

TypeScript 比 JavaScript 好吗?

对于大多数由多人维护、或者会存在几周以上的项目来说,是的:类型能发现一整类 bug,让重构变得安全,还能为编辑器的自动补全提供支持。对于简短的脚本、快速原型或练习题,纯 JavaScript 上手更快,也不需要配置构建。

TypeScript 比 JavaScript 运行得更快吗?

不会,也不会更慢。TypeScript 编译成 JavaScript,类型会被擦除,所以运行的代码就是你手写也会写出的那份 JavaScript。唯一的额外开销是开发时的编译时间。

应该先学 JavaScript 还是 TypeScript?

先学 JavaScript 基础,或者和 TypeScript 一起学。所有运行时行为(变量、函数、对象、数组、Promise)都是 JavaScript 的,TypeScript 只是描述它们。只要你能写小型 JavaScript 程序,再加上类型就只是一小步。

可以在同一个项目中同时使用 TypeScript 和 JavaScript 吗?

可以。在 tsconfig.json 中设置 "allowJs": true,编译器就会接受与 .ts 文件并存的 .js 文件。再加上 "checkJs": true(或者在每个文件中加 // @ts-check 注释),就能利用 JSDoc 注释中的类型对 JavaScript 文件也做类型检查。这是逐个文件迁移项目的常用方法。

Coddy 编程语言插图

和 Coddy 一起学编程

开始