逻辑运算符
逻辑运算符
逻辑运算符用于组合和操作布尔值。本文将介绍JavaScript中的与、或、非逻辑运算符,以及它们的短路求值特性和常见用法。
基本逻辑运算符
JavaScript提供了三种基本的逻辑运算符:
运算符 | 符号 | 描述 |
---|---|---|
逻辑与 | && | 当所有操作数都为真时返回真 |
逻辑或 | || | 当至少有一个操作数为真时返回真 |
逻辑非 | ! | 反转操作数的布尔值 |
此外,ES2020引入了一个新的逻辑运算符:
运算符 | 符号 | 描述 |
---|---|---|
空值合并 | ?? | 当左侧操作数为null或undefined时返回右侧操作数 |
逻辑与运算符 (&&)
逻辑与运算符(&&
)只有当所有操作数都为真时才返回真:
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false
console.log(false && false); // false
真值和假值
在JavaScript中,所有值都可以被转换为布尔值。以下值被视为假值(falsy):
false
0
-0
0n
(BigInt零)""
(空字符串)null
undefined
NaN
除了上述假值外,所有其他值都被视为真值(truthy),包括所有对象和数组(即使是空的)。
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(1)); // true
console.log(Boolean("hello")); // true
console.log(Boolean([])); // true
console.log(Boolean({})); // true
console.log(Boolean(function(){})); // true
短路求值
逻辑与运算符使用短路求值:如果第一个操作数为假,则不会评估第二个操作数,因为结果已经确定为假。
console.log(false && someUndefinedVariable); // false,不会评估第二个操作数
console.log(true && "返回这个值"); // "返回这个值"
返回值
逻辑与运算符不一定返回布尔值。它返回:
- 如果第一个操作数为假,则返回第一个操作数
- 如果第一个操作数为真,则返回第二个操作数
console.log(0 && "hello"); // 0
console.log("" && "world"); // ""
console.log("hello" && "world"); // "world"
console.log("hello" && ""); // ""
console.log("hello" && 0); // 0
逻辑或运算符 (||)
逻辑或运算符(||
)当至少有一个操作数为真时返回真:
console.log(true || true); // true
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false
短路求值
逻辑或运算符也使用短路求值:如果第一个操作数为真,则不会评估第二个操作数,因为结果已经确定为真。
console.log(true || someUndefinedVariable); // true,不会评估第二个操作数
console.log(false || "返回这个值"); // "返回这个值"
返回值
逻辑或运算符不一定返回布尔值。它返回:
- 如果第一个操作数为真,则返回第一个操作数
- 如果第一个操作数为假,则返回第二个操作数
console.log("hello" || "world"); // "hello"
console.log(0 || "hello"); // "hello"
console.log("" || "world"); // "world"
console.log(null || "default"); // "default"
console.log(undefined || 0); // 0
逻辑非运算符 (!)
逻辑非运算符(!
)反转操作数的布尔值:
console.log(!true); // false
console.log(!false); // true
console.log(!"hello"); // false("hello"是真值)
console.log(!""); // true(""是假值)
console.log(!0); // true(0是假值)
console.log(!1); // false(1是真值)
双重否定
双重否定(!!
)可以用来将任何值转换为其对应的布尔值:
console.log(!!true); // true
console.log(!!false); // false
console.log(!!"hello"); // true
console.log(!!""); // false
console.log(!!0); // false
console.log(!!1); // true
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!{}); // true
console.log(!![]); // true
这相当于使用Boolean()
函数:
console.log(Boolean("hello") === !!"hello"); // true
空值合并运算符 (??)
空值合并运算符(??
)是ES2020引入的新运算符,它只有当左侧操作数为null
或undefined
时才返回右侧操作数,否则返回左侧操作数:
console.log(null ?? "default"); // "default"
console.log(undefined ?? "default"); // "default"
console.log(0 ?? "default"); // 0(0不是null或undefined)
console.log("" ?? "default"); // ""(空字符串不是null或undefined)
console.log(false ?? "default"); // false(false不是null或undefined)
console.log(NaN ?? "default"); // NaN(NaN不是null或undefined)
与逻辑或的区别
空值合并运算符(??
)与逻辑或运算符(||
)的主要区别在于,??
只将null
和undefined
视为"缺失"值,而||
将所有假值都视为"缺失":
// 使用||时,所有假值都会被替换
console.log(0 || "default"); // "default"(0是假值)
console.log("" || "default"); // "default"(空字符串是假值)
// 使用??时,只有null和undefined会被替换
console.log(0 ?? "default"); // 0
console.log("" ?? "default"); // ""
安全使用
为了避免优先级问题,当与&&
或||
一起使用时,必须使用括号明确指定优先级:
// 错误:不允许直接与&&或||组合使用
// console.log(null || undefined ?? "default"); // SyntaxError
// 正确:使用括号明确优先级
console.log((null || undefined) ?? "default"); // "default"
逻辑运算符的常见用法
1. 默认值设置
使用逻辑或运算符(||
)为变量设置默认值:
function greet(name) {
name = name || "访客";
return `你好,${name}!`;
}
console.log(greet("张三")); // "你好,张三!"
console.log(greet("")); // "你好,访客!"(空字符串被视为假值)
console.log(greet()); // "你好,访客!"(undefined被视为假值)
使用空值合并运算符(??
)为变量设置默认值,只有当变量为null
或undefined
时才使用默认值:
function greet(name) {
name = name ?? "访客";
return `你好,${name}!`;
}
console.log(greet("张三")); // "你好,张三!"
console.log(greet("")); // "你好,!"(保留空字符串)
console.log(greet(0)); // "你好,0!"(保留0)
console.log(greet()); // "你好,访客!"(undefined被替换)
2. 条件执行
使用逻辑与运算符(&&
)进行条件执行,只有当第一个条件为真时才执行第二个表达式:
function processUser(user) {
// 只有当user存在且有name属性时才打印名字
user && user.name && console.log(`用户名: ${user.name}`);
// 等价于以下if语句
// if (user && user.name) {
// console.log(`用户名: ${user.name}`);
// }
}
processUser({ name: "张三" }); // 输出: "用户名: 张三"
processUser({}); // 不输出任何内容
processUser(null); // 不输出任何内容
3. 条件赋值
使用逻辑运算符进行条件赋值:
// 只有当user.isAdmin为真时,才将canEdit设为true
const canEdit = user.isAdmin && true;
// 如果user.role存在,则使用它,否则使用"guest"
const role = user.role || "guest";
// 如果user.preferences存在,则使用它,否则使用默认对象
const preferences = user.preferences || { theme: "light", fontSize: "medium" };
// 只有当user.preferences为null或undefined时,才使用默认对象
const safePreferences = user.preferences ?? { theme: "light", fontSize: "medium" };
4. 简化条件语句
使用逻辑运算符简化条件语句:
// 传统if语句
if (isLoggedIn) {
redirectToHome();
}
// 使用&&简化
isLoggedIn && redirectToHome();
// 传统if-else语句
let message;
if (isAdmin) {
message = "欢迎,管理员";
} else {
message = "欢迎,用户";
}
// 使用||简化
const message = isAdmin ? "欢迎,管理员" : "欢迎,用户";
// 或者
const message = isAdmin && "欢迎,管理员" || "欢迎,用户";
5. 安全访问嵌套属性
使用逻辑与运算符(&&
)安全地访问嵌套对象属性,避免出现"Cannot read property of undefined"错误:
const user = {
name: "张三",
address: {
city: "北京"
}
};
// 不安全的访问方式
// console.log(user.contact.email); // 错误:Cannot read property 'email' of undefined
// 使用&&安全访问
const email = user.contact && user.contact.email;
console.log(email); // undefined,不会报错
// 使用可选链操作符(ES2020)更简洁
const email2 = user.contact?.email;
console.log(email2); // undefined,不会报错
6. 函数参数验证
使用逻辑运算符验证函数参数:
function createUser(name, email, options) {
// 验证必要参数
if (!name || !email) {
throw new Error("名字和邮箱是必填项");
}
// 使用默认选项
const userOptions = options || { role: "user", active: true };
// 或者使用空值合并运算符
const safeOptions = options ?? { role: "user", active: true };
// 创建用户...
}
逻辑运算符的优先级
逻辑运算符的优先级从高到低为:
- 逻辑非(
!
) - 逻辑与(
&&
) - 逻辑或(
||
) - 空值合并(
??
)
console.log(!true && false); // false(先计算!true得到false,然后计算false && false)
console.log(true || false && true); // true(先计算false && true得到false,然后计算true || false)
为了避免混淆,建议使用括号明确表达优先级:
console.log(!(true && false)); // true
console.log((true || false) && true); // true
逻辑运算符与其他运算符组合
与比较运算符组合
逻辑运算符经常与比较运算符一起使用:
// 检查年龄是否在有效范围内
const isValidAge = age >= 18 && age <= 65;
// 检查用户是否有权限
const hasAccess = isAdmin || isPremiumUser || (isRegularUser && hasPurchased);
// 检查输入是否无效
const isInvalidInput = !input || input.length < 3;
与赋值运算符组合
ES2021引入了逻辑赋值运算符,将逻辑运算符与赋值运算符组合:
// 逻辑与赋值
let x = 1;
x &&= 2; // 等价于:x = x && 2
console.log(x); // 2
let y = 0;
y &&= 2; // 等价于:y = y && 2
console.log(y); // 0(因为y初始值为0,是假值)
// 逻辑或赋值
let a = 0;
a ||= 5; // 等价于:a = a || 5
console.log(a); // 5(因为a初始值为0,是假值)
let b = 10;
b ||= 5; // 等价于:b = b || 5
console.log(b); // 10(因为b初始值为10,是真值)
// 空值合并赋值
let c = null;
c ??= 5; // 等价于:c = c ?? 5
console.log(c); // 5(因为c初始值为null)
let d = 0;
d ??= 5; // 等价于:d = d ?? 5
console.log(d); // 0(因为d初始值为0,不是null或undefined)
实际应用示例
1. 表单验证
function validateForm(formData) {
// 检查必填字段
if (!formData.username) {
return { valid: false, error: "用户名是必填项" };
}
if (!formData.email) {
return { valid: false, error: "邮箱是必填项" };
}
// 验证邮箱格式
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(formData.email)) {
return { valid: false, error: "邮箱格式不正确" };
}
// 验证密码长度
if (formData.password && formData.password.length < 8) {
return { valid: false, error: "密码长度不能少于8个字符" };
}
// 验证密码确认
if (formData.password && formData.password !== formData.confirmPassword) {
return { valid: false, error: "两次输入的密码不一致" };
}
return { valid: true };
}
const formData = {
username: "user123",
email: "user@example.com",
password: "securepass",
confirmPassword: "securepass"
};
console.log(validateForm(formData)); // { valid: true }
2. 权限控制
function checkAccess(user, resource) {
// 管理员有所有权限
if (user.role === "admin") {
return true;
}
// 检查资源所有者
const isOwner = resource.ownerId === user.id;
// 检查资源共享设置
const isSharedWithUser = resource.sharedWith &&
resource.sharedWith.includes(user.id);
// 检查资源是否公开
const isPublic = resource.visibility === "public";
// 组合逻辑条件
return isOwner || isSharedWithUser || isPublic;
}
const user = { id: 123, role: "user" };
const resource = {
id: 456,
ownerId: 789,
sharedWith: [123, 456],
visibility: "private"
};
console.log(checkAccess(user, resource)); // true(资源已共享给用户)
3. 配置合并
function mergeConfig(userConfig, defaultConfig) {
// 使用空值合并运算符处理顶层配置
const theme = userConfig.theme ?? defaultConfig.theme;
const language = userConfig.language ?? defaultConfig.language;
// 处理嵌套配置
const display = {
fontSize: userConfig.display?.fontSize ?? defaultConfig.display.fontSize,
colorScheme: userConfig.display?.colorScheme ?? defaultConfig.display.colorScheme,
showSidebar: userConfig.display?.showSidebar ?? defaultConfig.display.showSidebar
};
// 合并数组(保留用户配置中的项,然后添加默认配置中不重复的项)
const plugins = [
...(userConfig.plugins || []),
...(defaultConfig.plugins.filter(plugin =>
!(userConfig.plugins || []).includes(plugin)
))
];
return {
theme,
language,
display,
plugins
};
}
const defaultConfig = {
theme: "light",
language: "en",
display: {
fontSize: "medium",
colorScheme: "default",
showSidebar: true
},
plugins: ["basic", "advanced", "export"]
};
const userConfig = {
theme: "dark",
display: {
fontSize: "large"
},
plugins: ["basic", "custom"]
};
console.log(mergeConfig(userConfig, defaultConfig));
// 输出:
// {
// theme: "dark",
// language: "en",
// display: {
// fontSize: "large",
// colorScheme: "default",
// showSidebar: true
// },
// plugins: ["basic", "custom", "advanced", "export"]
// }
常见错误和最佳实践
常见错误
混淆短路求值的返回值:
// 错误 const isAdmin = user.role && "admin"; // 如果user.role为真,返回"admin",而不是布尔值 // 正确 const isAdmin = user.role === "admin"; // 返回布尔值
忽略空字符串和0是假值:
// 可能导致意外行为 function processCount(count) { count = count || 10; // 如果count为0,会使用默认值10 // ... } // 更安全的方式 function processCount(count) { count = count ?? 10; // 只有当count为null或undefined时才使用默认值 // 或者 count = count !== undefined ? count : 10; }
过度使用逻辑运算符代替条件语句:
// 难以理解的代码 const result = condition1 && value1 || condition2 && value2 || defaultValue; // 更清晰的代码 let result; if (condition1) { result = value1; } else if (condition2) { result = value2; } else { result = defaultValue; } // 或者使用条件运算符 const result = condition1 ? value1 : (condition2 ? value2 : defaultValue);
最佳实践
使用空值合并运算符处理默认值:
// 推荐 const config = userConfig ?? defaultConfig;
使用可选链操作符代替多个&&:
// 不推荐 const city = user && user.address && user.address.city; // 推荐 const city = user?.address?.city;
使用显式布尔转换:
// 不推荐 if (value) { // value可能是任何真值 } // 推荐(更明确) if (Boolean(value)) { // 明确转换为布尔值 } // 或者使用双重否定 if (!!value) { // 也是明确转换为布尔值 }
使用括号明确优先级:
// 不清晰 const result = a || b && c || d; // 更清晰 const result = a || (b && c) || d;
避免在条件语句中进行赋值:
// 容易混淆 if (user = getUser()) { // ... } // 更清晰 const user = getUser(); if (user) { // ... }
总结
JavaScript的逻辑运算符不仅用于布尔逻辑操作,还具有短路求值和返回实际值(而不仅仅是布尔值)的特性,使它们成为编写简洁代码的强大工具:
逻辑与(&&):当所有操作数都为真时返回真,否则返回假。它返回第一个假值,或者最后一个操作数。
逻辑或(||):当至少有一个操作数为真时返回真,否则返回假。它返回第一个真值,或者最后一个操作数。
逻辑非(!):反转操作数的布尔值。
空值合并(??):当左侧操作数为null或undefined时返回右侧操作数,否则返回左侧操作数。
这些运算符的短路求值特性和返回值规则使它们在默认值设置、条件执行、安全属性访问等场景中非常有用。通过合理使用这些运算符,可以编写更加简洁、高效的JavaScript代码。
然而,过度使用逻辑运算符的简写形式可能会降低代码的可读性。在复杂的条件逻辑中,使用传统的if语句或条件运算符可能更加清晰。选择最适合特定场景的方式,平衡简洁性和可读性,是编写高质量JavaScript代码的关键。
练习
编写一个函数,接受任意数量的参数,返回第一个真值,如果没有真值则返回null。
实现一个深度合并函数,可以合并两个对象,保留第一个对象中的值,除非它们是null或undefined。
创建一个函数,可以安全地访问嵌套对象的属性,如果路径中的任何部分不存在,则返回默认值。
编写一个简单的权限检查函数,根据用户角色和资源类型确定用户是否有权限执行特定操作。
实现一个函数,可以验证一个对象是否满足特定的模式(例如,检查对象是否具有所有必需的属性)。