02期:jQuery最佳实践(包含常见优化写法)

来源:互联网 发布:数据库服务 编辑:程序博客网 时间:2024/06/06 02:00

​前言

自01期分享了《web浏览器传播机制》之后,本打算在02期补充一些关于jQuery事件委托机制的内容,以加深大家对web浏览器传播机制的理解。但考虑到单独拿一期来讲jQuery事件委托机制,内容可能会略显单薄。与此同时,本人最近参与了一个以jQuery为核心框架的前端项目(需要兼容ie8浏览器),但是从组员的代码中,明显感觉到部分同学对jQuery的一些优化写法缺乏实践经验,造成这种状况的主要原因恐怕是MVVM框架其势正盛,在很多项目中已逐渐取代了jQuery核心框架的地位,以致减少了前端同学对jQuery的可实践机会。所以我想把02期分享的主题稍微再升华一下,系统分享一下jQuery的一些最佳实践,而不仅仅是局限于jQuery事件委托机制,并稍微介绍一下各个模块的要点。


目标分享读者

此次分享的目标读者主要是对jQuery使用较少或掌握不深的同学,jQuery老司机也不妨进来叙叙旧,分享一下你掌握的姿势~


现在我们来进入今天的主题:


一、使用documentready事件(尽早为元素绑定事件监听或初始化插件)

先来看一段代码,

相信任何用过jQuery的同学,都对这段代码再熟悉不过。但我觉得,越常见的东西,我们往往越容易忽略其技术细节。在继续往下看之前,各位不妨先试着回答以下几个问题:


1. ready事件的触发条件是?

2. ready事件的handler适合执行什么操作?

3. ready事件可以用于任何一个元素节点吗?

4. 它和window对象的load事件有什么区别?

5. DOM加载完毕和window加载完毕,二者具体不同在哪里?


没有思考的话,请不要往下看(不然围观你)

想好了吗?想好了我们一起来一一解答:

首先回答第一个问题(ready事件的触发条件是?)

是当DOM已经完全加载时(其它等价说法还有DOM ready、DOM生成、DOM稳定)。以下截图包含官方答案:

然后来回答第二个问题(ready事件的handler适合执行什么操作?)

先看官方的回答

官方的意思就是说,ready的handler适合执行的操作(文中对应tasks),是那些在用户还没有看到页面或和页面进行交互前,就需要完成的操作(以确保用户对页面的浏览与交互不受影响),如事件绑定和插件初始化。

接着继续回答第三个问题(ready事件可以用于任何一个元素节点吗?)

答案是不能的,官方接口文档已详细说明,仅作用于DOM,即document,而不是DOM元素。

我们要区分DOM和DOM元素这两个概念,前者是指代document,而后者实际是DOM's 元素,即domcument’元素;前者是一棵树,而后者是结点。


再回答第四个问题(DOM ready事件和window对象的load事件有什么区别?)

1. 触发时机不同,DOM ready事件先于window的load事件,前者只需要document加载完毕,后者需要window加载完毕;

2. DOM ready事件是jQuery实现的自定义事件,而window的load事件是W3C定义的标准事件;

3. 适合执行的操作不一样。前者适合的操作是:那些在用户还没有看到页面或和页面进行交互前,就需要完成的操作(以确保用户对页面的浏览与交互不受影响),后者适合执行的操作是:依赖于页面的assets(可理解为外部资源)加载完成方能执行的操作,如获取某个图片的尺寸,其中图片也归属于页面assets。

最后回答第五个问题(DOM加载完毕和window加载完毕,二者具体不同在哪里?)

DOM加载完毕,指的是DOM已经建立,各节点已经生成好(暂不考虑结点的渲染);而window加载完毕,指的是除了DOM已稳定之外,还要等页面assets(外部资源,包括link的文件如css和字体文件等、js文件、图片、子窗口等)也加载完毕。


why

前面五个问题,都是为了让同学们了解一些基本概念,现在回到本模块的主题:为什么说使用DOM的ready事件,在其handler里尽早为DOM元素绑定事件监听和初始化插件等,是一种最佳实践(优化写法)?换一个问法,为什么要在DOM一加载完毕,就执行这一类操作:给DOM元素添加事件监听和初始化插件等,而不等到window的load事件触发时


以一个背景是图片且注册了click事件监听的按钮来解释:在网络环境不好的情况下,按钮的背景图片可能需要较长的加载时间,而在实际应用场景中,在这个图片还没有加载成功前,用户就可能一次或多次点击了这个按钮。


试想,如果要等到window加载完才给按钮绑定click事件监听,那么在网络环境不好的情况下,就会导致事件监听迟迟不能绑定到按钮上,用户不免会崩溃:不给看,还不给用。所以,我们要尽早给DOM元素绑定事件监听(DOM一加载完毕就绑定,而不必等到window加载完毕)。在任何情况下,美化都不应该阻碍功能,要先保证功能,再谈美化


how

谈完了为什么用,再讲一下怎么用。虽然关于给DOM事件绑定ready事件的语法很多,但是官方新版仅推荐使用$(function () {}),原因就是其它写法要么效率低,要么会让使用者产生错误想象,如$(document).ready(handler)很容易让使用者认为DOM元素也有ready事件,前面的问题3也是为了强调和澄清一个事实:仅document有ready事件。以下摘自官方原话:


二、使用事件委托机制(减少事件监听器的重复绑定)


定义

先给事件委托机制下个定义:将本来要绑定到一个或多个子元素的(事件类型和处理均相同的)事件监听器,转而绑定到其(共同的)外层元素,通过对外层元素的事件监听处理,来实现对这些子元素的监听处理。


应用场景及好处

事件委托机制主要有下两个应用场景:一是需给后插入到DOM树的子元素添加事件监听,使用jQuery事件委托机制,就能动态地给子元素“添加”事件监听;二是众多子元素拥有事件类型和处理均相同的事件监听器,那么将这些事件监听器合众为一,并委托给它们共同的外层元素,就可以减少了事件监听器的重复绑定,这样做不仅提高了性能,也减少了代码冗余。


实现原理

它的实现原理其实就是基于浏览器的事件传播机制的两个特性:1. 具有嵌套关系的元素,内层元素的可传播事件被触发时,其外层元素的同类型事件也会被触发;2. 由相同的用户行为触发的可传播事件,不管是内层元素的handler,还是外层元素的handler,其event.target始终一致。

正是基于这两个特性,使得我们有实现事件委托的思路: 

当委托给外层元素的事件监听被触发时:

1. 从event.target元素开始(包括其自身),向外层开始搜寻,看有没有和给定子元素选择器匹配的元素,无则退出事件处理,有则进行下一步;

2. 判断匹配的元素是否包含于被委托了事件监听的外层元素,无则退出事件处理,有则进行下一步;

3. 到达第三步,说明委托外层元素进行事件处理的子元素,其事件确实触发了,此时就可以执行子元素委托给外层元素的handler, 并把相应的事件信息,封装到一个事件对象中,一同传给这个handler。

算法实现大致如下:

demo(拓展)

  • html

  • view(点击绿色区域,即对应span)


  • js(同时调用自实现和jQuery实现的委托函数)


  • result(自实现和原生的结果对比)


  • 结果分析

在事件委托机制的实现中,事件监听本是要绑定到子元素li,而实际上是绑定到了外层元素ul#container。那么如何保证在被委托的事件监听中,其event.currentTarget和this均指向子元素?不妨回顾一下我们的实验。


在上述实验,我们首先通过点击元素span来触发li的事件监听,进而再触发外层元素ul#container的事件监听。通过控制台的打印结果可知,在事件委托的handler中,event.currentTarget和this,是指向子元素li,而不是外层元素ul#container,可见子元素li在被委托的事件监听中的“主人翁”地位是保住了。那么是如何保住的?其实是这样:事件委托机制,在确认子元素li也捕获到事件时,重新为子元素li创建了一个event对象,然后把currentTarget设置为子元素li,并在handler被调用时,将其上下文对象也指定为子元素li。


其它思考

1. 在实现jQuery事件委托机制时,为什么不提供参数来配置handler可在Capturing方向执行?(你先思考一下,思考完可以在留言@我,我的理解将在留言中分享)


三、让jQuery选择器的指代更加精确


jQuery名称的源由和讨论

在讲本章节前,先问大家一个问题:What is jQuery short for?(老规则,先思考一下)


答案是:

我记得是在一个风和日丽的傍晚,我和一个朋友漫步在公园里,他跟我分享了这个知识点,当时我有种开脑洞的感觉。大家知道,query是查询的意思,那作者为什么这样命名呢?虽然在官方网址找不到相应的资料,但正如官网所说,使用jQuery无非做两件事:"select some elements and do something with them.",其中select element的工作是由jQuery选择器机制来实现的,可见jQuery选择器机制是jQuery两个重要组成部分之一。也就是说,用好选择器也很重要。


提高jQuery选择器的查询效率

怎么才能算是用好jQuery选择器?我觉得应该尽量提高选择器的查询效率。那怎么提高选择器的查询效率?就是在保证准确性和代码可读性的基础上,尽量提高查询索引(即选择器的传入参数)的权重,让指代更加精确(ps:该原则在理论上没有问题,但由于不同浏览器的具体实现不同,可能会存在相反的情况)

在学习jQuery选择器之前,我们肯定是先接触css选择器,二者的语法几乎一致。常见的jQuery选择器也有基础类的选择器(id选择器、class选择器、伪类元素选择器和属性选择器)和结构类的选择器(兄弟选择器、子元素选择器和上下文选择器),通过各种选择器的组合使用,就能够提高搜索索引的权重,以尽量缩小元素的搜索范围。

举个例子:

$('li.active')优于$('.active');

$('#wrapper .active')优于$('.active');

$('#wrapper li.active')优于$('#wrapper .active') 。


特别地,jQuery选择器还可以通过指定上下文对象(使用find()实现),来缩小元素的查找范围,以达到提高查询效率的目的。具体使用可参见如下官方接口定义:

举个例子:


选择器过度组合带来的问题

虽然各类选择器的组合次数越多,权重也越大,但是过度组合,就会影响代码的可读性,有些组合也显得画蛇添足。

影响可读性的坏模式:

$('#center .tabbar .container ul li div>a.active')  // 嵌套太深

画蛇添足的坏模式:

$('div#idValue')  // 使用$('#idValue')即可

$('#tabbar #tab1') //使用$('#tab1')即可

$('#table thread tr th.special')  // 使用$('#table th.special')即可


关于组合使用选择器的几点建议

1. 子元素选择器和上下文选择器的使用次数最好不要超过三个;

2. 结构类的选择器,第一个选择器最好是id选择器,其次是class选择器;


四、值和对象的复用(值缓存、对象缓存和使用链式写法)


jQuery坏模式的重灾区

从使用上来说,此模块是出现jQuery坏模式的重灾区。很多同学在使用jQeury的时候,都没有复用查询结果值和jQuery对象的意识。


什么是复用

从字面的意思来解释,就是重复利用。我们常见的复用例子有哪些?设计模式的单例模式,就体现了复用思想,它避免了对象的重复创建引起的开销;浏览器对页面静态资源的缓存,也是一种复用,它减少了相同文件的重复传输,节约了带宽;面向对象编程语言的继承机制和项目公共代码的抽离与引用,是一种代码复用,它减少了代码的冗余......


复用的好处

以获取一个input元素的value值的例子来说,$('#input').val()这个操作会进行两次查询和一次jQuery对象创建:第一次查询是为了获得目标结点#input,第二次查询是为了获得结点的内部属性value的值,一次jQuery对象创建是指把目标结点#input包装为JQuery对象


所以对于那些没有发生变化且需要重复使用的值或对象来说,可以用一个变量缓存起来,既能避免重复查询,也能避免对象的重复创建,减少了系统资源的开销。特别地,jQuery支持链式写法,其本质也是对象复用的一种实践。下面分别举一个没有进行值缓存、对象缓存的例子。


没有使用值缓存的坏模式

修正前:


修正后:

没有复用对象的坏模式:

修正前:

修正后:


五、尽量减少调用DOM元素的操作次数


why

在讲解此模块前,就不得不先谈页面渲染的两个重要概念:回流和重绘。

  • 回流

当执行元素增删、字号调整、元素尺寸调整和resize浏览器窗口等操作时,会导致页面结构发生变化,此时浏览器会重新调整页面布局,这个过程则称为回流,又俗称重排。

  • 重绘

当重新设置元素的修饰样式时(并没有改变元素的尺寸,即没有改变页面的结构布局),会重新设置元素的显示样式,此过程称之为重绘。

  • 回流与重绘的联系与开销

回流一定引起重绘,而重绘不会引起回流,其中开销属回流最大。

而一个dom元素操作,如果引起页面结构发生变化,就会引起页面回流,当这种操作频繁发生时,就会增加页面回流的次数,进而引发整体性能的下降。


how

具体谈到解决措施,我们可以先对引发回流的dom操作进行分类,一类是修改了DOM树结的结构,如增、删结点和调整结点位置;一类是修改了结点的显示样式,如修改结点的尺寸和调整字号。针对这两种分类,则有不同的解决措施:

  • 常见的由于DOM结构变化引发的回流的优化方案有:

1. 添加结点时:先拼接html字符串,拼接完之后,最后统一用html()、append()和prepend等方法插入;

  • 常见的由于DOM元素样式变化引发的回流的优化方案有:

1. 不直接修改css属性,而是通过修改class,来一次、统一地调整样式。常见的jQuery class操作有addClasss()、removeClass()和toggleClass()。(特别地,一些MVVM框架如angular和vue,在语言设计上,也是推崇通过改变class,来改变元素样式,如ng-show的实现原理)


六、使用jQuery data机制给dom元素增加自定义数据属性

对于页面的元素,有时候我们除了为其绑定事件监听之外,还需要为其额外配置的自定义数据属性。当元素的事件被触发时,事件handler可以通过attr()方法来读取这些自定义数据属性,以进行下一步处理。但是这会遇到两个问题:

1. 在特定业务场景,自定义的数据属性,其命名难免会和元素的内置属性名称冲突;

2. 驼峰命名法广泛采用于变量命名,但是html代码不区分大小写,如果在html代码中使用驼峰命名法给自定义属性命名,那么自定义属性中出现的大写字母,也终将被解析为小写字母。


为了解决这两个问题,jQuery提供了data机制:

  • 在html代码中,规定自定义数据属性需添加data-前缀,以和元素的内置属性区分,进而避免命名冲突;同时,规定自定义属性使用中横线命名法,以应对html代码不区分大小写的问题

  • 在js代码中,使用data(key)来读取自定义数据属性值,其中自定义属性名key采用驼峰命名法。


所以,十分建议使用jQuery提高的data机制来读取或设置元素的自定义数据属性。jQuery的data机制,不仅解决了上述两个问题,而且使得代码更具有一致性、条理性。关于jQuery的data机制,举例如下:

html代码:

js代码:


结束语

本文大致从jQuery选择器机制、事件机制、data机制、浏览器的渲染机制和复用这五大方面,来分享了一些可以提高jQuery性能和规范代码的最佳实践。本文所分享的内容亦为自己在实际项目中的使用心得,由于水平和经验有限,如发现纰漏或有补充,望不吝赐教,敬候达(lao)文(si)西(ji)回复。



示例代码地址:

https://github.com/momopig/simplicity/tree/master/02:jQuery_best_practise



原创粉丝点击