ARTICLE DETAIL

资讯详情

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

jstips 技巧 6:巧用 `Array.prototype.concat` 编写同时兼容单个元素与数组的方法

jstips 技巧 6:巧用 `Array.prototype.concat` 编写同时兼容单个元素与数组的方法 教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载本文对应 jstips 仓库英文原帖 _posts/en/javascript/2016-01-06-writing-a-single-method-for-arrays-and-a-single-element.md仓库内另有 简体中文译本 与 西班牙语译本。本篇技术指南讲解一个高频实用的小技巧只写一个方法就能同时处理单个元素与元素数组两种入参。核心做法是一行[].concat(words || [])把任意入参先归一化成数组再遍历。读完本文你将掌握这一输入归一化范式、Array.prototype.concat的底层语义与边界行为并能直接把它应用到 DOM 批量操作、工具函数设计等真实场景中。一、问题为什么需要一个方法吃两种入参在日常开发中我们经常面对这样的 API 设计难题某个业务方法有时需要处理一个元素有时需要处理一批元素。如果不加处理常见的做法是写两个方法或者在使用侧强行if (Array.isArray(x))分支这会造成大量重复代码。jstips 原帖给出了一个非常形象的类比jQuery 的很多方法就是这样工作的——例如css方法传入一个选择器时它会修改所有匹配到的元素而不是只改第一个。也就是说jQuery 把集合操作作为默认语义单个元素只是长度为 1 的集合的特例。这种设计让调用方无需关心自己拿到的是单个节点还是节点列表。本文要解决的问题正是让你的自定义函数拥有同样的宽容度——传入字符串cactus和传入数组[cactus, bear, potato]都能被同一套逻辑正确处理。二、核心一行代码[].concat(words || [])原帖给出的解法非常精简——不需要判断类型只需要把入参拼进一个空数组var elements [].concat(words || []);这一行同时完成了两件事归一化Array.prototype.concat接受数组也接受单个元素。传入cactus时结果是[cactus]传入[cactus, bear]时结果是[cactus, bear]。无论哪种入参elements最终都是一个真正的数组。兜底防错words || []保证了当参数为undefined、null或干脆没有传参时elements退化为空数组[]从而避免了对undefined调用.toUpperCase()之类的TypeError。值得注意的是这一技巧依赖concat的一个关键特性它返回一个全新数组而不会修改原数组。因此它可以安全地包裹任意来源的数据不用担心污染调用方的原数组。三、完整示例printUpperCase原帖给出了完整可运行示例。下面这段代码定义了printUpperCase它现在可以同时接受单个单词或单词数组function printUpperCase(words) { var elements [].concat(words || []); for (var i 0; i elements.length; i) { console.log(elements[i].toUpperCase()); } }调用与输出printUpperCase(cactus); // CACTUS printUpperCase([cactus, bear, potato]); // CACTUS // BEAR // POTATO可以看到传入单个字符串时它被包成单元素数组后照常遍历传入数组时三个元素依次被转成大写输出。printUpperCase现在已准备好接受单个节点或一组节点作为参数同时避免了不传参数时抛出TypeError的隐患——这正是原帖要表达的核心收益。四、Array.prototype.concat的底层语义要真正用好这个技巧需要准确理解concat的三个底层行为1. 展开行为只展开一层concat会把传入的数组参数的元素逐个追加到结果中但只展开一层不会递归展开。这一点在 jstips 仓库的另一篇 tip _posts/en/javascript/2016-02-07-flattening-multidimensional-arrays-in-javascript.md 中有更详细的论述——该文总结了 5 种扁平化多维数组的方法其中第一种就是var myNewArray [].concat.apply([], myArray);以及 ES6 版本的展开符写法var myNewArray4 [].concat(...myArray);也就是说concat天然具备单层扁平化能力——把嵌套数组摊平一层——这正是单个元素与数组统一处理得以成立的底层机制。但要注意如果入参是更深层的嵌套数组如[[1, 2], [3, 4]][].concat只能摊平到[1, 2, 3, 4]这一层若要处理任意深度的嵌套需要flat(Infinity)或递归方案。2. 非破坏性返回新数组concat不会修改调用它的数组而是返回合并后的新数组。这与push、unshift、splice等原地修改的方法形成对比。仓库另一篇 tip _posts/en/javascript/2015-12-29-insert-item-inside-an-array.md 在讨论向数组插入元素时也对比了arr2 arr.concat([6])返回新数组与arr.push(6)修改原数组两种路径可见concat的返回新数组语义在数组操作中是一以贯之的。3. 参数个数不限concat可以接受任意多个参数且每个参数都可以是数组或单值。利用这一点仓库的 _posts/en/javascript/2016-01-19-safe-string-concatenation.md 展示了.concat(one, two, three)这种安全的字符串拼接技巧——避免运算符因类型隐式转换产生意外结果例如1 2 3得到33而非123。同一个concat方法族数组版与字符串版在仓库里被反复用于消除隐式类型歧义这也侧面印证了用 concat 统一输入是社区公认的稳健写法。五、边界情况与注意事项1. 不传参 / 传undefined/ 传nullwords || []覆盖了这三种情况。但要注意||的语义是真值判断如果调用方显式传入0、、false等假值它们同样会被[]替换。对printUpperCase这类方法来说假值输入本就无意义行为是合理的但如果你要处理的对象可能合法地等于假值例如数字0则需要改用words null ? [] : ...之类的空值判断而不是||。2. 传入类数组对象concat只认识真正的数组以及Symbol.isConcatSpreadable标记为可展开的对象。如果传入arguments或document.querySelectorAll()返回的NodeList这类类数组对象[].concat(nodeList)得到的是[nodeList]——一个把整个 NodeList 当作单个元素包起来的数组而不是把里面的节点逐个展开。处理 DOM 集合时通常需要先Array.prototype.slice.call(nodeList)或Array.from(nodeList)转成真数组再交给统一逻辑处理。这一点在 _posts/en/javascript/2016-01-08-converting-a-node-list-to-an-array.md 中有专门讨论。3. 深层嵌套如前所述concat只做单层展开。若业务中确实需要任意深度拍平 单元素兼容可以组合flat(Infinity)或在归一化后再调用扁平化方法。六、现代 ES6 替代方案对比原帖写于 ES5 时代用的是var和concat。在现代代码中同一诉求也有多种等价写法// 方案 Aconcat 归一化原帖思路 function printUpperCase(words) { var elements [].concat(words || []); // ... } // 方案 BArray.isArray 显式分支 function printUpperCase(words) { var elements Array.isArray(words) ? words : [words]; // ... } // 方案 CES6 展开符 条件 function printUpperCase(words) { var elements words null ? [] : (Array.isArray(words) ? [...words] : [words]); // ... }各方案的取舍方案 A[].concat代码最短一行同时完成归一化与空值兜底且天然处理数组参数无需分支代价是||会吞掉所有假值。方案 BArray.isArray分支语义最直白、可读性最强但多一层条件判断且需要额外处理空值。方案 C展开符现代风格但代码反而更长。从原帖的视角看方案 A 之所以值得推荐不只是因为短更因为它把单元素是长度为 1 的集合这个设计意图直接固化在代码里。当然如果团队追求显式语义方案 B 也是完全合理的替代。仓库中还有一个相关的设计模式值得一提_posts/en/javascript/2016-05-06-use-optional-arguments.md 讨论的是如何让函数参数可选它同样主张用统一的参数处理逻辑把arguments转成数组后逐个解析代替为每种调用形态写独立分支。这两篇 tip 一脉相承与其在调用方做类型判断不如在函数内部做一次统一的输入归一化。七、实战场景场景 1DOM 批量操作最常见的落地场景是单个 DOM 节点 vs 节点数组/NodeList的统一处理function setText(content, nodes) { var targets [].concat(nodes || []); targets.forEach(function (node) { node.textContent content; }); } setText(Hello, document.getElementById(title)); // 单个节点 setText(Hello, document.querySelectorAll(.item)); // 多个节点提醒如第四节所述querySelectorAll返回的是 NodeList属于类数组[].concat不会展开它。实战中可先Array.from(nodes)再归一化或在方法内对类数组做一次检测。场景 2API 输入归一化当你的工具函数可能从不同调用方拿到单值或数组时用同一模式统一内部处理可以让下游逻辑永远只面对数组function normalizeTags(tags) { return [].concat(tags || []).filter(Boolean); } normalizeTags(js); // [js] normalizeTags([js, tips]); // [js, tips] normalizeTags(); // []场景 3配合reduce做组合处理仓库的 _posts/en/javascript/2016-02-17-reminders-about-reduce-function-usage.md 在讲解reduce时其累加示例也大量依赖prev.concat(curr)——同样是利用 concat 的单层展开 返回新数组语义来持续累积结果。把输入归一化与reduce这类函数式工具结合可以写出非常简洁的批量处理管线。八、总结本文围绕 jstips 第 6 期 tip 的核心理念展开要点如下理念让一个方法同时接受单个元素与元素数组参照 jQuerycss的集合默认语义避免为同一逻辑写两套方法。手法[].concat(words || [])一行完成归一化 空值兜底concat接受数组或单元素、返回新数组、只展开一层。收益消除了入参形态差异顺带规避了不传参数时的TypeError。边界注意||对假值的吞并、类数组NodeList、arguments不会被自动展开、深层嵌套需要额外扁平化。演进ES6 时代可用Array.isArray分支或展开符替代但 concat 写法依然是最简洁的集合化输入方案。这一技巧既适用于 ES5 时代的遗留代码也完全兼容现代代码风格——它是统一输入、简化分支这一设计思想的最小实现值得在日常工具函数中反复使用。赞分享教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载相关推荐drawDB批量操作多元素同时编辑技巧drawDB批量操作多元素同时编辑技巧 概述 drawDB作为一款强大的数据库模式编辑器提供了丰富的批量操作功能让用户能够高效地同时编辑多个数据库元素。本前端数据库数据库客户端WPScan 动态版本检测实战以 Grid Plus 插件 CHANGELOG.md 为例解析 ChangeLog 版本指纹机制WPScan 动态版本检测实战以 Grid Plus 插件 CHANGELOG.md 为例解析 ChangeLog 版本指纹机制 WPScan 作为面向 Wo教程HeyGem.ai 无水印输出3 条解锁路径 1 个隐藏参数成片一次到位HeyGem.ai 无水印输出3 条解锁路径 1 个隐藏参数成片一次到位 HeyGem.ai 是一款支持本地离线运行的开源数字人视频工具HeyGem.人工智能AI 应用数字人媒体生成桌面应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表