Javascript 函数节流和函数去抖场景介绍

函数节流场景
例如:实现一个原生的拖拽功能(如果不用H5 Drag和Drop API),我们就需要一路监听mousemove事件,在回调中获取元素当前位置,然后重置dom的位置。如果我们不加以控制,每移动一定像素而出发的回调数量是会非常惊人的,回调中又伴随着DOM操作,继而引发浏览器的重排和重绘,性能差的浏览器可能会直接假死。这时,我们就需要降低触发回调的频率,比如让它500ms触发一次或者200ms,甚至100ms,这个阀值不能太大,太大了拖拽就会失真,也不能太小,太小了低版本浏览器可能会假死,这时的解决方案就是函数节流【throttle】。函数节流的核心就是:让一个函数不要执行得太频繁,减少一些过快的调用来节流。

函数去抖场景
例如:对于浏览器窗口,每做一次resize操作,发送一个请求,很显然,我们需要监听resize事件,但是和mousemove一样,每缩小(或者放大)一次浏览器,实际上会触发N多次的resize事件,这时的解决方案就是节流【debounce】。函数去抖的核心就是:在一定时间段的连续函数调用,只让其执行一次

函数节流的实现
函数节流的第一种方案封装如下

function throttleFunc(method,context)
{
  clearTimeout(method.timer);  //为什么选择setTimeout 而不是setInterval
  method.timer = setTimeout(function(){
    method.call(context);
  },100);
}

看一个封装的demo

window.onscroll = function(){
  throttleFunc(show);
}
function show(){
  console.log(1);
}
function throttleFunc(method){
  clearTimeout(method.timer);
  method.timer = setTimeout(function(){
    method();
  },100);
}

也可以使用闭包的方法对上面的函数进行再封装一次

function throttle(fn, delay) {
  var timer = null;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn();
    }, delay);
 };
};

调用

var func = throttle(show,100);
function show() {
  console.log(1);
}
window.onscroll = function() {
  func();
}

封装2

function throttle(fn, delay, runDelay) {
  var timer = null;
  var t_start;
  return function() {
    var t_cur = new Date();
    timer && clearTimeout(timer);
    if (!t_start) {
      t_start = t_cur;
    }
    if (t_cur - t_start >= runDelay) {
      fn();
      t_start = t_cur;
    } else {
      timer = setTimeout(function() {
        fn();
      }, delay);
    }
  }
}

调用

var func = throttle(show, 50,100);
function show() {
  console.log(1);
}
window.onscroll = function() {
  func();
}
函数去抖的实现:

代码在underscore的基础上进行了扩充

// 函数去抖(连续事件触发结束后只触发一次)
// sample 1: _.debounce(function(){}, 1000)
// 连续事件结束后的 1000ms 后触发
// sample 1: _.debounce(function(){}, 1000, true)
// 连续事件触发后立即触发(此时会忽略第二个参数)

_.debounce = function(func, wait, immediate) { 
  var timeout, args, context, timestamp, result; 
  var later = function() { 
    // 定时器设置的回调 later 方法的触发时间,和连续事件触发的最后一次时间戳的间隔 
    // 如果间隔为 wait(或者刚好大于 wait),则触发事件 
    var last = _.now() - timestamp; 

    // 时间间隔 last 在 [0, wait) 中 
    // 还没到触发的点,则继续设置定时器 
    // last 值应该不会小于 0 吧? 
    if (last < wait && last >= 0) { 
      timeout = setTimeout(later, wait - last); 
    } else { 
      // 到了可以触发的时间点 timeout = null; 
      // 可以触发了 
      // 并且不是设置为立即触发的 
      // 因为如果是立即触发(callNow),也会进入这个回调中 
      // 主要是为了将 timeout 值置为空,使之不影响下次连续事件的触发
      // 如果不是立即执行,随即执行 func 方法 
      if (!immediate) { 
        // 执行 func 函数 
        result = func.apply(context, args); 
        // 这里的 timeout 一定是 null 了吧 
        // 感觉这个判断多余了 
        if (!timeout)  
          context = args = null; 
        } 
     } 
   };
   // 嗯,闭包返回的函数,是可以传入参数的 
   return function() { 
    // 可以指定 this 指向 
    context = this; 
    args = arguments; 

    // 每次触发函数,更新时间戳 
    // later 方法中取 last 值时用到该变量 
    // 判断距离上次触发事件是否已经过了 wait seconds 了 
    // 即我们需要距离最后一次触发事件 wait seconds 后触发这个回调方法
    timestamp = _.now(); 
    // 立即触发需要满足两个条件 
    // immediate 参数为 true,并且 timeout 还没设置 
    // immediate 参数为 true 是显而易见的 
    // 如果去掉 !timeout 的条件,就会一直触发,而不是触发一次 
    // 因为第一次触发后已经设置了 timeout,所以根据 timeout 是否为空可以判断是否是首次触发 
    var callNow = immediate && !timeout; 

    // 设置 wait seconds 后触发 later 方法 
    // 无论是否 callNow(如果是 callNow,也进入 later 方法,去 later 方法中判断是否执行相应回调函数) 
    // 在某一段的连续触发中,只会在第一次触发时进入这个 if 分支中 
    if (!timeout) 
      // 设置了 timeout,所以以后不会进入这个 if 分支了 
      timeout = setTimeout(later, wait); 
    // 如果是立即触发 
    if (callNow) { 
      // func 可能是有返回值的 
      result = func.apply(context, args); 
      // 解除引用 
      context = args = null; 
    } 
    return result; 
  };
};

参考文献:
http://www.cnblogs.com/tugenhua0707/p/5272539.html
https://github.com/hanzichi/underscore-analysis/issues/21

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,597评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,053评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,583评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,888评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,772评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,536评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,922评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,554评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,838评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,861评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,677评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,483评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,928评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,104评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,403评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,945评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,156评论 2 339

推荐阅读更多精彩内容