运算符与表达式
运算符是 JavaScript 中执行运算、比较和赋值的符号,由运算符与操作数组成的式子称为表达式。掌握运算符的语义、类型转换规则与优先级,是写出正确、简洁代码的基础。本文按类别逐一讲解各类运算符,并给出大量可运行的示例。
一、算术运算符
JavaScript 提供了 7 个算术运算符:加、减、乘、除、取余、指数与一元正负号。
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
+ | 加法 | 5 + 3 | 8 |
- | 减法 | 5 - 3 | 2 |
* | 乘法 | 5 * 3 | 15 |
/ | 除法 | 5 / 2 | 2.5 |
% | 取余 | 5 % 2 | 1 |
** | 指数 | 2 ** 3 | 8 |
+(一元) | 转数字 | +"42" | 42 |
-(一元) | 取负 | -5 | -5 |
1.1 取余运算符
取余 % 返回两数相除后的余数,结果的符号与被除数一致:
console.log(10 % 3); // 1
console.log(-10 % 3); // -1
console.log(10 % -3); // 1
console.log(7.5 % 2); // 1.5典型应用:判断奇偶、循环取下标、处理分页。
// 判断奇偶
function isEven(n) {
return n % 2 === 0;
}
console.log(isEven(4)); // true
// 轮播图取当前索引
let current = 0;
const total = 5;
current = (current + 1) % total; // 11.2 指数运算符
** 用于幂运算,优先级高于乘除,结合方向为从右到左:
console.log(2 ** 10); // 1024
console.log(4 ** 0.5); // 2(开平方)
console.log(2 ** 3 ** 2); // 512,即 2 ** (3 ** 2)1.3 除法与加法中的特殊行为
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(0 / 0); // NaN
console.log("6" + 1); // "61",加号遇字符串执行拼接
console.log("6" - 1); // 5,减号强制转数字二、比较运算符
比较运算符返回布尔值,包括相等与关系两大类。
| 运算符 | 含义 | 运算符 | 含义 |
|---|---|---|---|
== | 相等(宽松) | === | 全等(严格) |
!= | 不相等(宽松) | !== | 不全等(严格) |
> | 大于 | >= | 大于等于 |
< | 小于 | <= | 小于等于 |
2.1 宽松相等与严格相等
== 比较前会进行类型转换,=== 要求类型与值都相同,两者不相等则直接返回 false。
console.log(1 == "1"); // true,字符串被转为数字
console.log(1 === "1"); // false,类型不同
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(0 == false); // true,false 转为 0
console.log(0 === false); // false
console.log("" == 0); // true,空串转为 0
console.log(NaN == NaN); // false,NaN 不等于任何值(包括自身)== 的类型转换规则可以概括为:
- 数字与字符串比较,字符串转数字;
- 布尔值参与比较,先转为数字(
true为 1,false为 0); null与undefined互等,且只与彼此相等;- 对象与原始值比较,调用对象的
valueOf()/toString()转换。
2.2 关系比较
console.log("2" > "10"); // true,字符串按字典序比较
console.log(2 > "10"); // false,"10" 转数字 10
console.log("apple" < "banana"); // true,逐字符比较
console.log(3 > "2"); // true
console.log("a" > 3); // false,NaN 参与比较恒为 false2.3 比较运算典型应用
// 浮点比较使用误差容限
console.log(0.1 + 0.2 === 0.3); // false
console.log(Math.abs(0.1 + 0.2 - 0.3) < 1e-10); // true
// Object.is 与 === 的区别
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(-0, 0)); // false,=== 认为二者相等三、逻辑运算符
逻辑运算符包括 &&(与)、||(或)与 !(非),它们基于真值与假值工作。
3.1 假值与真值
JavaScript 中以下 6 个值是假值,其余一律为真值:
false, 0, "" (空字符串), null, undefined, NaNif (0 || "" || null || undefined || NaN || false) {
// 永远不会进入
}3.2 短路求值与返回值
&& 与 || 返回的是操作数的值而非布尔值,并且具有短路行为:
a && b:a为假时直接返回a,否则返回b;a || b:a为真时直接返回a,否则返回b。
console.log(0 && "hello"); // 0
console.log(1 && "hello"); // "hello"
console.log("" || "默认值"); // "默认值"
console.log("你好" || "默认值"); // "你好"典型应用:
// 短路求值:左侧成立才执行右侧
let user = { name: "张三" };
user && console.log(user.name); // 张三
// 或运算提供默认值
function greet(name) {
name = name || "游客";
console.log("你好," + name);
}
greet(); // 你好,游客
greet("李四"); // 你好,李四! 运算符返回布尔值并反转真值:
console.log(!true); // false
console.log(!0); // true
console.log(!!"abc"); // true,双非用于转布尔四、位运算符
位运算符将操作数当作 32 位有符号整数处理,常用于底层运算与性能敏感场景。
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
& | 按位与 | 5 & 3 | 1 |
| | 按位或 | 5 | 3 | 7 |
^ | 按位异或 | 5 ^ 3 | 6 |
~ | 按位取反 | ~5 | -6 |
<< | 左移 | 1 << 4 | 16 |
>> | 有符号右移 | -8 >> 1 | -4 |
>>> | 无符号右移 | -1 >>> 1 | 2147483647 |
// 5 = 0101,3 = 0011
console.log(5 & 3); // 1,逐位与:0101 & 0011 = 0001
console.log(5 | 3); // 7,逐位或:0101 | 0011 = 0111
console.log(5 ^ 3); // 6,逐位异或:0101 ^ 0011 = 0110
console.log(~5); // -6,~x 等价于 -(x + 1)4.1 典型应用
// 判断奇偶:与 1 按位与
function isOdd(n) {
return (n & 1) === 1;
}
console.log(isOdd(7)); // true
// 乘 2 / 除 2:移位替代乘除
console.log(3 << 1); // 6
console.log(16 >> 2); // 4
// 交换两数(不引入临时变量)
let a = 5, b = 3;
a ^= b; b ^= a; a ^= b;
console.log(a, b); // 3 5
// 权限组合:使用位掩码
const READ = 1, WRITE = 2, EXEC = 4;
let perm = READ | WRITE; // 3
console.log(perm & READ); // 1,拥有读权限
console.log(perm & EXEC); // 0,没有执行权限五、三元运算符
三元运算符 条件 ? 值1 : 值2 是 if/else 的紧凑写法:
const age = 18;
const status = age >= 18 ? "成年" : "未成年";
console.log(status); // 成年
// 可嵌套,但不建议过深
const score = 85;
const level = score >= 90 ? "优秀" : score >= 60 ? "及格" : "不及格";
console.log(level); // 及格六、赋值运算符
6.1 基础赋值与复合赋值
| 复合运算符 | 等价写法 | 复合运算符 | 等价写法 |
|---|---|---|---|
+= | 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 n = 10;
n += 5; // 15
n -= 3; // 12
n *= 2; // 24
n /= 4; // 6
n %= 4; // 2
n **= 3; // 86.2 链式赋值
赋值表达式的值就是被赋的值,因此可以链式使用:
let a, b, c;
a = b = c = 0; // 三个变量同时赋 0七、解构赋值
解构赋值(Destructuring)可以从数组或对象中一次性提取多个值到变量。
7.1 数组解构
const [x, y] = [1, 2];
console.log(x, y); // 1 2
// 跳过元素、默认值、剩余项
const [first, , third] = [10, 20, 30];
console.log(first, third); // 10 30
const [m = 0, n = 0] = [5];
console.log(m, n); // 5 0
const [head, ...rest] = [1, 2, 3, 4];
console.log(head, rest); // 1 [2, 3, 4]
// 交换变量
let a2 = 1, b2 = 2;
[a2, b2] = [b2, a2];
console.log(a2, b2); // 2 17.2 对象解构
const person = { name: "张三", age: 30, city: "北京" };
const { name, age } = person;
console.log(name, age); // 张三 30
// 重命名与默认值
const { name: username = "匿名", job = "无" } = person;
console.log(username, job); // 张三 无
// 嵌套解构
const data = { user: { info: { id: 7 } } };
const { user: { info: { id } } } = data;
console.log(id); // 7
// 剩余属性
const { city, ...others } = person;
console.log(city, others); // 北京 { name: '张三', age: 30 }
// 函数参数解构
function show({ name, age }) {
console.log(`${name} 今年 ${age} 岁`);
}
show(person); // 张三 今年 30 岁八、可选链与空值合并
8.1 可选链 ?.(ES2020)
可选链在访问属性、调用方法或取下标时,若左侧为 null 或 undefined 则返回 undefined,而不是抛出错误:
const user = { profile: null };
console.log(user.profile?.name); // undefined,不报错
console.log(user.profile?.address?.city); // undefined,链式安全
// 调用方法
const obj = {};
console.log(obj.sayHi?.()); // undefined,方法不存在也不报错
// 结合下标
const arr = null;
console.log(arr?.[0]); // undefined8.2 空值合并 ??(ES2020)
?? 只在左侧为 null 或 undefined 时返回右侧值,与 || 的区别在于它不会把 0、""、false 当作需要替换的值:
const a = 0;
console.log(a || 10); // 10,0 是假值被替换
console.log(a ?? 10); // 0,仅 null/undefined 才替换
const b = "";
console.log(b || "默认"); // 默认
console.log(b ?? "默认"); // ""8.3 结合使用
const config = { timeout: null };
const timeout = config.timeout ?? 5000;
console.log(timeout); // 5000注意:?? 不能与 &&、|| 无括号混用,否则会抛出语法错误:
// 错误:SyntaxError
// console.log(a || b ?? c);
// 正确写法:用括号明确优先级
const a = 0, b = 1, c = 2;
console.log((a || b) ?? c); // 1九、逻辑赋值运算符(ES2021)
&&=、||=、??= 将逻辑运算与赋值合并,先判断再赋值:
// 等价于 a && (a = b):a 为真时才赋值
let x = 1;
x &&= 10;
console.log(x); // 10
let y = 0;
y &&= 10;
console.log(y); // 0
// 等价于 a || (a = b):a 为假时才赋值
let m = "";
m ||= "默认值";
console.log(m); // 默认值
// 等价于 a ?? (a = b):a 为 null/undefined 时才赋值
let n = null;
n ??= "兜底值";
console.log(n); // 兜底值
let z = 0;
z ??= 100;
console.log(z); // 0典型应用:给对象设置默认配置。
const settings = {};
settings.theme ??= "light";
settings.count ||= 10;
console.log(settings); // { theme: 'light', count: 10 }十、运算符优先级
优先级决定了表达式的结合顺序,数字越大优先级越高;同优先级运算符再按结合方向(左结合或右结合)运算。
| 优先级 | 运算符 |
|---|---|
| 最高 | ()、[]、?. |
| 高 | !、~、一元 +/-、++、-- |
| 中高 | **(右结合) |
| 中 | *、/、% |
| 中低 | +、- |
| 低 | <<、>>、>>> |
| 较低 | <、<=、>、>= |
| 低 | ==、!=、===、!== |
| 低 | & |
| 更低 | ^ |
| 更低 | | |
| 低 | && |
| 更低 | || |
| 最低 | ?:、赋值、, |
// 优先级示例
console.log(2 + 3 * 4); // 14,乘法先算
console.log((2 + 3) * 4); // 20,括号改变顺序
console.log(3 > 2 && 1 < 0); // false,比较优先于逻辑
console.log(2 ** 3 ** 2); // 512,指数右结合书写建议:复杂的混合表达式尽量使用括号显式表达意图,既避免出错,也便于他人阅读。当逻辑、比较、赋值混用时,务必要确认预期优先级。
十一、综合实战
把本章运算符组合起来,完成一个简单的配置合并工具:
// 合并用户配置与默认配置
const defaults = { theme: "light", pageSize: 20, debug: false };
const userConfig = { pageSize: 50 };
const config = {
theme: userConfig.theme ?? defaults.theme,
pageSize: userConfig.pageSize ?? defaults.pageSize,
debug: userConfig.debug ?? defaults.debug,
};
console.log(config); // { theme: 'light', pageSize: 50, debug: false }
// 统计并限制结果数量
const results = ["a", "b", "c", "d", "e"];
const limit = 3;
const page = results.slice(0, limit);
const hasMore = results.length > limit;
console.log(page, hasMore); // ['a','b','c'] true运算符是 JavaScript 的"动词",理解每种运算符的返回值、短路行为和类型转换规则,才能写出既简洁又符合预期的表达式。建议在浏览器控制台或 Node.js 中逐行运行本文示例,加深记忆。