ARTICLE DETAIL

资讯详情

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

jQuery 到原生 DOM API 完整替代实战:解析 Atom 仓库中的 “You Don‘t Need jQuery“ 参考文档

jQuery 到原生 DOM API 完整替代实战:解析 Atom 仓库中的 “You Don‘t Need jQuery“ 参考文档 jQuery 到原生 DOM API 完整替代实战解析 Atom 仓库中的 You Dont Need jQuery 参考文档【免费下载链接】atom:atom: The hackable text editor项目地址: https://gitcode.com/gh_mirrors/at/atom本文围绕 Atom 编辑器测试 fixture 中收录的《You Dont Need jQuery》参考文档展开系统讲解如何用现代浏览器的原生 DOM/BOM API 逐条替代 jQuery 的查询、样式、DOM 操作、Ajax、事件与工具方法并结合仓库内附带的 Karma 测试用例验证每一组jQuery 写法 vs 原生写法结果的一致性帮助读者掌握一套可复制、可验证的去 jQuery 迁移方案。这份文档在 Atom 仓库中的位置与用途《You Dont Need jQuery》是 OneUI Group 维护的一份jQuery 方法 → 原生 JavaScript 实现对照指南目标是引入针对 IE 10 的现代浏览器原生替代实现。在 Atom 仓库中它以 git 子模块 fixture 的形式被收录用于 Git 相关功能如 GitRepository 与项目仓库观测的测试数据。从仓库结构看spec/fixtures/git/repo-with-submodules/.gitmodules 中声明了该 fixture 的子模块[submodule You-Dont-Need-jQuery] path You-Dont-Need-jQuery url https://github.com/oneuijs/You-Dont-Need-jQuery [submodule jstips] path jstips url https://github.com/loverajoel/jstips因此本文分析的主体文档位于 README.ko-KR.md韩文翻译版同目录下还保留了 README.md 英文版以及多个语言版本中文、俄语、西班牙语、葡萄牙语等。这份 fixture 不只是静态文本spec/project-spec.js 的.observeRepositories()用例会拷贝repo-with-submodules/git.git作为.git目录断言observed[1].getReferenceTarget(refs/heads/master)等于预期提交哈希说明该带子模块的仓库是 Atom Git 集成功能的真实测试数据。如何验证文档中的每一组写法fixture 内置了与文档条目一一对应的测试用例。test/README.md 说明了两条命令npm run test一次性运行全部测试npm run tdd以 TDD 模式自动 watch运行测试。结合 package.json测试栈为 Karma Mocha Chai Webpackkarma start --single-run并在 devDependencies 中同时引入jquery与chai这正是逐条对比 jQuery 与原生 API 行为的前提。以 test/query.spec.js 为例它构造了一段嵌套ul/li的 DOM然后断言const $els $(li.item[data-rolered]); const els document.querySelectorAll(li.item[data-rolered]); expect($els.length).to.equal(2); [].forEach.call($els, function($el, i) { expect($el).to.equal(els[i]); // jQuery 结果与 querySelectorAll 结果逐元素相同 });类似的对比覆盖在test/css.spec.js、test/dom.spec.js、test/utilities.spec.js中。下文各章节即按原文档的编号脉络完整继承其代码示例并结合这些测试做纵深说明。一、Query Selector选择器查询的原生替代原文档开篇给出三条核心结论迁移时必须牢记普通的 class、id、attribute 选择器可以用document.querySelector/document.querySelectorAll替代document.querySelector返回第一个匹配的元素document.querySelectorAll返回所有匹配元素组成的 NodeList可用[].slice.call转换为 Array匹配不到元素时jQuery 返回[]而 DOM API 返回null——必须注意空指针风险性能提示document.querySelector(All)相对较慢使用getElementById、document.getElementsByClassName、document.getElementsByTagName可以获得更好的性能。以下按原文档 1.0–1.11 编号完整列出替代写法。1.0 用选择器查询// jQuery $(selector); // Native document.querySelectorAll(selector);1.1 用 class 查询// jQuery $(.class); // Native document.querySelectorAll(.class); // or document.getElementsByClassName(class);getElementsByClassName是上面性能提示的首选test/query.spec.js中Query by class用例正是用document.getElementsByClassName(item)与$(.item)逐元素比对验证两者一致。1.2 用 id 查询// jQuery $(#id); // Native document.querySelector(#id); // or document.getElementById(id);1.3 用属性attribute查询// jQuery $(a[target_blank]); // Native document.querySelectorAll(a[target_blank]);1.4 在子元素中查询// jQuery $el.find(li); // Native el.querySelectorAll(li);对应测试用例Query in descendents断言$(#query-selector-test1).find(.item)与document.getElementById(query-selector-test1).querySelectorAll(.item)均返回 4 个元素且顺序一致。1.5 查找兄弟 / 上一个 / 下一个元素兄弟元素// jQuery $el.siblings(); // Native [].filter.call(el.parentNode.children, function(child) { return child ! el; });上一个元素// jQuery $el.prev(); // Native el.previousElementSibling;下一个元素// jQuery $el.next(); // Native el.nextElementSibling;1.6 closest从当前元素出发向 document 方向逐级上溯返回与给定选择器匹配的第一个祖先元素。// jQuery $el.closest(selector); // Native - 最新浏览器可用, IE 不支持 el.closest(selector); // Native - 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; }这段兼容实现的核心是matches的浏览器前缀归一化webkitMatchesSelector/mozMatchesSelector/msMatchesSelector与 1.7 的parentsUntil复用同一模式。1.7 parentsUntil从元素的父节点开始逐级向上把匹配的父元素收集起来直到碰到符合selector的元素为止返回 DOM 节点数组。// jQuery $el.parentsUntil(selector, filter); // Native function parentsUntil(el, selector, filter) { const result []; const matchesSelector el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; // match start from parent 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; }filter参数可选不传时收集全部祖先传入时只收集同时匹配filter的祖先。1.8 表单取值与索引Input/Textarea 取值// jQuery $(#my-input).val(); // Native document.querySelector(#my-input).value;求e.currentTarget是.radio集合中的第几个// jQuery $(e.currentTarget).index(.radio); // Native [].indexOf.call(document.querySelectorAll(.radio), e.currentTarget);1.9 iframe 内容$(iframe).contents()在 iframe 语境下返回contentDocument。获取 iframe contents// jQuery $iframe.contents(); // Native iframe.contentDocument;在 iframe 内查询// jQuery $iframe.contents().find(.css); // Native iframe.contentDocument.querySelectorAll(.css);1.10 获取 body// jQuery $(body); // Native document.body;1.11 获取与设置属性获取属性// jQuery $el.attr(foo); // Native el.getAttribute(foo);设置属性jQuery 的attr在内存中生效而不修改 DOM 之外的状态原生setAttribute行为一致// jQuery $el.attr(foo, bar); // Native el.setAttribute(foo, bar);读取data-属性// jQuery $el.data(foo); // Native使用 getAttribute el.getAttribute(data-foo); // Native若只需支持 IE 11 及以上使用 dataset el.dataset[foo];注意dataset的键会去掉data-前缀并转为 camelCase而getAttribute(data-foo)在更老的浏览器上也可用迁移时应按浏览器支持范围二选一。二、CSS Style样式读取与 class 操作2.1 CSS读取 style 值// jQuery $el.css(color); // Native // NOTE: 已知浏览器 bugstyle 值为 auto 时会返回 auto const win el.ownerDocument.defaultView; // null 表示不返回虚拟样式pseudo-element win.getComputedStyle(el, null).color;设置 style 值// jQuery $el.css({ color: #ff0011 }); // Native el.style.color #ff0011;同时读取或设置多个 style 值可以借助setStyles类工具函数原项目建议配合 oneuijs 的 dom utils 包实现核心思路是遍历样式对象逐项写入el.style。class 增删查改四件套这一组在 Atom 自身源码中也是高频用法下文仓库实践佐证一节有对应证据// 添加 class // jQuery: $el.addClass(className); // Native el.classList.add(className); // 移除 class // jQuery: $el.removeClass(className); // Native el.classList.remove(className); // 判断是否包含 // jQuery: $el.hasClass(className); // Native el.classList.contains(className); // 切换 class // jQuery: $el.toggleClass(className); // Native el.classList.toggle(className);2.2 宽度与高度宽度和高度处理方式相同以高度为例。Window 高度// window 高度 $(window).height(); // 与 jQuery 一致排除滚动条 window.document.documentElement.clientHeight; // 包含滚动条 window.innerHeight;文档高度// jQuery $(document).height(); // Native document.documentElement.scrollHeight;Element 高度// jQuery $el.height(); // Native 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; // 可能是浮点数border-box 时即 heightcontent-box 时相当于 height padding border el.getBoundingClientRect().height;三个取值口径差异的要点el.clientHeight是整数且不包含 bordergetBoundingClientRect().height精度更高可带小数但同样不含 border手写getHeight则从offsetHeight含 padding 与 border中减去四边 padding/border得到真正的 content 高度。2.3 Position 与 OffsetPosition相对最近定位祖先// jQuery $el.position(); // Native { left: el.offsetLeft, top: el.offsetTop }Offset相对文档// jQuery $el.offset(); // Native 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返回的是相对视口的坐标因此需要加上pageXOffset/pageYOffset即滚动量并减去clientTop/clientLeft文档根的外边框/边框偏移才能得到与 jQuery 一致的文档坐标。2.4 Scroll Top// jQuery $(window).scrollTop(); // Native (document.documentElement document.documentElement.scrollTop) || document.body.scrollTop;这个||写法覆盖了不同浏览器把滚动位置挂在documentElement或body上的差异。三、DOM 操作3.1 移除元素// jQuery $el.remove(); // Native el.parentNode.removeChild(el);3.2 Text// 获取 // jQuery: $el.text(); // Native el.textContent; // 设置 // jQuery: $el.text(string); // Native el.textContent string;3.3 HTML// 获取 // jQuery: $el.html(); // Native el.innerHTML; // 设置 // jQuery: $el.html(htmlString); // Native el.innerHTML htmlString;3.4 在子元素末尾追加Append把元素作为父元素的最后一个子节点插入// jQuery $el.append(div idcontainerhello/div); // Native el.insertAdjacentHTML(beforeend, div idcontainerhello/div);insertAdjacentHTML的四个位置参数beforeend/afterend/beforebegin/afterbegin分别对应追加到末尾、插入到外面之后、插入到外面之前、追加到开头一条 API 同时覆盖了 append/prepend 两个方向。3.5 在子元素开头插入Prepend// jQuery $el.prepend(div idcontainerhello/div); // Native el.insertAdjacentHTML(afterbegin, div idcontainerhello/div);3.6 在元素之前插入insertBefore// jQuery $newEl.insertBefore(queryString); // Native const target document.querySelector(queryString); target.parentNode.insertBefore(newEl, target);3.7 在元素之后插入insertAfter// jQuery $newEl.insertAfter(queryString); // Native const target document.querySelector(queryString); target.parentNode.insertBefore(newEl, target.nextSibling);注意原生 DOM 没有直接的insertAfter方法惯用技巧是借助target.nextSibling作为参照调用parentNode.insertBefore。3.8 is判断元素是否匹配某个选择器匹配则返回true// jQuery $el.is(selector); // Native el.matches(selector);3.9 clone// jQuery $el.clone(); // Native el.cloneNode(); // 深克隆请将参数设为 true3.10 empty移除全部子节点// jQuery $el.empty(); // Native el.innerHTML ;四、Ajax从 XHR/jQuery.ajax 到 Fetch APIFetch API 是替代 XMLHttpRequest 完成 ajax 请求的新标准Chrome 与 Firefox 原生支持也可以引入 polyfill 让其在旧版浏览器中工作。原文档给出的配套方案包括支持 IE9 的github/fetchpolyfill支持 IE8 的fetch-ie8用于 JSONP 请求的fetch-jsonp。即迁移路线是$.ajax→fetch跨域/旧浏览器场景再叠加对应 polyfillJSONP 需求则交给专门的 fetch-jsonp 模块。五、事件绑定、解绑与触发原文档指出如果需要完整的 namespace 命名空间与事件委托delegation能力可以考虑 oneuijs 的 dom events 工具包oui-dom-events日常场景用原生事件三件套即可。5.1 绑定事件// jQuery $el.on(eventName, eventHandler); // Native el.addEventListener(eventName, eventHandler);5.2 解绑事件// jQuery $el.off(eventName, eventHandler); // Native el.removeEventListener(eventName, eventHandler);原生解绑要求传入与绑定时同一个函数引用这是从 jQuery 迁移时最常见的坑jQuery 可以按字符串/命名空间批量解绑而原生 API 必须持有 handler 的引用例如保存为成员属性才能移除。5.3 触发事件Trigger// jQuery $(el).trigger(custom-event, {key1: data}); // Native 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);initCustomEvent分支是旧版浏览器如 IE的兼容路径第一个布尔参数是冒泡第二个是可取消。jQuery 通过第二参数传递的数据在原生 CustomEvent 中对应放在event.detail中监听侧需从e.detail读取。六、工具函数$.xxx静态方法的原生替代6.1 判断是否为数组isArray// jQuery $.isArray(range); // Native Array.isArray(range);6.2 去除首尾空白trim// jQuery $.trim(string); // Native string.trim();6.3 Object Assign如需在旧浏览器使用可引入Object.assignpolyfill如 ljharb 维护的 object.assign。// jQuery $.extend({}, defaultOpts, opts); // Native Object.assign({}, defaultOpts, opts);$.extend({}, a, b)的合并语义后者优先、浅拷贝与Object.assign一致是配置合并场景最直接的替换。6.4 包含判断contains// jQuery $.contains(el, child); // Native el ! child el.contains(child);前置的el ! child判定是必要的Node.contains规定节点包含其自身而 jQuery 的$.contains不认为元素包含自己。6.5 inArray// jQuery $.inArray(item, array); // Native array.indexOf(item);两者语义一致找到下标或返回-1。6.6 map// jQuery $.map(array, function(value, index) { }); // Native array.map(function(value, index) { });七、替代方案、多语言版本与浏览器支持相关替代资源原文档대안방법替代方案一节推荐了两类补充资源一是介绍用原生 JavaScript 处理常见事件、元素、ajax的 You Might Not Need jQuery 示例集二是可以在 npm 上按单个模块检索 DOM 能力的 npm-dom 与 webmodules 组织。它们与本文的整表对照互为补充前者偏技巧示例npm 模块包偏工程化拆分。多语言版本该文档在 fixture 中保留了完整的多语言对照均位于 spec/fixtures/git/repo-with-submodules/You-Dont-Need-jQuery 目录下韩文本文主体简体中文马来语印尼语葡萄牙语PT-BR越南语西班牙语俄语土耳其语意大利语浏览器支持范围原文档声明的浏览器支持矩阵如下ChromeFirefoxIEOperaSafari最新版 ✔最新版 ✔10 ✔最新版 ✔6.1 ✔也就是说文中的原生替代写法以IE 10为兼容底线个别条目如el.closest、el.dataset要求更新的浏览器文档中已分别给出 IE 10 兼容的手写实现见 1.6、1.11。文档以 MIT 协议发布。八、仓库实践佐证从测试 fixture 到 Atom 自身源码测试层Karma 驱动的等价性验证fixture 内的测试用例test/query.spec.js、test/css.spec.js、test/dom.spec.js、test/utilities.spec.js统一采用双跑比对模式同一段 DOM 上分别执行 jQuery 与原生 API用 Chai 断言两者逐元素相等。以 1.0 条目的验证为例li.item[data-rolered]的选择器查询断言了length 2且每个位置上的节点引用相同。这套用例与文档编号1.0–1.4 等一一对应相当于给本文每一张对照表都配了可执行的回归测试在 fixture 目录内执行npm run test即karma start --single-run即可复现。实现层Atom 自身就是原生 DOM 写法的实践者从源码结构看Atom 的src/目录本身就是文档主张的印证至少 15 个源文件如 src/tooltip.js、src/title-bar.js、src/panel.js、src/theme-manager.js 等直接调用classList.add/classList.toggle/classList.contains等 API 来驱动界面状态而不是引入类库封装。可以推断Atom 的 UI 组件代码验证了本文 2.1 节class 四件套在生产级 Electron 应用中的可用性——这也解释了该文档作为子模块 fixture 被长期保留的合理性它既是 Git 功能测试数据也是一份随仓库分发的、带测试的迁移参考实现。结语这份文档的价值在于把去 jQuery 化从口号拆成了 30 余条可直接替换的 API 对照并标注了每条的兼容性边界IE 10 底线、closest/dataset的新浏览器要求、getComputedStyle的已知 quirk。配合 fixture 中可运行的 Karma 测试与 Atom 自身源码中的真实用法它构成了一条文档 → 测试 → 生产代码相互印证的证据链当你的项目面临移除 jQuery 依赖的决策时可以按本文的条目逐一替换并用双跑比对式用例确认新旧行为等价再针对解绑事件需保留函数引用、contains需排除自身、querySelectorAll结果为 null 的三类高频差异做回归验证。【免费下载链接】atom:atom: The hackable text editor项目地址: https://gitcode.com/gh_mirrors/at/atom创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表