随着JavaScript本身的完善,越来越多的人开始喜欢使用原生JavaScript 开发代替各种库,其中不少人发出了用原生JavaScript代替jQuery的声音。这并不是什么坏事,但也不见得就是好事。如果你真的想把jQuery从前端依赖库中移除掉,我建议你慎重考虑。
首先jQuery是一个第三方库。库存在的价值之一在于它能极大地简化开发。一般情况下,第三方库都是由原生语言特性和基础API库实现的。因此,理论上来说,任何库第三方库都是可以用原生语言特性代替的,问题在于是否值得?
jQuery的作用
引用一段jQuery官网的话:
jQuery is a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers.
这一段话很谦虚的介绍了jQuery在处理DOM和跨浏览器方面做出的贡献。而事实上,这也正是我们选用jQuery的主要原因,并顺带使用了它带来的一些工具,比如数组工具,Deferred等。
对于我来说,最常用的功能包括
在DOM树中进行查询
修改DOM树及DOM相关操作
事件处理
Ajax
Deferred和Promise
对象和数组处理
还有一个一直在用却很难在列清单时想到的——跨浏览器
到底是谁在替代谁?
上面提到的所有功能都能用原生代码来实现。从本质上来说,jQuery就是用来代替原生实现,以达到减少代码,增强可读性的目的的——所以,到底是用jQuery代替原生代码,还是用原生代码代替jQuery?这个先后因果关系可否搞明白?
我看到说用querySelectorAll()代替$()的时候,不禁在想,用jQuery一个字符就能解决的,为什么要写十六个字符?大部分浏览器是有实现$(),但是写原生代码的时候你会考虑$()的浏览器兼容性吗?jQuery已经考虑了!
我看到一大堆创建DOM结构的原生JavaScript代码的时候,不禁在想,用jQuery只需要一个方法链就解决了,我甚至可以用和HTML结构类似的代码(包含缩进),比如
这段代码用document.createElement()来实现完全没有问题,只不过代码量要大得多,而且会出现大量重复(或类似)的代码。当然是可以把这些重复代码提取出来写成函数的……不过jQuery已经做了。
注,拼HTML的方法实在弱爆了,既容易出错,又不易阅读。如果有ES6的字符串模板之后,用它来写HTML也是个不错的主意。
就DOM操作这一部分来说,jQuery仍然是一个非常好用的工具。这是jQuery替代了原生JavaScript,以前如此,现在仍然如此。
没落的jQuery工具函数
jQuery 2006年被发明出来的时候,还没有ES5(2011年6月发布)。即使在ES5发布之后很长一段时间里,也不是所有浏览器都支持。因此在这一时期,除DOM操作外,jQuery的巨大贡献在于解决跨浏览器的问题,以及提供了方便的对象和数组操作工具,比如each()、index()和filter等。
如今ECMAScript刚刚发布了2017的标准,浏览器标准混乱的问题也已经得到了很好的解决,前端界还出现了Babel这样的转译工具和TypeScript之类的新语言。所以现在大家都尽可放心的使用各种新的语言特性,哪怕ECMAScript的相关标准还在制定中。在这一时期,jQuery提供的大量工具方法都已经有了原生替代品——在使用上差别不大的情况下,确实宁愿用原生实现。
事实上,jQuery也在极尽可能地采用原生实现,以提高执行效率。jQuery没有放弃这些已有原生实现的工具函数/方法,主要还是因为向下兼容,以及一如既往的提供浏览器兼容性——毕竟不是每一个使用jQuery的开发者都会使用转译工具。
那么,对于JavaScript开发者而言,jQuery确实有很多工具方法可以被原生JavaScript函数/方法替代。比如
$.parseJSON()可以用JSON.parse()替代,而且JSON.stringify()还弥补了jQuery没有$.toJSON()的不足;
$.extend()的部分功能可以由Object.assign()替代`
$.fn的一些数据处理工具方法,比如each()、index()等都可以用Array.prototype中相应的工具方法替代,比如forEach()、indexOf()等。
$.Deferred()和jQuery Promise在某些情况下可以用原生Promise替代。它们在没有ES6之前也算是个不错的Promise实现。
......
$.fn就是jQuery.prototype,也就是jQuery对象的原型。所以在其上定义的方法就是jQuery对象的方法。
这些工具方法在原生JavaScript中已经逐渐补充完善,但它们仍然只是在某些情况下可以被替代……因为jQuery对象是一个特有的数据结构,针对jQuery自身创建的工具方法在作用于jQuery对象的时候会有一些针对性的实现——既然DOM操作仍然不能把jQuery抛开,那这些方法也就不可能被完全替换掉。
jQuery与原生JavaScript的结合
有时候需要用jQuery,有时候不需要用,该如何分辨?
jQuery的优势在于它的DOM处理、Ajax,以及跨浏览器。如果在项目中引入jQuery,多半是因为对这些功能的需求。而对于不操作DOM,也不需要考虑跨浏览器(比如用于转译工具)的部分,则考虑尽可能的用原生JavaScript实现。
如此以来,一定会存在jQuery和原生JavaScript的交集,那么,就不得不说说需要注意的地方。
jQuery对象实现了部分数组功能的伪数组
首先要注意的一点,就是jQuery对象是一个伪数组,它是对原生数组或伪数组(比如DOM节点列表)的封装。
如果要获得某个元素,可以用[]运算符或get(index)方法;如果要获得包含所有元素的数组,可以使用toArray()方法,或者通过ES6中引入的Array.from()来转换。
注意each/map和forEach/map回调函数的参数顺序
jQuery定义在$.fn上的each()和map()方法与定义在Array.prototype上的原生方法forEach()和map()对应,它们的参数都是回调函数,但它们的回调函数定义有一些细节上的差别。
$.fn.each()的回调定义如下:
Function(Integerindex,Elementelement)
回调的第一个参数是数组元素所在的位置(序号,从0开始),第二个参数是元素本身。
而Array.prototype.forEach()的回调定义是
Function(currentValue,index,array)
回调的第一个参数是数组元素本身,第二个参数才是元素所有的位置(序号)。而且这个回调有第三个参数,即整个数组的引用。
请特别注意这两个回调定义的第一个参数和第二个参数,所表示的意义正好交换,这在混用jQuery和原生代码的时候很容易发生失误。
对于$.fn.map()和Array.prototype.map()的回调也是如此,而且由于这两个方法同名,发生失误的概率会更大。
注意each()/map()中的this
$.fn.each()和$.fn.map()回调中经常会使用this,这个this指向的就是当前数组元素。正是因为有这个便利,所以jQuery在定义回请贩时候没有把元素本身作为第一个参数,而是把序号作为第一个参数。
不过ES6带来了箭头函数。箭头函数最常见的作用就是用于回调。箭头函数中的this与箭头函数定义的上下文相关,而不像普通函数中的this是与调用者相关。
现在问题来了,如果把箭头函数作为$.fn.each()或$.fn.map()的回调,需要特别注意this的使用——箭头函数中的this不再是元素本身。鉴于这个问题,建议若非必要,仍然使用函数表达式作为$.fn.each()和$.fn.map()的回调,以保持原有的jQuery编程习惯。实在需要使用箭头函数来引用上下文this的情况下,千万记得用其回调定义的第二个参数作为元素引用,而不是this。
$.fn.map()返回的并不是数组
与Array.prototype.map()不同,$.fn.map()返回的不是数组,而是jQuery对象,是伪数组。如果需要得到原生数组,可以采用toArray()或Array.from()输出。
jQuery Promise
jQuery是通过$.Deferred()来实现的Promise功能。在ES6以前,如果引用了jQuery,基本上不需要再专门引用一个Promise库,jQuery已经实现了Promise的基本功能。
不过jQuery Promise虽然实现了then(),却没有实现catch(),所以它不能兼容原生的Promise,不过用于co或者ES2017的async/await毫无压力。
虽然jQuery的Promise没有catch(),但是提供了fail事件处理,这个事件在Deferred reject()的时候触发。相应的还有done事件,在Deferred resovle()的时候触发,以及always事件,不论什么情况都会触发。
与一次性的then()不同,事件可以注册多个处理函数,在事件触发的时候,相应的处理函数会依次执行。另外,事件不具备传递性,所以fail()不能在写在then()链的最后。
结语
总的来说,在大量操作DOM的前端代码中使用jQuery可以带来极大的便利,也使DOM操作的相关代码更易读。另一方面,原生JavaScript带来的新特性确实可以替代jQuery的部分工具函数/方法,以降低项目对jQuery的依赖程序。
jQuery和原生JavaScript应该是共生关系,而不是互斥关系。应该在合适的时候选用合适的方法,而不是那么绝对的非要用谁代替谁。
�S����