尧图精选

jQuery 到原生 DOM/BOM API 的完整替代指南:解析 Atom 子模块测试夹具中的 You-Dont-Need-jQuery 文档

🕒 发布时间:2026/9/5 17:40:54 📁 来源:尧图网络
jQuery 到原生 DOM/BOM API 的完整替代指南解析 Atom 子模块测试夹具中的 You-Dont-Need-jQuery 文档【免费下载链接】atom:atom: The hackable text editor项目地址: https://gitcode.com/gh_mirrors/at/atom本篇指南完整继承并深入讲解 You-Dont-Need-jQuery 项目的意大利语版文档README-it.md从 Query Selector、CSS 样式操作、DOM 操纵、Ajax、事件绑定到工具函数逐一给出 jQuery 方法到原生 JavaScript 实现的一对一替换写法并标注了返回null与[]的语义差异、getComputedStyle的已知陷阱、dataset的浏览器限制等关键细节。该文档在 Atom 仓库中作为 git 子模块测试夹具存在位于 spec/fixtures/git/repo-with-submodules/You-Dont-Need-jQuery/README-it.md读完后你既能掌握 IE 10 环境下用纯原生 API 重写 jQuery 代码的实战方案也能理解 Atom 是如何在git-repository测试中把带子模块的仓库作为固定 fixture 来验证Project.observeRepositories()的。文档定位为什么这份文档出现在 Atom 仓库里在展开替换内容之前先说清这份文档在本仓库中的角色。Atom 的 Git 仓库支持实现于 src/git-repository.js需要一套可离线、可重复的测试数据其中带子模块submodule的仓库结构是最复杂的场景之一因为子模块在工作区里表现为一个指向.git/modules/name的 gitdir 指针文件而不是普通的.git目录。本仓库为此保留了完整夹具 spec/fixtures/git/repo-with-submodules顶层是一个git.git裸仓库目录其 git.git/config 中声明了jstips与You-Dont-Need-jQuery两个[submodule]段子模块工作区目录 You-Dont-Need-jQuery 内部有一个名为git.git的指针文件内容为gitdir: ../.git/modules/You-Dont-Need-jQuery这正是 Git 子模块工作区的标准形态该子模块的内容是 You-Dont-Need-jQuery 项目的一份历史快照而本指南的主角就是其中的意大利语文档 README-it.md。Atom 的测试用例 spec/project-spec.js 中的.observeRepositories()测试会把这个夹具拷贝到临时目录并断言observed[1].getReferenceTarget(refs/heads/master)返回d2b0ad9cbc6f6c4372e8956e5cc5af771b2342e5——子模块夹具里的提交树内容包括这份 README-it.md直接参与了断言的指纹。也就是说这份“jQuery 替代指南”在 Atom 工程体系里承担的是“结构稳定、内容固定”的测试语料角色。下面进入文档本身的技术内容。核心观点现代浏览器已足以替代 jQuery文档开篇给出的论点是现代浏览器已经实现了足够多的 DOM/BOM APIDOM 操纵与事件处理不再需要从零学起 jQuery与此同时React、Angular、Vue 等框架的普及使得直接操作 DOM 本身变成了反模式jQuery 的重要性进一步下降。这个项目的目标正是总结绝大多数 jQuery 方法的原生替代实现并且以IE 10为兼容性基线文档末尾的支持矩阵为Chrome 最新、Firefox 最新、IE 10、Opera 最新、Safari 6.1 均打勾。从源码结构看这个“兼容性基线”并非空话夹具中保留的测试代码 test/query.spec.js 全程使用[].forEach.call、[].filter.call这类把数组方法借用给类数组对象的写法以及el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector这样的多引擎前缀回退——它们都是在没有Array.from、没有标准matches的时代IE 10/11、旧 WebKit下保证可运行的典型手法。夹具自带的 package.json 也印证了这套验证机制test脚本为karma start --single-run用 Karma Mocha Chai 驱动真实浏览器把 jQuery 结果与原生 API 结果逐条断言相等ES6 语法则通过.babelrc中声明的es2015与stage-0预设转译。Query Selector查询与定位1.0 – 1.9这是文档的第一大节也是最容易踩坑的部分。文档先给出三条通用规则document.querySelector只返回第一个匹配元素document.querySelectorAll返回匹配的 NodeList可用[].slice.call(document.querySelectorAll(selector) || [])转成真正的 Array语义差异无匹配时 jQuery 返回[]而 DOM API 返回null必须警惕空指针异常可以用|| []提供默认值例如document.querySelectorAll(selector) || []性能提示querySelector/querySelectorAll相对较慢追求性能时优先用getElementById、document.getElementsByClassName、document.getElementsByTagName。基础查询1.0 – 1.3// 1.0 按选择器 // jQuery $(selector); // 原生 document.querySelectorAll(selector); // 1.1 按类名 // jQuery $(.class); // 原生 document.querySelectorAll(.class); // 或更快 document.getElementsByClassName(class); // 1.2 按 id // jQuery $(#id); // 原生 document.querySelector(#id); // 或 document.getElementById(id); // 1.3 按属性 // jQuery $(a[target_blank]); // 原生 document.querySelectorAll(a[target_blank]);在已有元素范围内查找1.4查找子节点// jQuery $el.find(li); // 原生 el.querySelectorAll(li);查找 body// jQuery $(body); // 原生 document.body;读取属性// jQuery $el.attr(foo); // 原生 e.getAttribute(foo);读取>// jQuery $el.data(foo); // 原生 // 用 getAttribute 兼容所有目标浏览器 el.getAttribute(data-foo); // dataset 仅在 IE 11 可用 el.dataset[foo];兄弟、前驱与后继元素1.5// jQuery $el.siblings(); // 原生 [].filter.call(el.parentNode.children, function(child) { return child ! el; }); // jQuery $el.prev(); // 原生 el.previousElementSibling; // jQuery $el.next(); // 原生 el.nextElementSibling;closest 与 parentsUntil1.6 – 1.7$el.closest(queryString)返回从当前元素向上到 document 之间第一个匹配的元素。原生el.closest(selector)较新IE 不支持文档给出了 IE10 的手写版本// 原生 - IE10 兼容实现 function closest(el, selector) { const matchesSelector el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; while (el) { if (matchesSelector.call(el, selector)) { return el; } else { el el.parentElement; } } return null; }parentsUntil(selector, filter)返回当前元素匹配集合中每个元素的、直到但不包含匹配 selector 的父级链filter 可选。原生等价实现function parentsUntil(el, selector, filter) { const result []; const matchesSelector el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; // 匹配从父级开始 el el.parentElement; while (el !matchesSelector.call(el, selector)) { if (!filter) { result.push(el); } else { if (matchesSelector.call(el, filter)) { result.push(el); } } el el.parentElement; } return result; }这两个手写实现的模式值得注意都是“从自己或父级沿parentElement上行 matchesSelector命中即停”的循环配合多引擎前缀回退是 IE 10 环境下遍历祖先链的通用模板。表单与 Iframe1.8 – 1.9// 1.8 读取输入值 // jQuery $(#my-input).val(); // 原生 document.querySelector(#my-input).value; // 在 .radio 集合中取 e.currentTarget 的索引 // jQuery $(e.currentTarget).index(.radio); // 原生 [].indexOf.call(document.querySelectAll(.radio), e.currentTarget);$iframe.contents()返回该 iframe 的contentDocument// Iframe 内容 // jQuery $iframe.contents(); // 原生 iframe.contentDocument; // Iframe 内查询 // jQuery $iframe.contents().find(.css); // 原生 iframe.contentDocument.querySelectorAll(.css);CSS 与样式2.1 – 2.4读取与写入样式2.1// 读取 // jQuery $el.css(color); // 原生 // 注意已知 bug——当 style 的值本身是 auto 时会返回 auto const win el.ownerDocument.defaultView; // 第二个参数传 null 表示不取伪元素样式 win.getComputedStyle(el, null).color; // 写入 // jQuery $el.css({ color: #ff0011 }); // 原生 el.style.color #ff0011;文档还特别指出若要一次性设置多个样式可以参考 oneuijs 组织 oui-dom-utils 包中的setStyles方法这里仅提及库名不展开外链。classListjQuery 类名操作的直接映射// jQuery // 原生 $el.addClass(className); → el.classList.add(className); $el.removeClass(className); → el.classList.remove(className); $el.hasClass(className); → el.classList.contains(className); $el.toggleClass(className); → el.classList.toggle(className);这四个映射没有陷阱是整份文档中最“无痛”的一组替换。宽高2.2Width 与 Height 的处理方式一致文档以 Height 为例// 窗口高度 $(window).height(); // 不含滚动条与 jQuery 行为一致 window.document.documentElement.clientHeight; // 含滚动条 window.innerHeight; // 文档高度 // jQuery $(document).height(); // 原生 document.documentElement.scrollHeight; // 元素高度内容区需扣除 border 与 padding // jQuery $el.height(); // 原生 function getHeight(el) { const styles this.getComputedStyles(el); const height el.offsetHeight; const borderTopWidth parseFloat(styles.borderTopWidth); const borderBottomWidth parseFloat(styles.borderBottomWidth); const paddingTop parseFloat(styles.paddingTop); const paddingBottom parseFloat(styles.paddingBottom); return height - borderBottomWidth - borderTopWidth - paddingTop - paddingBottom; } // 精确到整数border-box 下即 heightcontent-box 下为 height padding border el.clientHeight; // 精确到小数同上 el.getBoundingClientRect().height;文档给出的口径是offsetHeight减去计算样式中的四条边得到内容高度clientHeight是整数近似getBoundingClientRect().height是小数近似。位置与偏移2.3// $el.position() { left: el.offsetLeft, top: el.offsetTop } // $el.offset() —— 相对整个文档的坐标 function getOffset(el) { const box el.getBoundingClientRect(); return { top: box.top window.pageYOffset - document.documentElement.clientTop, left: box.left window.pageXOffset - document.documentElement.clientLeft } }核心思路是getBoundingClientRect给出相对视口的坐标加上pageYOffset/pageXOffset滚动量、减去clientTop/clientLeft的边框修正即得到相对文档的绝对位置。滚动位置2.4// jQuery $(window).scrollTop(); // 原生兼容 body 滚动与 documentElement 滚动两种情况 (document.documentElement document.documentElement.scrollTop) || document.body.scrollTop;DOM 操纵3.1 – 3.8// 3.1 移除节点 // jQuery $el.remove(); // 原生 el.parentNode.removeChild(el); // 3.2 读写文本 // jQuery $el.text(); // 原生: el.textContent; $el.text(string); // 原生: el.textContent string; // 3.3 读写 HTML // jQuery $el.html(); // 原生: el.innerHTML; $el.html(htmlString); // 原生: el.innerHTML htmlString; // 3.4 追加在父元素的最后一个子节点之后插入 // jQuery $el.append(div idcontainerhello/div); // 原生 el.insertAdjacentHTML(beforeend, div idcontainerhello/div); // 3.5 前置插入到父元素最前面 // jQuery $el.prepend(div idcontainerhello/div); // 原生 el.insertAdjacentHTML(afterbegin, div idcontainerhello/div);insertAdjacentHTML的四个方位参数beforebegin/afterbegin/beforeend/afterend是 append/prepend 类操作的通用底座beforeend等价 append、afterbegin等价 prepend这一点在 3.6 与 3.7 中还会复用// 3.6 insertBefore在目标元素之前插入新节点 // jQuery $newEl.insertBefore(queryString); // 原生 const target document.querySelector(queryString); target.parentNode.insertBefore(newEl, target); // 3.7 insertAfter在目标元素之后插入新节点 // jQuery $newEl.insertAfter(queryString); // 原生 const target document.querySelector(queryString); target.parentNode.insertBefore(newEl, target.nextSibling); // 3.8 判断是否匹配选择器 // jQueryis 还支持 function/elements 参数此处不展开 $el.is(selector); // 原生 el.matches(selector);insertAfter没有原生直接对应惯用解法就是文档所示的“insertBeforenextSibling”。Ajax用 fetch 替代文档对 Ajax 的结论很干脆用fetch及fetch-jsonp替代。需要注意兼容前提——原生fetch在 IE 全系不可用若要维持 IE 10 基线必须引入 polyfill文档推荐了针对 IE8 起可用的 fetch 实现与 fetch-jsonp 实现此处仅保留结论。这也是整份文档中唯一无法靠“原生 API 手写回退”完全自洽的一节。事件5.1 – 5.3文档说明需要命名空间namespace与事件委托的完整替代方案时应参考 oneuijs 组织的 oui-dom-events 库基础绑定则用标准 API// 5.1 绑定 // jQuery $el.on(eventName, eventHandler); // 原生 el.addEventListener(eventName, eventHandler); // 5.2 解绑 // jQuery $el.off(eventName, eventHandler); // 原生 el.removeEventListener(eventName, eventHandler); // 5.3 触发含自定义事件 // jQuery $(el).trigger(custom-event, {key1: data}); // 原生 if (window.CustomEvent) { const event new CustomEvent(custom-event, {detail: {key1: data}}); } else { const event document.createEvent(CustomEvent); event.initCustomEvent(custom-event, true, true, {key1: data}); } el.dispatchEvent(event);trigger的替换点在于jQuery 把data挂在事件对象上原生 CustomEvent 则要求数据放进detail字段旧浏览器的document.createEventinitCustomEvent(type, bubbles, cancelable, detail)是回退路径。工具函数6.1 – 6.4// 6.1 数组判断 // jQuery $.isArray(range); // 原生 Array.isArray(range); // 6.2 去首尾空白 // jQuery $.trim(string); // 原生 string.trim(); // 6.3 对象合并旧环境可用 object.assign polyfill 兜底 // jQuery $.extend({}, defaultOpts, opts); // 原生 Object.assign({}, defaultOpts, opts); // 6.4 包含判断 // jQuery $.contains(el, child); // 原生 el ! child el.contains(child);其中 6.4 的el ! child前置判断值得留意Node.contains会把节点自身视为“包含”而$.contains不含自身因此需要显式排除。工程验证文档结论背后的测试体系这份文档的每条“jQuery 写法 ≈ 原生写法”都不是口头结论。夹具保留了完整的验证工程测试文件按文档分节组织test/query.spec.js、test/css.spec.js、test/dom.spec.js、test/utilities.spec.js以 query.spec.js 为例它在beforeEach中注入含嵌套ul、data-role属性与 class 组合的固定 DOM 结构然后断言$(li.item[data-rolered]).length与document.querySelectorAll(...)的结果逐元素相等测试编号1.0、1.1、1.2……与文档章节号一一对应运行方式由 package.json 定义npm test即karma start --single-run、npm run tdd自动监听、npm run test-cov覆盖度、npm run lint。这套“文档条目 ↔ 测试用例编号”的对应关系是核对某个替换写法是否被验证过的可靠路径。多语言版本与延伸阅读该 README-it.md 是项目 10 个语言版本之一同目录spec/fixtures/git/repo-with-submodules/You-Dont-Need-jQuery/下还保留了英文原文 README.md简体中文 README.zh-CN.md韩国语 README.ko-KR.md马来语 README-my.md印尼语 README-id.md葡萄牙语巴西README.pt-BR.md越南语 README-vi.md西班牙语 README-es.md土耳其语 README-tr.md文档末尾还列出了两条延伸方向youmightnotneedjquery 风格的纯 JS 示例站以及聚合单文件 DOM 模块的 npm-dom / webmodules 组织此处仅保留线索不展开外部链接。关键陷阱速查把文档中散落的注意事项集中成一张自检清单陷阱说明nullvs[]DOM 查询无结果返回nulljQuery 返回空集链式调用前先|| []或判空dataset可用性仅 IE 11IE 10 必须走getAttribute(data-foo)closest/matches无原生支持时需多前缀回退 手写上行循环见 1.6 / 1.7 实现getComputedStyle样式值为auto时存在返回auto的已知问题传null表示不取伪元素样式滚动位置需同时兼容documentElement.scrollTop与body.scrollTop两种滚动宿主$.contains原生contains包含自身需el ! child前置排除Ajaxfetch在 IE 无原生实现IE 10 基线依赖 polyfill性能热点路径优先getElementById/getElementsByClassName/getElementsByTagName小结这份文档的价值在于把“去 jQuery 化”压缩成一张可逐条对号入座、且每条都有浏览器兼容性标注的映射表查询层用querySelector(All)matches/多前缀回退样式层用classListgetComputedStyle结构层用insertAdjacentHTMLinsertBefore(nextSibling)事件层用addEventListener/removeEventListener CustomEvent 回退工具层用Array.isArray/String.prototype.trim/Object.assign。它同时以 Atom 仓库中 spec/fixtures/git/repo-with-submodules 子模块夹具的身份存在——Atom 的 Project.observeRepositories() 测试 依赖其提交内容保持恒定这也从侧面说明一份内容稳定的第三方文档快照正是 Git 子模块 fixture 的理想语料。【免费下载链接】atom:atom: The hackable text editor项目地址: https://gitcode.com/gh_mirrors/at/atom创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →