====== 第二章 运算符与表达式 ======
===== 2.1 运算符概述 =====
运算符(Operator)是用于执行操作的符号,表达式(Expression)是由运算符和操作数组成的代码片段,会产生一个值。
JavaScript的运算符可以分为以下几类:
* 算术运算符
* 赋值运算符
* 比较运算符
* 逻辑运算符
* 位运算符
* 条件(三元)运算符
* 其他运算符(typeof、instanceof、in等)
===== 2.2 算术运算符 =====
==== 2.2.1 基本算术运算符 ====
let a = 10, b = 3;
// 加法
a + b; // 13
"Hello" + " " + "World"; // "Hello World"
// 减法
a - b; // 7
// 乘法
a * b; // 30
// 除法
a / b; // 3.333...
// 取余(模运算)
a % b; // 1(10除以3余1)
10 % 2; // 0(偶数)
// 指数运算(ES2016)
a ** b; // 1000(10的3次方)
Math.pow(a, b); // 1000(等价写法)
// 字符串与数字运算
"5" + 3; // "53"(字符串拼接)
"5" - 3; // 2(字符串转数字)
"5" * 3; // 15
"5" / 3; // 1.666...
==== 2.2.2 一元算术运算符 ====
let x = 5;
// 一元正号(通常无实际作用,但可用于类型转换)
+"123"; // 123(转数字)
+true; // 1
// 一元负号(取反)
-x; // -5
// 递增(++)
let y = 5;
y++; // 后置递增,返回5,然后y变为6
++y; // 前置递增,y先变为7,然后返回7
// 递减(--)
let z = 5;
z--; // 后置递减,返回5,然后z变为4
--z; // 前置递减,z先变为3,然后返回3
// 前置和后置的区别
let a = 5;
let b = a++; // b = 5, a = 6
let c = ++a; // a = 7, c = 7
let m = 5;
let n = --m; // m = 4, n = 4
let p = m--; // p = 4, m = 3
==== 2.2.3 算术运算符详解 ====
**特殊数值运算**:
// Infinity运算
Infinity + Infinity; // Infinity
Infinity - Infinity; // NaN
Infinity * 0; // NaN
Infinity / Infinity; // NaN
10 / 0; // Infinity
-10 / 0; // -Infinity
// NaN传播
NaN + 5; // NaN
NaN * 0; // NaN
NaN / NaN; // NaN
// 任何数与NaN比较都是false
NaN === NaN; // false
NaN == NaN; // false
isNaN(NaN); // true
Number.isNaN(NaN); // true
**浮点数运算问题**:
// 精度丢失
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3; // false
// 解决方案
// 1. 使用toFixed
parseFloat((0.1 + 0.2).toFixed(10)); // 0.3
// 2. 使用整数运算
(1 + 2) / 10; // 0.3
// 3. 使用Number.EPSILON
function floatEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
floatEqual(0.1 + 0.2, 0.3); // true
===== 2.3 赋值运算符 =====
==== 2.3.1 基本赋值运算符 ====
// 简单赋值
let x = 10;
// 连续赋值(从右到左)
let a, b, c;
a = b = c = 5; // a=5, b=5, c=5
==== 2.3.2 复合赋值运算符 ====
let x = 10;
// 加法赋值
x += 5; // x = x + 5; 结果为15
// 减法赋值
x -= 3; // x = x - 3; 结果为12
// 乘法赋值
x *= 2; // x = x * 2; 结果为24
// 除法赋值
x /= 4; // x = x / 4; 结果为6
// 取余赋值
x %= 4; // x = x % 4; 结果为2
// 指数赋值(ES2016)
x **= 3; // x = x ** 3; 结果为8
// 位运算赋值(稍后讲解)
let y = 5;
y &= 3; // y = y & 3;
y |= 4; // y = y | 4;
y ^= 2; // y = y ^ 2;
y <<= 1; // y = y << 1;
y >>= 1; // y = y >> 1;
y >>>= 1; // y = y >>> 1;
==== 2.3.3 解构赋值(ES6) ====
// 数组解构
let [a, b] = [1, 2];
console.log(a, b); // 1 2
// 跳过元素
let [x, , z] = [1, 2, 3];
console.log(x, z); // 1 3
// 剩余元素
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [2, 3, 4]
// 默认值
let [m = 1, n = 2] = [10];
console.log(m, n); // 10 2
// 对象解构
let {name, age} = {name: "张三", age: 25};
console.log(name, age); // "张三" 25
// 重命名
let {name: userName, age: userAge} = {name: "李四", age: 30};
console.log(userName); // "李四"
// 默认值
let {status = "active"} = {};
console.log(status); // "active"
// 嵌套解构
let user = {
info: {
address: {
city: "北京"
}
}
};
let {info: {address: {city}}} = user;
console.log(city); // "北京"
===== 2.4 比较运算符 =====
==== 2.4.1 相等比较 ====
// 宽松相等(==)- 会进行类型转换
5 == "5"; // true
0 == false; // true
"" == false; // true
null == undefined; // true
// 严格相等(===)- 不进行类型转换(推荐)
5 === "5"; // false
0 === false; // false
null === undefined; // false
// 不等比较
5 != "5"; // false
5 !== "5"; // true
**严格相等 vs 宽松相等**:
始终使用 === 和 !==,避免意外的类型转换。
// 宽松相等的奇怪行为
[] == false; // true
[] == ![]; // true
'' == false; // true
0 == '0'; // true
0 == []; // true
'0' == []; // false
// NaN的特殊性
NaN == NaN; // false
NaN === NaN; // false
Object.is(NaN, NaN); // true(ES6新方法)
==== 2.4.2 大小比较 ====
let a = 5, b = 10;
// 大于
a > b; // false
// 小于
a < b; // true
// 大于等于
a >= 5; // true
// 小于等于
b <= 10; // true
// 字符串比较(按字典序)
"apple" < "banana"; // true
"Apple" < "apple"; // true(大写字母ASCII码小)
"10" < "2"; // true(字符串逐字符比较)
**字符串比较原理**:
// Unicode码点比较
"a".charCodeAt(0); // 97
"b".charCodeAt(0); // 98
"A".charCodeAt(0); // 65
// 中文字符比较
"中".charCodeAt(0); // 20013
"国".charCodeAt(0); // 22269
"中国" < "美国"; // true(因为 20013 < 32654)
===== 2.5 逻辑运算符 =====
==== 2.5.1 逻辑与(&&) ====
// 基本用法
true && true; // true
true && false; // false
false && true; // false
false && false; // false
// 短路求值
// 如果第一个操作数为假,直接返回第一个,不计算第二个
let x = 0;
false && (x = 10); // x 仍然是 0
// 常用模式:条件执行
isValid && processData();
// 返回第一个假值或最后一个真值
0 && "hello"; // 0
1 && "hello"; // "hello"
"hello" && 42; // 42
==== 2.5.2 逻辑或(||) ====
// 基本用法
true || true; // true
true || false; // true
false || true; // true
false || false; // false
// 短路求值
// 如果第一个操作数为真,直接返回第一个,不计算第二个
true || console.log("不会执行");
// 常用模式:默认值
let name = userName || "Anonymous";
// 返回第一个真值或最后一个假值
0 || "hello"; // "hello"
"" || 42; // 42
null || undefined; // undefined
**空值合并运算符 ??(ES2020)**:
// || 的问题:假值会被替换
0 || 100; // 100(但0可能是有效值)
"" || "default"; // "default"(但空字符串可能是有效的)
// ?? 只在 null 和 undefined 时返回右侧值
0 ?? 100; // 0
"" ?? "default"; // ""
null ?? "default"; // "default"
undefined ?? "default"; // "default"
==== 2.5.3 逻辑非(!) ====
// 基本用法
!true; // false
!false; // true
// 双重否定转布尔
!!"hello"; // true
!!0; // false
!!{}; // true
!![]; // true
// 等价的布尔转换
Boolean("hello"); // true
Boolean(0); // false
==== 2.5.4 逻辑赋值运算符(ES2021) ====
let x = 0;
let y = 10;
// 逻辑与赋值(&&=)
x &&= y; // 等价于 x = x && y
// x 是 0(假值),所以 x 保持 0
// 逻辑或赋值(||=)
let name = "";
name ||= "default"; // name 是 ""(假值),所以赋值为 "default"
// 空值合并赋值(??=)
let value = null;
value ??= "fallback"; // value 是 null,所以赋值为 "fallback"
let zero = 0;
zero ??= 100; // zero 保持 0(不是null或undefined)
===== 2.6 位运算符 =====
位运算符直接操作数字的二进制表示。
==== 2.6.1 基础位运算 ====
// 按位与(&)- 两个都为1才是1
5 & 3; // 1
// 5: 101
// 3: 011
// &: 001 = 1
// 按位或(|)- 有一个为1就是1
5 | 3; // 7
// 5: 101
// 3: 011
// |: 111 = 7
// 按位异或(^)- 不同为1,相同为0
5 ^ 3; // 6
// 5: 101
// 3: 011
// ^: 110 = 6
// 按位非(~)- 取反(包括符号位)
~5; // -6
// 5: 0000...00000101
// ~5: 1111...11111010 = -6(补码表示)
// 左移(<<)- 乘以2的n次方
5 << 1; // 10
5 << 2; // 20
// 右移(>>)- 带符号右移(除以2的n次方,保留符号)
-16 >> 2; // -4
16 >> 2; // 4
// 无符号右移(>>>)- 用0填充高位
-16 >>> 2; // 1073741820(很大的正数)
==== 2.6.2 位运算实际应用 ====
**权限管理**:
// 使用位运算管理权限
const PERMISSION_READ = 1 << 0; // 1 (0001)
const PERMISSION_WRITE = 1 << 1; // 2 (0010)
const PERMISSION_EXECUTE = 1 << 2; // 4 (0100)
const PERMISSION_DELETE = 1 << 3; // 8 (1000)
// 组合权限
let userPermissions = PERMISSION_READ | PERMISSION_WRITE; // 3
// 检查权限
if (userPermissions & PERMISSION_READ) {
console.log("有读权限");
}
if (userPermissions & PERMISSION_EXECUTE) {
console.log("有执行权限"); // 不会执行
}
// 添加权限
userPermissions |= PERMISSION_EXECUTE; // 现在为7
// 移除权限
userPermissions &= ~PERMISSION_WRITE; // 现在为5
// 切换权限
userPermissions ^= PERMISSION_DELETE; // 添加或删除权限
**颜色处理**:
// RGB颜色提取
let color = 0xFF5733; // 16734003
let red = (color >> 16) & 0xFF; // 255
let green = (color >> 8) & 0xFF; // 87
let blue = color & 0xFF; // 51
// 组合RGB
let newColor = (red << 16) | (green << 8) | blue;
**判断奇偶**:
// 比 % 2 更快
function isOdd(n) {
return (n & 1) === 1;
}
function isEven(n) {
return (n & 1) === 0;
}
isOdd(5); // true
isEven(4); // true
**交换两个数**:
// 不使用临时变量
let a = 5, b = 3;
a = a ^ b;
b = a ^ b; // b 现在是原来的 a
a = a ^ b; // a 现在是原来的 b
console.log(a, b); // 3 5
===== 2.7 其他运算符 =====
==== 2.7.1 条件(三元)运算符 ====
// 语法:condition ? valueIfTrue : valueIfFalse
let age = 20;
let status = age >= 18 ? "成年" : "未成年";
// 嵌套三元(不推荐,可读性差)
let category = age < 13 ? "儿童" :
age < 20 ? "青少年" :
age < 60 ? "成年人" : "老年人";
// 与if-else等价
let message;
if (age >= 18) {
message = "成年";
} else {
message = "未成年";
}
==== 2.7.2 typeof运算符 ====
typeof undefined; // "undefined"
typeof null; // "object"(历史bug)
typeof true; // "boolean"
typeof 42; // "number"
typeof 42n; // "bigint"
typeof "hello"; // "string"
typeof Symbol(); // "symbol"
typeof {}; // "object"
typeof []; // "object"
typeof function() {}; // "function"
// 判断数组
Array.isArray([]); // true
==== 2.7.3 instanceof运算符 ====
检查对象是否是某个类的实例。
[] instanceof Array; // true
{} instanceof Object; // true
new Date() instanceof Date; // true
// 构造函数
function Person() {}
let p = new Person();
p instanceof Person; // true
// 原型链
class Animal {}
class Dog extends Animal {}
let dog = new Dog();
dog instanceof Dog; // true
dog instanceof Animal; // true
dog instanceof Object; // true
==== 2.7.4 in运算符 ====
检查属性是否在对象中。
let obj = { name: "张三", age: 25 };
"name" in obj; // true
"toString" in obj; // true(继承自原型)
let arr = ["a", "b", "c"];
0 in arr; // true
3 in arr; // false
"length" in arr; // true
// 删除属性后
delete obj.age;
"age" in obj; // false
==== 2.7.5 可选链操作符 ?.(ES2020) ====
let user = {
profile: {
name: "张三"
}
};
// 传统写法(容易出错)
// let name = user.profile.name; // 如果profile为null/undefined会报错
// 安全访问
let name = user && user.profile && user.profile.name;
// 使用可选链
let name2 = user?.profile?.name; // "张三"
// 访问不存在的嵌套属性
let city = user?.address?.city; // undefined,不会报错
// 与函数调用结合
let result = someObject?.someMethod?.();
// 与方括号结合
let prop = user?.["profile"]?.["name"];
==== 2.7.6 展开运算符 ...(ES6) ====
// 展开数组
let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// 复制数组(浅拷贝)
let copy = [...arr1];
// 合并数组
let merged = [...arr1, ...arr2];
// 展开对象(ES2018)
let obj1 = { a: 1, b: 2 };
let obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// 对象合并(后面的属性覆盖前面的)
let defaults = { theme: "light", fontSize: 14 };
let userPrefs = { fontSize: 16 };
let settings = { ...defaults, ...userPrefs }; // { theme: "light", fontSize: 16 }
// 函数参数展开
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// 数组解构中的剩余元素
let [first, second, ...rest] = [1, 2, 3, 4, 5];
===== 2.8 运算符优先级 =====
运算符优先级决定了表达式中运算的执行顺序。
// 优先级从高到低:
// 1. 分组 ( )
(2 + 3) * 4; // 20
// 2. 成员访问 . 、计算属性 [] 、new (带参数列表) 、函数调用 ()
obj.property;
obj["property"];
new Date();
func();
// 3. new (无参数列表)
new Date;
// 4. 后置递增/递减 ++ --
x++;
x--;
// 5. 逻辑非 ! 、按位非 ~ 、一元加减 + - 、前置递增/递减 ++ -- 、typeof 、void 、delete 、await
!true;
~5;
+x;
++x;
typeof x;
await promise;
// 6. 指数 **(右结合)
2 ** 3 ** 2; // 512(2 ** (3 ** 2) = 2 ** 9)
// 7. 乘除取余 * / %
10 / 2 * 5; // 25(从左到右)
// 8. 加减 + -
5 + 3 - 2; // 6(从左到右)
// 9. 移位 << >> >>>
8 >> 1; // 4
// 10. 比较 < <= > >= instanceof in
5 > 3;
"a" in obj;
// 11. 相等 == != === !==
5 === "5";
// 12. 按位与 &
5 & 3;
// 13. 按位异或 ^
5 ^ 3;
// 14. 按位或 |
5 | 3;
// 15. 逻辑与 &&
true && false;
// 16. 逻辑或 ||
true || false;
// 17. 空值合并 ??
null ?? "default";
// 18. 条件 ?:
condition ? a : b;
// 19. 赋值 = += -= 等
x = 5;
x += 3;
// 20. yield 、yield* 、=>
function* gen() { yield 1; }
() => {};
// 21. 展开 ...
[...arr];
**优先级示例**:
// 复杂的优先级示例
let a = 1, b = 2, c = 3;
// 等价于:a + (b * c)
a + b * c; // 7
// 等价于:a * (b + c)
a * b + c; // 5(不是9)
// 等价于:(a && b) || c
a && b || c; // 2
// 使用括号明确意图
(a + b) * c; // 9(更清晰)
===== 2.9 练习题 =====
==== 练习1:运算符优先级 ====
预测以下表达式的结果:
2 + 3 * 4;
(2 + 3) * 4;
10 / 2 * 5;
10 / (2 * 5);
5 > 3 && 2 > 4;
5 > 3 || 2 > 4 && 1 > 0;
2 ** 3 ** 2;
(2 ** 3) ** 2;
==== 练习2:位运算应用 ====
完成以下位运算练习:
// 1. 编写函数 isPowerOfTwo,判断一个数是否是2的幂次方
// 提示:2的幂次方的二进制表示只有一个1
// 2. 编写函数 getBit,获取数字n的第i位(从0开始)
// getBit(5, 0) 应该返回 1(5的二进制是101)
// 3. 编写函数 setBit,将数字n的第i位设为1
// 4. 编写函数 clearBit,将数字n的第i位设为0
// 5. 编写函数 toggleBit,切换数字n的第i位(0变1,1变0)
==== 练习3:短路求值 ====
分析以下代码的执行结果:
let x = 0;
let y = 5;
let result1 = x && (y = 10);
console.log(x, y, result1);
let result2 = x || (y = 20);
console.log(x, y, result2);
let result3 = null ?? (y = 30);
console.log(y, result3);
let result4 = 0 ?? (y = 40);
console.log(y, result4);
==== 练习4:综合练习 ====
编写一个函数 `safeGet`,使用可选链和空值合并运算符,安全地获取嵌套对象属性,如果属性不存在则返回默认值:
const user = {
profile: {
address: {
city: "北京"
}
}
};
safeGet(user, "profile.address.city", "未知"); // "北京"
safeGet(user, "profile.address.zipCode", "未知"); // "未知"
safeGet(user, "settings.theme", "light"); // "light"
safeGet(null, "any.path", "default"); // "default"
===== 参考答案 =====
**练习1答案**:
2 + 3 * 4; // 14(先乘后加)
(2 + 3) * 4; // 20
10 / 2 * 5; // 25(从左到右)
10 / (2 * 5); // 1
5 > 3 && 2 > 4; // false
5 > 3 || 2 > 4 && 1 > 0; // true(&&优先级高于||)
2 ** 3 ** 2; // 512(右结合)
(2 ** 3) ** 2; // 64
**练习2答案**:
// 1. 判断2的幂次方
function isPowerOfTwo(n) {
return n > 0 && (n & (n - 1)) === 0;
}
// 原理:2的幂次方只有一位是1,n-1会将该位及后面所有位反转
// 8 = 1000
// 7 = 0111
// 8 & 7 = 0000 = 0
// 2. 获取第i位
function getBit(n, i) {
return (n >> i) & 1;
}
// 3. 设置第i位为1
function setBit(n, i) {
return n | (1 << i);
}
// 4. 清除第i位
function clearBit(n, i) {
return n & ~(1 << i);
}
// 5. 切换第i位
function toggleBit(n, i) {
return n ^ (1 << i);
}
**练习3答案**:
let x = 0;
let y = 5;
let result1 = x && (y = 10);
// x是0(假值),短路返回x
console.log(x, y, result1); // 0, 5, 0
let result2 = x || (y = 20);
// x是0(假值),执行y=20,返回20
console.log(x, y, result2); // 0, 20, 20
let result3 = null ?? (y = 30);
// null,执行y=30,返回30
console.log(y, result3); // 30, 30
let result4 = 0 ?? (y = 40);
// 0不是null/undefined,短路返回0,y保持30
console.log(y, result4); // 30, 0
**练习4答案**:
function safeGet(obj, path, defaultValue) {
if (obj == null) return defaultValue;
const keys = path.split('.');
let result = obj;
for (const key of keys) {
result = result?.[key];
if (result === undefined) {
return defaultValue;
}
}
return result ?? defaultValue;
}
// 或使用reduce的简洁版本
function safeGet2(obj, path, defaultValue) {
const result = path.split('.').reduce((o, k) => o?.[k], obj);
return result ?? defaultValue;
}
// 测试
const user = {
profile: {
address: {
city: "北京"
}
}
};
console.log(safeGet(user, "profile.address.city", "未知")); // "北京"
console.log(safeGet(user, "profile.address.zipCode", "未知")); // "未知"
console.log(safeGet(user, "settings.theme", "light")); // "light"
console.log(safeGet(null, "any.path", "default")); // "default"
===== 本章小结 =====
本章详细介绍了JavaScript的各种运算符:
* **算术运算符**:+、-、*、/、%、**、++、--
* **赋值运算符**:=、+=、-= 等复合赋值
* **比较运算符**:==、===、!=、!==、>、<、>=、<=
* **逻辑运算符**:&&、||、!、??
* **位运算符**:&、|、^、~、<<、>>、>>>
* **其他运算符**:条件运算符、typeof、instanceof、in、?.、...
关键要点:
* 始终使用 === 和 !== 进行比较
* 理解短路求值的机制
* 掌握位运算的实际应用场景
* 了解运算符优先级,必要时使用括号
下一章将学习控制流语句,实现程序的条件执行和循环处理。