const result = 1 + 2;
在这段代码中:
* ''1'' 是一个操作数
* ''2'' 是另一个操作数
* ''+'' 是运算符
* ''1 + 2'' 是一个表达式
* 表达式最终会产生一个值,即 ''3''
根据操作数数量的不同,运算符可以分为:
* 一元运算符:只需要一个操作数,例如 ''!flag''、''typeof value''、''-num''
* 二元运算符:需要两个操作数,例如 ''a + b''、''x === y''
* 三元运算符:需要三个操作数,例如 ''condition ? a : b''
本章重点讲解二元运算符和三元运算符。
----
===== 3.2 什么是二元运算符 =====
二元运算符,英文通常称为 Binary Operator,是指需要两个操作数才能完成运算的运算符。
基本格式如下:
左操作数 运算符 右操作数
例如:
const sum = 10 + 20;
其中:
* ''10'' 是左操作数
* ''20'' 是右操作数
* ''+'' 是二元运算符
再比如:
const isAdult = age >= 18;
其中:
* ''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"
分析过程如下:
* ''1 + 2'' 先执行,得到数字 ''3''
* ''3 + "3"'' 再执行,因为右侧是字符串,所以执行字符串拼接
* 最终结果是字符串 ''"33"''
再看另一个例子:
const b = "1" + 2 + 3;
console.log(b); // "123"
分析过程如下:
* ''"1" + 2'' 先执行,得到字符串 ''"12"''
* ''"12" + 3'' 再执行,得到字符串 ''"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'' 是一个赋值表达式,其中:
* 左操作数是 ''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 中常见的假值包括:
* ''false''
* ''0''
* ''-0''
* ''0n''
* $''$
* ''null''
* ''undefined''
* ''NaN''
除了这些值之外,大多数值都是真值。
----
===== 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;
规则是:
* 如果左侧是 ''null'' 或 ''undefined'',则返回右侧
* 否则返回左侧
示例:
const username = null;
const displayName = username ?? "匿名用户";
console.log(displayName); // "匿名用户"
如果左侧不是 ''null'' 或 ''undefined'':
const username = "";
const displayName = username ?? "匿名用户";
console.log(displayName); // ""
这里结果是空字符串,而不是 ''"匿名用户"''。因为空字符串虽然是假值,但不是 ''null'' 或 ''undefined''。
这正是 ''??'' 和 ''||'' 的重要区别。
----
===== 3.11 ?? 与 || 的区别 =====
''??'' 只关心左侧是否是 ''null'' 或 ''undefined''。
''||'' 关心左侧是否是假值。
对比示例:
const a = 0 ?? 100;
const b = 0 || 100;
console.log(a); // 0
console.log(b); // 100
分析:
* ''0 ?? 100'' 中,左侧是 ''0'',不是 ''null'' 或 ''undefined'',所以返回 ''0''
* ''0 || 100'' 中,左侧是 ''0'',是假值,所以返回 ''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 中还有 ''??=''。
''??='' 是空值合并赋值运算符。
它的含义是:如果左侧变量当前是 ''null'' 或 ''undefined'',就把右侧的值赋给它;否则保持原值不变。
示例:
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
这里:
* ''READ | WRITE'' 得到组合权限
* ''permission & READ'' 用于检查是否包含读取权限
----
===== 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 ? "成年人" : "未成年人";
这里:
* ''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 ? "成年人" : "未成年人";
两者的区别是:
* ''if else'' 是语句,更适合执行多行逻辑
* 三元运算符是表达式,更适合根据条件返回一个值
例如,如果只是根据条件决定一个变量的值,三元运算符会比较简洁:
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
? "及格"
: "不及格";
这段代码的含义是:
* 如果 ''score >= 90'',结果是 ''"优秀"''
* 否则继续判断 ''score >= 60''
* 如果 ''score >= 60'',结果是 ''"及格"''
* 否则结果是 ''"不及格"''
虽然这段代码不长,但如果条件继续增加,就会变得难以阅读。
例如:
const label = type === "admin"
? "管理员"
: type === "editor"
? "编辑者"
: type === "guest"
? "访客"
: type === "anonymous"
? "匿名用户"
: "未知用户";
这种写法虽然合法,但可读性较差。
更推荐使用对象映射或 ''switch'':
const labels: Record = {
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 (
);
}
再例如根据用户状态显示不同内容:
function UserPanel({ isLoggedIn }: { isLoggedIn: boolean }) {
return (
{isLoggedIn ? 欢迎回来
: 请先登录
}
);
}
在 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 {text}
;
}
这样 JSX 部分会更清晰。
----
===== 3.21 三元运算符与空值合并运算符的组合 =====
三元运算符经常和 ''??''、''?.'' 等运算符一起使用。
例如:
const displayName = user.name ?? "匿名用户";
如果逻辑更复杂:
const displayName = user.name
? user.name
: user.nickname ?? "匿名用户";
这里的逻辑是:
* 如果 ''user.name'' 是真值,使用 ''user.name''
* 否则尝试使用 ''user.nickname''
* 如果 ''user.nickname'' 是 ''null'' 或 ''undefined'',使用 ''"匿名用户"''
但要注意,''user.name ? user.name : ...'' 会把空字符串当作假值。如果空字符串也应该被认为是有效值,就应该使用 ''??'':
const displayName = user.name ?? user.nickname ?? "匿名用户";
这表示只有当前一个值为 ''null'' 或 ''undefined'' 时,才继续使用下一个值。
----
===== 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'' 不是 ''null'' 或 ''undefined'',先得到 ''a'';否则得到 ''b''。然后再把这个结果与 ''c'' 做逻辑或。
第二种:
const value = a ?? (b || c);
表示如果 ''a'' 不是 ''null'' 或 ''undefined'',直接返回 ''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
分析:
* ''value1'' 中,''a ?? b'' 的结果是 ''0'',然后 ''0 || c'' 的结果是 ''"C"''
* ''value2'' 中,''a'' 不是空值,所以 ''a ?? (b || c)'' 直接返回 ''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: 0'' 和 ''retry: 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 = {
idle: "等待开始",
loading: "加载中",
success: "加载成功",
error: "加载失败"
};
return map[status];
}
如果状态值可能来自外部字符串:
function getStatusText(status: string): string {
const map: Record = {
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");
}
这里:
* ''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;
这样只有当 ''inputPageSize'' 是 ''null'' 或 ''undefined'' 时才使用默认值。
----
===== 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
它适合根据条件选择一个值,但不适合承载过于复杂的业务逻辑。
在实际开发中,应特别注意以下几点:
* ''??'' 是空值合并运算符,也是二元运算符
* ''??'' 只在左侧为 ''null'' 或 ''undefined'' 时使用右侧
* ''||'' 会在左侧是假值时使用右侧
* ''&&'' 和 ''||'' 不一定返回布尔值
* 三元运算符适合简单二选一
* 嵌套三元运算符要谨慎使用
* 比较对象时,''==='' 比较的是引用,而不是内容
* 复杂表达式建议使用括号提高可读性
* TypeScript 会根据运算符推断表达式类型
* 当三元运算符两个分支类型不同,结果通常是联合类型
理解这些规则之后,就能更准确地阅读和编写 TypeScript 表达式,也能避免许多由隐式类型转换、默认值处理、逻辑短路和类型推断造成的常见错误。