如何稀释onscroll事件

面试时问到这个问题,是这样的:
    面试官问一个关于滚动到某个位置的时候出现一个顶部的导航栏,答完之后,她接着问一滚动onscroll就会执行很多很多次,如何稀释它?为了确定她说的是“稀释”,我让她重复了遍,我给出的解决方法是,用一个变量,在事件处理的时候让它自增,判断达到一定大小就执行一次实际的事件:

var i = 0;    // 累积变量
window.onscroll = function(){
    i++;
    if(i%500==0){
        // 执行实际的事件
    }
}
  • 她并不满意,问最后如何释放这个变量?
  • ……
  • 接着她说:“我要的是稀释onscroll的执行次数,而不是这个(我所指的实际的事件)的执行次数。”
  • 我很是不解,鼠标一滚动就触发这个事件,如何能减少它的执行次数,如何稀释它?难道动态绑定/解绑事件,如何操作?

    网上都没有找到相关类型的问题,这个问题算不算变态级的面试题?如果不是,请给出解决方案,先谢谢了。

阅读 19.4k
10 个回答

我觉得他是问函数节流的问题?(´・_・`)

//参考
var timer = null;
window.addEventListener('scroll', function () {
    if (typeof timer === 'number') {
        clearTimeout(timer);
    }
    timer = setTimeout(function () {
        //添加onscroll事件处理
        console.log('onscroll');
    }, 1000);
}, false);

如下代码不知是否可以"稀释"一些scroll。

var cb = {
  onscroll:function() {
    console.log("scrolling");
    window.removeEventListener("scroll", cb.onscroll, false);
    setTimeout(function() {
        console.log("DONE");
        window.addEventListener("scroll", cb.onscroll, false);
    }, 200);
  }
};
window.addEventListener("scroll", cb.onscroll, false);

楼上说的debounce是种方法, 但是本质上不解决问题。

首先我们来看下为什么要做这个稀释,
这里有几个概念要知道,
1、浏览器事件的callback会block浏览器自身的行为, 也就是比如一个scroll触发的callback要执行一秒,那么浏览器会等callback执行的一秒结束后,才会开始执行自己的scroll行为。这样的话如果每滑动一次就执行一次, 这个页面就废了。 所以要尽量保证callback能立即执行完(及时有更大的运算也要扔到另一个function作为callback)
2、 浏览器的渲染是有一个频率的, 比如你一秒一个动画执行了80帧, 而浏览器只支持60帧, 那么中间会有剩下的那20帧浪费了, 也会中间失去的那20帧也会造成很突兀的视觉效果。所以最好的策略是浏览器每渲染一次之前就执行一次动画。
3、 scroll事件是每次滚动的时候都会触发, 很多时候我们只需要一个结果很一个很平滑的视觉过度,而中间有很多不必要的执行过程, 这些就是需要“稀释”的地方。

另外既然是scroll事件的callback, 那么至少要保证它是时时执行的,用debounce我们还要手动设置这个方法时间, 满足了3,可以通过曲线的方式(手动获取渲染频率)勉强满足2。

所以一般推荐用requestAnimationFrame, 而且在面试中提到requestAnimationFrame, 肯定也是一个加分项哦
资料:http://www.html5rocks.com/en/tutorials/speed/animations/

这题关键是对面试官所谓“稀释”语义的解释。

在我看来这里这个稀释可以有三个语义
1.“稀释”——稀释的是onscroll相对于浏览器本身的触发。
2.“稀释”——稀释的是onscroll回调的执行次数。
3.“稀释”——稀释的是绑定给onscroll中具体想要执行的业务逻辑的执行次数。

第一种,无解,引擎细节不对外暴露,触发这件事本身不受编码者控制,我没看过具体实现,但是猜测一下,内部存在一个基于多播模型的队列,如果队列不为空,则逐个取出且执行(部分浏览器中不会按序取出),如果为空则跳过,因此编码者能做的最多就是向第二种“稀释”状态迁移。

第二种,就像题主你提到的,第一次执行时移除事件绑定,然后设定一个延迟函数再次绑定事件,假设一个10秒的时间内,每间隔1s执行一次滚动,延迟函数为5s,那么在整个过程中,触发onscroll的次数为10次,而触发回调为2次。

第三种,则是诸位提到过的函数节流,基本上就是用warp函数包裹具体的业务函数,由warp函数控制具体的业务函数执行次数,因此沿用上一种状态提到的流程,假设节流的delay/wait time是5s,整个过程中,触发onscroll次数为10次,触发回调10次,具体执行定义的业务逻辑为2次。

所以,面试时问这种语义不清的名词,叫人怎么回答呢....

稀释的意思是让scroll的事件处理器执行次数减少,以降低浏览器性能消耗。Underscore.js里有2个比较相似的方法,一个是函数节流,.debounce(function, wait, [immediate]) ,即若第一个函数参数下一次执行与上次执行在固定时间wait间隔内的话就不会执行,这种状态一直持续下去直到事件不再触发则执行最后一次;还有一种是固定时间间隔的执行函数,.throttle(function, wait, [options]) ,类似于setInterval()。按照面试官的意思应该是用第二种。

用一个变量记录上次执行的时间不就行了?

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏