一个符号让 indexOf 判断更优雅!JavaScript 位运算的隐藏技巧

前言

在日常开发中,你是否经常写这样的代码?

js 复制代码
const index = source.indexOf(target);
if (index !== -1) {
  // 找到了,执行逻辑
} else {
  // 没找到,执行逻辑
}

今天我要分享一个只需要一个符号就能让这段代码变得更优雅的技巧!不仅如此,我们还会深入探讨其背后的计算机原理,让你真正理解位运算的魅力。

问题场景

假设我们需要判断一个元素是否存在于数组中,这是前端开发中最常见的场景之一。

传统写法

js 复制代码
const arr = ['apple', 'banana', 'orange'];
const index = arr.indexOf('apple');

if (index !== -1) {
  console.log('找到了 apple');
} else {
  console.log('没找到 apple');
}

这种写法虽然清晰,但每次都要写 !== -1 是不是有点繁琐?

优雅的解决方案

使用位运算的取反操作(~),我们可以将代码简化为:

js 复制代码
const arr = ['apple', 'banana', 'orange'];

if (~arr.indexOf('apple')) {
  console.log('找到了 apple');
} else {
  console.log('没找到 apple');
}

仅仅一个 ~ 符号就完成了判断!

深入理解位运算 ~

JavaScript 数字的存储方式

首先,我们需要了解 JavaScript 中数字的存储方式:

JavaScript 采用"IEEE 754 标准定义的双精度64位格式"表示数字。

虽然 JavaScript 使用 64 位浮点数存储数字,但在进行位运算时,会转换为 32 位有符号整数进行计算。

位运算 ~ 的工作原理

位运算 ~ 的作用是按位取反,包括符号位。让我们通过一个例子来理解:

问题:~0 等于多少?

让我们逐步分析:

  1. 转换为 32 位二进制

    js 复制代码
    0 => 0000 0000 0000 0000 0000 0000 0000 0000
  2. 按位取反

    js 复制代码
    ~(0000 0000 0000 0000 0000 0000 0000 0000) 
    > 1111 1111 1111 1111 1111 1111 1111 1111
  3. 解析结果 : 由于最高位(第31位)为 1,表示这是一个负数。要得到这个负数的值,我们需要进行补码的逆运算

补码的逆运算

补码的逆运算过程:

  1. 减 1:

    js 复制代码
      1111 1111 1111 1111 1111 1111 1111 1111
    -                                       1
    = 1111 1111 1111 1111 1111 1111 1111 1110
  2. 按位取反:

    js 复制代码
    ~(1111 1111 1111 1111 1111 1111 1111 1110)
    > 0000 0000 0000 0000 0000 0000 0000 0001 (=1)
  3. 添加负号:-1

因此,~0 = -1

通用规律

通过大量测试,我们可以总结出一个规律: 一个数的取反操作结果等于 -(当前数 + 1)

例如:

  • ~0 = -1
  • ~1 = -2
  • ~(-1) = 0
  • ~(-2) = 1

补码的计算机原理

为了更好地理解位运算,我们需要了解计算机中负数的表示方法------补码。

什么是补码?

补码是一种数值转换方法,分为两步:

  1. 按位取反:将每一位都取反(0 变 1,1 变 0);
  2. 加 1:将取反后的结果加 1。

示例:求 -8 的补码表示

js 复制代码
原数 0000 1000 (8)
取反 1111 0111
加一 1111 0111 + 1 = 1111 1000
结果 1111 1000 就是 -8 的补码表示

为什么使用补码?

计算机使用补码表示负数有以下几个优势:

  1. 简化运算:减法可以转换为加法运算
  2. 统一表示:正数、负数和 0 都有唯一的表示
  3. 扩展范围:8 位补码可以表示 [-128, 127],比原码和反码多表示一个数

补码的本质

补码的本质是:负数 = 0 - 正数

以 -8 为例:

js 复制代码
   0 0 0 0 0 0 0 0  (0)
-  0 0 0 0 1 0 0 0  (8)
=  1 1 1 1 1 0 0 0  (-8)

由于被减数小于减数,需要借位,实际上相当于:

js 复制代码
   1 0 0 0 0 0 0 0 0  (借位后的被减数)
-    0 0 0 0 1 0 0 0  (减数)
=  1 1 1 1 1 0 0 0 0  (结果)

进一步分解:

js 复制代码
> 1 0 0 0 0 0 0 0 0 = 1 1 1 1 1 1 1 1 + 0 0 0 0 0 0 0 1

So:
   1 1 1 1 1 1 1 1
-  0 0 0 0 1 0 0 0
=  1 1 1 1 0 1 1 1
+  0 0 0 0 0 0 0 1
=  1 1 1 1 1 0 0 0

这就是补码转换步骤的由来。

回到业务场景

现在我们可以完美解释为什么 ~source.indexOf(target) 能够优雅地判断元素是否存在:

分析过程

  1. indexOf 返回 -1(元素不存在):

    • ~(-1) = 0
    • 0 在布尔上下文中为 false
  2. indexOf 返回其他值(元素存在):

    • ~0 = -1~1 = -2~2 = -3
    • 非零值在布尔上下文中为 true

代码示例

js 复制代码
const arr = ['apple', 'banana', 'orange'];

// 传统写法
if (arr.indexOf('apple') !== -1) {
  console.log('找到了 apple');
}

// 优雅写法
if (~arr.indexOf('apple')) {
  console.log('找到了 apple');
}

// 更多示例
console.log(~arr.indexOf('grape'));  // 0 (false)
console.log(~arr.indexOf('apple'));  // -1 (true)
console.log(~arr.indexOf('banana')); // -2 (true)

性能考虑

虽然 ~ 操作看起来更简洁,但在实际项目中需要考虑:

  1. 可读性:对于不熟悉位运算的开发者,可能影响代码可读性
  2. 性能:位运算通常比普通比较运算更快
  3. 兼容性:现代浏览器都支持位运算

其他位运算技巧

除了 ~ 操作,JavaScript 中还有其他有用的位运算技巧:

js 复制代码
// 快速判断奇偶
const isEven = n => !(n & 1);

// 快速取整
const floor = n => n | 0;

// 快速判断是否为2的幂
const isPowerOf2 = n => n > 0 && !(n & (n - 1));

// 快速交换两个数(不使用临时变量)
let a = 5, b = 3;
a ^= b; b ^= a; a ^= b;
console.log(a, b); // 3, 5

总结

通过深入理解补码原理和位运算机制,我们不仅学会了如何优雅地使用 ~ 操作符简化 indexOf 的判断逻辑,更重要的是理解了计算机底层的数据表示方式。

这种从实际问题出发,深入原理的学习方式,能够帮助我们更好地掌握编程技能,写出更优雅、更高效的代码。

记住这个公式~n = -(n + 1)

记住这个技巧~indexOf() 替代 !== -1


参考资料

相关推荐
刺客-Andy19 分钟前
React 第七十节 Router中matchRoutes的使用详解及注意事项
前端·javascript·react.js
前端工作日常34 分钟前
我对eslint的进一步学习
前端·eslint
禁止摆烂_才浅1 小时前
VsCode 概览尺、装订线、代码块高亮设置
前端·visual studio code
程序员猫哥2 小时前
vue跳转页面的几种方法(推荐)
前端
代码老y2 小时前
十年回望:Vue 与 React 的设计哲学、演进轨迹与生态博弈
前端·vue.js·react.js
一条上岸小咸鱼2 小时前
Kotlin 基本数据类型(五):Array
android·前端·kotlin
zzywxc7872 小时前
详细探讨AI在金融、医疗、教育和制造业四大领域的具体落地案例,并通过代码、流程图、Prompt示例和图表等方式展示这些应用的实际效果。
开发语言·javascript·人工智能·深度学习·金融·prompt·流程图
大明882 小时前
用 mouseover/mouseout 事件代理模拟 mouseenter/mouseleave
前端·javascript
小杨梅君2 小时前
vue3+vite中使用自定义element-plus主题配置
前端·element
一个专注api接口开发的小白2 小时前
Python + 淘宝 API 开发:自动化采集商品数据的完整流程
前端·数据挖掘·api