免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

jstips 第 15 期:用按位非操作符 `~` 把 indexOf 写成更简洁的 contains 判断

jstips 第 15 期:用按位非操作符 `~` 把 indexOf 写成更简洁的 contains 判断 教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载本文是 jstips 仓库gh_mirrors/js/jstips第 15 期技巧的中文版详解主题为更简单的使用 indexOf 实现 contains 功能。JavaScript 原生没有contains方法本文将从最直观的indexOf ! -1写法出发拆解按位非操作符~的原理还原 Express 源码中的真实用法并延伸到 ES6/ES7 的String.prototype.includes()与Array.prototype.includes()让你在字符串与数组的包含性判断上写出更简洁、更地道的代码。传统写法用 indexOf 判断是否包含JavaScript 并没有为字符串或数组提供开箱即用的contains方法。要检测一个子字符串是否存在于某个字符串中或一个变量是否存在于某个数组中最常见的做法是借助indexOf的返回值var someText javascript rules; if (someText.indexOf(javascript) ! -1) { // 包含 javascript } // or if (someText.indexOf(javascript) 0) { // 包含 javascript }原理很简单indexOf在找到目标时返回首次出现的位置索引0或更大的非负整数找不到时返回-1。因此! -1与 0两种写法语义等价都能区分包含与不包含。这两种写法完全正确也很常见——但它们把是否包含这一布尔语义翻译成了索引值与 -1 的关系阅读时需要多一层心智转换。在 ES5 时代有没有更简洁的等价写法答案是有的而且它来自真实的工业级代码。Express 源码中的真实用法~操作符原文档引用了 Express当时托管于 strongloop 组织下的三处源码片段展示了~按位非操作符在真实项目中的用法。片段一examples/mvc/lib/boot.js中遍历对象属性时跳过保留字段for (var key in obj) { // reserved exports if (~[name, prefix, engine, before].indexOf(key)) continue; }这里[name, prefix, engine, before].indexOf(key)若找到返回索引0起找不到返回-1~会把结果翻转为找到 → 非零真值找不到 → 0 假值于是if (~...) continue;直接把key 是保留字段当成条件使用。片段二lib/utils.js中的normalizeType用~type.indexOf(/)判断 MIME 类型字符串是否包含斜杠exports.normalizeType function(type){ return ~type.indexOf(/) ? acceptParams(type) : { value: mime.lookup(type), params: {} }; };type形如text/html时包含/~indexOf为真走acceptParams(type)分支否则走纯 MIME lookup 分支。片段三examples/web-service/index.js中校验 API Key 是否合法// key is invalid if (!~apiKeys.indexOf(key)) return next(error(401, invalid api key));apiKeys.indexOf(key)找不到时返回-1~(-1)得到0假值!0为true于是进入 401 分支。这一行把key 不在合法列表中表达得极为紧凑。原理拆解~为什么能当 contains 用关键在按位非操作符~。MDN 对其的经典描述是位操作符操作数字的二进制形式但返回值依然是标准的 JavaScript 数值。具体到~它会先把操作数转换为 32 位有符号整数然后对所有位取反其数学本质是~x -(x 1)于是对indexOf的两类返回值找不到时返回-1~(-1) -(-1 1) 0而0在 JavaScript 中是 falsy假值找到时返回0、1、2……~0 -1、~1 -2、~3 -4全部是非零数值在布尔上下文中均为 truthy真值。也就是说~indexOf(...)的结果天然地编码了是否包含这一布尔语义包含 → 真值不包含 → 0假值。原文档中的示例代码完整地演示了这一点var someText text; !!~someText.indexOf(tex); // someText contains tex - true !~someText.indexOf(tex); // someText NOT contains tex - false ~someText.indexOf(asd); // someText doesnt contain asd - false ~someText.indexOf(ext); // someText contains ext - true逐行核对text的索引分别是tex→0asd→-1ext→1~0 -1!!-1→true包含tex~0 -1!-1→false即包含的否定为假注意!~表达的是对包含取反因此此处结果false恰说明确实包含~(-1) 0不包含asd条件为假~1 -2包含ext条件为真。可以看出~写法把indexOf的数值结果直接当作条件使用省略了! -1的比较同时!!~与!~还能显式地把结果归一化为布尔值或对结果取反——后者与仓库中第 30 期技巧 将 truthy/falsy 转换为布尔值 中!!的用法一脉相承。实战建议~写法的使用边界~indexOf写法足够简洁但需要留意它的适用条件与代价仅适用于以 -1 表示未找到的 APIString.prototype.indexOf与Array.prototype.indexOf均满足找不到返回 -1因此可以直接套用对语义不同的方法例如返回布尔值的includes则完全没必要再用~。位运算有类型转换开销~会把操作数强制转换为 32 位有符号整数对超出 32 位范围的索引值理论上存在截断风险实际业务中的数组/字符串索引几乎不会触及该上限。在性能敏感的循环里原生indexOf本身仍是 O(n) 线性扫描~并不会改变算法复杂度。可读性权衡~[a,b].indexOf(x)是老一辈工程师钟爱的一行判断但团队协作时若成员不熟悉位运算x ! -1反而更直白。建议在代码库中统一风格或辅以注释说明。此外~技巧不止能判断是否包含还能用于白名单过滤、参数归一化等场景。仓库第 37 期技巧 数组去重 中同样用到了Array.prototype.indexOf配合filter实现arr.indexOf(el) i可作为索引语义的延伸阅读。ES6 时代的正解String.prototype.includes()如果你不需要兼容老旧浏览器ES6 引入了专门的原生方法includes()语义上就是是否包含something.includes(thing); // true相比~技巧includes直接返回布尔值消除了位运算带来的心智负担也与contains的直觉语义完全一致。String.prototype.includes还支持可选的第二参数position起始搜索位置比裸indexOf的从头搜索更灵活。ES7 的数组版Array.prototype.includes()ECMAScript 2016即 ES7进一步把includes扩展到了数组。原文档将两种写法并列对比!!~[1, 2, 3].indexOf(1); // true [1, 2, 3].includes(1); // true两者结果一致但includes的意图一目了然。值得注意的是数组版includes与indexOf在相等性判定上存在细微差异indexOf基于严格相等比较而includes还能识别NaN。例如[NaN].indexOf(NaN)返回-1而[NaN].includes(NaN)返回true——在需要判断数组中是否存在NaN时includes是更可靠的选项。兼容性提醒与选型总结原文档给出了明确的兼容性警告这里完整保留并补充说明不幸的是只有 Chrome、Firefox、Safari 9 及其更高版本和 Edge 支持了includes功能IE11 及其更低版本并不支持。最好在受控的环境中使用此功能。也就是说String.prototype.includes与Array.prototype.includes都属于较新的 ES 标准 API面向 IE11 及以下浏览器时必须借助 polyfill 或降级方案在受控环境如现代浏览器内部工具、Node.js 服务端、Electron 等确定运行时版本的项目中应优先使用includes因为它语义最清晰在不可控的浏览器环境中~indexOf技巧不依赖任何新 API仅用 ES3 时代的位运算即可实现同样的判断是兼容性最稳的写法若仍使用indexOf也建议用! -1这类显式比较方便他人阅读。小结从indexOf ! -1到~indexOf再到String/Array.prototype.includes这条演进路径恰好浓缩了 JavaScript 判断是否包含的三代写法写法适用时代返回值可读性兼容性indexOf(...) ! -1ES3 起布尔中等极好~indexOf(...)/!!~indexOf(...)ES3 起真值/布尔较低需懂位运算极好includes(...)ES6字符串/ ES7数组布尔最高IE11 及以下不支持本文的完整原始内容对应仓库中的英文原版 _posts/en/javascript/2016-01-15-even-simpler-way-of-using-indexof-as-a-contains-clause.md、中文简体版 _posts/zh_CN/javascript/2016-01-15-even-simpler-way-of-using-indexof-as-a-contains-clause.md、中文繁体版 _posts/zh_TW/javascript/2016-01-15-even-simpler-way-of-using-indexof-as-a-contains-clause.md 及西班牙语版 _posts/es_ES/javascript/2016-01-15-even-simpler-way-of-using-indexof-as-a-contains-clause.md更多同类技巧可查看仓库 README.md 中的 Tips 列表。实战中请结合团队约定与目标运行时环境在简洁与可读性之间做出取舍。赞分享教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载相关推荐jstips 技巧 15用 ~ 位运算把 indexOf 变成优雅的 contains 判断jstips 技巧 15用 ~ 位运算把 indexOf 变成优雅的 contains 判断 JavaScript 原生没有提供 contains 方法日常教程Arthas sm 命令完全指南Search Method 搜索已加载类的方法信息Arthas sm 命令完全指南Search Method 搜索已加载类的方法信息 sm Search Method 的缩写是 Arthas 内置的类方法教程jstips 精讲第 30 期用 !! 操作符将 truthy/falsy 值转换为布尔值jstips 精讲第 30 期用 !! 操作符将 truthy/falsy 值转换为布尔值 导读 在 JavaScript 开发中 if value 这教程上一篇Trigger.dev 数据库包完全指南Prisma Schema、迁移工作流与大表索引最佳实践下一篇Ragas 评估结果可视化两行代码将评测分数接入 Tonic Validate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表