一个符号让 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


参考资料

相关推荐
Setsuna_F_Seiei12 分钟前
前端的 AI 学习之路 01 之 Agent API 调用 - 和 Agent 的基础对话
前端·人工智能·ai编程
threerocks1 小时前
AI 原生软件开发生命周期手册 - 如何借助 AI,逐阶段改造软件开发生命周期
前端·javascript·后端
徐小夕1 小时前
3分钟从想法到Agent上线:我们开源了一款AI可视化工作流“IDE”
前端·算法·github
ly76892 小时前
JavaScript 从入门到进阶:核心语法、异步编程与工程化实践
开发语言·javascript·ecmascript
why技术3 小时前
eli5,我觉得这个全网在吹的技能,使用体验真的很一般啊。
前端·人工智能·后端
excel3 小时前
记录升级 nuxt3 到 nuxt4 记录
前端
剑胆琴心静水深流3 小时前
全栈之路6---web集成与呈现
前端·vue.js·spring boot·分布式·spring·前端框架·npm
前端snow4 小时前
ai agent -- Memory汇总
前端
ITresearchGuest4 小时前
AI 焦虑下,前端该何去何从
前端·人工智能
沙盘客4 小时前
AFSIM 示例解读(03)· 六自由度飞行仿真 six_dof(下):机场运作与弹道导弹投送
java·javascript·经验分享