现代网页的实现上,会有很多交互上的优化,比如常见的 滚动加载
,输入联想
等等。他们的实现思路很简单,以滚动加载
而言,无非就是去是增加一个滚动的事件监听,每次滚动判断当前的元素是否已经滚动到了用户的可视区,然后根据判断结果来决定是否来加载相关数据。 输入联想
也类似,无非就是修改一下监听的事件类型,和判断的相关逻辑。
前端交互的优化上,我们总是需要需要事件监听来知道用户进行了什么操作,从而设定页面给出怎样的反应。但本文主要的内容不是讲如何具体的实现某个交互,而是对交互实现的依赖的事件监听那部分的优化。
滚动加载图片的例子
1 |
var onScroll = function() { |
这样理论上就实现了图片的滚动加载或者说是按需加载,懒加载等等。
这样的实现乍看没有任何问题,但在实际的场景中,问题还是很严重的。
如下:
很明显,当你在 codepen 中滚动鼠标的时候,显示的数字蹭蹭蹭的加大, 这个数字就是过程中,浏览器触发 scroll 事件的数字, 可以看出,浏览器触发 scroll 事件是很频繁的。
所以,回到上面提出的问题中,因为浏览器触发 scroll 会很平凡, 也就意味着事件的监听回调函数 onScroll 会被很频繁的执行。这就是问题之所在,且不说我们的 onScroll 里面的逻辑可能会是大量的计算。如此频繁的计算就算可以很轻松的被完成,结果也是没有意义的。所以我们要在这里来优化代码,倒不是优化 onScroll 本省,而是想办法让 onScroll 执行的次数可以减少。
函数节流
在原本的逻辑中,假设在滚动条连续的滚动中,浏览器每 10ms 触发一次scroll 事件,就意味着浏览器每 10ms 就要去执行一次 onScroll,如果想让 onScroll 不那么频繁的被执行,怎么办?
很明显,想去改动 scroll 触发的频率在前端这一层面上肯定是不行,因为他们浏览器自身的设定。那么是否可以在触发 scroll 事件之后,不直接调用 onScroll 函数从而进行优化呢?比如,假设scroll 事件持续触发,我们让 onScroll 函数每 250ms 触发。
1 |
var throttle = function() { |
这样调用是不会有结果的
1 |
window.addEventListener('scroll',throttle ,false); |
上述的代码旨在说明实现函数节流的思路,著名的 underscore.js
和 lodash
都有节流方法的实现。
输入联想的例子
当你在使用 google 搜索内容的时候,有些关键词输入到一般,谷歌会展示一个可选列表,是根据你当前输入的内容作出的一个猜测联想,如果有中目标的联想,你就可以不需要输入接下来的内容,直接选择自动补全就好。
这种场景下,我们一般是去监听用户的 onKeyRress
或者 onKeyUp
等等键盘事件。在当用户输入一些文字的时候,我们一监听到键盘事件就将用户当前输入的内容去发送一个 ajax 查询请求显然是不行的。那么在节流函数内部去调用 ajax 请求呢?
在这种场景下,其实函数节流也是不合适,因为,函数节流只是减小了函数执行的在事件触发过程中频率,将原来可能调用1000次减少到调用100次。然而对输入联想的场景来说,只要用户还在触发键盘事件,那么发送请求就是没有意义的。
所以,我们现在需要的一种实现是应该是在用户停止键盘事件一段时间后,去发送一个请求。
比如应该这么设计这个过程,用户在输入,用户输入有所停顿(也就是不再触发键盘事件),当这个事件大于 250ms 之后,去触发一个查询请求。 如果用户输入停顿的时间小于 250ms,不去发送查询请求。
电梯模型
输入联想的执行过程,应该和生活中的电梯执行过程是一样的。
比如,当你正在座电梯,当电梯门正要关闭的时候,这是有一个同学又冲过来,按了一下电梯的按钮,这个电梯门会打开然后等待一定的时间,如果在等待的过程中,又有另外一位同学过来按了按钮,之前等待的时间清零,继续等待一定的时间,直到在等待的时间中,不在有别的人去按按钮。门才会关闭,然后上下运行。
函数去抖
了解这个过程之后,来简单的实现下这个过程。
1 |
// 如下搜索控件 |
1 |
var sendAjax = function(obj) { |
总结
函数节流和函数去抖的使用场景略有不同。
函数节流:在事件触发过程中,减小相关回调函数的执行频率。
函数去抖:在事件触发结束后一定时间后去执行事件回调函数,如果在这一定时间内又触发了相关事件,则不去触发事件回调函数,下一次执行事件回调函数时间仍是确定的一定时间。