资讯动态

jQuery杂项方法深度解析:工具函数的使用技巧与现代原生API取舍

发布时间:2026/10/24 4:36:14 来源:尧图企业网站定制
1. 先说清“杂项”到底是些什么方法第一次翻 jQuery 文档的人多半会在侧边栏里看到“杂项方法”那个归类里面的方法看起来不像.on()、.ajax()那样目标明确倒像是一堆“不知道往哪放”的工具函数。比如$.each、$.map、$.extend、$.grep、$.trim、$.type还有$.inArray、$.makeArray这些。我当初刚接触时也犯过嘀咕这些也能算一类用多了之后才意识到正是这些不起眼的工具函数构成了整个 jQuery 生态的“基础设施”。所谓“杂项方法”我们通常叫它们 jQuery 工具函数英文名是 Utilities也有叫静态方法的。它们挂载在$或jQuery这个全局对象上不依赖 DOM 节点不负责事件绑定、动画或 AJAX核心作用就是处理数组、对象、类型判断、字符串、伪数组转换这类常规操作。你平时写业务代码的很多沟坎比如判断一个变量到底是不是数组、想把类数组对象变成真数组、想合并一堆配置对象、想在数组里找元素但不影响原数据结构都可以在这些方法里找到答案。这篇文章适合谁看两类人。一类是长期维护老项目的开发者项目里还在用 jQuery 2.x 甚至 1.x每天面对一大堆业务代码但认知停留在“jQuery 就是查 DOM 和发 AJAX”的水平遇到这些工具函数只能现翻文档另一类是那种想系统梳理底层工具、把代码写得更简洁干脆的新手。我会把这批方法的用法、坑、设计逻辑一次性说透顺便给出一套“什么时候该继续用、什么时候该换成原生 API”的判断标准免得你越用越纠结。2. 集合处理三件套$.each、$.map、$.grep 的真正用法2.1 $.each不仅是数据的迭代器还要学会“提前退出”$.each(collection, callback)接受两种集合数组或类数组和普通对象。它和原生forEach的差别很多人用了一年也没搞清——最核心的一点是$.each能提前退出循环。$.each([1, 2, 3, 4], function (index, value) { if (value 3) { return false; // 相当于 break } console.log(index, value); }); // 只会输出 0 1 和 1 23 和 4 不再处理回调里的return false是终止循环而return true、return undefined或者干脆不写 return都表示继续下一个。这个语义很容易和普通循环搞混我见过有人想跳过当前元素时写return false结果整个循环提前结束后续数据全部丢失。还有一个细节$.each遍历对象时回调参数顺序是(key, value)而遍历数组时顺序是(index, value)。对象场景下想拿第一个参数当成“下标”大概率会踩坑。$.each({ name: tom, age: 18 }, function (key, value) { console.log(key value); }); // name tom // age 18遍历类数组对象时比如{ 0: a, 1: b, length: 2 }$.each会按 length 递归遍历和原生forEach不同forEach遇到这种对象根本不能调用。这个特性在做数据兼容处理时很有用。实际操作中我推荐一条铁律只要回调里可能提前退出就直接用$.each如果只是简单跑一圈用原生forEach也行但要记得处理 this 指向问题。2.2 $.map 与原生 map 的差别以及 NULL 元素处理$.map(arrayOrObject, callback(value, indexOrKey))最反直觉的地方在于返回值处理。原生Array.prototype.map要求回调必须返回一个值每个元素都会保留在结果里$.map则把返回null或undefined的元素直接丢弃回收的数组长度会变短。var arr [1, 2, 3, 4]; var result $.map(arr, function (value) { if (value % 2 0) { return value * 10; } return null; // 这个元素从结果里消失 }); // result 是 [20, 40]这个设计当初是为了方便筛选但新手常把它当成普通 map 用结果发现结果数组莫名其妙少了几项。需要注意的是回调里返回空字符串、数字0、布尔false时这些值不会丢弃仍然保留在结果里。只有null和undefined会被过滤。如果你不希望任何值被吞掉就必须保证每条分支都有 return。$.map好用还有一个地方遍历对象时能轻松转成数组。比如拿到一个配置对象想把它转成[{name: x, value: 1}]结构直接$.map(obj, function(value, key) { return {name: key, value: value}; })就能做到。原生方案需要Object.keys加map两行代码变三行区别不算大但写起来更一气呵成。2.3 $.grep过滤数组的高效姿势$.grep(array, callback(value, index), invert)的作用是筛数组和原生filter类似。第三个参数invert大多数人没用过传true时逻辑会反转即保留那些回调返回 false 的元素。var scores [60, 80, 45, 90]; var pass $.grep(scores, function (value) { return value 60; }); // pass: [60, 80, 90] var failed $.grep(scores, function (value) { return value 60; }, true); // failed: [45]如果你既要拿到通过项又要拿到未通过项用 invert 参数能少写一个回调。原生filter每次都得写完整判断有时候两者实在各有取舍。我的建议是如果项目里已经用了 jQuery配合$.grep $.map做数据清洗那是一套非常顺手的组合拳胜过调用好几次原生方法。2.4 $.inArray 与 $.merge 的边界行为$.inArray(value, array)返回数组中的索引找不到时返回-1很多新手会把判断条件写反。正确的姿势是if ($.inArray(value, arr) 0)或! -1而不是if ($.inArray(value, arr))——因为当元素正好在第一个位置时返回00会被当成 false。$.merge(first, second)是把第二个数组内容追加到第一个数组上直接修改第一个数组并返回它。它支持类数组对象比如可以合并arguments对象。这个效果用原生concat也能实现但concat返回新数组不改变原数组两者行为不同。var arr1 [1, 2]; var arr2 [3, 4]; var result $.merge(arr1, arr2); // result arr1arr1 变成 [1, 2, 3, 4]潜在隐患是它有去重行为吗没有。合并时如果前后存在相同引用会原样保留两份。想合并并去重得配合$.inArray自己加判断或者用 ES6 的Set。3. 对象和数据类型判断的细节$.extend、$.type、其他类型工具3.1 $.extend 的深浅拷贝与覆盖策略$.extend(target, obj1, obj2)是杂项方法里出场率最高的方法处理配置合并、对象克隆都靠它。不传true时是浅拷贝传true作为第一个参数时是深拷贝。var defaults { name: demo, options: { size: 1, color: red } }; var userSettings { options: { size: 2 } }; var result1 $.extend({}, defaults, userSettings); // result1.options 是 { size: 2 } // 浅拷贝userSettings.options 直接覆盖 defaults.options // defaults.options 里的 color 直接丢了 var result2 $.extend(true, {}, defaults, userSettings); // result2.options 是 { size: 2, color: red } // 深拷贝子对象一层层合并浅拷贝和多层对象合并是绝大多数配置类 bug 的来源。我踩过一次很典型的坑表单 A 有一组默认参数用户只修改了其中一部分直接$.extend({}, defaults, formData)结果嵌套的options被整体覆盖。后来我统一调整成深拷贝后续再没犯过。需要提醒的是深拷贝也不是万能的循环引用对象会直接报错函数、正则、Date 实例在某些旧版本里处理得也不完美。$.extend还有几个隐藏能力如果只传一个参数会把内容合并到 jQuery 全局对象上这可以用来扩展工具函数类似写插件时的$.fn.extend的表兄弟如果目标对象是true那它的第一参数位置要注意偏移因为布尔值被当作深浅拷贝标志了。3.2 $.makeArray 与伪数组转换$.makeArray(obj)把类数组对象转成真正的数组。最常见的场景是拿arguments、批量取到的 DOM 节点集合或者以索引和 length 为骨架的对象。function demo() { var args $.makeArray(arguments); console.log(args); // 真正的数组 } var fakeArr { 0: a, 1: b, length: 2 }; var realArr $.makeArray(fakeArr); // [a, b]这个东西在原生 ES6 里基本可以用Array.from替代但两者有一点不同Array.from(fakeArr)也支持类数组对象但Array.from还能传第二个 map 映射参数这点反而更灵活。所以如果你维护的代码不需要兼容 IE8 那种环境转换这步其实交给原生更干净。只有确实在远古项目里做兼容时$.makeArray才有绝对优势。3.3 类型判断的组合拳$.type、$.isArray、$.isEmptyObject$.type(obj)会返回变量的精确类型和原生typeof有很大不同。typeof []返回object而$.type([])返回array$.type(null)返回null$.type(null)在其他工具判断里容易翻车但这里很稳。它还能识别date、regexp、error等类型。$.type([]); // array $.type(null); // null $.type(new Date()); // date $.type(undefined); // undefined$.isArray(value)本质是对$.type(value) array的封装$.isFunction判断函数$.isEmptyObject(obj)判断是否有可枚举属性注意它不关心原型链上的属性也不区分对象是{}还是 new 出来的实例。$.isNumeric(value)比较有意思它把数字字符串也算作数字。$.isNumeric(12)返回 true$.isNumeric(12px)返回 false$.isNumeric()返回 false。这个方法和typeof value number的习惯不一样接收表单输入时反而更实用。$.isNumeric(12); // true $.isNumeric(12.5); // true $.isNumeric(0x10); // true十六进制字符串也算 $.isNumeric(); // false一个实际体会判断一个参数是否为“非空对象”用!$.isEmptyObject(value)会非常直观。但要小心$.isEmptyObject(new Date())返回 true因为 Date 实例默认没有自身可枚举属性它的时间是内部槽位枚举不出来。3.4 兼容旧代码时要留意的历史包袱jQuery 这些工具函数诞生的年代原生 JS 的功能还很弱Object.keys、Array.isArray、Array.prototype.map都还没普及。因此很多工具函数本质上是为了“抹平浏览器差异、补齐缺失能力”而存在的。今天再用它们有些纯粹是冗余有些则仍然有价值。举个例子$.trim(str)在 IE8 环境能替代原生String.prototype.trim但现代浏览器已经原生支持$.trim(str)执行效率反而不如原生。$.parseJSON(str)在 jQuery 3.0 里已被废弃因为原生JSON.parse早已全面普及。$.isArray同样可以用原生Array.isArray完全替代。因此我整理了一套判断标准如果你面向的浏览器最低版本是 IE10 以下那这些兼容函数还是留着的理由如果项目最低已经到 IE11或者干脆是内部系统用现代浏览器那优先用原生。老方法有时候反而会掩盖错误——$.parseJSON在传入非法 JSON 时会返回 null而原生JSON.parse会直接抛异常。后者虽然“脾气差”但更容易暴露数据问题。4. 一份实际案例用杂项方法处理表单提交数据4.1 场景设定假设我在维护一个后台商品管理页面页面上有一堆筛选条件包括关键词、价格区间、类目、多选标签另外还有分页、排序字段。提交到后端之前需要把表单数据先做一轮清洗去掉空值转换数字格式合并默认配置再把最终数据交给 AJAX。这个场景天然适合杂项方法组合出场。4.2 第一步收集并清洗表单数据把表单控件手动收集成对象筛掉空字符串和 null。var rawData { keyword: 充电器, minPrice: 19.9, maxPrice: , category: 数码, tags: [a, b], page: 1 }; var cleaned $.grep($.map(rawData, function (value, key) { if (value || value null || value undefined) { return null; } var obj {}; obj[key] value; return obj; }), function (item) { return !!item; });这里我用了$.map先遍历对象把空值字段变成 null再转成{key: value}的微型对象然后用$.grep过滤掉空项。第一次写的时候我也觉得多此一举后来发现这样得到的结果非常好用直接$.extend.apply($, [{}].concat(cleaned))就可以把多个对象合并成一个。如果只是纯过滤直接$.grep(Object.keys(rawData)...)来也行但做成对象数组的形态后后续扩展很方便。4.3 第二步数值字段的格式化价格、数量这些表单数据天然是字符串提交前最好转成数字并过滤非法值。这里$.isNumeric就派上用场了。function normalizeNumeric(input, radix) { if ($.isNumeric(input)) { return parseFloat(input); } return null; } rawData.minPrice normalizeNumeric(rawData.minPrice); rawData.maxPrice normalizeNumeric(rawData.maxPrice);我见过有人用parseInt直接硬转结果输入是 “12abc” 竟然变成 12这种脏数据会带着错误价格提交到后端。$.isNumericparseFloat组合能有效避免这种问题。4.3 第三步合并默认配置每个查询接口都有一些后端要求的固定参数比如页面大小、排序方向、是否需要返回明细。这些参数不该由页面一一写死应该放在默认配置里合并。var searchConfig { pageSize: 20, sortField: default_sort, order: desc, needDetail: 1 }; var finalParams $.extend({}, searchConfig, cleanedData);合并时用深拷贝还是浅拷贝在这个场景里cleanedData中可能有嵌套的tags数组如果用浅拷贝finalParams.tags会直接引用表单里的数组后续如果对数组做排序或修改可能导致原始表单状态被污染。刻意避开这种隐患后我统一改成$.extend(true, {}, searchConfig, cleanedData)。4.4 第四步把类数组 DOM 集合转成可操作数组页面上还有一组复选标签用document.querySelectorAll拿到的结果是NodeList在老浏览器里它不支持 forEach。借助$.makeArray转成真数组后配合$.each收集被选中的值。var selectedTags $.makeArray(document.querySelectorAll(.tag-checkbox:checked)) .map(function (checkbox) { return checkbox.getAttribute(data-tag-id); });这里其实没有用$.each因为NodeList转成数组后原生map完全可用。这种“能用原生就用原生”的思路会让代码在兼容性和可读性之间保持平衡。4.5 完整串接把上面几步串起来后整个提交前的数据处理流程是这样function collectAndPrepare(pageNum) { var formData { keyword: getValue(#keyword), minPrice: getValue(#minPrice), maxPrice: getValue(#maxPrice), category: getValue(#category), page: pageNum }; Object.keys(formData).forEach(function (key) { if (formData[key] ) { delete formData[key]; } }); formData.minPrice formData.minPrice ? parseFloat(formData.minPrice) : undefined; formData.maxPrice formData.maxPrice ? parseFloat(formData.maxPrice) : undefined; var finalParams $.extend(true, {}, searchConfig, formData); finalParams.tags selectedTags; return finalParams; }这套流程后来我在好几个项目里复用不断简化成模板。关键是认识到$.extend合并配置 $.map/$.grep清洗数据 $.makeArray转换集合这三类需求覆盖了前端表单处理的大部分底层操作。5. 常见问题与排查技巧实录下面这些是我在实战项目里反复踩过、或者看到别人反复踩的坑整理成速查表方便你直接对照。5.1 $.each 回调的返回值陷阱注意$.each中return false是终止循环不是跳过当前项。想跳过当前项应该return true或return undefined。这个错误隐蔽性很高。很多人从原生each或for循环转过来后写if (condition) return;以为只是跳过结果循环直接断了。正确写法$.each(params, function (key, value) { if (value null) { return true; // 跳到下一个 } if (key stopKey) { return false; // 终止整个循环 } console.log(key, value); });5.2 $.extend 浅拷贝导致子对象覆盖典型事故现场默认配置里有嵌套对象用户传入的配置里只有部分字段因为浅拷贝子对象整体被替换未传字段消失。var defaults { filter: { status: 1, dateRange: last7d } }; var input { filter: { status: 2 } }; var merged $.extend({}, defaults, input); // merged.filter.dateRange 直接没了排查建议在控制台直接打merged看结果如果嵌套层级超过一层但输出结果和预期不符立刻想到深浅拷贝问题。修法就是$.extend(true, {}, defaults, input)。5.3 $.type 对 null 和 NaN 的判断结果$.type(NaN)返回nan不是number这一点可能跟你预期不一样。想判断数字的 NaN用$.isNumeric(NaN)会返回 false因为NaN不是 numeric。判断“是不是 NaN”还是要回归原生Number.isNaN或者isNaN。$.isEmptyObject对没有可枚举自有属性的对象返回 true包括new Date()、new RegExp()、new Map()这类实体内置对象的实例。如果业务上认为 Date 实例也算“有内容”就需要额外判断类型。5.4 老版本 jQuery 方法的废弃与移除jQuery 3.x 正式移除$.parseJSON、$.isArray和$.isFunction等部分方法在文档中也被标记为废弃。虽然老项目不升级就不会有影响但如果你混用新老两种写法容易出现“页面明明引用了 jQuery 3.x但调用$.parseJSON直接报错”的问题。排查技巧升级 jQuery 前先全局搜索这些方法名批量替换成原生方案其实工程量不大。我处理过一个老项目升级时顺手用grep -rn \$\.parseJSON src/把所有出现点找出来一次性手动改完。遇到$.type需要替换时可以用一套简单映射表覆盖常见类型。5.5 杂项方法性能与体积的现实取舍jQuery 完整包体积不小如果只为了用几个工具函数而引入整个 jQuery在只看性能和加载体积的现代前端体系里不划算。我们可以把$.each、$.map换成原生方法但有一类场景例外你已经在项目里引用了 jQuery 做 DOM 操作和 AJAX 了再多用几个工具函数属于零成本。另一个取舍点是代码的易读性。$.grep配合自定义回调比多次filter更加紧凑尤其当一个数组要经过多轮清洗时链路一眼就看明白。我以前在某个后台项目里把数组数据清洗写成了“一串 jQuery 链式工具调用”队友起初不熟悉但看懂之后反而说比一堆中间变量好维护。5.6 反直觉的数组索引与参数顺序$.each、$.map回调参数顺序是(index, value)$.grep也是(value, index)反过来这种不一致很容易写错变量名。如果回调里用到 index先打印一下确认方向。$.each([10, 20], function (index, value) { console.log(index, value); // 0 10 / 1 20 }); $.grep([10, 20], function (value, index) { console.log(value, index); // 10 0 / 20 1 });避免低级的参数错位我会在写工具方法前先明确约定一律把 value 放前面、index 放后面不依赖官方具体顺序这样回调函数以后迁移到原生方法时也更自然。6. 杂项方法背后的设计哲学与我的使用习惯我自己用这些工具函数五年多最后形成的习惯可以概括为三点第一点动态类型判断用$.type做兜底尤其接手别人代码、不确定变量是不是数组时$.type(x) array比x instanceof Array更稳定因为它不受跨 iframe 或跨 window 影响。原生Array.isArray也有这个优点所以现在我会首选Array.isArray只有在处理极其老的环境时退回到$.type。第二点所有配置合并都先想清楚“浅拷贝还是深拷贝”再决定是否用$.extend。我写完任何一处 extend会主动检查目标对象会不会在后续被修改。如果存在这种可能无脑深拷贝。第三点工具函数的本质是“降低认知负担”。当你看到一串$.map(...).filter(...)和一堆for循环嵌套时前者显然更容易让人理解意图。但前提是团队成员都对这批方法有共识。团队里如果有新手我会先简单做一次方法分享把$.each的 return 语义和$.extend的深浅拷贝讲清楚后面写代码顺手很多。如果你正在优化老项目的前端代码我个人的建议是不要因为“能用原生就绝不使用库函数”的教条而把每个工具函数都改掉。杂项方法的真正价值在于统一封装、语义清晰以及和现有 jQuery 代码风格保持一致。你只需要记住哪些方法是“时代遗留”、哪些仍然实用然后在具体场景里灵活选择。最后分享一个偏技巧的用法想在$.each循环结束后拿到统计结果可以在回调外层声明一个变量累加或者用$.map先转换再聚合。我自己常用$.map生成一个统计数组再用原生 reduce 计算总和这种组合比在循环里憋一个复杂逻辑清晰得多。这类小工具函数的利用方式其实就是你日常写代码时顺手总结、不断沉淀出来的用多了自然会有自己的套路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑