简述JavaScript函数节流

为什么要用函数节流

浏览器中某些计算和处理要比其他的昂贵很多。例如,DOM 操作比起非 DOM 交互需要更多的内存和 CPU 时间。连续尝试进行过多的 DOM 相关操作可能会导致浏览器挂起,有时候甚至会崩溃。尤其在 IE 中使用 onresize 事件处理程序的时候容易发生,当调整浏览器大小的时候,该事件会连续触发。在 onresize 事件处理程序内部如果尝试进行 DOM 操作,其高频率的更改可能会让浏览器崩溃。为了绕开这个问题,你可以使用定时器对该函数进行节流。

常见的有:重新调整浏览器窗口大小(resize),浏览器页面滚动(scroll),鼠标移动(mousemove)。

函数节流的基本思想

函数节流背后的基本思想是指,某些代码不可以在没有间断的情况下连续重复执行。第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。

说白一点就是:当你改变窗体大小的时候,不管你怎么拉,怎么拽,都没有立刻生效,而是在你改变完大小后的一会儿,它的内容才进行缩放适应。

该模式的基本形式:

var processor = {
  timeoutId: null,
  // 实际进行处理的方法
  performProcessing: function(){
    // 实际执行的代码
  },
  // 初始处理调用的方法
  process: function(){
    clearTimeout(this.timeoutId);
    var that = this;
    this.timeoutId = setTimeout(function(){
      that.performProcessing();
    }, 100);
  }
};
// 尝试开始执行

processor.process();

在这段代码中,创建了一个叫做 processor 的对象。这个对象还有2个方法:process() 和 performProcessing()。前者是初始化任何处理所必须调用的,后者则实际进行应完成的处理。当调用了 process(),第一步是清除存好的 timeoutId,来阻止之前的调用被执行。然后,创建一个新的定时器调用 performProcessing()。由于 setTimeout() 中用到的函数的环境总是 window ,所以有必要保存 this 的引用以方便以后使用。

时间间隔设为了 100ms,这表示最后一次调用 process() 之后至少 100ms 后才会调用 performProcessing()。所以如果 100ms 之内调用了 process() 共20次,performProcessing() 仍只会被调用一次。

这个模式可以使用 throttle() 函数来简化

这个函数可以自动进行定时器的设置和清除,如下例所示:

function throttle(method, context){
  clearTimeout(method.tId);
  method.tId = setTimeout(function(){
    method.call(context);
  }, 100);
}    

throttle() 函数接受两个参数:要执行的函数以及在哪个作用域中执行。上面这个函数首先清除之前设置的任何定时器。定时器 ID 是存储在函数的 tId 属性中的,第一次把方法传递给 throttle() 的时候,这个属性可能并不存在。接下来,创建一个新的定时器,并将其 ID 存储在方法的 tId 属性中。如果这是第一次对这个方法调用 throttle() 的话,那么这段代码会创建该属性。定时器代码使用 call() 来确保方法在适当的环境中执行。如果没有给出第二个参数,那么就在全局作用域内执行该方法。

前面提到过,节流在 resize 事件中是最常用的。如果你基于该事件来改变页面布局的话,最好控制处理的频率,以确保浏览器不会在极短的时间内进行过多的计算。例如,假设有一个 <div/> 元素需要保持它的高度始终等于宽度。那么实现这一功能的 JavaScript 可以如下编写:

window.onresize = function(){
  var div = document.getElementById("myDiv");
  div.style.height = div.offsetWidth + "px";
};

这段非常简单的例子有两个问题可能会造成浏览器运行缓慢。首先,要计算 offsetWidth 属性,如果该元素或者页面上其他元素有非常复杂的 CSS 样式,那么这个过程将会很复杂。其次,设置某个元素的高度需要对页面进行回流来令改动生效。如果页面有很多元素同时应用了相当数量的 CSS 的话,这又需要很多计算。这就可以用到 throttle() 函数,如下例所示:

function resizeDiv(){
  var div = document.getElementById("myDiv");
  div.style.height = div.offsetWidth + "px";
}

window.onresize = function(){
  throttle(resizeDiv);
};

这里,调整大小的功能被放入了一个叫做 resizeDiv() 的单独函数中。然后 onresize 事件处理程序调用 throttle() 并传入 resizeDiv 函数,而不是直接调用 resizeDiv() 。多数情况下,用户是感觉不到变化的,虽然给浏览器节省的计算可能会非常大。

只要代码是周期性执行的,都应该使用节流,但是你不能控制请求执行的速率。这里展示的 throttle() 函数用了 100ms 作为间隔,你当然可以根据你的需要来修改它。

再来看一个封装的方法:

var throttle = function(fn, delay){
     var timer = null;
     return function(){
         var context = this, args = arguments;
         clearTimeout(timer);
         timer = setTimeout(function(){
             fn.apply(context, args);
         }, delay);
     };
 };

它使用闭包的方法形成一个私有的作用域来存放定时器变量timer。而调用方法为

window.onresize = throttle(myFunc, 100);

两种方法各有优劣,前一个封装函数的优势在把上下文变量当做函数参数,直接可以定制执行函数的this变量;后一个函数优势在于把延迟时间当做变量(当然,前一个函数很容易做这个拓展)。

使用闭包代码结构会更优,且易于拓展定制其他私有变量,缺点就是虽然使用apply把调用throttle时的this上下文传给执行函数,但毕竟不够灵活。

这里主要了解一点:throttle 被调用后返回的function才是真正的onresize 触发时需要调用的函数

现在看起来这个方法已经接近完美了,然而实际使用中并非如此。举个例子:
如果用户不断的resize浏览器窗口大小,这时延迟处理函数是一次都不会执行的

于是我们又要添加一个功能:当用户触发resize的时候应该在某段时间内至少触发一次,既然是在某段时间内,那么这个判断条件就可以取当前的时间毫秒数,每次函数调用把当前的时间和上一次调用时间相减,然后判断差值如果大于某段时间就直接触发,否则还是走 timeout 的延迟逻辑。

var throttleV2 = function(fn, delay, mustRunDelay){
     var timer = null;
     var t_start;
     return function(){
         var context = this, args = arguments, t_curr = +new Date();
         clearTimeout(timer);
         if(!t_start){
             t_start = t_curr;
         }
         if(t_curr - t_start >= mustRunDelay){
             fn.apply(context, args);
             t_start = t_curr;
         }
         else {
             timer = setTimeout(function(){
                 fn.apply(context, args);
             }, delay);
         }
     };
 };

在这个拓展后的节流函数升级版,我们可以设置第三个参数,即必然触发执行的时间间隔。如果用下面的方法调用

window.onresize = throttleV2(myFunc, 50, 100);

则意味着,50ms的间隔内连续触发的调用,后一个调用会把前一个调用的等待处理掉,但每隔100ms至少执行一次(即在100ms内,如果还是在触发的话,还是会执行一次)。原理也很简单,打时间tag,一开始记录第一次调用的时间戳,然后每次调用函数都去拿最新的时间跟记录时间比,超出给定的时间就执行一次,更新记录时间。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>throttle</title>
</head>
<body>
<div style="height:5000px">
<div id="demo" style="position:fixed;"></div>
</div>
<script>
var COUNT = 0, demo = document.getElementById(‘demo‘);
function testFn() {demo.innerHTML += ‘testFN 被调用了 ‘ + ++COUNT + ‘次<br>‘;}

var throttle = function(fn, delay, mustRunDelay){
     var timer = null;
     var t_start;
     return function(){
         var context = this, args = arguments, t_curr = +new Date();
         clearTimeout(timer);
         if(!t_start){
             t_start = t_curr;
         }
         if(t_curr - t_start >= mustRunDelay){
             fn.apply(context, args);
             t_start = t_curr;
         }
         else {
             timer = setTimeout(function(){
                 fn.apply(context, args);
             }, delay);
         }
     };
 };
// window.onscroll = throttle(testFn, 200);
window.onscroll = throttle(testFn, 500, 1000);
</script>
</body>
</html>

参考:

《JavaScript高级程序设计》

浅谈javascript的函数节流

JavaScript函数节流

JavaScript节流函数throttle详解

时间: 2024-10-11 22:31:53

简述JavaScript函数节流的相关文章

再谈javascript函数节流

之前写过但是不记得在哪了,今天同事要一个滑到页面底部加载更多内容的效果,又想起了这玩意儿,确实挺实用和常用的,谨此记之. 函数节流从字面上的意思就是节约函数的执行次数,其实现的主要思想是通过定时器阻断函数的连续执行,尤其适合用在频繁操作,比如window的resize和scroll事件等. window的默认scroll事件间隔时间大概只有十几毫秒,如果频繁的scroll,然后去请求,然后渲染,对性能肯定有很大的影响. 大概模式: var processor = { timer: null, p

JavaScript函数节流与函数去抖

介绍 首先解释一下这两个概念: 函数节流(throttle):是让一个函数无法在很短的时间间隔内连续调用,当上一次函数执行后过了规定的时间间隔,才能进行下一次该函数的调用. 函数去抖(debounce):让一个函数在一定间隔内没有被调用时,才开始执行被调用方法. 两个方法都是用来提升前端性能,减轻浏览器压力. 应用 理解起来有点费力,通过应用来理解就轻松了.通常,我们会在有用户交互参与的地方添加事件,而往往这种事件会被频繁触发. 想象一下窗口的resize事件或者是一个元素的onmousemov

JavaScript函数节流

函数节流 技术一般水平有限,有什么错的地方,望大家指正. 函数节流就是节约函数的调用,让函数少执行几次,一般用在onmousemove,onresize这种我们只要稍微一动就会砰砰砰执行多次的事件处理函数上.如果处理函数很复杂有执行很多次就很耗性能关键了有的是没有必要执行的. 我们先说一个经常遇见的情况,鼠标放在按钮上显示下拉菜单,鼠标离开下拉菜单消失,通常我们这么写: //title表示标题元素,content表示下拉菜单 $("#title").hover(function(){

JavaScript函数节流和函数防抖的原理与区别

一.概念解释 ?函数节流和函数防抖,两者都是优化高频率执行js代码的一种手段.?大家大概都知道旧款电视机的工作原理,就是一行行得扫描出色彩到屏幕上,然后组成一张张图片.由于肉眼只能分辨出一定频率的变化,当高频率的扫描,人类是感觉不出来的.反而形成一种视觉效果,就是一张图.就像高速旋转的风扇,你看不到扇叶,只看到了一个圆一样.?同理,可以类推到js代码.在一定时间内,代码执行的次数不一定要非常多.达到一定频率就足够了.因为跑得越多,带来的效果也是一样.倒不如,把js代码的执行次数控制在合理的范围.

JavaScript函数节流和函数防抖之间的区别

一.概念解释 ?函数节流和函数防抖,两者都是优化高频率执行js代码的一种手段.?大家大概都知道旧款电视机的工作原理,就是一行行得扫描出色彩到屏幕上,然后组成一张张图片.由于肉眼只能分辨出一定频率的变化,当高频率的扫描,人类是感觉不出来的.反而形成一种视觉效果,就是一张图.就像高速旋转的风扇,你看不到扇叶,只看到了一个圆一样.?同理,可以类推到js代码.在一定时间内,代码执行的次数不一定要非常多.达到一定频率就足够了.因为跑得越多,带来的效果也是一样.倒不如,把js代码的执行次数控制在合理的范围.

JavaScript 函数节流和函数去抖

概念 函数防抖(debounce) 当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间 函数节流(throttle) 预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期 函数节流(throttle)与 函数防抖(debounce)都是为了限制函数的执行频次,以优化函数触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象. 函数防抖(debounce) 如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行

JavaScript 函数节流

其实最早看设计模式是单纯的了解js语言本身;所以看了理解了之后还是容易忘记;可能之后实际的工作需要才能记住吧; 看下面:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0&q

javascript函数节流(throttle)与函数去抖(debounce)

throttle 等时间 间隔执行函数. debounce 时间间隔 t 内若再次触发事件,则重新计时,直到停止时间大于或等于 t 才执行函数. 1.throttle函数的简单实现 function throttle(fn, threshhold, scope) { threshhold || (threshhold = 250); var last, timer; return function () { var context = scope || this; var now = +new

浅谈javascript的函数节流

什么是函数节流? 介绍前,先说下背景.在前端开发中,有时会为页面绑定resize事件,或者为一个页面元素绑定拖拽事件(其核心就是绑定mousemove),这种事件有一个特点,就是用户不必特地捣乱,他在一个正常的操作中,都有可能在一个短的时间内触发非常多次事件绑定程序.而大家知道,DOM操作时很消耗性能的,这个时候,如果你为这些事件绑定一些操作DOM节点的操作的话,那就会引发大量的计算,在用户看来,页面可能就一时间没有响应,这个页面一下子变卡了变慢了.甚至在IE下,如果你绑定的resize事件进行