目录

第三章:TypeScript 中的二元运算符与三元运算符

3.1 运算符的基本概念

在 TypeScript 中,运算符是用于对一个或多个值进行操作的符号。例如:

const result = 1 + 2;

在这段代码中:

根据操作数数量的不同,运算符可以分为:

本章重点讲解二元运算符和三元运算符。


3.2 什么是二元运算符

二元运算符,英文通常称为 Binary Operator,是指需要两个操作数才能完成运算的运算符。

基本格式如下:

左操作数 运算符 右操作数

例如:

const sum = 10 + 20;

其中:

再比如:

const isAdult = age >= 18;

其中:

TypeScript 中的大多数常用运算符都是二元运算符。例如:

a + b
a - b
a * b
a / b
a % b
a ** b
a === b
a !== b
a > b
a < b
a >= b
a <= b
a && b
a || b
a ?? b
a = b
a += b

这些写法都有一个共同点:运算符左右两边各有一个表达式。


3.3 二元运算符的基本特点

二元运算符有以下几个重要特点。

第一,二元运算符必须有两个操作数。

例如下面的代码是正确的:

const result = a + b;

但是下面的代码是不完整的:

const result = a +;

因为 + 作为二元运算符时,右侧缺少一个操作数。

第二,二元运算符通常会产生一个新值。

例如:

const result = 3 * 4;

表达式 3 * 4 的结果是 12

第三,不同的二元运算符有不同的返回类型。

例如:

const a = 1 + 2;        // number
const b = "a" + "b";    // string
const c = 10 > 5;       // boolean
const d = true && false; // boolean

在 TypeScript 中,编译器会根据运算符和操作数类型推断表达式的结果类型。

第四,二元运算符可能涉及类型转换。

例如:

const result = "1" + 2;

这里的结果不是数字 3,而是字符串 “12”。因为当 + 的任意一侧是字符串时,JavaScript 会倾向于执行字符串拼接。

TypeScript 虽然增加了静态类型检查,但运行时行为仍然遵循 JavaScript。


3.4 算术二元运算符

算术运算符用于进行数学计算。常见的算术二元运算符包括:

运算符 名称 示例
+ 加法 a + b
- 减法 a - b
* 乘法 a * b
/ 除法 a / b
% 取余 a % b
$**$ 幂运算 $a ** b$

示例:

const a = 10;
const b = 3;
 
const sum = a + b;        // 13
const diff = a - b;       // 7
const product = a * b;    // 30
const quotient = a / b;   // 3.3333333333333335
const remainder = a % b;  // 1
const power = a ** b;     // 1000

在 TypeScript 中,这些算术运算符通常要求操作数是数字类型,或者是可以参与数值运算的类型。

例如:

const x: number = 10;
const y: number = 5;
 
const result = x - y;

这是合理的。

但是下面的代码通常不推荐:

const result = "10" - 5;

虽然 JavaScript 运行时可能会把字符串 “10” 转成数字 10,然后得到结果 5,但是在 TypeScript 中应尽量避免依赖隐式类型转换。

更推荐的写法是:

const value = Number("10");
const result = value - 5;

这样代码意图更加清晰。


3.5 加号运算符的特殊性

在 TypeScript 和 JavaScript 中,+ 是一个比较特殊的二元运算符。它既可以表示数字加法,也可以表示字符串拼接。

数字加法:

const result = 1 + 2; // 3

字符串拼接:

const message = "Hello, " + "TypeScript"; // "Hello, TypeScript"

数字与字符串相加:

const result = "Age: " + 18; // "Age: 18"

在这种情况下,数字会被转换为字符串,然后进行拼接。

例如:

const a = 1 + 2 + "3";
console.log(a); // "33"

分析过程如下:

再看另一个例子:

const b = "1" + 2 + 3;
console.log(b); // "123"

分析过程如下:

因此,在实际开发中,如果希望明确进行数字计算,应先把数据转换为数字:

const input = "100";
const result = Number(input) + 20;

如果希望明确进行字符串拼接,可以使用模板字符串:

const name = "Alice";
const age = 18;
 
const message = `${name} is ${age} years old.`;

模板字符串通常比多个 + 拼接更清晰。


3.6 赋值二元运算符

赋值运算符用于把右侧的值赋给左侧变量或属性。

最基本的赋值运算符是:

=

示例:

let count = 0;
count = 10;

这里的 count = 10 是一个赋值表达式,其中:

常见的复合赋值运算符包括:

运算符 含义 等价写法
+= 加后赋值 a = a + b
-= 减后赋值 a = a - b
*= 乘后赋值 a = a * b
/= 除后赋值 a = a / b
%= 取余后赋值 a = a % b
$**=$ 幂运算后赋值 $a = a ** b$
&&= 逻辑与赋值 a = a && b 的近似形式
||= 逻辑或赋值 a = a || b 的近似形式
??= 空值合并赋值 a = a ?? b 的近似形式

示例:

let count = 10;
 
count += 5;  // 15
count -= 3;  // 12
count *= 2;  // 24
count /= 4;  // 6
count %= 4;  // 2

需要注意的是,复合赋值并不总是简单的文本替换。例如:

obj.value += 1;

大致可以理解为:

obj.value = obj.value + 1;

但在涉及 getter、setter 或复杂属性访问时,实际求值过程可能有更细致的规则。因此,在学习阶段可以先按等价写法理解,在深入阶段再研究求值顺序。


3.7 比较二元运算符

比较运算符用于比较两个值,并返回布尔值。

常见比较运算符如下:

运算符 名称 示例
== 相等 a == b
!= 不相等 a != b
=== 严格相等 a === b
!== 严格不相等 a !== b
> 大于 a > b
< 小于 a < b
>= 大于等于 a >= b
小于等于 a ⇐ b

示例:

const age = 20;
 
const a = age > 18;   // true
const b = age < 18;   // false
const c = age >= 20;  // true
const d = age <= 20;  // true

在 TypeScript 中,推荐优先使用严格相等 === 和严格不相等 !==,而不是 ==!=

原因是 == 会进行隐式类型转换。

例如:

console.log(1 == "1");  // true
console.log(1 === "1"); // false

第一行中,== 会尝试进行类型转换,所以数字 1 和字符串 “1” 被认为相等。

第二行中,=== 不会进行这种隐式类型转换。数字 1 和字符串 “1” 类型不同,因此结果是 false

在 TypeScript 项目中,使用 === 可以减少很多潜在错误。


3.8 逻辑二元运算符

逻辑二元运算符主要包括:

运算符 名称 示例
&& 逻辑与 a && b
|| 逻辑或 a || b

虽然逻辑非 ! 也属于逻辑运算符,但它是一元运算符,不属于本节重点。

逻辑与 && 的基本规则是:

示例:

const result1 = true && "hello";  // "hello"
const result2 = false && "hello"; // false

逻辑或 || 的基本规则是:

示例:

const result1 = "hello" || "default"; // "hello"
const result2 = "" || "default";      // "default"

需要注意的是,&&|| 返回的不一定是布尔值,它们返回的是其中一个操作数的值。

例如:

const name = "";
const displayName = name || "匿名用户";
 
console.log(displayName); // "匿名用户"

这里 name 是空字符串,属于假值,因此 name || “匿名用户” 返回右侧的 “匿名用户”

JavaScript 中常见的假值包括:

除了这些值之外,大多数值都是真值。


3.9 逻辑短路特性

逻辑运算符具有短路特性。

所谓短路,是指如果通过左操作数已经能够确定整个表达式的结果,那么右操作数就不会再被执行。

逻辑与 && 的短路:

function sayHello() {
  console.log("hello");
  return true;
}
 
const result = false && sayHello();

在这段代码中,sayHello() 不会被调用。因为 false && 任意值 的结果都可以由左侧的 false 决定。

逻辑或 || 的短路:

function getDefaultName() {
  console.log("get default name");
  return "匿名用户";
}
 
const name = "Alice";
const displayName = name || getDefaultName();

这里 getDefaultName() 不会被调用,因为左侧 name 是真值,整个表达式直接返回 name

短路特性在实际开发中非常常见。例如:

isReady && start();

这表示:如果 isReady 为真,就执行 start()

但是在 TypeScript 中,为了代码可读性,很多团队更推荐写成:

if (isReady) {
  start();
}

尤其是在有副作用的函数调用中,显式的 if 语句通常更容易理解。


3.10 空值合并二元运算符

空值合并运算符是:

??

它也是一个二元运算符,因为它需要左操作数和右操作数。

基本格式如下:

const result = left ?? right;

规则是:

示例:

const username = null;
const displayName = username ?? "匿名用户";
 
console.log(displayName); // "匿名用户"

如果左侧不是 nullundefined

const username = "";
const displayName = username ?? "匿名用户";
 
console.log(displayName); // ""

这里结果是空字符串,而不是 “匿名用户”。因为空字符串虽然是假值,但不是 nullundefined

这正是 ??|| 的重要区别。


3.11 ?? 与 || 的区别

?? 只关心左侧是否是 nullundefined

|| 关心左侧是否是假值。

对比示例:

const a = 0 ?? 100;
const b = 0 || 100;
 
console.log(a); // 0
console.log(b); // 100

分析:

再看空字符串:

const title1 = "" ?? "默认标题";
const title2 = "" || "默认标题";
 
console.log(title1); // ""
console.log(title2); // "默认标题"

因此,当你希望只有在值“缺失”时才使用默认值,应优先使用 ??

例如:

interface Config {
  retryCount?: number;
}
 
const config: Config = {
  retryCount: 0
};
 
const retryCount = config.retryCount ?? 3;
 
console.log(retryCount); // 0

这里用户明确设置了 retryCount: 0,表示不重试。如果使用 ||

const retryCount = config.retryCount || 3;

结果会变成 3,这可能不是我们想要的。


3.12 空值合并赋值运算符 ??=

除了 ??,TypeScript 中还有 ??=

??= 是空值合并赋值运算符。

它的含义是:如果左侧变量当前是 nullundefined,就把右侧的值赋给它;否则保持原值不变。

示例:

let name: string | null = null;
 
name ??= "匿名用户";
 
console.log(name); // "匿名用户"

如果变量已有值:

let name = "Alice";
 
name ??= "匿名用户";
 
console.log(name); // "Alice"

可以把:

value ??= defaultValue;

理解为:

value = value ?? defaultValue;

但和其他复合赋值运算符一样,在复杂属性访问场景下,实际求值过程并不完全等同于简单文本替换。


3.13 位运算二元运算符

位运算符用于按二进制位进行操作。常见的位运算二元运算符包括:

运算符 名称 示例
& 按位与 a & b
| 按位或 a | b
^ 按位异或 a ^ b
« 左移 a « b
» 有符号右移 a » b
»> 无符号右移 a »> b

注意:按位非 ~ 是一元运算符,不属于二元运算符。

示例:

const a = 5; // 二进制 0101
const b = 3; // 二进制 0011
 
console.log(a & b); // 1,二进制 0001
console.log(a | b); // 7,二进制 0111
console.log(a ^ b); // 6,二进制 0110

位运算在普通业务代码中不算特别常见,但在以下场景中可能会用到:

例如,用位标记表示权限:

const READ = 1;    // 0001
const WRITE = 2;   // 0010
const DELETE = 4;  // 0100
 
let permission = READ | WRITE;
 
const canRead = (permission & READ) !== 0;
const canDelete = (permission & DELETE) !== 0;
 
console.log(canRead);   // true
console.log(canDelete); // false

这里:


3.14 关系二元运算符

关系运算符用于判断两个值之间的某种关系。除了前面讲到的大小比较之外,TypeScript 中还常见以下关系运算符:

运算符 名称 示例
in 属性存在判断 key in object
instanceof 实例判断 obj instanceof ClassName

3.14.1 in 运算符

in 用于判断某个属性名是否存在于对象中,包括对象自身属性和原型链上的属性。

示例:

const user = {
  name: "Alice",
  age: 18
};
 
console.log("name" in user); // true
console.log("email" in user); // false

在 TypeScript 中,in 还经常用于类型收窄。

例如:

type Dog = {
  bark: () => void;
};
 
type Cat = {
  meow: () => void;
};
 
function makeSound(animal: Dog | Cat) {
  if ("bark" in animal) {
    animal.bark();
  } else {
    animal.meow();
  }
}

if (“bark” in animal) 分支中,TypeScript 可以推断 animal 更可能是 Dog 类型,因此允许调用 animal.bark()

3.14.2 instanceof 运算符

instanceof 用于判断一个对象是否是某个构造函数或类的实例。

示例:

class User {
  constructor(public name: string) {}
}
 
const user = new User("Alice");
 
console.log(user instanceof User); // true
console.log(user instanceof Date); // false

instanceof 也可以用于类型收窄:

function formatValue(value: string | Date) {
  if (value instanceof Date) {
    return value.toISOString();
  }
 
  return value.toUpperCase();
}

value instanceof Date 分支中,TypeScript 会把 value 视为 Date 类型;在另一个分支中,value 会被视为 string 类型。


3.15 类型相关的二元运算注意事项

TypeScript 的核心优势在于静态类型检查。二元运算符在 TypeScript 中不仅要考虑 JavaScript 的运行时行为,还要考虑编译阶段的类型规则。

例如:

const a: number = 10;
const b: string = "20";
 
const result = a - b;

这段代码在 JavaScript 中运行时可能得到 -10,因为字符串 “20” 会被转换为数字。但在 TypeScript 中,这通常会被认为是不合适的写法,因为 - 运算符两侧应是可进行数值运算的值。

推荐写法:

const a: number = 10;
const b: string = "20";
 
const result = a - Number(b);

又如:

const value: string | undefined = undefined;
 
const result = value.toUpperCase();

如果开启了 strictNullChecks,TypeScript 会提示 value 可能是 undefined

可以使用 ?? 提供默认值:

const value: string | undefined = undefined;
 
const result = (value ?? "").toUpperCase();

也可以使用条件判断:

if (value !== undefined) {
  console.log(value.toUpperCase());
}

3.16 三元运算符的基本概念

三元运算符,英文通常称为 Ternary Operator,是指需要三个操作数的运算符。

在 TypeScript 和 JavaScript 中,最常见也几乎是唯一常用的三元运算符是条件运算符:

condition ? expressionIfTrue : expressionIfFalse

它也常被称为“条件三元运算符”。

结构如下:

条件表达式 ? 条件为真时的结果 : 条件为假时的结果

例如:

const age = 20;
const message = age >= 18 ? "成年人" : "未成年人";

这里:


3.17 三元运算符与 if else 的关系

三元运算符可以看作是 if else 的表达式形式。

例如:

const age = 20;
 
let message: string;
 
if (age >= 18) {
  message = "成年人";
} else {
  message = "未成年人";
}

可以改写为:

const age = 20;
 
const message = age >= 18 ? "成年人" : "未成年人";

两者的区别是:

例如,如果只是根据条件决定一个变量的值,三元运算符会比较简洁:

const buttonText = isLoading ? "加载中..." : "提交";

如果条件分支中有多步操作,则推荐使用 if else

if (isLoading) {
  console.log("开始加载");
  showSpinner();
  disableButton();
} else {
  console.log("加载结束");
  hideSpinner();
  enableButton();
}

不要为了追求简短而滥用三元运算符。


3.18 三元运算符的返回类型推断

TypeScript 会根据三元运算符两个结果分支推断最终类型。

例如:

const result = true ? 1 : 2;

这里 result 的类型会被推断为数字相关类型。

如果两个分支是不同类型:

const result = Math.random() > 0.5 ? "success" : 0;

那么 result 的类型会被推断为:

string | number

也就是联合类型。

再看一个例子:

const status = isSuccess ? "success" : "error";

如果 isSuccess 是布尔值,TypeScript 可能会把 status 推断为字符串字面量联合类型:

"success" | "error"

这在实际开发中非常有用。

例如:

type Status = "success" | "error" | "loading";
 
const status: Status = isLoading
  ? "loading"
  : isSuccess
    ? "success"
    : "error";

不过上面这个例子使用了嵌套三元运算符,虽然可以工作,但可读性需要谨慎考虑。


3.19 嵌套三元运算符

三元运算符可以嵌套使用,但不推荐过度嵌套。

例如:

const score = 85;
 
const level = score >= 90
  ? "优秀"
  : score >= 60
    ? "及格"
    : "不及格";

这段代码的含义是:

虽然这段代码不长,但如果条件继续增加,就会变得难以阅读。

例如:

const label = type === "admin"
  ? "管理员"
  : type === "editor"
    ? "编辑者"
    : type === "guest"
      ? "访客"
      : type === "anonymous"
        ? "匿名用户"
        : "未知用户";

这种写法虽然合法,但可读性较差。

更推荐使用对象映射或 switch

const labels: Record<string, string> = {
  admin: "管理员",
  editor: "编辑者",
  guest: "访客",
  anonymous: "匿名用户"
};
 
const label = labels[type] ?? "未知用户";

或者:

let label: string;
 
switch (type) {
  case "admin":
    label = "管理员";
    break;
  case "editor":
    label = "编辑者";
    break;
  case "guest":
    label = "访客";
    break;
  case "anonymous":
    label = "匿名用户";
    break;
  default:
    label = "未知用户";
}

原则是:三元运算符适合简单条件,复杂分支应使用更清晰的结构。


3.20 三元运算符在 JSX 和模板中的使用

在 React 或其他模板场景中,三元运算符非常常见。

例如 React JSX 中:

function Button({ isLoading }: { isLoading: boolean }) {
  return (
    <button>
      {isLoading ? "加载中..." : "提交"}
    </button>
  );
}

再例如根据用户状态显示不同内容:

function UserPanel({ isLoggedIn }: { isLoggedIn: boolean }) {
  return (
    <div>
      {isLoggedIn ? <p>欢迎回来</p> : <p>请先登录</p>}
    </div>
  );
}

在 JSX 中,三元运算符比 if else 更常用于“根据条件渲染不同内容”的场景。

但是,如果条件逻辑复杂,建议提前在函数体中计算好结果:

function StatusText({ status }: { status: "loading" | "success" | "error" }) {
  let text: string;
 
  if (status === "loading") {
    text = "加载中";
  } else if (status === "success") {
    text = "加载成功";
  } else {
    text = "加载失败";
  }
 
  return <p>{text}</p>;
}

这样 JSX 部分会更清晰。


3.21 三元运算符与空值合并运算符的组合

三元运算符经常和 ???. 等运算符一起使用。

例如:

const displayName = user.name ?? "匿名用户";

如果逻辑更复杂:

const displayName = user.name
  ? user.name
  : user.nickname ?? "匿名用户";

这里的逻辑是:

但要注意,user.name ? user.name : … 会把空字符串当作假值。如果空字符串也应该被认为是有效值,就应该使用 ??

const displayName = user.name ?? user.nickname ?? "匿名用户";

这表示只有当前一个值为 nullundefined 时,才继续使用下一个值。


3.22 运算符优先级简介

当一个表达式中出现多个运算符时,运算符优先级决定了先执行哪个。

例如:

const result = 1 + 2 * 3;

结果是 7,而不是 9。因为乘法 * 的优先级高于加法 +

如果希望先执行加法,可以使用括号:

const result = (1 + 2) * 3;

这时结果是 9

在实际开发中,不建议过度依赖记忆运算符优先级。对于稍复杂的表达式,推荐使用括号明确表达意图。

例如:

const result = a && b || c;

可以改写为:

const result = (a && b) || c;

或者:

const result = a && (b || c);

根据实际需求明确分组。


3.23 ?? 与 &&、|| 混用时的注意事项

空值合并运算符 ?? 和逻辑与 &&、逻辑或 || 混用时需要特别注意。

在 JavaScript 和 TypeScript 中,不能在没有括号的情况下直接混用 ??||&&

例如下面的写法是错误的:

const value = a ?? b || c;

应该使用括号明确优先级:

const value = (a ?? b) || c;

或者:

const value = a ?? (b || c);

这两种写法含义不同。

第一种:

const value = (a ?? b) || c;

表示先判断 a 是否为空值。如果 a 不是 nullundefined,先得到 a;否则得到 b。然后再把这个结果与 c 做逻辑或。

第二种:

const value = a ?? (b || c);

表示如果 a 不是 nullundefined,直接返回 a;否则再计算 b || c

示例:

const a = 0;
const b = "B";
const c = "C";
 
const value1 = (a ?? b) || c;
const value2 = a ?? (b || c);
 
console.log(value1); // "C"
console.log(value2); // 0

分析:

因此混用这些运算符时,必须认真使用括号。


3.24 二元运算符和三元运算符的可读性原则

运算符虽然可以让代码更简洁,但并不意味着越短越好。

推荐原则如下:

例如,不推荐:

const result = a ? b ? c : d : e ? f : g;

更推荐:

let result: string;
 
if (a) {
  result = b ? c : d;
} else {
  result = e ? f : g;
}

虽然代码行数更多,但逻辑更清晰。


3.25 实际开发示例:默认配置

假设有一个配置对象:

interface Options {
  timeout?: number;
  retry?: number;
  debug?: boolean;
}
 
function createOptions(options: Options) {
  const timeout = options.timeout ?? 3000;
  const retry = options.retry ?? 3;
  const debug = options.debug ?? false;
 
  return {
    timeout,
    retry,
    debug
  };
}

这里使用 ?? 而不是 || 是非常重要的。

因为以下配置是有意义的:

createOptions({
  timeout: 0,
  retry: 0,
  debug: false
});

如果使用 ||

const timeout = options.timeout || 3000;
const retry = options.retry || 3;
const debug = options.debug || false;

那么 timeout: 0retry: 0 会被错误地替换为默认值。

使用 ?? 可以避免这个问题。


3.26 实际开发示例:状态显示

假设一个请求状态可能是:

type RequestStatus = "idle" | "loading" | "success" | "error";

可以用三元运算符显示文字:

function getStatusText(status: RequestStatus): string {
  return status === "loading"
    ? "加载中"
    : status === "success"
      ? "加载成功"
      : status === "error"
        ? "加载失败"
        : "等待开始";
}

虽然这段代码可以正常工作,但嵌套三元较多。

更推荐:

function getStatusText(status: RequestStatus): string {
  const map: Record<RequestStatus, string> = {
    idle: "等待开始",
    loading: "加载中",
    success: "加载成功",
    error: "加载失败"
  };
 
  return map[status];
}

如果状态值可能来自外部字符串:

function getStatusText(status: string): string {
  const map: Record<string, string> = {
    idle: "等待开始",
    loading: "加载中",
    success: "加载成功",
    error: "加载失败"
  };
 
  return map[status] ?? "未知状态";
}

这里 ?? 用来处理找不到映射结果的情况。


3.27 实际开发示例:表单校验

二元运算符和三元运算符在表单校验中非常常见。

例如:

function validateUsername(username: string): string {
  return username.length >= 3 ? "" : "用户名至少需要 3 个字符";
}

如果有多个条件:

function validatePassword(password: string): string {
  if (password.length < 8) {
    return "密码至少需要 8 个字符";
  }
 
  if (!/[A-Z]/.test(password)) {
    return "密码至少需要包含一个大写字母";
  }
 
  if (!/[0-9]/.test(password)) {
    return "密码至少需要包含一个数字";
  }
 
  return "";
}

这里不建议写成复杂嵌套三元:

function validatePassword(password: string): string {
  return password.length < 8
    ? "密码至少需要 8 个字符"
    : !/[A-Z]/.test(password)
      ? "密码至少需要包含一个大写字母"
      : !/[0-9]/.test(password)
        ? "密码至少需要包含一个数字"
        : "";
}

虽然它是合法的,但可读性不如多个 if


3.28 实际开发示例:权限判断

权限判断中常用逻辑二元运算符。

例如:

interface User {
  role: "admin" | "editor" | "guest";
  active: boolean;
}
 
function canEdit(user: User): boolean {
  return user.active && (user.role === "admin" || user.role === "editor");
}

这里:

如果不用括号:

return user.active && user.role === "admin" || user.role === "editor";

虽然按照优先级也能执行,但可读性不如带括号的版本,而且容易让读者误解。

更清晰的写法:

function canEdit(user: User): boolean {
  const isActive = user.active;
  const hasRole = user.role === "admin" || user.role === "editor";
 
  return isActive && hasRole;
}

这种写法在复杂业务逻辑中更容易维护。


3.29 常见错误一:把 || 当作默认值处理的一切方案

很多初学者会这样写:

const pageSize = inputPageSize || 20;

这在某些场景下没有问题,但如果 inputPageSize 可能是 0,就会出现问题。

例如:

const inputPageSize = 0;
const pageSize = inputPageSize || 20;
 
console.log(pageSize); // 20

如果 0 是一个合法值,那么这里就错了。

更推荐:

const pageSize = inputPageSize ?? 20;

这样只有当 inputPageSizenullundefined 时才使用默认值。


3.30 常见错误二:三元运算符嵌套过深

例如:

const result = a
  ? b
    ? c
    : d
  : e
    ? f
    : g;

这类代码虽然简短,但阅读成本较高。

建议改写为 if else

let result;
 
if (a) {
  if (b) {
    result = c;
  } else {
    result = d;
  }
} else {
  if (e) {
    result = f;
  } else {
    result = g;
  }
}

或者根据业务场景抽成函数:

function getResult() {
  if (a && b) {
    return c;
  }
 
  if (a && !b) {
    return d;
  }
 
  if (!a && e) {
    return f;
  }
 
  return g;
}
 
const result = getResult();

函数化往往能让复杂条件更容易测试和维护。


3.31 常见错误三:忽略 TypeScript 的联合类型

三元运算符可能产生联合类型。

例如:

const value = condition ? "ok" : 0;

此时 value 的类型可能是:

string | number

如果后续直接调用字符串方法:

console.log(value.toUpperCase());

TypeScript 会报错,因为 number 类型没有 toUpperCase 方法。

正确做法是先进行类型判断:

if (typeof value === "string") {
  console.log(value.toUpperCase());
}

或者保证三元表达式两个分支返回同一种类型:

const value = condition ? "ok" : "0";
console.log(value.toUpperCase());

3.32 常见错误四:误解 && 和 || 的返回值

很多人以为 &&|| 一定返回布尔值,其实它们返回的是操作数本身。

例如:

const result = "hello" && 123;
console.log(result); // 123

再比如:

const result = "" || "default";
console.log(result); // "default"

如果你需要明确得到布尔值,可以使用 Boolean() 或双重逻辑非 !!

const value = "hello";
 
const bool1 = Boolean(value);
const bool2 = !!value;

在 TypeScript 中,清楚区分“返回某个值”和“返回布尔值”非常重要。


3.33 常见错误五:对象比较使用 === 判断内容相等

比较运算符 === 比较对象时,比较的是引用地址,而不是对象内容。

例如:

const a = { name: "Alice" };
const b = { name: "Alice" };
 
console.log(a === b); // false

虽然两个对象内容看起来一样,但它们是两个不同对象。

如果是同一个引用:

const a = { name: "Alice" };
const b = a;
 
console.log(a === b); // true

数组也是一样:

console.log([1, 2] === [1, 2]); // false

如果需要比较内容,需要自己逐项比较,或者使用专门的工具函数。

简单数组比较示例:

function isSameNumberArray(a: number[], b: number[]): boolean {
  if (a.length !== b.length) {
    return false;
  }
 
  return a.every((item, index) => item === b[index]);
}

3.34 本章小结

本章详细介绍了 TypeScript 中的二元运算符与三元运算符。

二元运算符是需要两个操作数的运算符,例如:

a + b
a === b
a && b
a ?? b

常见二元运算符包括:

三元运算符是需要三个操作数的运算符。TypeScript 中最常见的三元运算符是条件运算符:

condition ? valueIfTrue : valueIfFalse

它适合根据条件选择一个值,但不适合承载过于复杂的业务逻辑。

在实际开发中,应特别注意以下几点:

理解这些规则之后,就能更准确地阅读和编写 TypeScript 表达式,也能避免许多由隐式类型转换、默认值处理、逻辑短路和类型推断造成的常见错误。