3. 运算符与表达式:算术、比较、逻辑、三元与优先级
运算符这东西,说白了就是让数据「动起来」的工具。我刚开始学 JavaScript 时,总觉得运算符很简单,不就是加减乘除嘛。但后来在项目中踩过几次坑,才发现这里面的门道比想象中多得多。
今天咱们就把运算符这块彻底捋清楚。我会结合自己实际开发中的经验,把每个运算符的用法、坑点、以及什么时候该用哪个,都给你讲明白。
3.1 算术运算符:不只是加减乘除
算术运算符大家最熟悉了:+、-、*、/、%(取余)、**(幂运算)。但有几个细节,我建议你特别注意。
核心要点:JavaScript 中的 + 运算符有双重身份——数值相加和字符串拼接。这是最容易出 bug 的地方。
// 数值相加
console.log(10 + 20); // 30
// 字符串拼接
console.log('10' + 20); // '1020'
console.log(10 + '20'); // '1020'
// 混合运算
console.log(10 + 20 + '30'); // '3030',先算 10+20=30,再拼 '30'
console.log('10' + 20 + 30); // '102030',从左到右全部拼接
我曾经在做一个购物车功能时,遇到过这么个问题:商品单价是数字,数量也是数字,但总价算出来却是个字符串。查了半天才发现,原来是从接口拿到的单价字段是字符串类型。嗯,从那以后我养成了一个习惯——做数值运算前,先用 Number() 或一元 + 转一下。
小技巧:一元 + 运算符可以快速将字符串转为数字:+'42' 结果是 42。但要注意,如果字符串不能转成有效数字,会得到 NaN。
取余运算符 % 在实际开发中非常实用。比如判断奇偶、分页计算、循环队列等场景都会用到。
// 判断奇偶
let num = 7;
if (num % 2 === 0) {
console.log('偶数');
} else {
console.log('奇数');
}
// 分页:计算当前页第一条数据的索引
let page = 3;
let pageSize = 10;
let startIndex = (page - 1) * pageSize; // 20
// 循环队列
let index = 0;
let arr = ['a', 'b', 'c'];
index = (index + 1) % arr.length; // 1
index = (index + 1) % arr.length; // 2
index = (index + 1) % arr.length; // 0,回到开头
3.2 比较运算符:小心隐式类型转换
比较运算符包括 ==、===、!=、!==、>、<、>=、<=。
我个人习惯:永远使用 === 和 !==。为什么?因为 == 会做隐式类型转换,结果往往出乎意料。
// 看看这些结果,你猜对几个?
console.log(0 == false); // true
console.log('' == false); // true
console.log(null == undefined); // true
console.log('0' == 0); // true
console.log([] == false); // true
// 用 === 就清晰多了
console.log(0 === false); // false
console.log('' === false); // false
console.log(null === undefined); // false
注意:我曾经在写一个权限判断时,用了 == 比较用户角色。结果因为某个字段是字符串 '0',而另一个是数字 0,导致判断逻辑完全跑偏。那次排查花了我整整两个小时。所以,记住我的教训——能用 === 就别用 ==。
字符串比较是按字典序(Unicode 编码)进行的。这一点在排序时特别重要。
console.log('apple' > 'banana'); // false,因为 'a' 的编码小于 'b'
console.log('2' > '12'); // true,因为 '2' 的编码大于 '1'
console.log(2 > '12'); // false,因为数字比较时 '12' 被转成了 12
3.3 逻辑运算符:短路求值是个好东西
逻辑运算符有三个:&&(与)、||(或)、!(非)。它们有个重要特性——短路求值。
短路求值规则:
a && b:如果a为假,直接返回a,不再计算ba || b:如果a为真,直接返回a,不再计算b
这个特性在实际开发中非常实用。我经常用它来做「默认值」和「条件执行」。
// 设置默认值
let username = inputUsername || '匿名用户';
// 条件执行
isLoggedIn && renderDashboard();
// 更复杂的例子
let config = {
theme: userConfig.theme || 'light',
lang: userConfig.lang || 'zh-CN',
showTips: userConfig.showTips !== false // 默认 true
};
你想想看,如果没有短路求值,每次都要写 if...else,代码会变得很啰嗦。但用 || 和 && 一行就能搞定。
小技巧:用 !! 可以将任意值转为布尔类型:!!0 是 false,!!'hello' 是 true。这比 Boolean() 函数写起来更简洁。
3.4 三元运算符:简洁的条件表达式
三元运算符 条件 ? 值1 : 值2 是 if...else 的简洁写法。我个人非常喜欢用它,但有个原则——只在表达式简单时使用。
// 基础用法
let age = 20;
let status = age >= 18 ? '成年' : '未成年';
// 嵌套三元(不推荐,可读性差)
let score = 85;
let grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'D';
// 更好的写法:用 if...else 或 switch
function getGrade(score) {
if (score >= 90) return 'A';
if (score >= 80) return 'B';
if (score >= 70) return 'C';
return 'D';
}
注意:我曾经接手过一个项目,里面有个三层嵌套的三元运算符。我看了五分钟才搞明白它在做什么。从那以后,我给自己定了个规矩——超过一层的三元,一律改成 if...else 或 switch。代码是写给人看的,不是写给机器看的。
3.5 运算符优先级:别让代码「猜」你的意图
运算符优先级决定了表达式中哪个运算符先执行。虽然我们可以查表,但我建议你记住一个原则:不确定优先级时,就用括号。
| 优先级 | 运算符 | 说明 |
|---|---|---|
| 1(最高) | () |
括号分组 |
| 2 | ++ -- ! |
一元运算符 |
| 3 | * / % |
乘除取余 |
| 4 | + - |
加减 |
| 5 | < > <= >= |
比较运算符 |
| 6 | === !== |
相等性判断 |
| 7 | && |
逻辑与 |
| 8 | || |
逻辑或 |
| 9(最低) | ?: |
三元运算符 |
看这个表格你会发现,&& 的优先级比 || 高。这意味着 a || b && c 实际上被解释为 a || (b && c)。
// 容易混淆的例子
let a = true, b = false, c = true;
console.log(a || b && c); // true,等价于 a || (b && c)
console.log((a || b) && c); // true,但含义不同
// 实际开发中的坑
let user = { name: '张三', age: 25 };
// 我想判断:如果用户存在且年龄大于18,或者用户名为空
// 错误写法
if (user && user.age > 18 || !user.name) {
// 这里逻辑有问题,因为 && 优先级高于 ||
}
// 正确写法
if ((user && user.age > 18) || !user.name) {
// 用括号明确优先级
}
我的建议:不要依赖记忆中的优先级顺序。写代码时,只要涉及两种以上运算符,就用括号把意图表达清楚。这样既避免了 bug,也让读你代码的人一目了然。
3.6 综合实战:一个表单验证的例子
说了这么多,咱们来写一个实际点的例子。假设我们要验证一个用户注册表单:
function validateForm(formData) {
// 用户名:必填,长度 2-20
let username = formData.username || '';
if (!username || username.length < 2 || username.length > 20) {
return '用户名长度需在2-20位之间';
}
// 年龄:可选,但如果填写必须是 0-150 的数字
let age = Number(formData.age);
if (formData.age !== undefined && formData.age !== '') {
if (isNaN(age) || age < 0 || age > 150) {
return '年龄必须是0-150之间的数字';
}
}
// 邮箱:可选,但如果填写必须包含 @
let email = formData.email || '';
if (email && !email.includes('@')) {
return '邮箱格式不正确';
}
// 同意协议:必须勾选
if (!formData.agree) {
return '请先同意用户协议';
}
return null; // 验证通过
}
// 测试
console.log(validateForm({ username: 'abc', age: '25', email: 'test@example.com', agree: true }));
// null,验证通过
console.log(validateForm({ username: 'a', age: '-1', agree: false }));
// '用户名长度需在2-20位之间'
这个例子用到了咱们今天讲的大部分运算符:比较运算符、逻辑运算符、三元运算符(虽然这里没用,但你可以试试把 || 改成三元)。而且你发现没有,! 和 || 的配合使用,让代码变得非常简洁。
总结一下:运算符是 JavaScript 的基础,但基础不等于简单。我见过不少资深开发者在运算符上翻车。记住三点:用 === 代替 ==、善用短路求值、不确定优先级就用括号。做到这三点,运算符这块基本就不会出问题了。