编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

    • 基础入门

      • README
      • 入门介绍
      • 数据类型
      • 运算符
        • 3.1 算术运算符
          • 3.1.1 加法运算符
          • 3.1.2 余数运算符
          • 3.1.3 自增和自减
          • 3.1.4 指数运算符
        • 3.2 赋值运算符
          • 3.2.1 基本赋值
          • 3.2.2 复合赋值
          • 3.2.3 解构赋值
        • 3.3 比较运算符
          • 3.3.1 相等运算符
          • 3.3.2 严格等于
          • 3.3.3 相等比较的底层算法
        • 3.4 逻辑运算符
          • 3.4.1 逻辑与和逻辑或
          • 3.4.2 短路求值原理
          • 3.4.3 空值合并运算符
          • 3.4.4 逻辑赋值运算符
        • 3.5 位运算符
          • 3.5.1 位运算基础
          • 3.5.2 位运算的实际应用
        • 3.6 三元运算符
          • 3.6.1 基本用法与嵌套
        • 3.7 类型运算符
          • 3.7.1 typeof原理
          • 3.7.2 instanceof原理
        • 3.8 其他运算符
          • 3.8.1 可选链运算符
          • 3.8.2 展开运算符
          • 3.8.3 逗号运算符
        • 3.9 运算符优先级
          • 3.9.1 优先级表
      • 函数
      • 面向对象
      • 标准库
      • 异步操作
      • 事件设计
      • 错误机制
      • 模块开发
      • 字符串处理
      • 迭代器与生成器
      • Symbol
      • DOM操作
      • 网络请求
    • 综合案例

    • 专栏博客

  • CodeX
  • JavaScript入门
  • 基础入门
杨充
2025-06-24
目录

运算符

# 03.运算符

# 目录介绍

  • 3.1 算术运算符
    • 3.1.1 加法运算符
    • 3.1.2 余数运算符
    • 3.1.3 自增和自减
    • 3.1.4 指数运算符
  • 3.2 赋值运算符
    • 3.2.1 基本赋值
    • 3.2.2 复合赋值
    • 3.2.3 解构赋值
  • 3.3 比较运算符
    • 3.3.1 相等运算符
    • 3.3.2 严格等于
    • 3.3.3 相等比较的底层算法
  • 3.4 逻辑运算符
    • 3.4.1 逻辑与和逻辑或
    • 3.4.2 短路求值原理
    • 3.4.3 空值合并运算符
    • 3.4.4 逻辑赋值运算符
  • 3.5 位运算符
    • 3.5.1 位运算基础
    • 3.5.2 位运算的实际应用
  • 3.6 三元运算符
    • 3.6.1 基本用法与嵌套
  • 3.7 类型运算符
    • 3.7.1 typeof原理
    • 3.7.2 instanceof原理
  • 3.8 其他运算符
    • 3.8.1 可选链运算符
    • 3.8.2 展开运算符
    • 3.8.3 逗号运算符
  • 3.9 运算符优先级
    • 3.9.1 优先级表

# 3.1 算术运算符

算术运算符用于执行基本的数学运算。JavaScript 提供了以下算术运算符:

运算符 描述 示例
+ 加法 3 + 2 → 5
- 减法 5 - 2 → 3
* 乘法 3 * 2 → 6
/ 除法 6 / 2 → 3
% 取余 5 % 2 → 1
** 指数(幂) 2 ** 3 → 8
++ 自增 let a = 1; a++ → 2
-- 自减 let a = 1; a-- → 0

# 3.1.1 加法运算符

加法运算符(+)是最常见的运算符,用来求两个数值的和。

1 + 1 // 2
true + true // 2
1 + true // 2
'a' + 'bc' // "abc"
1
2
3
4

加法运算符是在运行时决定,到底是执行相加,还是执行连接。也就是说,运算子的不同,导致了不同的语法行为,这种现象称为"重载"(overload)。由于加法运算符存在重载,可能执行两种运算,使用的时候必须很小心。

加法运算符的底层原理:V8 引擎在执行 + 运算时,会先对两个操作数调用内部的 ToPrimitive() 抽象操作,将对象转为原始值。如果转换结果中有一个是字符串,则另一个也被转为字符串,执行字符串拼接(String Concatenation);否则两个操作数都被转为数字执行数值加法。这就解释了为什么 '3' + 4 + 5 结果是 "345"(从左到右,'3' + 4 先变成 "34",再 "34" + 5 变成 "345"),而 3 + 4 + '5' 结果是 "75"(3 + 4 先算出 7,再 7 + '5' 变成 "75")。

'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"
1
2

ToPrimitive 转换过程详解:

// 对象转原始值的步骤:
// 1. 如果有 [Symbol.toPrimitive] 方法,优先调用
// 2. 否则,hint 为 'number' 时:先 valueOf(),再 toString()
// 3. hint 为 'string' 时:先 toString(),再 valueOf()
// 4. hint 为 'default'(+ 运算符)时:同 'number'

// [] 的转换过程
[].valueOf()    // [] (返回自身,不是原始值)
[].toString()   // ""  (空字符串,是原始值,使用它)

// {} 的转换过程
({}).valueOf()   // {} (返回自身)
({}).toString()  // "[object Object]"

// 因此:
[] + []   // "" + "" = ""
[] + {}   // "" + "[object Object]" = "[object Object]"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3.1.2 余数运算符

余数运算符(%)返回前一个运算子被后一个运算子除,所得的余数。

12 % 5 // 2

// 需要注意的是,运算结果的正负号由第一个运算子的正负号决定。
-1 % 2 // -1
1 % -2 // 1

// 余数运算符还可以用于浮点数的运算。但是,由于浮点数不是精确的值,无法得到完全准确的结果。
6.5 % 2.1   // 0.19999999999999973
1
2
3
4
5
6
7
8

实际应用场景:

// 1. 判断奇偶
function isEven(n) {
    return n % 2 === 0;
}

// 2. 循环索引(轮播图、环形缓冲区)
function getCircularIndex(index, length) {
    return ((index % length) + length) % length;  // 处理负数
}
console.log(getCircularIndex(-1, 5)); // 4
console.log(getCircularIndex(7, 5));  // 2

// 3. 时间格式化
function formatTime(totalSeconds) {
    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const seconds = totalSeconds % 60;
    return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
console.log(formatTime(3725)); // "1:02:05"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 3.1.3 自增和自减

自增和自减运算符,是一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上1或者减去1。它们会修改原始变量。

var x = 1;
++x // 2
x // 2

--x // 1
x // 1
1
2
3
4
5
6

前置和后置的区别——这是面试高频考点:

let a = 5;
let b = a++;  // 后置:先返回 a 的当前值(5),再 a 加 1
console.log(a, b);  // 6, 5

let c = 5;
let d = ++c;  // 前置:先 c 加 1,再返回新值(6)
console.log(c, d);  // 6, 6

// 复杂表达式中的自增(面试题)
let i = 1;
let result = i++ + ++i;
// i++ 返回 1(i变为2),++i 返回 3(i先变为3)
// result = 1 + 3 = 4
console.log(result); // 4
console.log(i);      // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3.1.4 指数运算符

ES2016 引入了指数运算符 **,替代 Math.pow():

2 ** 3    // 8
2 ** 0.5  // 1.4142135623730951(等同于 Math.sqrt(2))
10 ** -2  // 0.01

// 右结合性(从右向左计算)
2 ** 3 ** 2   // 等于 2 ** (3 ** 2) = 2 ** 9 = 512
// 而不是 (2 ** 3) ** 2 = 8 ** 2 = 64

// 指数赋值
let base = 2;
base **= 10;
console.log(base); // 1024
1
2
3
4
5
6
7
8
9
10
11
12

# 3.2 赋值运算符

赋值运算符用于为变量赋值。

# 3.2.1 基本赋值

赋值运算符(Assignment Operators)用于给变量赋值。最常见的赋值运算符,当然就是等号(=)。

// 将 1 赋值给变量 x
var x = 1;

// 将变量 y 的值赋值给变量 x
var x = y;
1
2
3
4
5

# 3.2.2 复合赋值

运算符 描述 示例
= 赋值 let a = 5
+= 加后赋值 a += 2 → a = a + 2
-= 减后赋值 a -= 2 → a = a - 2
*= 乘后赋值 a *= 2 → a = a * 2
/= 除后赋值 a /= 2 → a = a / 2
%= 取余后赋值 a %= 2 → a = a % 2
**= 指数后赋值 a **= 2 → a = a ** 2
&&= 逻辑与赋值(ES2021) a &&= b → a && (a = b)
||= 逻辑或赋值(ES2021) a ||= b → a || (a = b)
??= 空值合并赋值(ES2021) a ??= b → a ?? (a = b)
// ES2021 逻辑赋值运算符
let config = { debug: false, verbose: null };

// ||= 在左侧为 falsy 时赋值
config.debug ||= true;    // debug 为 false(falsy),赋值为 true
console.log(config.debug); // true

// ??= 仅在左侧为 null/undefined 时赋值
config.verbose ??= 'normal';  // verbose 为 null,赋值为 'normal'
console.log(config.verbose);   // 'normal'

// &&= 在左侧为 truthy 时赋值
let user = { name: 'Alice', token: 'abc123' };
user.token &&= refreshToken();  // token 存在时才刷新
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.2.3 解构赋值

ES6 引入的解构赋值是一种从数组或对象中提取值的简洁语法:

// 数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c);  // 1 2 3

// 跳过元素
const [first, , third] = [1, 2, 3];
console.log(first, third);  // 1 3

// 默认值
const [x = 10, y = 20] = [1];
console.log(x, y);  // 1 20

// 交换变量(不需要临时变量!)
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n);  // 2 1

// 剩余元素
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

// 对象解构
const { name, age, job = 'Developer' } = { name: 'Alice', age: 25 };
console.log(name, age, job);  // Alice 25 Developer

// 重命名
const { name: userName, age: userAge } = { name: 'Alice', age: 25 };
console.log(userName, userAge);  // Alice 25

// 嵌套解构
const { address: { city, zip } } = {
    address: { city: 'Beijing', zip: '100000' }
};
console.log(city, zip);  // Beijing 100000

// 函数参数解构
function greet({ name, greeting = 'Hello' }) {
    console.log(`${greeting}, ${name}!`);
}
greet({ name: 'Alice' });  // Hello, Alice!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

底层原理:解构赋值在编译时被转换为普通的属性访问语句。例如 const { a, b } = obj 会被转换为类似 const a = obj.a; const b = obj.b; 的代码。数组解构使用迭代器协议——任何可迭代对象(实现了 [Symbol.iterator])都可以被数组解构。

# 3.3 比较运算符

比较运算符用于比较两个值,返回布尔值(true 或 false)。

运算符 描述 示例
== 等于(值相等) 5 == '5' → true
=== 严格等于(值和类型都相等) 5 === '5' → false
!= 不等于 5 != '5' → false
!== 严格不等于 5 !== '5' → true
> 大于 5 > 3 → true
< 小于 5 < 3 → false
>= 大于等于 5 >= 5 → true
<= 小于等于 5 <= 3 → false

# 3.3.1 相等运算符

描述:比较两个值是否相等(会进行类型转换)。

1 == 1.0
// 等同于
1 === 1.0
1
2
3

相等运算符隐藏的类型转换,会带来一些违反直觉的结果。

0 == ''             // true
0 == '0'            // true

2 == true           // false
2 == false          // false

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.3.2 严格等于

描述:比较两个值是否相等(不会进行类型转换,值和类型都必须相同)。

如果两个值的类型不同,直接返回false。

1 === "1" // false
true === "true" // false
1
2

同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false。

1 === 0x1 // true
1

undefined和null与自身严格相等。

undefined === undefined // true
null === null // true
1
2

# 3.3.3 相等比较的底层算法

==(抽象相等比较算法 Abstract Equality Comparison)的完整规则:

1. 如果两个操作数类型相同,执行严格相等比较 ===
2. null == undefined → true
3. number == string → 将 string 转为 number 再比较
4. boolean == 其他 → 将 boolean 转为 number(true→1, false→0)再比较
5. object == string/number → 将 object 转为原始值(ToPrimitive)再比较
1
2
3
4
5
// 用规则推导:'0' == false
// 规则4: false 转为 0 → '0' == 0
// 规则3: '0' 转为 0 → 0 == 0
// 规则1: 同类型比较 → true

// 用规则推导:[] == false
// 规则4: false 转为 0 → [] == 0
// 规则5: [] 转为原始值 → '' == 0
// 规则3: '' 转为 0 → 0 == 0
// 规则1: 同类型比较 → true

// 用规则推导:null == 0
// 规则2只说 null == undefined 为 true
// null 和 0 不匹配任何规则 → false
1
2
3
4
5
6
7
8
9
10
11
12
13
14

===(严格相等比较)和 Object.is() 的区别:

// === 的两个特殊情况
NaN === NaN         // false(这是 IEEE 754 的规定)
+0 === -0           // true

// Object.is() 修正了这两个特殊情况
Object.is(NaN, NaN) // true
Object.is(+0, -0)   // false

// 实际开发中的选择
// 99% 的场景用 ===
// 需要区分 +0/-0 或判断 NaN 时用 Object.is()
1
2
3
4
5
6
7
8
9
10
11

# 3.4 逻辑运算符

逻辑运算符用于组合或操作布尔值。

# 3.4.1 逻辑与和逻辑或

运算符 描述 示例
&& 逻辑与 true && false → false
|| 逻辑或 true || false → true
! 逻辑非 !true → false

# 3.4.2 短路求值原理

短路求值的底层原理:逻辑运算符 && 和 || 在 JavaScript 中实现了短路求值(Short-circuit Evaluation)。关键点是它们返回的是操作数的值本身,而不是布尔值。

// && 返回第一个 falsy 值,或最后一个值
console.log(1 && 2 && 3);     // 3(全部truthy,返回最后一个)
console.log(1 && 0 && 3);     // 0(遇到第一个falsy就返回)
console.log(null && 'hello'); // null

// || 返回第一个 truthy 值,或最后一个值
console.log(0 || '' || 'hello');  // 'hello'
console.log(0 || '' || null);     // null(全部falsy,返回最后一个)

// 实际应用
// 1. 短路保护(安全访问属性)
const name = user && user.profile && user.profile.name;

// 2. 设置默认值(注意 0、''、false 也会触发)
const port = config.port || 3000;

// 3. 条件执行
isReady && doSomething();  // 等价于 if(isReady) doSomething()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3.4.3 空值合并运算符

ES2020 引入的 ?? 运算符专门解决 || 对 falsy 值误判的问题:

// || 的问题:0、''、false 也被当作"空"
const count1 = 0 || 10;      // 10(不符合预期!0 是有效值)
const count2 = '' || 'N/A';  // 'N/A'(不符合预期!空字符串可能是有效值)

// ?? 只在 null/undefined 时才使用右侧值
const count3 = 0 ?? 10;      // 0(正确!)
const count4 = '' ?? 'N/A';  // ''(正确!)
const count5 = null ?? 10;   // 10
const count6 = undefined ?? 10; // 10
const count7 = false ?? true;   // false(false不是null/undefined)

// 实际应用:配置合并
function getConfig(userConfig) {
    return {
        timeout: userConfig.timeout ?? 5000,
        retries: userConfig.retries ?? 3,
        debug: userConfig.debug ?? false,  // 用 || 这里会出错
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3.4.4 逻辑赋值运算符

ES2021 引入的逻辑赋值运算符结合了逻辑运算和赋值:

// ||= 逻辑或赋值
let a = null;
a ||= 'default';   // a = a || 'default' → 'default'

// &&= 逻辑与赋值
let b = 'hello';
b &&= b.toUpperCase();  // b 有值时才执行 → 'HELLO'

let c = null;
c &&= c.toUpperCase();  // c 为 null,不执行 → null

// ??= 空值合并赋值
let config = {};
config.port ??= 3000;       // port 不存在(undefined) → 3000
config.debug ??= false;     // debug 不存在 → false
config.port ??= 8080;       // port 已经是 3000,不覆盖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3.5 位运算符

位运算符对操作数的二进制表示进行操作。JavaScript 中的数字在进行位运算前会被转换为32位有符号整数。

# 3.5.1 位运算基础

运算符 描述 示例
& 按位与 5 & 1 → 1
\| 按位或 5 \| 1 → 5
^ 按位异或 5 ^ 1 → 4
~ 按位非 ~5 → -6
<< 左移 5 << 1 → 10
>> 右移 5 >> 1 → 2
>>> 无符号右移 -5 >>> 1 → 2147483645
// 5 的二进制: 00000101
// 1 的二进制: 00000001

// 按位与(都为1才是1)
5 & 1  // 00000101 & 00000001 = 00000001 = 1

// 按位或(有一个1就是1)
5 | 1  // 00000101 | 00000001 = 00000101 = 5

// 按位异或(不同为1,相同为0)
5 ^ 1  // 00000101 ^ 00000001 = 00000100 = 4

// 按位非(取反,然后结果是 -(n+1))
~5     // -(5+1) = -6

// 左移(相当于乘以2的n次方)
5 << 1  // 00000101 << 1 = 00001010 = 10
5 << 2  // 00000101 << 2 = 00010100 = 20

// 右移(相当于除以2的n次方并取整)
5 >> 1  // 00000101 >> 1 = 00000010 = 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 3.5.2 位运算的实际应用

// 1. 快速取整(比 Math.floor 快,但只适用于32位范围内的数字)
console.log(3.7 | 0);   // 3
console.log(-3.7 | 0);  // -3(注意:不同于Math.floor(-3.7)=-4)
console.log(~~3.7);      // 3(双按位非也可以取整)

// 2. 判断奇偶(比 % 2 快)
console.log(5 & 1);      // 1(奇数)
console.log(4 & 1);      // 0(偶数)

// 3. 交换两个变量(不用临时变量,不用解构)
let a = 5, b = 3;
a ^= b;  // a = 5 ^ 3 = 6
b ^= a;  // b = 3 ^ 6 = 5
a ^= b;  // a = 6 ^ 5 = 3
console.log(a, b); // 3 5

// 4. 权限系统(标志位)
const READ    = 1;   // 0001
const WRITE   = 2;   // 0010
const EXECUTE = 4;   // 0100
const ADMIN   = 8;   // 1000

let permissions = READ | WRITE;  // 0011 = 3

// 检查权限
console.log((permissions & READ) !== 0);    // true
console.log((permissions & EXECUTE) !== 0); // false

// 添加权限
permissions |= EXECUTE;  // 0111 = 7

// 移除权限
permissions &= ~WRITE;   // 0101 = 5

// 5. RGB 颜色处理
function rgbToHex(r, g, b) {
    return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
}
console.log(rgbToHex(255, 128, 0)); // "#ff8000"

function hexToRgb(hex) {
    const num = parseInt(hex.slice(1), 16);
    return {
        r: (num >> 16) & 255,
        g: (num >> 8) & 255,
        b: num & 255,
    };
}
console.log(hexToRgb('#ff8000')); // {r: 255, g: 128, b: 0}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 3.6 三元运算符

# 3.6.1 基本用法与嵌套

三元运算符(条件运算符)是 JavaScript 中唯一的三元运算符,格式为 条件 ? 表达式1 : 表达式2。

// 基本用法
const age = 20;
const status = age >= 18 ? '成年' : '未成年';

// 嵌套三元(不推荐超过2层)
const score = 85;
const grade = score >= 90 ? 'A'
            : score >= 80 ? 'B'
            : score >= 70 ? 'C'
            : score >= 60 ? 'D'
            : 'F';

// 三元运算符 vs if...else 的选择
// 三元:适合简单的值选择
const display = isVisible ? 'block' : 'none';

// if...else:适合有副作用的操作
if (isReady) {
    startProcess();
    logEvent('started');
} else {
    showLoading();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.7 类型运算符

# 3.7.1 typeof原理

typeof 运算符返回一个表示操作数类型的字符串。

console.log(typeof 42);           // "number"
console.log(typeof 'hello');      // "string"
console.log(typeof true);         // "boolean"
console.log(typeof undefined);    // "undefined"
console.log(typeof null);         // "object"  ← 历史 bug!
console.log(typeof Symbol('id')); // "symbol"
console.log(typeof 123n);         // "bigint"
console.log(typeof {});           // "object"
console.log(typeof []);           // "object"  ← 数组也返回 object
console.log(typeof function(){}); // "function"
1
2
3
4
5
6
7
8
9
10

typeof 的底层原理:在 JavaScript 的第一版实现中,值以32位为单位存储,前3位是类型标记(type tag):000 表示对象,001 表示整数,010 表示浮点数,100 表示字符串,110 表示布尔值。null 被表示为空指针(全0),所以它的类型标记也是 000(对象),因此 typeof null === "object"。这是一个25年前的 bug,但由于太多代码依赖了这个行为,TC39 委员会曾在 ES6 中提议修复但最终被否决。

# 3.7.2 instanceof原理

instanceof 运算符检测构造函数的 prototype 属性是否出现在对象的原型链上:

console.log([] instanceof Array);     // true
console.log({} instanceof Object);    // true
console.log(() => {} instanceof Function); // true

// instanceof 的本质:遍历原型链
// obj instanceof Constructor
// 等价于检查 Constructor.prototype 是否在 obj 的原型链上

// 手动实现 instanceof
function myInstanceof(obj, Constructor) {
    let proto = Object.getPrototypeOf(obj);
    while (proto !== null) {
        if (proto === Constructor.prototype) return true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
}

console.log(myInstanceof([], Array));   // true
console.log(myInstanceof([], Object));  // true(Array 继承自 Object)

// instanceof 的局限性:跨 iframe/realm 失效
// 因为不同 iframe 有各自独立的 Array 构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.8 其他运算符

# 3.8.1 可选链运算符

ES2020 引入的 ?. 运算符在访问深层嵌套属性时提供安全保障:

const user = {
    profile: {
        address: {
            city: 'Beijing'
        }
    }
};

// 传统方式(冗长且容易出错)
const city1 = user && user.profile && user.profile.address && user.profile.address.city;

// 可选链(简洁安全)
const city2 = user?.profile?.address?.city;  // 'Beijing'

// 如果中间有 null/undefined,直接返回 undefined 而不报错
const zip = user?.profile?.address?.zip;     // undefined
const country = user?.location?.country;      // undefined(不会报错)

// 可选链调用方法
const length = someArray?.length;             // 如果数组存在则返回长度
const result = obj?.method?.();               // 如果方法存在则调用

// 可选链访问数组元素
const firstItem = arr?.[0];

// 配合空值合并运算符
const displayName = user?.profile?.name ?? 'Anonymous';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

底层原理:?. 在引擎内部被转换为条件判断——a?.b 等价于 a == null ? undefined : a.b(注意是 == 不是 ===,所以 null 和 undefined 都会触发短路)。

# 3.8.2 展开运算符

... 展开运算符(Spread Operator)可以展开可迭代对象:

// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];  // [1, 2, 3, 4, 5]

// 对象展开(浅拷贝)
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };  // { a: 1, b: 2, c: 3 }

// 函数参数展开
function sum(a, b, c) { return a + b + c; }
console.log(sum(...[1, 2, 3]));  // 6

// 合并数组(比 concat 更直观)
const merged = [...arr1, ...arr2];

// 字符串展开为字符数组
const chars = [..."hello"];  // ['h', 'e', 'l', 'l', 'o']

// 去重(配合 Set)
const unique = [...new Set([1, 2, 2, 3, 3])];  // [1, 2, 3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 3.8.3 逗号运算符

逗号运算符从左到右依次执行表达式,返回最后一个表达式的值:

let a = (1, 2, 3);
console.log(a); // 3

// 常见应用:for 循环中初始化多个变量
for (let i = 0, j = 10; i < j; i++, j--) {
    console.log(i, j);
}
1
2
3
4
5
6
7

# 3.9 运算符优先级

# 3.9.1 优先级表

JavaScript 中的运算符优先级决定了表达式中运算的顺序。以下是完整的优先级(从高到低):

优先级 运算符 描述
1 () 分组
2 . [] ?. () 成员访问、可选链、函数调用
3 new(带参数) 构造函数调用
4 ++ --(后缀) 后缀自增/自减
5 ! ~ + - typeof void delete await 一元运算符
6 ** 指数(右结合)
7 * / % 乘除余
8 + - 加减
9 << >> >>> 位移
10 < <= > >= instanceof in 比较
11 == != === !== 相等
12 & 按位与
13 ^ 按位异或
14 \| 按位或
15 && 逻辑与
16 \|\| 逻辑或
17 ?? 空值合并
18 ?: 条件(三元)
19 = += -= 等 赋值
20 , 逗号
// 常见的优先级陷阱
console.log(1 + 2 * 3);       // 7(* 优先于 +)
console.log((1 + 2) * 3);     // 9(括号最高优先级)

// && 优先于 ||
console.log(true || false && false);  // true
// 等价于 true || (false && false) → true || false → true

// 赋值运算符优先级很低
let x = 1 + 2 * 3;  // x = 7(先算右边)
1
2
3
4
5
6
7
8
9
10
上次更新: 2026/06/10, 11:13:41
数据类型
函数

← 数据类型 函数→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式