3.2 处理简单的事件
3.2 处理简单的事件
除了页面加载之外,我们也想在其他时刻完成某个任务。正如JavaScript
可以让我们通过<body onload="">
或window.onload
来截获页面加载事件一样,它对用户发起的事件也提供了相似的”挂钩” (hook
) 。例如,鼠标单击(onclick
) 、表单被修改(onchange
)以及窗口大小变化(onresize
)等。在这些情况下,如果直接在DOM
中为元素指定行为,那么这些挂钩也会与我们讨论的onload
一样具有类似的缺点。为此,jQuery
也为处理这些事件提供了一种改进的方式。
3.2.1 简单的样式转换器
为了说明某些事件处理技术,我们假设希望某个页面能够基于用户的输入呈现出不同的样式。也就是说,允许用户通过单击按钮来切换视图,包括正常视图、将文本限制在窄列中的视图和适合打印的大字内容区视图。
用于样式转换器的HTML
标记如下所示:
1 | <div id="switcher" class="switcher"> |
在与页面中其他HTML
标记和基本的CSS
组合以后,我们可以看到如图3-1所示的页面外观。
首先,我们来编写Large Print
按钮的功能。此时,需要一点CSS
代码来实现页面的替换视图:
1 | body.large .chapter { |
然后,我们的目标就是为<body>
标签应用large
类。这样会导致样式表对页面进行重新格式化。按照第2章介绍的知识,添加类的语句如下所示:
1 | $('body').addClass('large'); |
但是,我们希望这条语句在用户单击按钮时执行(而不是像我们到目前为止看到的那样在页面加载后执行)。为此,我们需要引入.on()
方法。通过这个方法,可以指定任何DOM
事件,并为该事件添加一种行为。此时,事件是click
,而行为则是由上面的一行代码构成的函数,参见代码清单3-1。
1 | $(document).ready(function () { |
现在,当单击Large Print
按钮时,就会运行函数中的代码,而页面的外观将如图3-2所示。
这里的全部操作就是绑定了一个事件。我们前面介绍的.ready()
方法的优点在此也同样适用。多次调用.on()
也没有任何问题,即可以按需为同一个事件追加更多的行为。
3.2.2 启用其他按钮
现在,Large Print
按钮开始生效了。接下来,我们要以类似的方式处理其他两个按钮(Default
和Narrow
) ,让它们也都执行各自的任务。这个过程很简单,即分别使用.on()
为它们添加一个单击处理程序,同时视情况移除或添加类。完成之后的代码如代码清单3-2所示。
1 | $(document).ready(function () { |
以下是配套的narrow
类的CSS
规则:
1 | body.narrow .chapter { |
现在,如果单击Narrow Column
按钮,随着相应的CSS
生效,文本会相应变化,如图3-3所示。
单击Default
按钮将从<body
>标签中同时移除两个类,让页面恢复为初始状态。
3.2.3 利用事件处理程序的上下文
虽然样式转换器的功能很正常,但我们并没有就哪个按钮处于当前使用状态对用户给出反馈。为此,我们的方法是在按钮被单击时,为它应用selected
类,同时从其他按钮上移除这个类。selected
类只是为按钮文本添加了粗体样式:
1 | .selected { |
为了实现类的变换,可以按照前面的做法,通过ID
来引用每个按钮,然后再视情况为它们应用或移除类。不过,这一次我们要探索一种更优雅也更具扩展性的解决方案,这个方案利用了事件处理程序运行的上下文。
当触发任何事件处理程序时,关键字this
引用的都是携带相应行为的DOM
元素。前面我们谈到过,$()
函数可以将DOM
元素作为参数,而this
关键字是实现这个功能的关键①。通过在事件处理程序中使用$(this)
,可以为相应的元素创建jQuery
对象,然后就如同使用CSS
选择符找到该元素一样对它进行操作。
知道了这些之后,我们可以编写出下面的代码:
1 | $(this).addClass('selected'); |
把这行代码放到那3个事件处理程序中,就可以在按钮被单击时为按钮添加selected
类。要从其他按钮中移除这个类,可以利用jQuery
的隐式迭代特性,并编写如下代码:
1 | $('#switcher button').removeClass('selected'); |
这行代码会移除样式转换器中每个按钮的selected
类。
我们还应该在文档就绪时把这个类添加到Default
按钮上。因此,按照正确的次序放置它们,就可以得到代码清单3-3。
1 | $(document).ready(function () { |
这样,样式转换器就会对用户给出适当的反馈了。
利用处理程序的上下文将语句通用化,可以使代码更高效。我们可以把负责突出显示的代码提取到一个单独的处理程序中,因为针对3个按钮的突出显示代码都一样,结果如代码清单3-4所示。
1 | $(document).ready(function () { |
这一步优化利用了我们讨论过的3种jQuery
特性。
- 第一,在通过对
.on()
的一次调用为每个按钮都绑定相同的单击事件处理程序时,隐式迭代机制再次发挥了作用。 - 第二,行为队列机制让我们在同一个单击事件上绑定了两个函数,而且第二个函数不会覆盖第一个函数。
- 最后,我们使用
jQuery
的连缀能力将每次添加和移除类的操作压缩到了一行代码中。
3.2.4 使用事件上下文进一步减少代码
我们刚才的代码优化实际上是在做重构——修改已有代码,以更加高效和简洁的方式实现相同任务。 为寻找进一步重构的机会,下面再看一看绑定到每个按钮的行为。其中, .removeClass()
方法的参数是可选的,即当省略参数时,该方法会移除元素中所有的类。利用这一点,可以把代码再改进得更简单一些,参见代码清单3-5。
1 | $(document).ready(function () { |
注意,为了适应更通用的移除类的操作,我们对操作顺序作了小小的调整——先执行.removeClass()
,以便它不会撤销几乎同时执行的.addClass()
。
此时,在每个按钮的处理程序中仍然会执行某些相同的代码。这些代码也可以轻而易举地提取到通用的按钮单击处理程序中,如代码清单3-6所示。
1 | // DOM加载完毕后执行 |
这里要注意的是,必须把通用的处理程序转移到特殊的处理程序上方,因为.removeClass()
需要先于.addClass()
执行。而之所以能够做到这一点,是因为**jQuery
总是按照我们注册的顺序来触发事件处理程序。
最后,可以通过再次利用事件的执行上下文来完全消除特殊的处理程序。因为上下文关键字this
引用的是DOM
元素**,而不是jQuery
对象,所以可以使用原生的DOM
属性来确定被单击元素的ID
。因而,就可以对所有按钮都绑定相同的处理程序,然后在这个处理程序内部针对按钮执行不同的操作,参见代码清单3-7。
1 | // 当DOM加载完毕是执行 |
这里bodyClass
变量的值根据按钮的ID
属性的值来动态生成,根据单击的按钮不同,bodyClass
变量的值可能是default
、narrow
或large
。这里与前面做法的不同之处在于,我们会在用户单击ID
为switcher-default
的按钮时,程序根据ID
属性值switcher-default
,得到default
类,然后给<body>
添加default
类。虽然在这儿添加这个类也用不着,但与因此降低的复杂性相比,仅仅添加一个用不上的类名还是很划算的。
3.2.5 简写的事件
鉴于为某个事件(例如简单的单击事件)绑定处理程序极为常用,jQuery
提供了一种简化事件操作的方式——简写事件方法,简写事件方法的原理与对应的.on()
方法调用相同,可以减少一定的代码输入量。
例如,不使用.on()
方法而使用.click()
方法可以将前面的样式转换器程序重写为如代码清单3-8所示。
1 | // DOM加载结束时调用 |
其他blur
、keydown
和scroll
等标准的DOM
事件,也存在类似前面这样的简写事件。这些简写的事件方法能够把一个事件处理程序绑定到同名事件上面。
3.2.6 显示和隐藏高级特性
假设我们想在不需要时隐藏样式转换器。隐藏高级特性的一种便捷方式,就是使它们可以折叠。因此,我们要实现的效果是:
- 在标签上单击时,可以隐藏所有按钮,最后只剩一个标签;
- 而再次单击标签时,则会恢复这些按钮。
这里所谓的高级特性就是指为页面提供样式切换能力的样式转换器。
为了隐藏按钮,我们还需要另外一个类:
1 | .hidden { |
为实现这个功能,可以把当前的按钮状态保存在一个变量中,每当标签被单击时,通过检查这个变量的值就能知道应该向这些按钮中添加,还是要从这些按钮中移除.hidden
类。
不过,jQuery
也为我们提供了一个简便的toggleClass()
方法,该方法能够根据相应的类是否存在而添加或删除类,参见代码清单3-9。
1 | $(document).ready(function () { |
在第一次单击后,所有按钮都会隐藏起来,如图3-4所示。
而第二次单击则又恢复了它们的可见性,如图3-5所示。
同样,这里我们依靠的仍然是jQuery
的隐式迭代能力,即一次就能隐藏所有按钮,而不需要使用包装元素(即不需要在这3个按钮外部再添加额外的标签如:<div>
。如果没有隐式迭代机制,那么想一次隐藏3个按钮,一种常见的方法就是隐藏包含这3个按钮的包装元素)。