ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

20个JavaScript单行代码技巧:从数组去重到浏览器API防坑指南

20个JavaScript单行代码技巧:从数组去重到浏览器API防坑指南 如果让我只推荐一份前端收藏夹我不会给你几百行的工具库而是这20个JavaScript单行代码小技巧。它们解决的是我日常开发里最常遇到的数组去重、类型判断、URL参数解析、复制到剪贴板、运行时错误排查这类琐碎需求。每一个都短到可以放在一条朋友圈里讲完但背后涉及的原型链、事件循环、浏览器 API 兼容性都值得你反复琢磨。这篇文章里我不光给代码更会把每个技巧的适用场景、边界条件和踩过的坑说清楚。初中级前端可以系统过一遍老手也能拿来查漏补缺。1. 单行代码不等于炫技我先说说筛选标准1.1 值得收藏的单行代码长什么样很多新手喜欢收集那种一长串操作符压出来的代码几十个字符里塞了三个?:和两个。可到了实际项目里没人愿意 review 一行两百个字符的表达式。我整理单行技巧的标准很朴素日常高频、运行结果可预期、可读性没有大幅下降。比如[...new Set(arr)]虽然涉及展开运算符和 Set 的去重逻辑但读起来依然是“把数组塞进 Set 再展开”一眼就能看懂在干嘛。真正的好技巧应该像一把趁手的小螺丝刀解决的都是几秒钟就能说完的需求但写起来能省掉一整个循环和一堆临时变量。收藏的时候我还会顺手记一句话注释说明它为什么不那么写这样下次复制出来时不会误用。1.2 不推荐硬凑单行的场景业务分支复杂、异常处理环节多、需要大量注释才能说清意图的地方我也不建议强改单行。防抖、节流这类函数参数多、闭包状态也多硬塞进一行反而让后续调试变成灾难。我见过有人在团队里推广“全部代码必须写成一行”结果代码评审变成猜谜大会后来默默改了回去。下面要讲的20个技巧我都按“能直接抄、抄完不后悔”的标准筛选过。遇到适合单行的地方就用不适合就老老实实写完整函数这才是正常的工程心态。单行代码的使命是减少重复表达不是压缩信息量。2. 数组与对象操作6个高频一行流2.1 数组去重、假值过滤与重复检查第一个技巧是const unique arr [...new Set(arr)]。Set 在 JavaScript 里就是专门用来收纳唯一值的容器传入数组后会自动丢掉重复项再用展开运算符把它变回数组。这个写法对数字、字符串数组很稳定但要注意如果数组里放的是对象那么去重判断是基于引用而不是内容两个结构完全相同的{a: 1}也不会被当成重复项。第二个是arr.filter(Boolean)作用是去掉数组里的所有假值。所谓假值就是false、0、空字符串、null、undefined、NaN。你在写列表接口时可以这样const validList rawList.filter(Boolean)就再也不用手写一行行判断了。需要保留 0 的场景千万别用它比如价格字段为 0 时也会被误删。第三个是检查重复const hasDuplicate arr new Set(arr).size ! arr.length。原理很简单去重后的长度如果和原来不同说明一定存在重复项。数据校验、表单标签去重场景里很实用。之前有同事问我为什么去重后的长度一样但还是报了重复数据最后发现数组里有对象和基本类型混在一起这种判断只适合用 Set 能正确比较的场景。2.2 数组扁平化、极值与洗牌第四是const flatArr arr.flat(Infinity)。flat方法可以指定展开的层级Infinity表示不管嵌套多少层都一次拍平。这个技巧在接口返回多级结构、菜单数据、树形列表转平铺时尤其好用。要注意flat不会修改原数组而且默认只展一层想完整展开就得带上Infinity。第五和第六可以放一起const max Math.max(...arr)、const min Math.min(...arr)。展开运算符把数组元素变成一个个参数传给Math.max几行手写循环就省下来了。但在超大数组上要小心展开太多参数同样会触发调用栈上限数量级超过几万时建议改用arr.reduce((a, b) Math.max(a, b))。再说洗牌arr.sort(() Math.random() - 0.5)是流传很广的写法原理是让排序比较器返回随机正负值。它写起来很爽但排序结果并不是均匀随机分布会有明显偏向。如果只是做人名随机展示这种不敏感场景可以临时用真正做抽奖、问卷乱序务必用 Fisher-Yates。2.3 对象字段筛选一行完成 pick第七个技巧是对象字段筛选const pick (obj, keys) Object.fromEntries(Object.entries(obj).filter(([k]) keys.includes(k)))。Object.entries把对象转成[key, value]数组filter留下目标字段再用Object.fromEntries还原成对象。比如后端返回一个很大的用户对象你只想取id、name、avatar三个字段就可以一行搞定。keys如果很长建议先包一层 Set把includes的时间复杂度降下来数据量大时差别很明显。这个写法的好处是不会污染原对象返回的是新对象适合前端做数据裁剪后传给组件。3. 类型判断、数字与随机数格式化3.1 用 toString 准确判断数据类型第八个是我最常用的调试工具const type v Object.prototype.toString.call(v).slice(8, -1).toLowerCase()。很多新手用typeof判断类型结果遇到typeof null得到object、遇到数组也得到object直接懵了。Object.prototype.toString在大多数内置类型上会返回[object Array]、[object Null]、[object Date]这样的字符串切一刀再转成小写基本就把typeof漏掉的类型补全了。这个问题在排查运行时错误时特别有用。比如接口返回的数据可能是个字符串也可能是个对象直接data.map(...)会报data.map is not a function。先用这行代码把真实类型打出来比瞎猜快得多。它也有局限遇到自定义 class 实例时返回的还是[object Object]这时候可以再配合instanceof使用。3.2 随机整数、货币与小数位格式化第九个是随机整数const randomInt (min, max) Math.floor(Math.random() * (max - min 1)) min。这个公式的关键是max - min 1不然生成不了max。我见过有人写Math.round(Math.random() * max)结果边界概率不均随机测试工具里一眼就能看出来。第十个保留两位小数我推荐Math.round(num * 100) / 100而不是直接num.toFixed(2)。原因是toFixed返回的是字符串后面再参与乘法运算会得到拼接结果。哪怕你有这个预期多数项目里还是容易踩。如果你就是想要字符串展示toFixed也行但要记得用parseFloat或者Number转回来。很多“JavaScript 运行时错误”都跟这种隐式类型转换有关排查半天发现不是逻辑错了是类型变了。第十一是货币格式化num.toLocaleString(zh-CN, { style: currency, currency: CNY })。它可以输出¥1,234.56这样的结果省去自己拼千分位和符号。不同浏览器的 locale 支持略有差异核心业务金额展示还是建议用封装好的库但给内部工具做一个金额预览这行代码完全够用。3.3 随机颜色生成也能一行第十二个是随机颜色# Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, 0)。很多人写Math.random().toString(16).slice(2, 8)颜色值经常不够 6 位生成#ab12c这种非法颜色。用Math.floor配合0xffffff先确定整数范围再转 16 进制最后用padStart补足位数生成的字符串才稳定。拿它给动态图表、Canvas 粒子、云彩素材做临时填充色非常方便。注意随机颜色在色差感知上可能很刺眼正式产品一般还是用预设调色板但原型阶段或者调试工具里这一行能省不少事。4. 浏览器交互复制、滚动、URL 与设备判断4.1 复制到剪贴板与平滑回顶第十三个是复制文本await navigator.clipboard.writeText(text)。这个 API 返回 Promise必须要在 HTTPS 或 localhost 环境里使用非安全上下文里navigator.clipboard是undefined所以要加?.或者try/catch。旧接口document.execCommand(copy)虽然废弃但兼容性老老旧项目还在用。实际项目中我更常这样写if (navigator.clipboard) await navigator.clipboard.writeText(text)。第十四个是平滑回顶window.scrollTo({ top: 0, behavior: smooth })。用于阅读类页面点击“回到顶部”按钮比window.scrollTo(0, 0)生硬跳转体验好很多。需要注意它默认滚动当前文档的绝对位置如果你想在某个容器内部滚动换成container.scrollTo({ top: 0, behavior: smooth })就好。高频触发时最好在事件回调里加个节流不然动画排队会卡。4.2 URL 参数解析与移动端判断第十五个是解析 URL 参数new URLSearchParams(location.search).get(id)。URLSearchParams是浏览器内置的查询字符串解析器会自动处理部分编码.get取不到时返回null。如果想要一个参数字典可以直接Object.fromEntries(new URLSearchParams(location.search))一下获得所有键值对。要小心同名参数例如?tagatagb这种get只返回第一个需要全部就遍历.getAll(tag)。第十六个是移动端判断const isCoarsePointer matchMedia((pointer: coarse)).matches。这比正则匹配 UA 字符串靠谱一点因为是从设备交互能力出发判断。我用它做不同交互布局例如鼠标悬停菜单在触屏上没法用就通过这个状态切换成点击展开。要注意很多触屏笔记本同时支持触控板和触摸屏返回结果可能是 true 也可能是 false具体取决于当前设备状态所以设计交互时不要依赖单一信号。5. 异步与空值兜底更稳的单行写法5.1 一个实用的 sleep 函数第十七个是const sleep ms new Promise(resolve setTimeout(resolve, ms))。有了它就可以这样写await sleep(200)。在前端模拟接口延迟、轮询重试、分步动画里都很实用。很多新手把setTimeout直接写在for循环里以为会间隔执行结果所有回调同时触发这就是典型的异步运行时错误。正确做法是在循环体内await sleep(1000)或者用递归调用。要理解这行代码关键是想清楚setTimeout和Promise的关系。setTimeout只负责把回调放进宏任务队列而Promise的resolve会让await后面的代码继续执行。把resolve传给setTimeout就相当于告诉事件循环等时间到了这个 Promise 才结束。5.2 空值合并与可选链组合第十八个是空值合并const value data ?? 默认值。它跟||最大的区别在于只处理null和undefined不会把0、false、空字符串这些合法值替换掉。比如组件参数count ?? 0如果上层传了count 0??老实保留 0而||会把 0 改成后面的默认值这在计数场景里就是 bug。第十九个是可选链const city user?.address?.city。当user或address为null、undefined时表达式会直接短路返回undefined而不是抛TypeError。项目里最烦的就是后端缺字段导致页面白屏看完报错才发现是“读不到 undefined 的某一层属性”。用了可选链加空值合并user?.address?.city ?? 未知一行代码把取属性、校验、给默认值全部做完了。6. 字符串处理与布尔转换6.1 一行反转字符串注意 Unicode第二十个是字符串反转[...str].reverse().join()。这里不用str.split()而用展开运算符是因为split()会把 emoji 等按 UTF-16 码元切开。比如.split()会得到两个半代理项反转后就变成了乱码。但是展开运算符对组合字符也不是完全保险比如 emoji 里带肤色修饰、或者多个字符组合成的一个可见符号反转后仍可能乱序。处理普通英文、数字、中文反转完全没问题处理复杂 emoji 就要引入Intl.Segmenter或专门的字符串库了。这个技巧在写回文判断、文字动画、验证码生成时经常用到。需要注意的是它不会修改原字符串因为reverse作用在展开后生成的新数组上。6.2!!的适用边界顺带说一个更简单的转换const bool !!value或const bool Boolean(value)。很多同学一开始不太理解!!为什么能转布尔因为第一个!把值转成布尔并取反第二个!再取反回来结果就是value对应的布尔值。它适合用在接口返回的空字符串、null、数字 0 需要统一收敛成 false 的环境。但有几种情况要考虑比如字符串0是true对象即使{}也是true别把业务判断逻辑搞混。我在状态开关组件里经常用它因为你不能保证外部传进来的是正经布尔值可能是字符串false、可能是0、可能是null先转成统一布尔再决定渲染逻辑能少写一堆if。7. 我实测下来最容易翻车的几个点7.1sort(() Math.random() - 0.5)不是真随机第六个技巧虽然写起来方便但被很多人当成了“标准洗牌”。V8 的排序算法在不同数组长度下会走不同的排序策略比较器返回随机值时会带来分布不均。我做过一个小实验让 5 万个长度 10 的数组用这种方式洗牌统计第一个位置出现频次明显和均匀分布有差距。需要公平随机或抽奖环节还是老老实实用 Fisher-Yates虽然多几行但结果是可信的。如果你只是想让页面上的占位数据看起来顺序有点变化这个写法还能用一旦涉及金额、奖品、账号权益千万别赌。7.2toFixed(2)返回的是字符串再说toFixed它返回字符串所以调用链很容易出问题。比如(0.1 0.2).toFixed(2)返回的是0.30不是数字0.3。展示给用户看没问题但拿去比较大小就会踩(0.1 0.2).toFixed(2) 0.30是true而(0.1 0.2).toFixed(2) 0.3是false。项目里凡是遇到这种类型混用我习惯先Number((0.1 0.2).toFixed(2))或者干脆用Math.round方案。7.3 安全上下文、浏览器兼容与兜底最后一个让我印象最深的是navigator.clipboard必须跑在 HTTPS 或 localhost。以前本机联调没问题一到内网测试环境就报错Cannot read properties of undefined (reading writeText)。后来我统一封装成const copyText async text navigator.clipboard ? navigator.clipboard.writeText(text) : fallbackCopy(text)。类似的还有URLSearchParams对旧浏览器不支持数字格式化里toLocaleString(zh-CN)在某些浏览器可能没有中文货币符号需要自行兜底。这些兼容问题不会每次出现但一旦遇到就是线上事故所以我习惯在代码注释里写上“为什么这里要判断”。技巧容易踩的坑我的兜底做法洗牌分布不均仅用于非敏感随机正式摇号用 Fisher-YatestoFixed返回字符串用 Number 包一层或直接用Math.roundclipboard非安全上下文 undefined判断存在性再调用外加回退复制Math.max(...arr)大数组调用栈超限改用reduce或循环我在实际开发里的体会是单行代码小技巧最大的价值不是“秀”而是帮你在反复出现的小需求上少写重复代码。每收藏一个新技巧我都会顺手在旁边记一条“为什么这样写”和“什么场景不推荐用”下次遇到类似问题就不会只复制代码而不懂原理。如果你也已经踩过其中某个坑不妨把这篇文章分享给同事如果收藏夹里还有更硬核的一行写法欢迎一起交流。
返回列表