TypeScript 入门:给 JavaScript 的每样东西,提前贴一张标签

如果你写过一点 JavaScript,被 undefined 的报错折磨过、被拼错的属性名坑过,那么这篇是给你的。我们不背语法表,而是把 TypeScript 想成一件事——给代码里的每样东西,提前贴一张写清楚「它是什么」的标签。

淡紫色调的插画:一位女生坐在沙发上抱着马克杯,配合 TypeScript 入门文章的封面

导读:如果你写过一点 JavaScript,被 undefined 的报错折磨过、被拼错的属性名坑过,那么这篇是给你的。我们不背语法表,而是把 TypeScript 想成一件事——给代码里的每样东西,提前贴一张写清楚「它是什么」的标签。读完你会知道类型到底替你挡了什么、any 为什么是陷阱、以及为什么这些标签在代码真正跑起来之前就被撕掉了。零基础从头读;已经会一点的,直接跳到第六、第七节。

一、为什么要给 JavaScript 加类型?

JavaScript 很自由,自由到有点危险。它允许你把不搭界的东西凑在一起,也不吭声:

"5" - 1   // 结果是 4  —— JS 偷偷把字符串当数字减了
"5" + 1   // 结果是 "51" —— 同样两个值,这次它当字符串拼了

更常见的是这一类:

const 用户 = { 名字: "小初" };
console.log(用户.名子);   // 你把「名字」拼成了「名子」
// JS 不报错,安静地给你一个 undefined

问题不在于「出错」——谁都会拼错字。问题在于 JavaScript 要等代码真的运行到这一行,才发现不对。而这一行,可能是用户在页面上点了某个按钮、走了某条分支才触发的。于是错误从你的键盘出发,一路躲过你,最后在用户的屏幕上炸开:Cannot read properties of undefined

TypeScript 要解决的就是这个「太晚了」。它的价值用一句话说清:

把「运行时才炸」的错,提前到「你打字时」就变红。

它怎么做到的?靠给代码里的每样东西事先贴一张标签,说清楚它是什么。变量是个盒子,标签写「只能装数字」;你往里塞了字符串,那个盯着标签的质检员——也就是编辑器和编译器——当场就拦住你,红线画在出错的那一行,而不是等到上线。这篇文章剩下的部分,都是在讲这些标签怎么贴、贴在哪。

二、给变量贴第一张标签:基础类型

给变量贴标签,语法就是在名字后面加一个冒号,冒号后面写类型:

let 年龄: number = 18;
let 名字: string = "小初";
let 是会员: boolean = true;

numberstringboolean 是你每天都会用到的三张基础标签。贴上之后,塞错东西就当场报错:

let 年龄: number = "十八";
// ❌ 报错:不能把 string 赋给 number

年龄 = "老了";
// ❌ 报错:标签一旦贴上,这个盒子以后也只能装数字

这里有个新手常有的误会:以为每个变量都得手写冒号,很累。其实不用——如果你在声明的同时就赋了值,TypeScript 会自己看这个值、推断出标签

let 名字 = "小初";
// 你没写冒号,但 TS 已经知道「名字」是 string
名字 = 42;   // ❌ 照样报错

这叫类型推断。所以实践里你写的冒号比想象中少:初始值能说明问题的地方,交给 TS 自己看就行;只有当下暂时没值、或者想把标签写得比 TS 猜的更精确时,才手动标注。

数组也是一张标签,写法是「元素类型 + 一对方括号」:

let 分数: number[] = [90, 85, 100];
分数.push(88);      // ✅ 数字,可以
分数.push("满分");   // ❌ 这个数组只装数字

标签不是负担,是让机器替你记住「这个盒子只装什么」——记性这种事,本来就不该靠人。

三、any:那张「随便装」的万能标签

TypeScript 里有一张特殊的标签,叫 any。贴上它,这个盒子就什么都能装、怎么用都不报错:

let 东西: any = 5;
东西 = "变成字符串";   // 不报错
东西 = true;          // 不报错
东西.随便点个方法();    // 也不报错

听起来很方便,其实这是全篇你最该警惕的东西。any 的真正含义是:把标签撕了,告诉质检员「这个你别管」。于是第二节讲的那套保护,在这个盒子上全部失效——你又回到了纯 JavaScript,还白白多打了 : any 几个字。

那最后一行 东西.随便点个方法() 就是典型的坑:它编译时一声不吭,运行时照样炸给用户看。你花力气引入 TypeScript,就是为了不让这种事发生,而 any 恰好把这道防线亲手拆了。

你会在几个地方遇到它:接手一段老 JS 代码、用了某个没写类型的第三方库、或者单纯图省事。TypeScript 有时也会自己冒出 any(比如一个函数参数你没给它贴标签,它就默认是 any)。

any 能让红线消失,但红线消失不等于错误消失——只是没人替你盯着了。把它当成「我暂时投降」的记号,越少越好。

四、给函数贴标签:进料口和出料口

函数像一台机器:参数是进料口,返回值是出料口,两头都能贴标签。进料口的标签写在参数后面,出料口的标签写在括号后面:

function 打招呼(名字: string): string {
  return "你好," + 名字;
}

这行代码里,名字: string 是进料口的标签,): string 是出料口的标签。贴好之后,喂错料当场就被拦:

打招呼("小初");   // ✅
打招呼(42);       // ❌ 进料口要 string,你给了 number
打招呼();         // ❌ 少喂了一个参数

出料口的标签也不是摆设。如果你在函数里手滑返回了不该返回的东西,它会当场报——相当于机器承诺「我出的是字符串」,你却让它吐了个数字,质检员不答应:

function 打招呼(名字: string): string {
  return 名字.length;   // ❌ 说好出 string,这里却要吐 number
}

如果某个参数可给可不给,在它后面加一个问号 ?,它就成了可选参数

function 打招呼(名字: string, 称呼?: string): string {
  return "你好," + (称呼 ? 称呼 : "") + 名字;
}
打招呼("小初");         // ✅ 不给称呼也行
打招呼("小初", "同学");   // ✅ 给了也行

给函数两头都贴好标签,别人调用它时不用翻你的源码——看标签就知道该喂什么、会吐什么。函数签名本身成了最新、最不会撒谎的文档。

五、描述一个对象长什么样:interfacetype

前端代码里最常打交道的不是单个数字字符串,而是对象:接口返回的一条数据、一个组件收到的一堆参数,都是对象。那怎么给「一个对象」贴标签?用 interface,写一张「清单」,列清楚这个对象该有哪些字段、每个字段是什么类型:

interface 用户 {
  名字: string;
  年龄: number;
  是会员: boolean;
}

有了这张清单,就能拿它当标签去贴:

const 小初: 用户 = {
  名字: "小初",
  年龄: 20,
  是会员: true,
};

接下来,凡是对不上清单的,都会被逐条挑出来:

const 甲: 用户 = { 名字: "甲", 年龄: 20 };
// ❌ 缺了「是会员」这一格

const 乙: 用户 = { 名字: "乙", 年龄: "二十", 是会员: true };
// ❌「年龄」这一格要 number,你放了 string

const 丙: 用户 = { 名字: "丙", 年龄: 20, 是会员: true, 头像: "x.png" };
// ❌ 清单里没有「头像」这一格

如果某个字段可有可无,同样在它后面加问号:

interface 用户 {
  名字: string;
  年龄: number;
  是会员: boolean;
  头像?: string;   // 有就填,没有也不报错
}

你可能还会看到另一种写法 type

type 用户 = {
  名字: string;
  年龄: number;
};

在「描述一个对象的形状」这件事上,interfacetype 几乎可以互换。它们有一些细微区别,但那是进阶话题——入门阶段你只要记住:两个都能用来描述对象,团队里统一一种就好。

这一节看起来简单,用处却极大。真实场景是这样的:后端接口返回 { id, title, status },你顺手给它写一个 interface,之后代码里但凡把 title 拼成 titel、或者以为 status 是数字,立刻就是一条红线,而不是等页面渲染出一片空白你再回来查半天。

interface 是一张「这个盒子里必须有哪些格子、每格装什么」的清单,机器拿着它,逐格替你核对。

六、一个东西可能是好几种:联合类型与字面量类型

现实没那么整齐。有时一个值可能是好几种类型——比如一个 id,老系统里是数字,新系统里换成了字符串,两种你都得接得住。这时用一根竖线 | 把几张标签并起来,叫联合类型

let 编号: string | number;
编号 = 42;      // ✅
编号 = "abc";   // ✅
编号 = true;    // ❌ 只允许 string 或 number

但联合类型有个配套规矩,这也是 TypeScript 特别聪明的地方:用它之前,你得先说清楚现在到底是哪一种。因为字符串能 .trim(),数字不能:

function 处理(编号: string | number) {
  编号.trim();
  // ❌ 报错:万一它是 number 呢?number 没有 trim

  if (typeof 编号 === "string") {
    编号.trim();   // ✅ 这里 TS 已经确定它是 string 了
  }
}

这不是找你麻烦,而是逼你把「万一是另一种情况」当场想清楚——而 bug 最爱藏的地方,恰恰就是「我以为它一定是那种」。

标签还能精确到只能是某几个具体的值,这叫字面量类型

let 状态: "成功" | "失败" | "加载中";
状态 = "加载中";   // ✅
状态 = "陈功";     // ❌ 拼错了,当场红,不在这三个之内

这个在前端里到处都是:一个请求的状态、一个按钮的尺寸、一个主题的名字,用字面量类型一钉死,拼错、写了没定义过的值,全都逃不掉。

最后还有一件 TypeScript 会盯着你的事:「可能没有值」本身也是一种类型。开了严格模式后,一个可能是 null 的东西,TS 不许你直接用,得先判过:

const 输入框 = document.querySelector("input");
// 页面上不一定真有这个元素,所以它的类型是 「元素 或 null」

输入框.value;
// ❌ 报错:万一没找到,null 上没有 value(这正是运行时会炸的那一步)

if (输入框) {
  输入框.value;   // ✅ 判过之后才让你用
}

联合类型让标签诚实地写出「它可能是这几种」,然后逼你在用之前把「到底是哪一种」搞清楚——把假设摊到明面上,bug 就没地方藏了。

七、类型是「打包时的标签」,不跟着货物发出去

最后这件事,很多人用了很久 TypeScript 都没意识到,但它能帮你想通一大堆疑惑:浏览器和 Node 其实根本不认识 TypeScript。

中间有一道你可能没太留意的工序,叫编译:一个叫 tsc 的工具(或者 Vite 这类构建工具顺手做掉),把你写的 .ts 变成 .js。而这一步,会把你贴的所有标签整个撕掉。对比一下前后:

// 你写的 .ts
function 打招呼(名字: string): string {
  return "你好," + 名字;
}
// 编译出来、真正运行的 .js
function 打招呼(名字) {
  return "你好," + 名字;
}

看到了吗?冒号后面的类型、返回值的标签,全没了。真正在浏览器里跑的,是右边这段干干净净的 JavaScript。

所以类型是「打包时贴、发货前撕」的标签:它在你开发、质检的阶段拦住错误;等货物(真正运行的 JS)出厂,标签一个字节都不带走。由此能推出两个结论,帮你避开常见误解:

  • 类型不会让你的代码变快或变慢——它在运行时压根不存在,谈不上影响性能。
  • 类型管得着「你写的代码」,管不着「运行时从外面进来的数据」——用户在输入框里敲的、网络接口返回的,都是货物已经上路之后才到的东西,那时标签早撕了。你标注 : 用户 只是承诺它长这样,并不能保证后端真按这个发。

第二条尤其重要,也常常被误解成「我写了类型就万无一失了」。给「外面来的数据」也把好关,是另一套功夫,叫运行时校验——那是下一篇的事,这里你只要先记住这条边界在哪。

TypeScript 帮的是「写」的阶段,不是「跑」的阶段。它是写给你和你的编辑器看的备注,不是写给用户的浏览器看的。

八、小结

很多人第一印象里,TypeScript 是「更严格、更啰嗦的 JavaScript」,写起来束手束脚。但读到这里你应该能反过来看它了:

TypeScript 把「每样东西到底是什么」这件事,从你的脑子里、从注释里、从口头约定里,搬到了编译器能替你逐一核对的地方。你写的仍然是 JavaScript,只是给每样东西多贴了一张机器能读的标签。于是拼错的属性名、喂错的参数、忘了判的空,不再等到用户点进来才炸,而是在你打字的那一刻就变成一条红线。

你担心的问题纯 JavaScriptTypeScript
拼错属性名 / 参数喂错运行到那行才炸,可能在用户端打字时当场变红
一个值可能有好几种类型全靠你自己记得判断联合类型逼你用前先分清
可能是 null 的东西忘了判空 → 运行时崩不判过不让你用
函数该传什么、返回什么翻源码或猜看签名标签就知道

入门到这儿就够你上手了。你已经能看懂大部分 TypeScript 代码里那些冒号在说什么,也知道了 any 是把双刃剑、类型在运行时并不存在这两件不那么显然的事。剩下的进阶话题——泛型、工具类型、以及怎么给「外面来的数据」在运行时也把关——都建立在这套「贴标签」的直觉之上。

别把 TypeScript 当成更凶的 JavaScript。它只是把你本来就默认在心里的那些约定,写成机器能读、能替你当场检查的标签而已。

没有回应

    发表回复

    您的邮箱地址不会被公开。 必填项已用 * 标注