在 JavaScript 编程中,运算符是进行各种操作的符号,它们允许我们对值(也称为操作数)执行数学计算、比较、逻辑判断、赋值以及更多高级功能。理解 JavaScript 丰富的运算符集合及其行为,是编写高效、精确和可预测代码的基础。

本教程将全面深入地探讨 JavaScript 中的各种运算符,从基本的算术和赋值,到复杂而关键的比较和逻辑运算符。我们将特别关注 JavaScript 的弱类型特性如何影响运算符的行为,特别是 ===== 之间的细微而重要的区别,以及 ||?? 在处理默认值时的不同。通过丰富的代码示例、常见陷阱分析和最佳实践建议,您将能够精准地选择和使用运算符,从而提升代码的质量和可靠性。


1. 运算符概览:JavaScript 操作的基石

运算符本质上是告知 JavaScript 引擎执行特定操作的符号。每个运算符都接受一个或多个值(操作数),并返回一个结果。

JavaScript 中的运算符可以大致分为以下几类:

  1. 算术运算符 (Arithmetic Operators):执行数学计算。
  2. 赋值运算符 (Assignment Operators):为变量赋值。
  3. 比较运算符 (Comparison Operators):比较两个值并返回布尔结果。
  4. 逻辑运算符 (Logical Operators):组合布尔表达式或操作布尔值。
  5. 位运算符 (Bitwise Operators):对数字的二进制位进行操作。
  6. 一元运算符 (Unary Operators):只操作一个操作数。
  7. 三元/条件运算符 (Ternary / Conditional Operator):根据条件选择两个值之一。
  8. 其他运算符 (Miscellaneous Operators):包括 typeofdeleteininstanceof 等。

理解这些运算符的优先级 (Precedence)结合性 (Associativity) 至关重要,它决定了表达式中多个运算符的求值顺序。


2. 算术运算符 (Arithmetic Operators)

这些运算符用于执行标准的数学计算。

  • + (加法 / 字符串拼接):
    • 如果两个操作数都是数字,执行加法。
    • 如果其中一个操作数是字符串,则执行字符串拼接(会将非字符串操作数转换为字符串)。
    • 常见陷阱: + 运算符的这种双重行为是 JavaScript 中常见的混淆点。
      console.log(5 + 3);       // 8 (数字加法)
      console.log("Hello" + " World"); // "Hello World" (字符串拼接)
      console.log("5" + 3);     // "53" (数字 3 被转换为字符串 "3" 进行拼接)
      console.log(5 + "3");     // "53" (数字 5 被转换为字符串 "5" 进行拼接)
      console.log(true + 1);    // 2 (true 被转换为 1 进行加法)
      
  • - (减法):
    • 将操作数转换为数字后执行减法。
      console.log(10 - 5);      // 5
      console.log("10" - 5);    // 5 (字符串 "10" 被转换为数字 10)
      console.log(10 - "5");    // 5 (字符串 "5" 被转换为数字 5)
      console.log("hello" - 5); // NaN (字符串 "hello" 无法转换为有效数字)
      
  • * (乘法):
    • 将操作数转换为数字后执行乘法。
      console.log(4 * 2);       // 8
      console.log("4" * "2");   // 8
      
  • / (除法):
    • 将操作数转换为数字后执行除法。
      console.log(10 / 2);      // 5
      console.log(10 / 0);      // Infinity (除以零)
      console.log(0 / 0);       // NaN (不定式)
      
  • % (取模 / 余数):
    • 返回除法操作的余数。
      console.log(10 % 3);      // 1
      console.log(10 % 2);      // 0
      
  • ** (幂运算 / 指数) (ES6):
    • 返回第一个操作数以第二个操作数为幂的结果。
      console.log(2 ** 3);      // 8 (2 的 3 次方)
      

3. 赋值运算符 (Assignment Operators)

赋值运算符用于为变量分配值。

  • = (简单赋值): 将右侧的值赋给左侧的变量。
    let x = 10;
    
  • += (加法赋值): x += y 等同于 x = x + y
    let a = 5;
    a += 3; // a 现在是 8
    
  • -= (减法赋值): x -= y 等同于 x = x - y
    let b = 10;
    b -= 4; // b 现在是 6
    
  • *= (乘法赋值): x *= y 等同于 x = x * y
  • /= (除法赋值): x /= y 等同于 x = x / y
  • %= (取模赋值): x %= y 等同于 x = x % y
  • **= (幂运算赋值) (ES6): x **= y 等同于 x = x ** y

4. 比较运算符 (Comparison Operators)

比较运算符用于比较两个值,并返回一个布尔值 (truefalse)。

  • == (相等 / 宽松相等):
    • 特点: 会在比较前进行隐式类型转换。如果操作数类型不同,JavaScript 会尝试将它们转换为相同类型再进行比较。
    • 常见陷阱: 这种自动转换可能导致一些非直观的或意外的结果,是 JavaScript 中许多 Bug 的来源。
    console.log(5 == '5');           // true (数字 5 被转换为字符串 '5' 进行比较)
    console.log(0 == false);         // true (0 被转换为 false 进行比较)
    console.log('' == false);        // true (空字符串被转换为 false 进行比较)
    console.log(null == undefined);  // true (特殊规则,它们被认为是等价的)
    console.log('0' == false);       // true ('0' 被转换为 0,然后 0 被转换为 false)
    console.log([1] == true);        // true ([1] 被转换为 "1","1" 被转换为 1,true 被转换为 1)
    
  • === (全等 / 严格相等):
    • 特点: 不进行任何类型转换。只有当值和类型都完全相同时才返回 true
    • 最佳实践: 始终使用 ===!== 来进行比较,以避免隐式类型转换带来的混淆和潜在 Bug,提高代码的可靠性。
    console.log(5 === '5');           // false (类型不同,一个是 number,一个是 string)
    console.log(0 === false);         // false (类型不同)
    console.log(null === undefined);  // false (类型不同)
    console.log([1] === true);        // false (类型不同)
    
  • != (不相等 / 宽松不相等): x != y 等同于 !(x == y)。同样会进行隐式类型转换。
    console.log(5 != '5');           // false
    
  • !== (不全等 / 严格不相等): x !== y 等同于 !(x === y)。不进行类型转换。
    • 最佳实践: 配合 === 使用,强烈推荐。
    console.log(5 !== '5');          // true
    
  • > (大于):
  • < (小于):
  • >= (大于等于):
  • <= (小于等于):
    • 这些运算符在比较不同类型的值时,也会尝试进行类型转换,通常会转换为数字进行比较。
      console.log(10 > 5);      // true
      console.log("10" > 5);    // true ("10" 转换为数字 10)
      console.log("apple" > "banana"); // false (按字母顺序比较)
      

5. 逻辑运算符 (Logical Operators)

逻辑运算符用于组合布尔表达式或操作布尔值。它们通常用于条件语句 (if/else) 和循环 (for/while)。

  • && (逻辑与 / AND):
    • 如果两个操作数都为 true,则返回 true。否则返回 false
    • 短路求值 (Short-Circuit Evaluation): 如果第一个操作数是 Falsy 值(false, 0, "", null, undefined, NaN),则不再评估第二个操作数,并直接返回第一个操作数的值。否则返回第二个操作数的值。
    • 用途: 确保多个条件都满足;提供条件性地执行代码。
      console.log(true && false);   // false
      console.log(5 > 3 && 10 < 20); // true
      console.log(0 && "Hello");    // 0 (0 是 Falsy 值,短路)
      console.log("Hi" && 123);     // 123 ("Hi" 是 Truthy 值,返回第二个)
      
  • || (逻辑或 / OR):
    • 如果两个操作数中至少有一个为 true,则返回 true。否则返回 false
    • 短路求值: 如果第一个操作数是 Truthy 值,则不再评估第二个操作数,并直接返回第一个操作数的值。否则返回第二个操作数的值。
    • 用途: 满足任一条件;为变量提供默认值(但有陷阱)。
      console.log(true || false);   // true
      console.log(5 < 3 || 10 < 20); // true
      console.log(0 || "Default");  // "Default" (0 是 Falsy 值,返回第二个)
      console.log("Hi" || 123);     // "Hi" ("Hi" 是 Truthy 值,短路)
      
      // 传统上用于设置默认值,但当 0 或 '' 为有效值时会出问题
      const userCount = 0;
      const displayCount = userCount || 10; // displayCount 是 10,因为 0 是 Falsy 值
      console.log(displayCount);
      
  • ! (逻辑非 / NOT):
    • 反转布尔值。将 Truthy 值转换为 false,将 Falsy 值转换为 true
    • 双重非 !! 常用于将任何值强制转换为其对应的布尔值。
      console.log(!true);           // false
      console.log(!0);              // true
      console.log(!"Hello");        // false
      console.log(!!0);             // false (将 0 转换为 false)
      console.log(!!"Any string");  // true (将字符串转换为 true)
      
  • ?? (空值合并运算符 / Nullish Coalescing Operator) (ES2020):
    • x ?? y
    • 特点: 如果左侧操作数是 nullundefined,则返回右侧操作数。否则返回左侧操作数。
    • 为什么重要? 解决了 || 在为变量设置默认值时,当 0"" (空字符串) 为有效值时会将其视为 false 的问题。?? 仅在左侧为 nullundefined 时才使用右侧的默认值。
    • 最佳实践: 当你需要为变量提供一个默认值,并且希望 0'' 等 Falsy 值被认为是有效值时,优先使用 ??
      const count = 0;
      const name = '';
      const user = null;
      const score = undefined;
      
      console.log(count ?? 10);     // 0 (count 不是 null 或 undefined)
      console.log(name ?? "Guest"); // "" (name 不是 null 或 undefined)
      console.log(user ?? "Anonymous"); // "Anonymous" (user 是 null)
      console.log(score ?? 50);     // 50 (score 是 undefined)
      

6. 位运算符 (Bitwise Operators)

位运算符直接对数字的二进制位进行操作。在大多数 Web 开发场景中,它们的使用频率较低,主要用于低级别、性能敏感或特定算法的场景(如权限掩码、图形操作等)。

  • & (位与 / AND)
  • | (位或 / OR)
  • ^ (位异或 / XOR)
  • ~ (位非 / NOT)
  • << (左移)
  • >> (右移 / 带符号)
  • >>> (无符号右移)
console.log(5 & 1); // 1 (0101 & 0001 = 0001)
console.log(~5);    // -6 (~0101 = 1010,在 32 位二进制补码中是 -6)

7. 一元运算符 (Unary Operators)

一元运算符只操作一个操作数。

  • ++ (递增):
    • 前缀递增 (++x): 先将变量值加 1,再返回新值。
    • 后缀递增 (x++): 先返回变量的原始值,再将变量值加 1。
      let i = 0;
      console.log(++i); // 1 (i 变为 1,返回 1)
      console.log(i);   // 1
      console.log(i++); // 1 (i 变为 2,返回 1)
      console.log(i);   // 2
      
  • -- (递减):
    • 前缀递减 (--x): 先将变量值减 1,再返回新值。
    • 后缀递减 (x--): 先返回变量的原始值,再将变量值减 1。
  • + (一元正号):
    • 尝试将操作数转换为数字。
      console.log(+"10");   // 10
      console.log(+"-5");   // -5
      console.log(+true);   // 1
      console.log(+"Hello"); // NaN
      
  • - (一元负号):
    • 尝试将操作数转换为数字,然后取其负值。
      console.log(-"10");   // -10
      console.log(-true);   // -1
      console.log(-"Hello"); // NaN
      
  • typeof (类型检查):
    • 返回一个字符串,表示操作数的类型。
      console.log(typeof 123);       // "number"
      console.log(typeof "hello");   // "string"
      console.log(typeof {});        // "object"
      console.log(typeof null);      // "object" (历史遗留)
      
  • delete (删除属性):
    • 删除对象的属性。
      const myObject = { a: 1, b: 2 };
      delete myObject.a;
      console.log(myObject); // { b: 2 }
      
  • void (执行表达式并返回 undefined):
    • 执行一个表达式,但返回值永远是 undefined。常用于 javascript:void(0) 来阻止链接的默认行为。
      console.log(void(1 + 2)); // undefined
      

8. 三元/条件运算符 (Ternary / Conditional Operator)

这是 JavaScript 中唯一一个接受三个操作数的运算符,提供了一种简洁的 if-else 语句替代方案。

  • 语法: condition ? expressionIfTrue : expressionIfFalse
  • 工作原理: 如果 conditiontrue,则返回 expressionIfTrue 的值;否则返回 expressionIfFalse 的值。
let age = 18;
let status = (age >= 18) ? "成年人" : "未成年人";
console.log(status); // "成年人"

9. 其他重要运算符

虽然不完全是“运算符”的范畴,但以下概念在 JavaScript 中也扮演着重要的“操作”角色,值得在此提及:

  • in 运算符:
    • 检查对象的属性是否存在。
    const car = { make: 'Honda', model: 'CRV' };
    console.log('make' in car);    // true
    console.log('year' in car);    // false
    
  • instanceof 运算符:
    • 检查对象是否是某个类的实例。
    const arr = [1, 2, 3];
    console.log(arr instanceof Array);  // true
    console.log(arr instanceof Object); // true (数组也是对象)
    
  • 展开运算符 (...) (Spread Operator) 和 剩余参数 (...) (Rest Parameters) (ES6):
    • 展开运算符: 用于展开可迭代对象(如数组)或将对象字面量中的属性展开。
      const arr1 = [1, 2];
      const arr2 = [3, 4];
      const combinedArr = [...arr1, ...arr2]; // [1, 2, 3, 4]
      const obj1 = { a: 1, b: 2 };
      const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
      
    • 剩余参数: 用于将函数传入的多个参数收集到一个数组中。
      function sumAll(...numbers) {
        return numbers.reduce((acc, num) => acc + num, 0);
      }
      console.log(sumAll(1, 2, 3, 4)); // 10
      
  • 可选链 (?.) (Optional Chaining) (ES2020):
    • 安全地访问嵌套对象的属性。如果链中的任何一个引用是 nullundefined,表达式会短路并返回 undefined,而不会抛出错误。
      const user = {
        name: 'Alice',
        address: {
          street: '123 Main St'
        }
      };
      console.log(user.address?.street);         // "123 Main St"
      console.log(user.contact?.email);          // undefined (user.contact 是 undefined)
      // console.log(user.contact.email); // TypeError: Cannot read properties of undefined
      

10. 运算符优先级和结合性

当一个表达式中包含多个运算符时,它们的执行顺序由优先级决定。优先级高的运算符先执行。如果优先级相同,则由结合性决定(从左到右或从右到左)。

  • 例如,乘法 (*) 和除法 (/) 的优先级高于加法 (+) 和减法 (-)。
    console.log(2 + 3 * 4); // 14 (3 * 4 先计算)
    
  • 可以使用括号 () 来明确指定运算顺序,括号内的表达式总是优先执行。
    console.log((2 + 3) * 4); // 20
    

建议: 如果不确定运算符的优先级,或者为了提高代码的可读性,始终使用括号来明确指定运算顺序


11. 最佳实践与注意事项

  1. 严格相等 (===): 优先使用 ===!== 进行比较,避免 ==!= 带来的隐式类型转换陷阱。
  2. + 的两面性: 在使用 + 运算符时,要明确你是想进行数字加法还是字符串拼接。如果涉及混合类型,JavaScript 会将所有操作数转换为字符串进行拼接。如有需要,使用 Number()parseInt() 进行显式转换。
  3. ??|| 的选择: 当为变量设置默认值时,如果 0"" 是有效值,请使用空值合并运算符 ??。如果所有 Falsy 值(包括 0, "", false)都应被视为无效并取默认值,则使用 ||
  4. 清晰的逻辑: 对于复杂的逻辑判断,使用括号来提高可读性,即使优先级规则已经很明确。
  5. 谨慎使用位运算符: 位运算符在大多数 Web 应用中不常用,如果不是处理特定低级别任务,应避免过度使用,以免降低代码可读性。
  6. 善用展开和可选链: 展开运算符 (...) 和可选链 (?.) 是现代 JavaScript 的强大工具,能够简化数组、对象操作和属性安全访问。
  7. 理解副作用: 递增/递减运算符 (++, --)、赋值运算符 (+= 等) 以及某些函数调用可能存在副作用,即它们不仅返回一个值,还会改变变量的状态。在复杂表达式中要特别注意这些。

12. 总结与展望

JavaScript 运算符是语言的骨架,支撑着所有的计算和逻辑流程。从基本的数学运算到高级的逻辑控制和数据解构,熟练掌握这些运算符是编写高效、健壮代码的关键。

通过本教程,您应该已经:

  • 了解了各种类型的 JavaScript 运算符及其基本功能。
  • 掌握了 ===== 的核心区别,并认识到在比较中使用 === 的重要性。
  • 理解了 + 运算符在数字加法和字符串拼接上的双重行为。
  • 区分了 ||?? 在处理默认值时的不同行为,并学会了如何根据场景选择合适的运算符。
  • 了解了运算符优先级和结合性如何影响表达式的求值顺序。
  • 认识到了一元运算符、三元运算符以及现代 JavaScript 中其他重要运算符 (..., ?.) 的用途。

持续的实践和对细节的关注,将使您能够更精确地控制程序的行为,并有效避免 JavaScript 动态特性可能带来的陷阱。随着您对这些基础知识的深入理解,您将能更加自信地构建复杂的应用程序。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐