从JS到TS:我们放弃了自由,却赢得了整个世界

TypeScript 和 JavaScript 到底差在哪儿?为啥越来越多项目从 JS 迁移到了 TS?用了大半年 TS 后,慢慢摸透了这两门语言的核心差异,不只是多了几个类型注解那么简单。

一、类型系统:从「动态宽松」到「静态严格」

JavaScript 最显著的特点就是动态类型 ------ 变量类型可以随时变化,声明时不用指定类型,运行时才确定。比如:

js 复制代码
let num = 123;
num = "hello"; 
num = { name: "test" }; 

这种灵活性在小脚本里很方便,但项目大了就容易出问题。比如写了个计算函数,本来该传数字,结果传了字符串,JS 不会提前提醒你:

js 复制代码
function add(a, b) {
  return a + b;
}
add("1", 2); // 返回 "12",可能不是预期结果

TypeScript 则引入了静态类型系统,变量声明时可以指定类型,且类型一旦确定(除非用 any)就不能随意更改。上面的函数用 TS 写会变成:

ts 复制代码
function add(a: number, b: number): number {
  return a + b;
}
add("1", 2); // 直接报错:类型"string"不能赋给类型"number"

TS 的类型远不止基本类型,还能定义接口、联合类型、交叉类型等复杂结构。比如定义一个用户类型:

ts 复制代码
interface User {
  name: string;
  age: number;
  isAdmin?: boolean; // 可选属性
}
const user: User = {
  name: "张三",
  age: 25
  // 少个属性或类型不对都会报错
};

联合类型能让变量具备多种可能的类型,比如一个既能是字符串也能是数字的变量:

ts 复制代码
let data: string | number;
data = "hello"; // 合法
data = 123; // 合法
data = true; // 报错:类型"boolean"不能赋给类型"string | number"

交叉类型则可以组合多个类型的特性,比如同时拥有用户和权限信息的对象:

ts 复制代码
interface User {
  name: string;
}
interface Permissions {
  canEdit: boolean;
}
type UserWithPermissions = User & Permissions;
const user: UserWithPermissions = {
  name: "李四",
  canEdit: true
};

这种类型约束让代码更健壮,尤其在多人协作时,类型定义本身就是一种清晰的文档。

二、代码检查:从「运行时报错」到「编译时预警」

JavaScript 的错误往往要等到代码运行时才会暴露。比如访问一个不存在的对象属性:

js 复制代码
const user = { name: "李四" };
console.log(user.age); //  undefined,不报错

如果是在复杂业务逻辑里,这种隐性错误很难排查。比如在处理后端返回数据时,经常会因为字段名拼写错误导致问题:

js 复制代码
// 后端返回数据格式:{ userName: "张三", userAge: 25 }
function formatUser(user) {
  return `姓名:${user.name},年龄:${user.age}`;
}
// 实际调用时返回 "姓名:undefined,年龄:undefined"

TypeScript 则把错误检查提前到了编译阶段。上面的代码用 TS 写,编辑器会立刻标红:

ts 复制代码
interface ApiUser {
  userName: string;
  userAge: number;
}
const user: ApiUser = { userName: "李四", userAge: 25 };
console.log(user.age); // 报错:属性"age"在类型"ApiUser"上不存在

更关键的是,TS 会做类型推断,即使不写类型注解,也能自动分析变量类型。比如:

ts 复制代码
let message = "hello";
message = 123; // 报错
// 数组类型推断
const numbers = [1, 2, 3];
numbers.push("4"); // 报错

虽然没写 : string 或 : number\[\],但 TS 能根据初始值推断出准确类型。这种无感化检查既保留了 JS 的灵活,又增加了安全性。

三、语言特性:从「原生语法」到「扩展增强」

TypeScript 作为 JS 的超集,不仅包含所有 JS 语法,还新增了很多实用特性。

比如枚举(Enum),JS 里没有原生枚举,通常用对象模拟:

js 复制代码
const Status = {
  PENDING: 0,
  SUCCESS: 1,
  ERROR: 2
};

但这样无法限制取值范围,不小心赋值成 3 也不会报错。TS 的枚举则能严格约束:

ts 复制代码
enum Status {
  PENDING,
  SUCCESS,
  ERROR
}
let status: Status = Status.PENDING;
status = 3; // 报错:不能将类型"3"赋给类型"Status"

枚举还支持字符串值,让代码更具可读性:

ts 复制代码
enum HttpMethod {
  GET = "GET",
  POST = "POST",
  PUT = "PUT",
  DELETE = "DELETE"
}
function request(url: string, method: HttpMethod) {
  // ...
}
request("/api/user", HttpMethod.GET); 

再比如泛型,能写出适配多种类型的通用代码,同时保持类型安全。JS 实现一个通用的取值函数可能会丢失类型信息:

js 复制代码
function getValue(obj, key) {
  return obj[key];
}
const user = { name: "王五", age: 30 };
const name = getValue(user, "name"); // 类型是 any

用 TS 泛型实现则能保留类型:

ts 复制代码
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
const user = { name: "王五", age: 30 };
const name = getValue(user, "name"); // 类型是 string
const age = getValue(user, "age"); // 类型是 number

TS 还提供了很多内置的泛型工具类型,比如 Partial 可以把对象的所有属性变为可选:

ts 复制代码
interface User {
  name: string;
  age: number;
}
type PartialUser = Partial<User>;
// 等价于 { name?: string; age?: number }

此外,TS 支持的接口、抽象类、类型别名等特性,让代码结构更清晰,尤其适合大型项目。比如用抽象类定义一个基础形状类,强制子类实现计算面积的方法:

ts 复制代码
abstract class Shape {
  abstract getArea(): number;
}
class Circle extends Shape {
  radius: number;
  constructor(radius: number) {
    super();
    this.radius = radius;
  }
  getArea() {
    return Math.PI * this.radius **2;
  }
}
class Square extends Shape {
  sideLength: number;
  constructor(sideLength: number) {
    super();
    this.sideLength = sideLength;
  }
  // 如果忘了实现 getArea 会直接报错
  getArea() {
    return this.sideLength** 2;
  }
}

四、编译过程:从「直接运行」到「转译执行」

JavaScript 是解释型语言,代码可以直接在浏览器或 Node 环境中运行,不需要额外处理。写好的 JS 文件,通过 <script> 标签引入就能执行:

js 复制代码
<script src="app.js"></script>

TypeScript 则必须经过编译转译,因为浏览器不认识 TS 语法。编译过程会做两件事:一是类型检查,二是把 TS 特有语法转成 JS 代码。比如这段 TS 代码:

ts 复制代码
enum Direction {
  UP,
  DOWN
}
function move(dir: Direction) {
  // ...
}

编译后会变成纯 JS:

js 复制代码
var Direction;
(function (Direction) {
  Direction[Direction["UP"] = 0] = "UP";
  Direction[Direction["DOWN"] = 1] = "DOWN";
})(Direction || (Direction = {}));
function move(dir) {
  // ...
}

可以看到,类型注解和枚举语法都被转成了 JS 兼容的代码,类型信息在编译后会被完全移除,不影响运行时性能。

这也意味着 TS 项目需要额外配置编译环境(比如 tsconfig.json),指定编译目标、模块规范等:

json 复制代码
{
  "compilerOptions": {
    "target": "ES6", // 编译到 ES6 版本
    "module": "ESNext", // 使用 ES 模块系统
    "strict": true, // 开启严格模式检查
    "outDir": "./dist", // 输出目录
    "rootDir": "./src" // 源代码目录
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

在开发过程中,通常会使用 tsc --watch 命令启动监听模式,当 TS 文件变化时自动重新编译,确保始终能运行最新的代码。

五、开发体验:从「全靠记忆」到「智能提示」

写 JavaScript 时,调用函数或访问对象属性全靠记忆或查文档。比如用数组方法时,经常要想 forEach 的参数顺序:

js 复制代码
[1, 2, 3].forEach((item, index) => {
  // 记不清参数是 (index, item) 还是 (item, index)?
});

使用第三方库时更麻烦,比如调用 axios 的 get 方法,得去查文档才知道参数格式和返回值结构:

js 复制代码
// 不确定第二个参数能不能传配置项
axios.get("/api/data", { timeout: 5000 });

TypeScript 配合现代 IDE(比如 VS Code)能提供精准的智能提示。输入 . 后,编辑器会自动列出可用的属性和方法,还会显示参数类型和返回值:

ts 复制代码
interface User {
  name: string;
  age: number;
  sayHi: () => void;
}
const user: User = { /* ... */ };
user. // 输入 . 后会提示 name、age、sayHi

引入带有类型定义的库(比如通过 @types 系列包)后,第三方库的方法也能获得完整的类型提示:

ts 复制代码
import axios from "axios";
// 输入 axios.get 时会提示参数类型和返回值结构
axios.get("/api/data", { timeout: 5000 });

这种提示不仅减少拼写错误,还能帮你快速探索不熟悉的 API。重构代码时更明显,改一个函数的参数类型,所有调用它的地方都会即时报错,不用担心漏改。比如把函数参数从字符串改成数字:

ts 复制代码
// 原函数
function calculate(num: string) {
  // ...
}
calculate("123");
// 修改参数类型后
function calculate(num: number) {
  // ...
}
// 调用处会立刻报错,提示需要传入 number 类型
calculate("123");

不是替代,而是增强

很多人觉得 TS 是 JS 的替代品,其实不是。TS 的本质是给 JS 加了一层类型保护,让代码在规模扩大时依然可控。小型项目用 JS 快速迭代没问题,但中大型项目用 TS,长期来看能节省大量调试和维护成本。

这两门语言的关系更像「铅笔与带橡皮的铅笔」------TS 保留了 JS 的灵活,又多了纠错能力。理解它们的核心区别,不是为了争论哪个更好,而是为了在合适的场景用对工具。当项目需要更严格的类型约束和更清晰的代码结构时,TS 无疑是更好的选择。

相关推荐
mldong3 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排3 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60614 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角5 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!5 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰5 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi7 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen7 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马8 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒8 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端