Giter Site home page Giter Site logo

Comments (20)

riskers avatar riskers commented on July 29, 2024 36

throttle 和 debounce 的应用场景应该是分的很清楚的

  • 按一个按钮发送 AJAX:给 click 加了 debounce 后就算用户不停地点这个按钮,也只会最终发送一次;如果是 throttle 就会间隔发送几次
  • 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次

from underscore-analysis.

oakland avatar oakland commented on July 29, 2024 29

@joesonw ,你说的 side effect 是可以消除的。其实不必要非得给 function 添加这个属性,只要是一个在 debounce 函数外部的变量就可以。高程三里的这个写法其实是可改成下面这个样子的:

var timer = null;
function debounce(method, context) {
    clearTimeout(timer);
    timer = setTimeout(function() {
        method.call(context);
    }, 1000);
}

function print() {
    console.log('hello world');
}

window.onscroll = function() {
    debounce(print);
};

为了避免对全局的污染,其实最好的方式是将 timer 放入函数中,成为一个局部变量,所以上面的写法可以改写成下面的方式:

function debounce(method, context) {
    var timer = null;
    return function() {
        clearTimeout(timer);
        timer = setTimeout(function() {
            method.call(context);
        }, 1000);
    }
}

function print() {
    console.log('hello world');
}

window.onscroll = debounce(print);

从这个意义上讲,闭包其实就是用来将两个内容隔离用的,将 timer 放入函数中,那么就需要将原来的语句放入函数中,使其与 timer 隔离,最近返回这个函数。结果就会和原来的效果是一样的。

from underscore-analysis.

joesonw avatar joesonw commented on July 29, 2024 5

改变了输入值, 给function多加了属性.

from underscore-analysis.

joesonw avatar joesonw commented on July 29, 2024 2

@oakland
恩, 这样就是接近underscore的方法的. 我的意思是原代码那样是会有隐患.

from underscore-analysis.

gitwd avatar gitwd commented on July 29, 2024 2

@joesonw 基础方法确实有隐患,如果传入的method是一个匿名函数,绑定到匿名函数的timer将不会被清理掉

from underscore-analysis.

warjiang avatar warjiang commented on July 29, 2024 1

最显而易见的是基础版每此触发事件都会取消定时器,然后重新设置定时器,而 underscore 中会在一定时间后才取消定时器,重新设置定时器

我认为你这儿说的有问题,setTimeout是不精准延时,debounce里面补充判断如果last在[0,wait)区间,则继续setTimeout一个wait-last的时间再执行函数,保证函数执行程序一定在延时了wait之后执行。

from underscore-analysis.

joesonw avatar joesonw commented on July 29, 2024

基础版最重要的是, 有side effect吧.

from underscore-analysis.

lessfish avatar lessfish commented on July 29, 2024

@joesonw 请教下 side effect 具体是?

from underscore-analysis.

jsspace avatar jsspace commented on July 29, 2024

发现个问题,这个 clearTimeout() 直接让 setTimeout() 中的函数不执行,而不是调用 clearTimeout 之后立即执行里面的函数。也就是说,setTimeout() 的回调会在最后一次执行 debounce() 后起作用。这样就保证了只执行一次,就是节流啊。。。

from underscore-analysis.

lessfish avatar lessfish commented on July 29, 2024

@jsspace 我的理解是 「节流」(throttle)是控制函数执行的频率,而不是只执行一次(debounce)

from underscore-analysis.

jsspace avatar jsspace commented on July 29, 2024

哦哦,我弄混了

from underscore-analysis.

lessfish avatar lessfish commented on July 29, 2024

@riskers 不错,给 throttle 应用加了这个 case

from underscore-analysis.

oakland avatar oakland commented on July 29, 2024

debounce 有种 hold 住的感觉,一个动作不停地被触发,但是又不停地被终止,两次触发之间的时间长于给定的时间段才会真正触发这个时间。
不断触发又终止的过程,其实有点像卡带一样,不停地在重复一个声音,但是这个声音刚出来就被终止刚出来就被终止,直到不再卡带才会顺畅的播放一次。
上面的代码再做修改,可以发现,其实 debounce() 函数被触发了很多次,不过 print 函数被不断地触发禁止,触发禁止...

function debounce(method, context) {
    var timer = null;
    var n = 0;
    return function() {
        clearTimeout(timer);
        timer = setTimeout(function() {
            method.call(context);
        }, 1000);
        console.log(n++);
    }
}

function print() {
    console.log('hello world');
}

window.onscroll = debounce(print);

from underscore-analysis.

gdh1995 avatar gdh1995 commented on July 29, 2024

// 这里的 timeout 一定是 null 了吧

我感觉这里是这样的:func作为用户传入的任意函数,有可能会反过来调用debounce返回的新函数,比如

var func, de, i = 0;
func = function() {
  i++;
  if (i < 10) {
    console.log(i);
    de();
    // setTimeout(de, 10);
  }
};
de = _.debounce(func, 40);
de();

这个会输出1到9,改改条件应该就能出现 de -> func -> de这种嵌套调用了。

更新1:我才意识到我也把debounce当成节流了,抱歉。
更新2:那么debounce函数里应该可以判断immediate,如果是true则不用储存context / args了,正如最新的jashkenas/underscore:master里的写法。

那么,同理我发现底下的if (callNow)有问题,可能会造成context和args被提前释放:

var f, d, tick = 0;
f = function() {
  console.log('tick:', ++tick, [].slice.call(arguments, 0));
  if (tick === 1) {
    return d(1, 2) || 'tick-1 but d(1,2) returns empty';
  }
  return 'tick-' + tick;
};
d = _.debounce(f, 100, true);
var ret1 = d('ni hao');
console.log('first result', ret1);

输出是:

VM94:63 call now: begin with ["ni hao"]
VM94:77 tick: 1 ["ni hao"]
VM94:66 call now: end with [1, 2]
VM94:85 first result tick-1 but d(1,2) returns empty

demo见https://jsfiddle.net/emx3zdd9/1/

from underscore-analysis.

gdh1995 avatar gdh1995 commented on July 29, 2024

话说您用的understore 1.8.3 和 现在的jashkenas/underscore:master (https://github.com/jashkenas/underscore/blob/97cfcbcbbcedf544a13127dcca3e0ddad94ff830/underscore.js) 差了很多啊,_.debounce 完全被重写了。

我有个疑问是,master上的debounce已经在每次进入时就clearTimeout了,和您的“性能优化”的解释不一样,请问这两个方案的真正差别是什么?是应用场景导致的取舍吗?

  • 我记得Chrome Developer Tools的Timeline里,每个setTimeout都要0.3-0.5ms(补充:好像是30~50us)吧
  • 我现在想对<input>.oninput做debounce (初步认为300ms比较好),已知用户打字够快且有时会连续输入大段文本,请问是否该每次清理计时器呢?

from underscore-analysis.

gdh1995 avatar gdh1995 commented on July 29, 2024

@gitwd 请问为什么匿名函数timer不会被清理?区别在哪里呢?

from underscore-analysis.

zhongdeming428 avatar zhongdeming428 commented on July 29, 2024

@hanzichi 韩老师你好!我想请教一下一个问题。

在您所阅读的underscore源码中(1.8.3),假设有如下代码:

var a = _.debounce((a)=>{
      console.log(a);
  }, 5000);
  a(1);
  a(2);
  a(3);
  a(4);
  a(5);
  a(6);

我的理解是:

a只有第一次被调用时才会进入later函数,但每次调用a都会更新时间戳Timestamp,而later内部会计算时间差,时间差不足时,递归调用later计算时间差,一旦时间差足够就触发传入的异步函数,最终执行的还是只有最后一个a函数。不知道正不正确?

我现在阅读的源码是最新版的,其中的_.debounce函数已经完全改进了,不再依赖于计算时间差,而是利用了JavaScript的异步机制:

code

假设有同样一段代码在最新版underscore中执行:

var a = _.debounce((a)=>{
      console.log(a);
  }, 5000);
  a(1);
  a(2);
  a(3);
  a(4);
  a(5);
  a(6);

我是否可以这样理解:

JavaScript优先执行完执行队列中的同步代码(以上所有代码)之后,再去执行事件队列中的异步代码。上方程序在执行所有同步代码时,每次a函数被调用,都会clearTimeout取消事件队列中的异步任务,导致前文a函数设置的异步任务被取消,直到最后一个a函数被执行时,才会开始计时,最终执行的也会是最后一个a函数。

两者相比较而言,后者使用变量更少,递归调用更少,数据计算更少;利用了JavaScript的异步机制,使用较少的代码较为自然的实现了去抖功能。

from underscore-analysis.

zhongdeming428 avatar zhongdeming428 commented on July 29, 2024

@hanzichi 韩老师您的文中还有一处小小的笔误:

// 设置 wait seconds 后触发 later 方法
    // 无论是否 callNow(如果是 callNow,也进入 later 方法,去 later 方法中判断是否执行相应回调函数)
    // 在某一段的连续触发中,只会在第一次触发时进入这个 if 分支中
    if (!timeout)
      // 设置了 timeout,所以以后不会进入这个 if 分支了
      timeout = setTimeout(later, wait);

第一行注释中,wait seconds是否应该改为wait milliseconds?
冒昧啦!

from underscore-analysis.

aswind7 avatar aswind7 commented on July 29, 2024

@gdh1995 请问为什么匿名函数timer不会被清理?区别在哪里呢?
/分割线/
因为每次执行throttle 都会创建一个新的匿名函数, 匿名函数身上没有tId.

from underscore-analysis.

Trendymen avatar Trendymen commented on July 29, 2024

underscore版虽然不用每次触发时都清除计时器,但是每次触发时也使用Date对象重新生成了一个时间戳呀。

from underscore-analysis.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.