请别再用jQuery了!
大家好,很高兴又见面了,我是" 前端进阶 ",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
前端进阶
今天在逛Github时无意间发现一个仓库《You Dont Need jQuery》。打开来看,发现很有意思!而且作者列举了很多jQuery常用的方法以及原生实现,因此这篇文章将以此展开。话不多说,直接开始!
关于更多jQuery的讨论可以阅读文末我的另一篇文章《18岁了!老伙计jQuery过的怎样?》前言
前端发展迅速,现代浏览器已经实现了大量足以用于生产的 DOM/BOM API。 不必从头开始学习 jQuery 来进行 DOM 操作或其他类型事件处理。 与此同时,由于 React、Angular 和 Vue 等前端库的普及,直接操作 DOM 并非最好的方法。 该项目总结了 jQuery 方法的原生 Javascript 替代方法,并支持 IE 10+。
ℹ️ 注意:本文并非否定jQuery,而是探讨是否可以在没有jQuery的场景下实现功能原生实现方案并非在所有场景下都完全等效jQuery,建议做浏览器兼容性测试。1.jQuery选择器
可以使用 document.querySelector 或 document.querySelectorAll 代替常见的jQuery选择器,如 class、id 或 attribute。 不同之处在于:document.querySelector 返回第一个匹配的元素document.querySelectorAll 将所有匹配的元素作为 NodeList 返回。 可以使用 Array.prototype.slice.call(document.querySelectorAll(selector)); 将其转换为 Array。如果没有匹配的元素,jQuery 和 document.querySelectorAll 将返回 [],而 document.querySelector 将返回 null。
注意:document.querySelector 和 document.querySelectorAll 非常慢,如果想获得最佳性能,可以使用 document.getElementById、document.getElementsByClassName 或 document.getElementsByTagName方法。
比如下面的jQuery方法都可以考虑使用原生方法来替代:// jQuery $("selector"); // 原生替代方法 document.querySelectorAll("selector");
class选择器:// jQuery $(".class"); // 原生替代方法 document.querySelectorAll(".class"); // 原生替代方法 document.getElementsByClassName("class");
id选择器:// jQuery $("#id"); // 原生替代方法 document.querySelector("#id"); // 或者 document.getElementById("id"); // 或者 window["id"]
属性选择器:// jQuery $("a[target=_blank]"); // 原生替代方法 document.querySelectorAll("a[target=_blank]");
子级选择器:// jQuery $el.find("li"); // 原生替代方法 el.querySelectorAll("li");
所有兄弟元素 // jQuery $el.siblings(); // 原生替代方法 - Edge13+ [...el.parentNode.children].filter((child) => child !== el ); // 原生替代方法- Edge13+ Array.from(el.parentNode.children).filter((child) => child !== el ); // 原生替代方法 - IE10+ Array.prototype.filter.call(el.parentNode.children, (child) => child !== el );
前面兄弟元素:// jQuery $el.prev(); // 原生替代方法 el.previousElementSibling;
后面兄弟元素:// jQuery $el.next(); // 原生替代方法 el.nextElementSibling;
所有前面兄弟元素:// jQuery $el.prevAll($filter); // 原生替代方法 function getPreviousSiblings(elem, filter) { var sibs = []; while (elem = elem.previousSibling) { if (elem.nodeType === 3) continue; // 忽略text类型 if (!filter || filter(elem)) sibs.push(elem); } return sibs; }
所有后面兄弟元素:// jQuery $el.nextAll($filter); // 原生替代方法 function getNextSiblings(elem, filter) { var sibs = []; var nextElem = elem.parentNode.firstChild; do { if (nextElem.nodeType === 3) continue; // 忽略文本 if (nextElem === elem) continue; // 忽略自己 if (nextElem === elem.nextElementSibling) { if (!filter || filter(elem)) { sibs.push(nextElem); elem = nextElem; } } } while(nextElem = nextElem.nextSibling) return sibs; }
closest方法(通过提供的选择器返回第一个匹配的元素,从当前元素向上遍历它在 DOM 树中的祖先):// jQuery $el.closest(selector); // 原生替代方法,不支持IE el.closest(selector); // 原生替代方法 - IE10+ function closest(el, selector) { const matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; while (el) { // while循环一直往上寻找 if (matchesSelector.call(el, selector)) { return el; } else { el = el.parentElement; } } return null; }
jQuery的parentsUntil方法:// jQuery $el.parentsUntil(selector, filter); // 原生替代方法 function parentsUntil(el, selector, filter) { const result = []; const matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; // 使用matchs方法 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; }2.jQuery的CSS & Style操作方法获取/设置Style// jQuery $el.css("color"); // 原生替代方法 // 注意: 已知错误,如果样式值为"auto",将返回"auto" const win = el.ownerDocument.defaultView; // null 表示不返回伪样式 win.getComputedStyle(el, null).color; // 原生替代方法 el.style.color = "#f01";添加/移除/判断/toggle类// jQuery $el.addClass(className); // 原生替代方法 el.classList.add(className); // 添加class el.classList.remove(className); // 移除class el.classList.contains(className); // 包含class el.classList.toggle(className); // toggle classPosition & Offset// jQuery $el.position(); // 原生替代方法 { left: el.offsetLeft, top: el.offsetTop } // 获取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 }; }ScrollTop// jQuery $(window).scrollTop(); // 原生替代方法 (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;3.DOM操作移除元素// jQuery $el.remove(); // 原生替代方法 el.parentNode.removeChild(el);get/set HTML// jQuery $el.html(); // 原生替代方法 el.innerHTML; // jQuery设置html $el.html(htmlString); // 原生替代方法设置html el.innerHTML = htmlString;append方法// jQuery:DOMString 和 Node 对象的统一语法 $parent.append(newEl | "Hello World"); // 原生方法:不同语法 parent.insertAdjacentHTML("beforeend", "Hello World"); parent.appendChild(newEl); // Native (ES6-way):统一语法 parent.append(newEl | "Hello World");prepend方法// jQuery:DOMString 和 Node 对象的统一语法 $parent.prepend(newEl | "Hello World"); // 原生方法:不同语法 parent.insertAdjacentHTML("afterbegin", "Hello World"); parent.insertBefore(newEl, parent.firstChild); // Native (ES6-way):统一语法 parent.prepend(newEl | "Hello World");insertBefore// jQuery $newEl.insertBefore(selector); // 原生方法 el.insertAdjacentHTML("beforebegin ", "Hello World"); // 原生 (元素) const el = document.querySelector(selector); if (el.parentNode) { // 父元素的insertBefore el.parentNode.insertBefore(newEl, el); }insertAfter// jQuery $newEl.insertAfter(selector); // 原生 (HTML字符串) el.insertAdjacentHTML("afterend", "Hello World"); // 原生 (元素) const el = document.querySelector(selector); if (el.parentNode) { // 利用父元素的insertBefore el.parentNode.insertBefore(newEl, el.nextSibling); }wrap// jQuery $(".inner").wrap(""); // 原生方法 Array.from(document.querySelectorAll(".inner")).forEach((el) => { const wrapper = document.createElement("p"); wrapper.className = "wrapper"; el.parentNode.insertBefore(wrapper, el); // 父元素的insertBefore wrapper.appendChild(el); });replaceWith// jQuery $(".inner").replaceWith(""); //原生替代方法- >= Edge17+ Array.from(document.querySelectorAll(".inner")).forEach((el) => { const outer = document.createElement("p"); outer.className = "outer"; el.replaceWith(outer); }); // 原生替代方法 Array.from(document.querySelectorAll(".inner")).forEach((el) => { const outer = document.createElement("p"); outer.className = "outer"; // 调用父元素的replaceChild el.parentNode.replaceChild(outer, el); });4.jQuery的Ajax请求
Fetch API 是替代 XMLHttpRequest 执行 ajax 的新标准。 它适用于 Chrome 和 Firefox,您可以使用 polyfills 使其适用于旧版浏览器。
在 IE9+ 上可以使用 github/fetch 或在 IE8+ 上使用 fetch-ie8,fetch-jsonp 来发出 JSONP 请求。// jQuery $(selector).load(url, completeCallback) // 原生替代方法 fetch(url).then(data => data.text()).then(data => { document.querySelector(selector).innerHTML = data }).then(completeCallback) // POST方法 await fetch("/my/url", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) });5.事件DOMContentLoaded // jQuery $(document).ready(eventHandler); // 原生替代方法 // 检查 DOMContentLoaded 是否已经完成 if (document.readyState !== "loading") { eventHandler(); } else { document.addEventListener("DOMContentLoaded", eventHandler); } // 原生替代方法 // 示例 2 - 三元运算符 // 异步检查 DOMContentLoaded 是否已经完成 (async function() { (document.readyState !== "loading") ? eventHandler() : document.addEventListener("DOMContentLoaded", function() { eventHandler(); // 事件处理函数 }); })(); // 原生替代方法 // 示例 3 - 三元运算符 //非异步检查 DOMContentLoaded 是否已经完成 (function() { (document.readyState !== "loading") ? eventHandler() : document.addEventListener("DOMContentLoaded", function() { eventHandler(); // 事件处理函数 }); })();绑定/移除事件// jQuery $el.on(eventName, eventHandler); // 添加:原生替代方法 el.addEventListener(eventName, eventHandler); // jQuery $el.off(eventName, eventHandler); // 移除:原生替代方法 el.removeEventListener(eventName, eventHandler);trigger事件// jQuery $(el).trigger("custom-event", {key1: "data"}); // 通过CustomEvent原生方法替代jQuery的trigger方法 if (window.CustomEvent) { const event = new CustomEvent("custom-event", {detail: {key1: "data"}}); } else { const event = document.createEvent("CustomEvent"); // 构造CustomEvent实例 event.initCustomEvent("custom-event", true, true, {key1: "data"}); } // 调用dispatchEvent发布事件 el.dispatchEvent(event);6.jQuery的Promise方法
Promise 表示异步操作的最终结果。 jQuery 有自己的方式来处理Promise。本机 JavaScript 实现了一个精简的最小 API 来根据 Promises/A+ 规范处理 promises。 done, fail, always
done 在 promise 被resolve时被调用,fail 在 promise 被reject时被调用,当 promise 被解决或被拒绝时always被调用。 // jQuery $promise.done(doneCallback).fail(failCallback).always(alwaysCallback) // Promise原生替代方法 promise.then(doneCallback, failCallback).then(alwaysCallback, alwaysCallback)when
when 用于处理多个promise。当所有promise都resolve时,它将resolve,如果任何一个被拒绝,它就会拒绝。 // jQuery $.when($promise1, $promise2).done((promise1Result, promise2Result) => { }); // when原生替代方法 Promise.all([$promise1, $promise2]).then([promise1Result, promise2Result] => {});Deferred
是jQuery一种创建Promise的方式。 // jQuery function asyncFunc() { const defer = new $.Deferred(); setTimeout(() => { if(true) { defer.resolve("some_value_computed_asynchronously"); } else { defer.reject("failed"); } }, 1000); return defer.promise(); } // 原生替代方法 function asyncFunc() { return new Promise((resolve, reject) => { setTimeout(() => { if (true) { resolve("some_value_computed_asynchronously"); } else { reject("failed"); } }, 1000); }); } // 通过实现jQuery的Deferred // 注意:jQuery的Deferred是它实现Promise能力的基础 function defer() { const deferred = {}; const promise = new Promise((resolve, reject) => { deferred.resolve = resolve; deferred.reject = reject; }); deferred.promise = () => { return promise; }; return deferred; } // asyncFunc 函数伺机调用resolve/reject方法 function asyncFunc() { const defer = defer(); setTimeout(() => { if(true) { // resolve defer.resolve("some_value_computed_asynchronously"); } else { // reject defer.reject("failed"); } }, 1000); return defer.promise(); }7.jQuery实现Animation动画Show & Hide// jQuery $el.show(); $el.hide(); // 原生替代方法 // 更多信息参考链接:https://github.com/oneuijs/oui-dom-utils/blob/master/src/index.js#L363 el.style.display = ""|"inline"|"inline-block"|"inline-table"|"block"; el.style.display = "none";toggle// jQuery $el.toggle(); // 原生替代方法 if (el.ownerDocument.defaultView.getComputedStyle(el, null).display === "none") { // 设置display属性值 el.style.display = ""|"inline"|"inline-block"|"inline-table"|"block"; } else { el.style.display = "none"; }FadeIn & FadeOut// jQuery $el.fadeIn(3000); $el.fadeOut(3000); // 原生替代方法:fadeOut function fadeOut(el, ms) { if (ms) { el.style.transition = `opacity ${ms} ms`; el.addEventListener( "transitionend", // 动画结束 function(event) { el.style.display = "none"; }, false ); } el.style.opacity = "0"; } // 原生替代方法:fadeIn function fadeIn(elem, ms) { elem.style.opacity = 0; if (ms) { let opacity = 0; const timer = setInterval(function() { // setInterval调用 opacity += 50 / ms; if (opacity >= 1) { clearInterval(timer); opacity = 1; } // 设置opacity elem.style.opacity = opacity; }, 50); } else { elem.style.opacity = 1; } }SlideUp & SlideDown// jQuery $el.slideUp(); $el.slideDown(); // 原生替代方法 const originHeight = "100px"; el.style.transition = "height 3s"; // 原生替代方法:slideUp el.style.height = "0px"; // 原生替代方法:slideDown el.style.height = originHeight;8.本文总结
本文主要和大家介绍jQuery的很多常用方法如何使用原生方法来替代,比如:选择器、动画、Promise、事件、Ajax、DOM操作等等,从而引出"你可能不需要jQuery"的结论。当然,每个人都会有不同的看法。如果你觉得引入jQuery的收益对于你的项目很大,那么你还是可以坚持使用它。
同时,文末的参考资料提供了大量优秀文档以供学习,如果有兴趣可以自行阅读。如果大家有什么疑问欢迎在评论区留言。参考资料
https://github.com/camsong/You-Dont-Need-jQuery
http://www.kehuanxianshi.com/work/JavaScript/now-ever-might-not-need-jquery.html
https://www.toutiao.com/article/7198323737454723622/
https://youmightnotneedjquery.com/
https://medium.com/@navneet.sahota/you-dont-need-jquery-ec070bc75238
图片版权:来自Navneet Singh的文章《You don’t need jQuery》!