JavaScript 运算符完全指南:从基础到位运算
本文全面解析 JavaScript 中的各种运算符,包含丰富的代码示例和详细的知识点讲解。
一、运算符基础概念
在 JavaScript 中,运算符是用于执行数据运算的符号,而参与运算的数据称为操作数。
操作符类型
按操作数数量分类:
- 一元运算符:只有一个操作数,如
++
、--
、typeof
、void
- 二元运算符:有两个操作数,如
+
、-
、*
、/
、=
、&&
、||
- 三元运算符:有三个操作数,即条件运算符
? :
按功能分类:
- 算术运算符
- 比较运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
- 其他特殊运算符
表达式与返回值
每个表达式都会产生一个运算结果,称为返回值,而返回值的类型称为返回类型。
let a = 5; // 赋值表达式返回赋值的结果
let b = a + 3; // 算术表达式返回运算结果
console.log(b); // 函数调用表达式返回undefined
知识点: Chrome浏览器控制台是REPL环境(Read-Eval-Print-Loop),会自动输出表达式的返回值。
二、算术运算符
JavaScript 提供以下算术运算符:
+
、-
、*
、/
:基本四则运算%
:求余(取模)++
、--
:自增自减**
:幂运算(ES6引入)
2.1 算术运算的特殊情况
数字运算的不精确性:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
知识点: 这是由于浮点数在计算机中的二进制表示导致的精度问题,并非JavaScript独有。
除数为0的情况:
console.log(5 / 0); // Infinity(正无穷)
console.log(-5 / 0); // -Infinity(负无穷)
console.log(0 / 0); // NaN(非数字)// 判断特殊值
console.log(isNaN(0 / 0)); // true
console.log(isFinite(5 / 0)); // false
知识点:
isNaN()
函数用于检测一个值是否为NaNisFinite()
函数用于检测一个值是否为有限数typeof
运算符返回类型的字符串表示
2.2 类型转换规则
非加号运算符的转换规则:
console.log(10 - true); // 9 (true转换为1)
console.log(10 - "5"); // 5 ("5"转换为5)
console.log(10 - "abc"); // NaN(转换失败)
console.log(10 - ""); // 10 (空字符串转换为0)
console.log(10 - null); // 10 (null转换为0)
console.log(10 - undefined); // NaN// 对象转换
let obj = {};
console.log(10 - obj); // NaN (obj→"[object Object]"→NaN)
知识点: 除加号外的算术运算符会将非数字类型隐式转换为数字类型。
加号运算符的特殊规则:
// 一边有字符串时进行字符串拼接
console.log(10 + "5"); // "105"(数字转换为字符串)
console.log(true + "abc"); // "trueabc"(布尔值转换为字符串)// 两边都没有字符串但有对象
console.log(10 + {}); // "10[object Object]"(对象转换为字符串)// 两边都没有字符串和对象
console.log(true + 5); // 6 (true转换为1)
console.log(null + 5); // 5 (null转换为0)
知识点: 加号运算符在有字符串参与时会优先进行字符串拼接,否则进行数学加法运算。
三、自增和自减运算符
自增(++
)和自减(--
)运算符是一元运算符,用于将变量的值增加或减少1。
3.1 前缀与后缀的区别
let x = 5;
let y = x++; // 后缀:先赋值后自增
console.log(x, y); // 6, 5let a = 5;
let b = ++a; // 前缀:先自增后赋值
console.log(a, b); // 6, 6
知识点: 前缀形式返回自增后的值,后缀形式返回自增前的值。
3.2 优先级问题
运算符优先级从高到低:
++
、--
*
、/
、%
+
、-
let x = 5;
let result = x++ * 2 + 3; // 5*2+3=13,然后x自增为6
console.log(result, x); // 13, 6
知识点: 优先级决定了表达式中运算的执行顺序,同级运算符从左到右计算。
四、比较运算符
比较运算符用于比较两个值,返回布尔值(true
或false
)。
4.1 大小比较运算符
>
、<
、>=
、<=
比较规则详解:
// 字符串比较(按字符编码逐位比较)
console.log("a" < "b"); // true(字符a的编码小于b)
console.log("2" > "10"); // true(字符"2"编码大于"1")// 类型不同时转换为数字比较
console.log("10" > 5); // true ("10"转换为10)
console.log("abc" > 5); // false ("abc"转换为NaN)// 特殊值比较规则
console.log(NaN > 5); // false(NaN与任何值比较都是false)
console.log(Infinity > 1000); // true(无穷大比任何有限数大)
console.log(-Infinity < -1000); // true(负无穷比任何有限数小)// 对象比较(先转换为原始类型)
let obj1 = {}, obj2 = {};
console.log(obj1 > obj2); // false (转换为"[object Object]")
console.log(obj1 == obj2); // false (比较的是引用地址)
知识点:
- 字符串比较基于Unicode编码值
- NaN与任何值(包括自身)比较都返回false
- 对象比较时先调用
valueOf()
或toString()
方法转换为原始类型
4.2 相等比较运算符
==
、!=
、===
、!==
==
和!=
的隐式类型转换规则:
// null和undefined的特殊规则
console.log(null == undefined); // true(它们只与彼此相等)
console.log(null == 0); // false(null只与undefined相等)// 类型不同时的数字转换
console.log("5" == 5); // true(字符串转换为数字)
console.log(true == 1); // true(布尔值转换为数字)
console.log("" == 0); // true(空字符串转换为0)// NaN的特殊性
console.log(NaN == NaN); // false(NaN不等于任何值,包括自身)// 对象比较引用地址
console.log({} == {}); // false(不同的对象实例)
知识点: ==
运算符会进行类型转换,遵循复杂的转换规则。
===
和!==
的严格比较:
// 严格相等要求值和类型都相同
console.log("5" === 5); // false(类型不同)
console.log(5 === 5); // true(值和类型都相同)// 特殊值的严格比较
console.log(NaN === NaN); // false
console.log(null === undefined); // false
console.log(0 === -0); // true(特殊情况)
知识点:
===
不进行类型转换,直接比较值和类型- 在实际开发中推荐使用
===
和!==
,避免隐式转换带来的意外结果
五、逻辑运算符
逻辑运算符用于布尔运算,但也可以用于非布尔值,返回的不一定是布尔值。
5.1 逻辑与(&&)
// 布尔运算
console.log(true && false); // false// 短路特性:如果第一个操作数为假,直接返回,不计算第二个
console.log(false && console.log("不会执行")); // false// 非布尔值运算:返回第一个为假的值,或最后一个为真的值
console.log(0 && "abc"); // 0(第一个假值)
console.log("abc" && 123); // 123(最后一个真值)
console.log(null && "abc"); // null(第一个假值)
知识点:
- 逻辑与的短路特性可用于条件执行
- 返回的是操作数本身,不是强制转换为布尔值
5.2 逻辑或(||)
// 布尔运算
console.log(true || false); // true// 短路特性:如果第一个操作数为真,直接返回,不计算第二个
console.log(true || console.log("不会执行")); // true// 非布尔值运算:返回第一个为真的值,或最后一个为假的值
console.log(0 || "abc"); // "abc"(第一个真值)
console.log("abc" || 123); // "abc"(第一个真值)
console.log(null || undefined); // undefined(最后一个假值)
知识点: 逻辑或常用于设置默认值
5.3 逻辑非(!)
// 对值进行布尔取反
console.log(!true); // false
console.log(!0); // true
console.log(!"abc"); // false// 双重非运算可用于将值转换为布尔类型
console.log(!!0); // false
console.log(!!"abc"); // true
假值列表(以下值在布尔上下文中被视为false):
null
undefined
false
NaN
''
(空字符串)0
(包括+0和-0)
知识点: 所有对象(包括空对象和数组)在布尔上下文中都被视为true。
六、条件(三目)运算符
条件运算符是JavaScript中唯一的三元运算符。
// 基本语法:条件 ? 表达式1 : 表达式2
let age = 20;
let message = age >= 18 ? "成年人" : "未成年人";
console.log(message); // "成年人"// 嵌套使用(可读性较差,不建议过多嵌套)
let score = 85;
let grade = score >= 90 ? "优秀" : score >= 80 ? "良好" : score >= 60 ? "及格" : "不及格";
console.log(grade); // "良好"
知识点:
- 条件运算符是if-else语句的简洁替代
- 过度嵌套会降低代码可读性,应谨慎使用
七、位运算符
位运算符将操作数视为32位二进制整数进行运算,小数部分会被截断。
7.1 位与(&)、位或(|)、位异或(^)
// 位与:两位都为1时结果为1
console.log(5 & 3); // 1 (0101 & 0011 = 0001)// 位或:有一位为1时结果为1
console.log(5 | 3); // 7 (0101 | 0011 = 0111)// 位异或:两位不同时结果为1
console.log(5 ^ 3); // 6 (0101 ^ 0011 = 0110)
知识点: 位运算前会将操作数转换为32位整数,小数部分被丢弃。
7.2 位非(~)和负数表示
// 位非:按位取反
console.log(~5); // -6// 快速取整技巧
console.log(~~3.14); // 3
console.log(~~-2.7); // -2
知识点:
- JavaScript使用二进制补码表示负数
~x
等价于-x - 1
~~
可用于快速取整,效果类似Math.trunc()
7.3 位移运算符
// 左移:<< (相当于乘以2的n次方)
console.log(5 << 2); // 20 (相当于5*4)// 有符号右移:>> (相当于除以2的n次方并取整)
console.log(20 >> 2); // 5 (相当于20/4)// 无符号右移:>>> (符号位也参与位移)
console.log(-20 >>> 2); // 1073741819
知识点:
- 左移n位相当于乘以2ⁿ
- 右移n位相当于除以2ⁿ并向下取整
- 无符号右移会将符号位当作普通位处理
7.4 位运算的实际应用
权限控制系统:
// 定义权限常量(使用2的幂次方)
const READ = 1; // 0001
const WRITE = 2; // 0010
const EXECUTE = 4; // 0100
const DELETE = 8; // 1000// 用户权限(使用位或组合权限)
let userPermissions = READ | WRITE; // 0011 (3)// 检查权限函数
function hasPermission(permissions, permission) {return (permissions & permission) === permission;
}console.log(hasPermission(userPermissions, READ)); // true
console.log(hasPermission(userPermissions, EXECUTE)); // false// 添加权限(位或操作)
userPermissions |= EXECUTE;
console.log(hasPermission(userPermissions, EXECUTE)); // true// 移除权限(位与和位非操作)
userPermissions &= ~WRITE;
console.log(hasPermission(userPermissions, WRITE)); // false
知识点: 位运算在权限控制、标志位处理等场景中非常高效。
八、其他重要运算符
8.1 模板字符串运算符
let name = "张三";
let age = 25;
// 使用反引号和${}插入表达式
console.log(`我叫${name},今年${age}岁`); // 我叫张三,今年25岁
console.log(`明年我就${age + 1}岁了`); // 明年我就26岁了
知识点: 模板字符串支持多行文本和表达式插值。
8.2 复合赋值运算符
let x = 10;
x += 5; // 等同于 x = x + 5
console.log(x); // 15x **= 2; // 等同于 x = x ** 2
console.log(x); // 225// 其他复合赋值运算符
let y = 10;
y -= 3; // y = 7
y *= 2; // y = 14
y /= 7; // y = 2
y %= 2; // y = 0
知识点: 复合赋值运算符是语法糖,使代码更简洁。
8.3 void运算符
// void表达式总是返回undefined
console.log(void 0); // undefined
console.log(void (5 + 3)); // undefined// 常用于立即执行函数避免污染全局空间
void function() {console.log("立即执行函数");
}();// 用于箭头函数返回undefined
const doSomething = () => void console.log("执行了");
知识点: void运算符确保表达式执行但返回undefined。
8.4 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" (历史遗留问题)
console.log(typeof []); // "object"
console.log(typeof {}); // "object"
console.log(typeof function() {}); // "function"// 括号可选
console.log(typeof(42)); // "number"
console.log(typeof 42); // "number"
知识点:
typeof null
返回"object"是JavaScript著名的历史bug- 函数有特殊的类型"function"
- 数组和普通对象都返回"object"
8.5 逗号运算符
// 依次执行表达式,返回最后一个表达式的值
let a = (1, 2, 3, 4);
console.log(a); // 4// 用于for循环中的多个变量
for(let i = 0, j = 10; i < j; i++, j--) {console.log(i, j);
}// 在一行中执行多个操作
let x = 0, y = 0, z = 0;
x = (y++, z++, y + z); // y和z自增,然后x赋值为y+z
console.log(x, y, z); // 2, 1, 1
知识点: 逗号运算符优先级最低,常用于需要在一行中执行多个操作的场景。
九、求余与求模的区别
虽然JavaScript中的%
运算符被称为"求模运算符",但实际上它实现的是求余运算。
求余(rem)与求模(mod)的数学区别:
- 求余:符号与被除数相同
- 求模:符号与除数相同
// 正数情况下两者结果相同
console.log(5 % 3); // 2 (余数)
// 5 rem 3 = 5 - 1*3 = 2 (商向0取整)
// 5 mod 3 = 5 - 1*3 = 2 (商向下取整)// 负数情况下结果不同
console.log(-5 % 3); // -2 (余数,符号与被除数-5相同)
// -5 rem 3 = -5 - (-1)*3 = -2 (商向0取整为-1)
// -5 mod 3 = -5 - (-2)*3 = 1 (商向下取整为-2)// JavaScript没有内置的求模运算,需要自行实现
function mod(n, m) {return ((n % m) + m) % m;
}
console.log(mod(-5, 3)); // 1 (模数,符号与除数3相同)
知识点: 在数学和计算机科学中,求余和求模是不同的概念,JavaScript的%
实现的是求余运算。
十、运算符优先级总结
JavaScript运算符优先级从高到低大致为:
- 成员访问:
.
[]
- new(带参数列表)
- 函数调用:
()
- new(无参数列表)
- 后置递增/递减:
++
--
- 逻辑非:
!
位非:~
typeof void delete await - 幂运算:
**
- 乘除取余:
*
/
%
- 加减:
+
-
- 位移:
<<
>>
>>>
- 关系比较:
<
<=
>
>=
in
instanceof
- 相等比较:
==
!=
===
!==
- 位与:
&
- 位异或:
^
- 位或:
|
- 逻辑与:
&&
- 逻辑或:
||
- 条件运算符:
? :
- 赋值:
=
+=
-=
等 - 逗号运算符:
,
知识点: 使用括号可以明确优先级,提高代码可读性。
十一、综合应用示例
利用运算符实现RGB颜色值的提取:
function getRGBComponents(color) {// 使用位移和位与提取颜色分量let r = (color >> 16) & 0xFF; // 提取红色分量let g = (color >> 8) & 0xFF; // 提取绿色分量let b = color & 0xFF; // 提取蓝色分量return [r, g, b];
}let color = 0xFF3366; // 粉色
let [r, g, b] = getRGBComponents(color);
console.log(`R: ${r}, G: ${g}, B: ${b}`); // R: 255, G: 51, B: 102
安全的属性访问:
// 使用逻辑或设置默认值
function getUserName(user) {return user && user.name || "匿名用户";
}console.log(getUserName({name: "张三"})); // "张三"
console.log(getUserName(null)); // "匿名用户"// 使用可选链运算符(ES2020)和空值合并运算符(ES2020)
function getUserNameSafe(user) {return user?.name ?? "匿名用户";
}
利用位运算判断奇偶性:
function isEven(num) {return (num & 1) === 0; // 最后一位为0则是偶数
}console.log(isEven(4)); // true
console.log(isEven(7)); // false
总结
JavaScript提供了丰富的运算符,从基本的算术运算到复杂的位运算,掌握这些运算符的特性和使用场景对于编写高效、简洁的代码至关重要。关键知识点包括:
- 理解隐式类型转换:特别是在使用
==
和+
运算符时 - 掌握运算符优先级:使用括号明确运算顺序,提高代码可读性
- 利用短路特性:
&&
和||
的短路特性可以简化条件逻辑 - 位运算的高效应用:在权限控制、标志位处理等场景中非常有用
- 推荐使用严格相等:
===
和!==
比==
和!=
更安全可靠 - 模板字符串的现代用法:简化字符串拼接和插值
希望本文能帮助你全面理解JavaScript运算符,并在实际开发中灵活运用它们,写出更优雅、高效的代码。