用户工具

站点工具


javascript:第二章运算符与表达式

第二章 运算符与表达式

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、?.、…

关键要点:

  • 始终使用 === 和 !== 进行比较
  • 理解短路求值的机制
  • 掌握位运算的实际应用场景
  • 了解运算符优先级,必要时使用括号

下一章将学习控制流语句,实现程序的条件执行和循环处理。

javascript/第二章运算符与表达式.txt · 最后更改: 127.0.0.1