告别 jQuery,拥抱新时代!

发表时间: 2023-02-22 06:11

家好,很高兴又见面了,我是"前端‬进阶‬",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

前端‬进阶

今天在逛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);// 原生替代方法,不支持IEel.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);// 添加classel.classList.remove(className);// 移除classel.classList.contains(className);// 包含classel.classList.toggle(className);// toggle class

Position & Offset

// jQuery$el.position();// 原生替代方法{ left: el.offsetLeft, top: el.offsetTop }// 获取offsetfunction 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);// 原生替代方法设置htmlel.innerHTML = htmlString;

append方法

// jQuery:DOMString 和 Node 对象的统一语法$parent.append(newEl | '<div id="container">Hello World</div>');// 原生方法:不同语法parent.insertAdjacentHTML('beforeend', '<div id="container">Hello World</div>');parent.appendChild(newEl);// Native (ES6-way):统一语法parent.append(newEl | '<div id="container">Hello World</div>');

prepend方法

// jQuery:DOMString 和 Node 对象的统一语法$parent.prepend(newEl | '<div id="container">Hello World</div>');// 原生方法:不同语法parent.insertAdjacentHTML('afterbegin', '<div id="container">Hello World</div>');parent.insertBefore(newEl, parent.firstChild);//  Native (ES6-way):统一语法parent.prepend(newEl | '<div id="container">Hello World</div>');

insertBefore

// jQuery$newEl.insertBefore(selector);//  原生方法el.insertAdjacentHTML('beforebegin ', '<div id="container">Hello World</div>');// 原生 (元素)const el = document.querySelector(selector);if (el.parentNode) {  // 父元素的insertBefore  el.parentNode.insertBefore(newEl, el);}

insertAfter

// jQuery$newEl.insertAfter(selector);// 原生 (HTML字符串)el.insertAdjacentHTML('afterend', '<div id="container">Hello World</div>');// 原生 (元素)const el = document.querySelector(selector);if (el.parentNode) {  // 利用父元素的insertBefore  el.parentNode.insertBefore(newEl, el.nextSibling);}

wrap

// jQuery$('.inner').wrap('<div class="wrapper"></div>');//  原生方法Array.from(document.querySelectorAll('.inner')).forEach((el) => {  const wrapper = document.createElement('div');  wrapper.className = 'wrapper';  el.parentNode.insertBefore(wrapper, el);  // 父元素的insertBefore  wrapper.appendChild(el);});

replaceWith

// jQuery$('.inner').replaceWith('<div class="outer"></div>');//原生替代方法- >= Edge17+Array.from(document.querySelectorAll('.inner')).forEach((el) => {  const outer = document.createElement('div');  outer.className = 'outer';  el.replaceWith(outer);});// 原生替代方法Array.from(document.querySelectorAll('.inner')).forEach((el) => {  const outer = document.createElement('div');  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的方式。

// jQueryfunction 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#L363el.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);// 原生替代方法:fadeOutfunction 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';}// 原生替代方法:fadeInfunction 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';// 原生替代方法:slideUpel.style.height = '0px';// 原生替代方法:slideDownel.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》!