表达式是产生值的代码片段。任何值本身都是表达式。
// 基本表达式
42 // 数字字面量
"hello" // 字符串字面量
true // 布尔字面量
null // null字面量
undefined // undefined
{x: 1, y: 2} // 对象字面量
[1, 2, 3] // 数组字面量
// 标识符表达式
x // 变量引用
Math.PI // 属性访问
console.log // 函数引用
// 复杂表达式
x + y // 算术表达式
x > y // 比较表达式
x && y // 逻辑表达式
x = 5 // 赋值表达式
x++ // 递增表达式
typeof x // typeof表达式
// 优先级从高到低(完整列表)
// 20: 成员访问 . [],带参数列表的调用 (),new(带参数)
// 19: new(无参数)
// 18: 后置递增/递减 x++ x--
// 17: 逻辑非 !,按位非 ~,一元加 +,一元减 -,
// 前置递增/递减 ++x --x,typeof,void,delete,await
// 16: 幂 **
// 15: 乘法 *,除法 /,取余 %
// 14: 加法 +,减法 -
// 13: 按位左移 <<,按位右移 >>,无符号右移 >>>
// 12: 关系 < <= > >= in instanceof
// 11: 相等 == != === !==
// 10: 按位与 &
// 9: 按位异或 ^
// 8: 按位或 |
// 7: 逻辑与 &&
// 6: 逻辑或 ||
// 5: 空值合并 ??
// 4: 条件(三元) ?:
// 3: 赋值 = += -= *= /= %= **= <<= >>= >>>= &= ^= |= &&= ||= ??=
// 2: 逗号 ,
// 示例:优先级影响结果
let x = 1 + 2 * 3; // 7,不是9(乘法优先)
let y = (1 + 2) * 3; // 9
// 结合性
// 左结合:大部分运算符(从左到右计算)
let a = 1,
b = 2,
c = 3;
let result = a + b + c; // (a + b) + c = 6
// 右结合:赋值、幂、三元、一元运算符
let d = (e = f = 5); // d = (e = (f = 5))
let g = 2 ** (3 ** 2); // 2 ** (3 ** 2) = 512,不是64
// 加法 +
console.log(1 + 2); // 3
console.log("a" + "b"); // 'ab'(字符串连接)
console.log(1 + "2"); // '12'(类型转换)
console.log("1" + 2); // '12'
// 减法 -
console.log(5 - 3); // 2
console.log("5" - 3); // 2(类型转换)
console.log("a" - 1); // NaN
// 乘法 *
console.log(3 * 4); // 12
console.log("3" * 4); // 12
console.log(Infinity * 0); // NaN
console.log(Infinity * 2); // Infinity
// 除法 /
console.log(10 / 2); // 5
console.log(10 / 0); // Infinity
console.log(0 / 0); // NaN
console.log(-10 / 0); // -Infinity
// 取余 %
console.log(10 % 3); // 1
console.log(10 % -3); // 1(符号与被除数相同)
console.log(-10 % 3); // -1
console.log(10.5 % 3); // 1.5
// 幂 **(ES2016+)
console.log(2 ** 3); // 8
console.log(4 ** 0.5); // 2
console.log(2 ** -2); // 0.25
console.log((-2) ** 3); // -8(注意括号)
let x = 5;
// 后置递增
let y = x++; // y = 5, x = 6
console.log(y, x); // 5 6
// 前置递增
let z = ++x; // x = 7, z = 7
console.log(z, x); // 7 7
// 后置递减
let a = x--; // a = 7, x = 6
console.log(a, x); // 7 6
// 前置递减
let b = --x; // x = 5, b = 5
console.log(b, x); // 5 5
// 浮点数运算
let f = 5.7;
console.log(++f); // 6.7
// 字符串运算(不推荐)
let s = "5";
console.log(++s); // 6(转换为数字)
console.log(s + 1); // '61'(字符串连接)
// 布尔值运算
let bool = true;
console.log(++bool); // 2(true转换为1)
// 一元加 +(转换为数字)
console.log(+'42'); // 42
console.log(+true); // 1
console.log(+false); // 0
console.log(+null); // 0
console.log(+undefined); // NaN
console.log(+'hello'); // NaN
// 一元减 -(取反)
console.log(-42); // -42
console.log(-'42'); // -42
console.log(-true); // -1
// 注意优先级
let x = 3;
let y = -x ** 2; // -9(相当于 -(x**2))
let z = (-x) ** 2; // 9
// 小于 <,大于 >,小于等于 <=,大于等于 >=
console.log(5 > 3); // true
console.log("5" > 3); // true(类型转换)
console.log("10" > "2"); // false(字符串比较,'1' < '2')
console.log("b" > "a"); // true(字母顺序)
console.log("B" > "a"); // false(大写字母码点小)
// 字符串比较(Unicode码点)
console.log("🍎" > "🍌"); // true(🍎码点:127822,🍌码点:127820)
console.log("2" > "12"); // true('2'.charCodeAt(0) > '1'.charCodeAt(0))
// 与NaN比较(总是false)
console.log(NaN < 5); // false
console.log(NaN > 5); // false
console.log(NaN <= NaN); // false
// 松散相等 ==(允许类型转换)
console.log(5 == "5"); // true(字符串转数字)
console.log(true == 1); // true(布尔转数字)
console.log(false == 0); // true
console.log(null == undefined); // true(特殊情况)
console.log(null == 0); // false(null只与undefined相等)
console.log(NaN == NaN); // false(NaN与任何值都不等)
// 严格相等 ===(不允许类型转换)
console.log(5 === "5"); // false
console.log(true === 1); // false
console.log(null === undefined); // false
console.log(NaN === NaN); // false
// 不相等 != 和 !==
console.log(5 != "5"); // false(转换后相等)
console.log(5 !== "5"); // true(类型不同)
// 特殊情况
console.log([] == ![]); // true(![] → false → 0,[] → '' → 0)
console.log({} == !{}); // false(!{} → false → 0,{} → '[object Object]' → NaN)
// 逻辑与 &&
console.log(true && true); // true
console.log(true && false); // false
console.log(5 && 3); // 3(返回最后一个真值)
console.log(0 && 3); // 0(短路:返回第一个假值)
// 逻辑或 ||
console.log(true || false); // true
console.log(false || false); // false
console.log(5 || 3); // 5(短路:返回第一个真值)
console.log(0 || 3); // 3
// 逻辑非 !
console.log(!true); // false
console.log(!false); // true
console.log(!0); // true(假值转true)
console.log(!5); // false(真值转false)
console.log(!!"hello"); // true(双重否定转为布尔值)
// 默认值设置
function greet(name) {
name = name || "访客"; // 如果name为假值,使用'访客'
console.log(`你好, ${name}`);
}
greet("张三"); // 你好, 张三
greet(""); // 你好, 访客
greet(0); // 你好, 访客
// ES2020+ 更好的方式:空值合并运算符 ??
function greet2(name) {
name = name ?? "访客"; // 只在null或undefined时使用默认值
console.log(`你好, ${name}`);
}
greet2(""); // 你好, ''
greet2(0); // 你好, 0
greet2(null); // 你好, 访客
// 条件执行
const config = { debug: true };
config.debug && console.log("调试模式开启");
// 属性访问保护
const user = null;
const name = user && user.name; // 不会报错,返回null
console.log(name); // null
// ES2020+ 可选链
const name2 = user?.name; // 更简洁,返回undefined
let x = 0,
y = 5;
// 逻辑或赋值 ||=
x ||= 10; // x = x || 10 → x = 0 || 10 → x = 10
y ||= 10; // y = 5 || 10 → y = 5
console.log(x, y); // 10 5
// 逻辑与赋值 &&=
let a = 5,
b = 0;
a &&= 10; // a = 5 && 10 → a = 10
b &&= 10; // b = 0 && 10 → b = 0
console.log(a, b); // 10 0
// 空值合并赋值 ??=
let c = null,
d = 0;
c ??= 10; // c = null ?? 10 → c = 10
d ??= 10; // d = 0 ?? 10 → d = 0
console.log(c, d); // 10 0
// 按位与 &
console.log(5 & 3); // 1(0101 & 0011 = 0001)
console.log(-5 & -3); // -7(负数以补码表示)
// 按位或 |
console.log(5 | 3); // 7(0101 | 0011 = 0111)
console.log(5 | 0); // 5
// 按位异或 ^
console.log(5 ^ 3); // 6(0101 ^ 0011 = 0110)
console.log(5 ^ 5); // 0(相同为0)
// 按位非 ~
console.log(~5); // -6(~0000000000000101 = 1111111111111010)
console.log(~0); // -1
// 左移 <<
console.log(5 << 1); // 10(0101 << 1 = 1010)
console.log(-5 << 1); // -10
// 右移 >>
console.log(5 >> 1); // 2(0101 >> 1 = 0010)
console.log(-5 >> 1); // -3(保持符号)
// 无符号右移 >>>
console.log(5 >>> 1); // 2
console.log(-5 >>> 1); // 2147483645(不保持符号)
// 1. 快速乘除2的幂次
let num = 16;
console.log(num << 2); // 64(16 * 4)
console.log(num >> 2); // 4(16 / 4)
// 2. 检查奇偶性
function isEven(n) {
return (n & 1) === 0;
}
console.log(isEven(4)); // true
console.log(isEven(5)); // false
// 3. 交换两个数(不使用临时变量)
let a = 5,
b = 10;
a = a ^ b; // a = 15 (0101 ^ 1010 = 1111)
b = a ^ b; // b = 5 (1111 ^ 1010 = 0101)
a = a ^ b; // a = 10 (1111 ^ 0101 = 1010)
console.log(a, b); // 10 5
// 4. 权限系统
const PERMISSIONS = {
READ: 0b001, // 1
WRITE: 0b010, // 2
DELETE: 0b100, // 4
};
let userPermissions = 0;
// 添加权限
userPermissions |= PERMISSIONS.READ;
userPermissions |= PERMISSIONS.WRITE;
// 检查权限
function hasPermission(permissions, permission) {
return (permissions & permission) === permission;
}
console.log(hasPermission(userPermissions, PERMISSIONS.READ)); // true
console.log(hasPermission(userPermissions, PERMISSIONS.DELETE)); // false
// 移除权限
userPermissions &= ~PERMISSIONS.WRITE;
console.log(hasPermission(userPermissions, PERMISSIONS.WRITE)); // false
// 简单赋值 =
let x = 10;
let y = x; // y = 10
// 链式赋值
let a = (b = c = 5); // 右结合:a = (b = (c = 5))
// 复合赋值运算符
let num = 10;
num += 5; // num = num + 5 → 15
num -= 3; // num = num - 3 → 12
num *= 2; // num = num * 2 → 24
num /= 4; // num = num / 4 → 6
num %= 4; // num = num % 4 → 2
num **= 3; // num = num ** 3 → 8
// 位运算复合赋值
let bits = 5;
bits &= 3; // bits = bits & 3 → 1
bits |= 8; // bits = bits | 8 → 9
bits ^= 4; // bits = bits ^ 4 → 13
bits <<= 1; // bits = bits << 1 → 26
bits >>= 2; // bits = bits >> 2 → 6
bits >>>= 1; // bits = bits >>> 1 → 3
// 数组解构
let [first, second, third] = [1, 2, 3];
console.log(first, second, third); // 1 2 3
// 对象解构
let { name, age } = { name: "张三", age: 25 };
console.log(name, age); // 张三 25
// 默认值
let [a = 1, b = 2] = [5];
console.log(a, b); // 5 2
let { x = 0, y = 0 } = { x: 10 };
console.log(x, y); // 10 0
// 剩余元素
let [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]
let { name: userName, ...rest } = { name: "李四", age: 30, city: "北京" };
console.log(userName, rest); // 李四 {age: 30, city: '北京'}
// 嵌套解构
let [p, [q, r]] = [1, [2, 3]];
console.log(p, q, r); // 1 2 3
let {
address: { city, street },
} = {
name: "王五",
address: { city: "上海", street: "南京路" },
};
console.log(city, street); // 上海 南京路
// 基本语法:condition ? expr1 : expr2
let age = 18;
let status = age >= 18 ? "成年" : "未成年";
console.log(status); // 成年
// 嵌套三元运算符
let score = 85;
let grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : score >= 60 ? "D" : "F";
console.log(grade); // B
// 作为表达式
let x = 5,
y = 10;
let max = x > y ? x : y;
console.log(max); // 10
// 函数返回
function getFee(isMember) {
return isMember ? "$2.00" : "$10.00";
}
console.log(getFee(true)); // $2.00
// 注意:避免过度嵌套影响可读性
// 返回最后一个表达式的值
let x = (1, 2, 3, 4, 5);
console.log(x); // 5
// for循环中的典型用法
for (let i = 0, j = 10; i < j; i++, j--) {
console.log(i, j);
}
// 同时执行多个操作
let a = 1,
b = 2,
c = 3;
let result = (a++, b++, c++);
console.log(result, a, b, c); // 3 2 3 4
// 简化代码(但可能降低可读性)
function process() {
return console.log("处理中"), fetch("/api/data"), console.log("完成");
}
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 Symbol()); // "symbol"
console.log(typeof 123n); // "bigint"
// 检测数组的更好方式
console.log(Array.isArray([])); // true
console.log([] instanceof Array); // true
console.log(Object.prototype.toString.call([])); // "[object Array]"
// 检查对象是否是某个类的实例
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(new Date() instanceof Date); // true
console.log(/regex/ instanceof RegExp); // true
// 继承关系
class Animal {}
class Dog extends Animal {}
const dog = new Dog();
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
// 边界情况
console.log("hello" instanceof String); // false(字面量不是实例)
console.log(new String("hello") instanceof String); // true
console.log(null instanceof Object); // false
console.log(undefined instanceof Object); // false
// 检查对象是否有某个属性
const car = { make: "Toyota", year: 2020 };
console.log("make" in car); // true
console.log("model" in car); // false
// 检查数组索引
const colors = ["red", "green", "blue"];
console.log(0 in colors); // true
console.log(3 in colors); // false
// 检查原型链上的属性
console.log("toString" in {}); // true(来自Object.prototype)
// 与 hasOwnProperty 的区别
console.log("toString" in {}); // true
console.log({}.hasOwnProperty("toString")); // false
// 检查 Map/Set 的键(ES2015+)
const map = new Map([["a", 1]]);
console.log("a" in map); // false(in不适用于Map)
console.log(map.has("a")); // true(使用has方法)
// 删除对象属性
const obj = { a: 1, b: 2, c: 3 };
console.log(delete obj.a); // true
console.log("a" in obj); // false
// 不能删除继承的属性
console.log(delete obj.toString); // true(但toString仍然存在)
console.log("toString" in obj); // true
// 删除数组元素(产生稀疏数组)
const arr = [1, 2, 3, 4];
console.log(delete arr[1]); // true
console.log(arr); // [1, empty, 3, 4]
console.log(arr.length); // 4(长度不变)
// 不能删除变量声明
let x = 5;
console.log(delete x); // false(严格模式下报错)
// 可以删除全局对象的属性
globalVar = 10; // 隐式全局变量
console.log(delete globalVar); // true
**指数运算符 (ES2016)// 基本使用
console.log(2 ** 3); // 8
console.log(10 ** -1); // 0.1
console.log(2 ** 0.5); // 1.4142135623730951
// 右结合性
console.log(2 ** (3 ** 2)); // 2 ** (3 ** 2) = 512
console.log((2 ** 3) ** 2); // 64
// 与 Math.pow 等价
console.log(2 ** 10 === Math.pow(2, 10)); // true
// 复合赋值
let x = 3;
x **= 2; // x = x ** 2
console.log(x); // 9
const user = {
name: "张三",
address: {
city: "北京",
street: null,
},
};
// 安全访问嵌套属性
console.log(user?.name); // 张三
console.log(user?.age); // undefined(不会报错)
console.log(user?.address?.city); // 北京
console.log(user?.address?.zipCode); // undefined
// 数组元素访问
const arr = null;
console.log(arr?.[0]); // undefined
console.log(arr?.[0]?.name); // undefined
// 函数调用
const obj = {
method() {
return "hello";
},
};
console.log(obj.method?.()); // hello
console.log(obj.nonexistent?.()); // undefined
const func = null;
console.log(func?.()); // undefined
// 与空值合并运算符结合
const city = user?.address?.city ?? "未知";
console.log(city); // 北京
const zip = user?.address?.zipCode ?? "000000";
console.log(zip); // 000000
// 只在左侧为 null 或 undefined 时返回右侧
console.log(null ?? "默认值"); // 默认值
console.log(undefined ?? "默认值"); // 默认值
console.log(0 ?? "默认值"); // 0
console.log("" ?? "默认值"); // ''
console.log(false ?? "默认值"); // false
console.log(NaN ?? "默认值"); // NaN
// 与逻辑或的区别
console.log(0 || "默认值"); // 默认值(假值都触发)
console.log(0 ?? "默认值"); // 0
console.log("" || "默认值"); // 默认值
console.log("" ?? "默认值"); // ''
// 链式使用
const config = {
timeout: 0,
retry: null,
};
const timeout = config.timeout ?? 3000; // 0
const retry = config.retry ?? 3; // 3
const host = config.host ?? "localhost"; // localhost
// 注意优先级
let x = null;
let y = 5;
let result = x ?? y; // 5
// 不能与 && 或 || 直接混用(需要括号)
// let z = a ?? b || c; // 语法错误
let z = (a ?? b) || c; // 正确
// BigInt 字面量
const big = 1234567890123456789012345678901234567890n;
// 算术运算
console.log(100n + 50n); // 150n
console.log(100n - 50n); // 50n
console.log(100n * 2n); // 200n
console.log(100n / 3n); // 33n(整除)
console.log(100n % 3n); // 1n
console.log(2n ** 10n); // 1024n
// 比较运算
console.log(100n == 100); // true(松散相等)
console.log(100n === 100); // false(严格不相等)
console.log(100n > 50); // true
console.log(100n < 150); // true
// 不能混合运算
// console.log(100n + 50); // TypeError
console.log(100n + BigInt(50)); // 150n
// 位运算(除无符号右移)
console.log(5n & 3n); // 1n
console.log(5n | 3n); // 7n
console.log(5n ^ 3n); // 6n
console.log(~5n); // -6n
console.log(5n << 1n); // 10n
console.log(5n >> 1n); // 2n
// console.log(5n >>> 1n); // TypeError(无符号右移不支持)
// 快速转换为数字
let str = "123";
let num = +str; // 123
let num2 = str * 1; // 123
let num3 = str - 0; // 123
// 快速转换为布尔值
let value = "hello";
let bool = !!value; // true
// 默认值设置
let name = userInput || "默认名称"; // 所有假值都触发
let name2 = userInput ?? "默认名称"; // 只对null/undefined触发
// 交换变量值
let a = 1,
b = 2;
[a, b] = [b, a]; // 使用解构
console.log(a, b); // 2 1
// 使用位运算替代部分数学运算
// 判断奇偶性
function isEven(n) {
return (n & 1) === 0;
}
// 取整
let float = 3.14;
console.log(float | 0); // 3(32位整数)
console.log(~~float); // 3(双按位非)
// 乘以2的幂
console.log(5 << 2); // 20(5 * 4)
// 除以2的幂(向下取整)
console.log(20 >> 2); // 5(20 / 4)
// 缓存计算结果
function expensiveOperation() {
if (!expensiveOperation.cache) {
expensiveOperation.cache = compute();
}
return expensiveOperation.cache;
}
// 理解求值顺序
let x = 0;
let result = x++ + ++x * x-- - --x;
// 分解:
// 1. x++: 使用0,x变为1
// 2. ++x: x变为2,使用2
// 3. x--: 使用2,x变为1
// 4. --x: x变为0,使用0
// 计算:0 + 2 * 2 - 0 = 4
console.log(result, x); // 4 0
// 短路求值的顺序
function a() {
console.log("a");
return false;
}
function b() {
console.log("b");
return true;
}
function c() {
console.log("c");
return true;
}
console.log((a() && b()) || c());
// 输出:a → c → true
// 解释:a()返回false,b()短路不执行,c()执行
// 逗号运算符的顺序
let y = (console.log("第一"), console.log("第二"), "结果");
// 输出:第一 第二
console.log(y); // 结果
// 1. 浮点数精度
console.log(0.1 + 0.2 === 0.3); // false
console.log(0.1 + 0.2); // 0.30000000000000004
// 解决方案
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // true
// 2. NaN 比较
console.log(NaN === NaN); // false
console.log(Number.isNaN(NaN)); // true(正确检查方式)
// 3. 类型转换陷阱
console.log([] == ![]); // true
console.log([] == 0); // true
console.log([] == ""); // true
// 4. 递增/递减副作用
let arr = [1, 2, 3];
let i = 0;
console.log(arr[i++], i); // 1 1(后置递增先使用后增加)
console.log(arr[++i], i); // 3 2(前置递增先增加后使用)