jQuery 那些容易被忽略的问题
伴随Vue
、Angular
、React
等编译式前端框架的崛起,前端开发人员逐渐从繁琐的
DOM
操作当中解脱出来。但是在项目实践过程当中,依然存有诸多问题需要通过直接操作
DOM 来解决,虽然现代化浏览器已经支持selectAll()
等 HTML5
新特性,但是针对一些强调页面兼容性的场景,为了屏蔽各款浏览器解析引擎所遵循规范的差异,依然需要借助于jQuery
来完成
DOM 文档操作。
因此jQuery
这款诞生于 2006 年的 JavaScript
库,依然在现代化前端开发当中扮演着较为重要角色。本文结合笔者
Web
前端开发工作当中积累的实践经验,较为系统的总结了jQuery
实践过程当中一些比较容易被开发人员所忽略的问题。例如
jQuery 对象与 DOM 对象的相互转换、jQuery
选择器性能、异步对象$.Deferred()
以及 JavaScript
性能优化相关的话题。
jQuery 对象和 DOM 对象
- DOM 对象:通过原生
javascript(如
getElementsByTagName
或getElementId
)获取的 html 节点。
- jQuery 对象:被 jQuery 包装过的 DOM 对象。
不能交换使用 jQuery 对象、DOM 对象上的属性,例如上面代码中的
innerHTML
与html()
。
jQuery 对象->DOM 对象
jQuery 提供了 2 种转换 DOM 对象的方法:
(1)jQuery
对象是一个类似于数组的对象,因此可以通过数组运算符[]
获取指定索引的
DOM 对象。
(2)通过 jQuery 本身提供的get(index)
方法来获取指定
index 的对象。
DOM 对象->jQuery 对象
将 DOM 对象通过$()
函数包装起来,就可以获得 jQuery
对象。
可以考虑使用$作为前缀为 jQuery 对象进行命名,例如上面代码中的变量
jq
可以命名为$jq
。
jQuery 选择器性能
提升选择器性能的有效途径是为选择器指定上下文,并以上下文为基础使用first()
、last()
、find()
、filter()
、hasClass()
等
jQuery 筛选 API。
下面对 jQuery 选择器的性能由高向低进行排序:
1、ID 选择器
底层通过调用document.getElementById()
实现。
2、标签选择器
底层通过调用document.getElementsByTagName()
实现。
3、类选择器
底层通过调用document.getElementsByClassName()
实现。
4、属性及其它选择器
底层通过对 HTML 字符串进行正则表达式匹配来实现。
jQuery 底层有使用原生
document.querySelectorAll()
,可以有效提升 IE8 及以上浏览器当中选择器的性能。
jQuery 作者已经将选择器引擎独立为Sizzle库,而 Sizzle 会按照从右到左的顺序来解析选择器字符串,从而提高查询效率,缩小查找范围和遍历次数。
缓存常用的 jQuery 选择器对象
将需要常用的 jQuery 选择器对象赋值给一个局部或全局变量,是有效提升 jQuery 运行性能的良好开端。
1 | var $jq = $("#app"); |
减少循环时的 DOM 操作
在for
、while
、$.each()
等循环语句中,尽量减少
DOM 操作的次数,最好先将模板在循环中组装完成之后再一次性插入 DOM。
1 | var $app = $("#app"); |
使用原生方式处理 jQuery 数组
jQuery
选择器的结果是一个数组类型的对象,建议使用for
或while
等原生语法对其进行处理,而非
jQuery 封装过的$.each()
。
1 | <body> |
1 | var $demo = $(".demo"); |
可以通过关键字length
检查数组长度,从而判断 jQuery
对象是否存在。
尽可能使用事件委托
jQuery3.x
版本继续简化了事件委托函数,仅剩下on()
、off()
、one()
、trigger()
、triggerHandler()
五个事件处理函数。
1 | <div id="app"> |
1 | /* on可以用于处理冒泡事件,但是无法捕获事件 */ |
单页面场景下绑定的
on
事件,必须在路由切换时通过off
解除事件绑定,否则会造成大量无用的事件句柄堆积在内存。
通过 extend()封装可复用代码
jQuery.extend()
:拓展全局对象$,例如下面例子中的$.test()
,$.demo()
;jQuery.fn.extend()
:拓展jQuery 对象数组,例如下面例子中的$("div").test()
,$("div").demo()
;
1 | (function ($) { |
两种方式的最大区别在于自定义方法所属的宿主对象不同。
使用 HTML5 的 data 属性绑定数据
通过 HTML5 提供的 data
属性可以更加方便的完成数据绑定,特别是在不借助handlebar
、lodash.template()
等模板引擎的时候。
1 | <div |
1 | $("#app").data("number"); |
尽量使用原生 JavaScript
在不影响浏览器兼容性的情况下,尽量使用 JavaScript 原生 API。
1 | var $demo = $(".demo"); // 缓存选择器 |
$(document).ready()
该函数内的代码会在 DOM 加载完毕后,内容(如图片)加载完成前执行;生产环境中,尽可能在每个 js 文件下使用该函数。
JavaScript 原生的
window.onload()
只会在 DOM 和图片等资源全部加载完成之后才执行。
延迟对象$.Deferred()
Deferred()
是一个工厂函数,用来建立新的 deferred
对象(deferred [dɪ'fɜ:d]
adj.延缓的),该对象上可以注册多个回调函数队列,这些函数的执行依赖于任意同步或异步函数的执行结果(sucess
或failure
)。该对象可以视为
jQuery 版本的Promise实现,可以更加优雅的解决 JavaScript
回调嵌套的问题。
jQuery 的 Deferred 对象是基于CommonJS Promises/A规范设计的。
deferred.notify()
触发 Deferred 上progress相关的回调函数。deferred.resolve()
Resolve 一个 Deferred 对象,并触发resolve状态相关的回调函数。deferred.reject()
Reject 一个 Deferred 对象,并触发reject状态相关的回调函数。deferred.progress()
该函数在 Deferred 对象生成progress通知时被调用。deferred.done()
该函数在 Deferred 对象被resolve时调用。deferred.fail()
该函数在 Deferred 对象被rejecte时调用。deferred.catch()
该函数在 Deferred 对象被rejecte时调用。deferred.then()
Deferred 对象resolved、rejected、progress时,都会被触发的回调函数。deferred.promise()
返回一个延迟的 Promise 对象。&.when()
提供一种基于零个或多个 Thenable 对象执行回调函数的方式,其参数是一个代表异步事件的 Deferred 对象。$("selector").promise()
返回一个 Promise 对象去观察所有绑定到集合、队列的确定类型行为是否已经完成。
1 | var deferred = $.Deferred(); |
$.ajax()
返回的就是一个 deferred 对象。
jQuery 那些容易被忽略的问题