前言
如果你点的椰果奶茶被做成了珍珠奶茶,虽然也能喝,但就是完全不是你想要的,至少对于我这种有点强迫症的人。那么 JavaScript 就是这样一个 “随性” 的奶茶店老板,而 TypeScript 就是那个拿着订单反复跟你确认 “少糖少冰” 的靠谱店员,从根源上避免了 “错单” 的尴尬。
用一句话来说其实就是:
TypeScript是更严谨的JavaScript。
一、有了 JavaScript 为什么还要有 TypeScript ?
写 JavaScript 就像开盲盒,你永远不知道下一个变量里装的是 数字、字符串还是 薛定谔的 undefined。我统称它们为 盲盒变量。
比如这段代码:
let n = 1, m = 0;
n = 'hello'; // 数字秒变字符串,JS 主打一个“灵活”
function add(a, b) {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
}
// 传入字符串,函数直接返回 undefined,Bug 这不就来了
console.log(add(1, '2'));
你以为你在写 “动态灵活” 的代码,其实是在给未来的自己 埋雷。比如上面这段代码,可能你知道等会要传 2个number类型,但是如果别人直接拿来Ctrl + cv 用你封装的函数,传了一个 string类型 那就坏了。
直到 TypeScript 出现,让变量从 “盲盒” 变成了 “明码标价的商品”。
二、弱类型:自由过了火就是混乱
在上面代码中有这样一个情况:
let n = 1;
n = 'hello'; // 在 JavaScript里面不报错
如果你是 C++、Java或者Go的工程师,你肯定会觉得这人怕不是敲代码敲疯了吧。在C++、Java或者Golang里面这代码直接就报错了。这就是因为 JavaScript 是典型的弱类型语言,变量不需要提前声明类型,随时可以 “变身”。
打印结果为 hello:
你可以让数字 a 一秒变成字符串,编辑器连个警告都没有。这种 “自由” 在小项目里或许能跑,但项目一复杂,就会出现 add(1, '2') 这种隐蔽 Bug,排查起来堪比大海捞针。
三、强类型:给变量上 “户口”
TypeScript 作为 JavaScript 的超集,核心就是给变量加上了类型声明。
首先要用 TypeScript,我们需要去下载它:
npm install -g typescript # 全局安装 TypeScript
tsc -v # 查看 TypeScript的版本
tsc project.ts # project 是你的文件名,编译 TypeScript文件
当编译完你会发现编译器给你编译出了一份对等的JavaScript文件:
这个时候你就可以用Node.js去跑这份文件,因为 TypeScript 本身不能直接运行,需要先编译成 JavaScript 再运行。不过现在有一些工具可以简化这个过程,比如 ts-node、deno 等。
我这里简要介绍下 ts-node的使用:
ls package.json # 首先检查项目是否有 package.json 文件
npm init -y # 如果没有,初始化一个
npm install -g ts-node # 然后安装 ts-node
npm install --save-dev ts-node # 或本地安装
# 运行 TypeScript文件
ts-node 2.ts # 全局安装时
npx ts-node 2.ts # 本地安装时
一般 TypeScript 都是在 React项目 等环境下运行,所以直接运行一个文件的比较少见,这里我们主要看 TypeScript 语法的使用和基础知识。
同样的代码,在 TypeScript 里面就会报错:
let a: number = 1;
a = 'hello'; // 编辑器直接标红:不能将类型 “string” 分配给类型 “number”
console.log(a);
细心的你很快就发现了猫腻:TypeScript 相较于 JavaScript 不同的地方就在于 TypeScript 的写法中明确标注了变量是什么类型。就比如这里 a 被明确声明为 number 类型,如果你想把它改成字符串,TypeScript 会立刻报错,把问题扼杀在编码阶段。这样就使得文件更加严谨。
四、TypeScript 数据类型全家桶
在之前我写过几篇 JavaScript数据类型 的文章,那我们现在来看看 TypeScript 类型全家桶,他们并不完全一样,但还是有很高的相似度。
比如这段代码:
let isDone: boolean = false; // boolean类型
let count: number = 123; // number类型
let str: string = 'Trae'; // string类型
const symbol: symbol = Symbol(); // symbol类型
let obj: object = {
[symbol]: 'Trae' // object类型(对象)
};
let list: number[] = [1, 2, 3]; // array类型(数组)
enum Color {
Red,
Green, // 类似于结构体
Blue
}
let color: Color = Color.Red;
let notSure: any = 10; // any类型
notSure = '123'; // any 类型可以随便变,是 TypeScript 里的 “漏网之鱼”
let value: unknown = 10; // unknown类型
value = '123';
let abc: string = 'hello';
// unknown 类型不能直接赋值给其他类型,比 any 更安全
// abc = value; // 报错
abc = notSure; // 不报错
let tuple: [number, string] = [10, 'hello']; // 元组:固定长度和类型的数组
function user1(): number {
return 123;
}
function user2(): Function {
return function fn(): number {
return 123;
}
}
// 报错
// function user2(): string {
// return 123;
// }
function user3(): void {} // void 表示没有返回值
let u: undefined = undefined; // undefined类型
let n: null = null; // null类型
基本上都与 JavaScript 相似,可以去看我之前写的 JavaScript数据类型。从基础的 boolean、number、string,到复杂的 enum、tuple、unknown,TypeScript 让每个变量都有了明确的 “身份”。
这里有一个注意的点就是 unknown 类型 和 any类型。unknown 类型不能直接赋值给其他类型,而 any 类型可以随便变,所以下次报错的时候看看,是不是这个原因。
五、对象与类型:不是所有空对象都一样
TypeScript 对对象的类型约束更严格:
const obj: object = {};
const obj2: Object = {};
const obj3: {} = {};
// 错误
// obj.a = 1; // 编译错误
// obj3.a = 1; // 编译错误
// 正确(类型断言)
(obj2 as any).a = 1;
console.log(obj2); // 输出: { a: 1 }
const hello = 'hello';
const a: 'hello' = 'hello';
object、Object 和 {} 看似相似,实际约束力度不同;字面量类型更是把变量锁死在特定值上,杜绝了 “意外变身”。
六、类型守卫🛡️:给你的代码装上 “火眼金睛”
TypeScript 的类型守卫,就像给你的代码配上了一个智能安检员,能在运行时精准识别变量类型。
// 类型守卫
interface Person {
name: string;
age: number;
sex?: unknown; // 可选属性,不是每个人都需要填写
}
const person: Person = {
name: 'henry',
age: 18,
sex: '男' // 可选属性,写不写都不会报错
};
// 举个类型守卫的例子:判断一个值是不是 Person 类型
function isPerson(value: unknown): value is Person {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
'age' in value
);
}
function printUserInfo(value: unknown) {
if (isPerson(value)) {
// 进入这个分支后,TypeScript 就知道 value 是 Person 类型了
console.log(`姓名:${value.name},年龄:${value.age}`);
if (value.sex) {
console.log(`性别:${value.sex}`);
}
} else {
console.log('这不是一个合法的 Person 对象');
}
}
printUserInfo(person); // 输出:姓名:henry,年龄:18
printUserInfo({ name: 'lucy' }); // 输出:这不是一个合法的 Person 对象
七、类型转换与组合:灵活不代表放纵
如果遇到类型不确定的场景,TypeScript 提供了类型断言来 “手动担保”:
let someValue: any = '123';
let strLength = (someValue as string).length; // 写法一
let strLength2 = (<string>someValue).length; // 写法二
还可以用 type 定义联合类型和交叉类型:
type Person = string | number | boolean;
const a: Person = 'hello';
const b: Person = 123;
const c: Person = true;
type PartialX = {x: number}
type Point = PartialX & {y: number} // 交叉类型:合并多个类型
const p: Point = {
x: 10,
y: 20
}
八、泛型:写一次,适配所有类型
泛型是 TypeScript 的 “秘密武器”,让函数和组件更通用。
function identity<T>(value: T) {
return value;
}
identity<number>(100); // 指定 T 为 number 类型
function identity2<T, U>(value: T, msg: U): T {
console.log(msg);
return value;
}
identity2<number, string>(100, 'hello'); // 多泛型参数
let arr: Array<number> = [1, 2, 3];
let arr2: Array<number | string> = [1, 2, 3, 'hello'];
泛型让 identity 函数既能处理数字,也能处理字符串,不用写多个重复函数,代码复用性直接拉满。
结语
从 JavaScript 的 “盲盒变量” 到 TypeScript 的 “精准类型”,本质是从 “靠运气写代码” 到 “靠逻辑写代码” 的转变。写的代码都不严谨,那还写什么代码呢😄。
TypeScript 不是给你套枷锁,而是给你装护栏 —— 它不会限制你的创造力,只会帮你提前避开那些低级 Bug。所以,不要害怕红色的报错,而是试着去解决它。
我的
JavaScript数据类型文章:栈与堆的精妙舞剧:JavaScript 数据类型深度解析