Skip to content

Instantly share code, notes, and snippets.

@ambar
Created October 23, 2011 04:55
Show Gist options
  • Save ambar/1306893 to your computer and use it in GitHub Desktop.
Save ambar/1306893 to your computer and use it in GitHub Desktop.
throttle and debounce
/*
* 频率控制 返回函数连续调用时,action 执行频率限定为 次 / delay
* @param delay {number} 延迟时间,单位毫秒
* @param action {function} 请求关联函数,实际应用需要调用的函数
* @param tail? {bool} 是否在尾部用定时器补齐调用
* @return {function} 返回客户调用函数
*/
var throttle = function(delay,action,tail,debounce) {
var now = Date.now, last_call = 0, last_exec = 0, timer = null, curr, diff,
ctx, args, exec = function() {
last_exec = now();
action.apply(ctx,args);
};
return function() {
ctx = this, args = arguments,
curr = now(), diff = curr - (debounce?last_call:last_exec) - delay;
clearTimeout(timer);
if(debounce){
if(tail){
timer = setTimeout(exec,delay);
}else if(diff>=0){
exec();
}
}else{
if(diff>=0){
exec();
}else if(tail){
timer = setTimeout(exec,-diff);
}
}
last_call = curr;
}
}
/*
* 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 idle,action 才会执行
* @param idle {number} 空闲时间,单位毫秒
* @param action {function} 请求关联函数,实际应用需要调用的函数
* @param tail? {bool} 是否在尾部执行
* @return {function} 返回客户调用函数
*/
var debounce = function(idle,action,tail) {
return throttle(idle,action,tail,true);
}
<html>
<head>
<title></title>
<meta charset="utf-8">
<style type="text/css">
body{height:999em;font:12px Monoco;}
#mousemove div{float:left;}
#mousemove .play{width:400px;height:300px;background:#07a;color:#eee;}
#mousemove .desc{margin:0 .5em;}
#mousemove .desc ul{padding-left:1em;}
#plot-wrapper{width:100%;overflow:scroll;}
#control-button{position:fixed;top:1em;right:1em;}
.clear{clear:both;}
li{list-style:none;}
h2{font-size:1em;};
</style>
</head>
<body>
<div id="control-button">
<button id="log">reduce log</button>
<button id="reset">reset</button>
</div>
<section id="mousemove">
<div class="play">
mousemove playground
</div>
<div class="desc">
<h2>类型点选</h2>
<ul>
<li><label><input type="radio" name="throttle-type" value="debounce_head">debounce(150,trace)</label></li>
<li><label><input type="radio" name="throttle-type" value="debounce_tail">debounce(150,trace,true)</label></li>
<li><label><input type="radio" name="throttle-type" value="throttle_head">throttle(150,trace)</label></li>
<li><label><input type="radio" name="throttle-type" value="throttle_tail" checked>throttle(150,trace,true)</label></li>
</ul>
<p>空闲延迟判断 debounce(delay,action,tail?)</p>
<p>连续延迟判断 throttle(delay,action,tail?)</p>
</div>
<br class="clear">
</section>
<hr>
<section id="plot-wrapper">
<canvas id="plot"></canvas>
</section>
<hr>
<div>
<input id="autocomplete" placeholder="autocomplete query"><span id="query"></span>
</div>
<script>
var $ = function(sel) {
return document.querySelector(sel);
}
var $$ = function(sel) {
return document.querySelectorAll(sel);
}
Function.prototype.curry = function() {
var fn = this, args = arguments;
return function() {
fn.apply(this,args);
}
}
Function.prototype.after = function(inject) {
var fn = this, args = arguments;
return function() {
inject.apply(this,args);
return fn.apply(this,args);
}
}
Array.prototype.last = function(){
return this[this.length-1];
}
Number.prototype.times = function(action,scope){
var i = 0, n = this.valueOf(), scope = scope || window;
n < 0 && (n=0);
while(i<n)
action.call(scope,i++);
};
var stack = [];
// 内部跟踪
var debug = true;
var debug_started = 0;
var debug_inject = function() {
var curr = Date.now(), tag = {executed:false,time:curr};
stack.push(tag);
if(!debug_started) debug_started = curr;
add_line(curr-debug_started,false);
return tag;
}
var post_debug_inject = function(tag) {
tag.time = Date.now();
tag.executed = true;
}
var throttle = function(delay,action,tail,debounce) {
var now = Date.now, last_call = 0, last_exec = 0, timer = null, curr, diff,
ctx, args, exec = function() {
last_exec = now();
action.apply(ctx,args);
};
return function() {
ctx = this, args = arguments,
curr = now(), diff = curr - (debounce?last_call:last_exec) - delay;
if(debug){
var _tmp = exec;
exec = exec.after( post_debug_inject.curry( debug_inject() ) );
}
clearTimeout(timer);
if(debounce){
if(tail){
timer = setTimeout(exec,delay);
}else if(diff>=0){
exec();
}
}else{
if(diff>=0){
exec();
}else if(tail){
timer = setTimeout(exec,-diff);
}
}
last_call = curr;
debug && (exec = _tmp);
}
}
/*
* @param {bool} tail 对应两种常见情形
* 1) true 先计算空闲时间再执行
* 如: 自动完成,keyup事件之后,统计时间,满足延迟才执行
* 2) false 先执行再计算空闲时间
* 如: 游戏射击,键盘keydown事件开火,按住不放的话,必须keyup之后有足够延迟才再次开火。
*/
var debounce = function(delay,action,tail) {
return throttle(delay,action,tail,true);
}
var repeat = function(char,n){
var stars = [];
n.times(function(){ stars.push(char) });
return stars.join('')
};
var plot = $('#plot');
var plot_wrapper = $('#plot-wrapper');
var w = window.innerWidth - 80, h = 300;
var ctx = plot.getContext('2d');
// 外部跟踪
var trace_count = 0, trace_last = 0, trace_started = 0;
var trace = function(e){
var now = Date.now(), elapsed = now - trace_last;
// console.log(trace_count++,'@ ',elapsed,e.type,e,this.nodeType);
trace_last = now;
if(debug_started){
trace_started = debug_started;
if(stack.length) now = stack[stack.length-1].time;
}
if(!trace_started) trace_started = now;
add_line(now-trace_started,true);
};
var log = function() {
if(!stack.length){
console.log('empty log');
return;
}
// stack = stack.filter(function(s) { return s.executed })
// stack.forEach(function(f){ console.info(f) })
stack.reduce(function(a,b){
var diff = b.time-a.time;
console[b.executed?'warn':'error']( repeat('*',Math.min(100,diff)), diff )
return b;
},{time:stack[0].time});
var marked = lines
.filter(function(l){ return l.highlight })
.map(function(l){ return l.x })
console.log(marked);
}
var reset = function() {
debug_started = trace_started = lines.length = stack.length = 0
w = window.innerWidth - 80;
repaint();
}
var clear = function() {
ctx.fillStyle = 'rgba(33,33,33,.8)';
ctx.fillRect(0,0,w,h);
}
var resize = function() {
plot.width = w;
plot.height = h;
}
var bg_unit = 50;
var bg_offset_x = 15;
var bg_offset_y = 10;
var draw_bg = function() {
// ctx.scale(.5,.5);
ctx.beginPath();
ctx.lineWidth = .5
ctx.strokeStyle = '#eee';
var row = h/bg_unit*2, col = w/bg_unit;
// console.log(row,col);
row.times(function(x) {
var end = 100 * x + bg_offset_y;
ctx.moveTo(bg_offset_x,end)
ctx.lineTo(w,end)
})
col.times(function(x) {
var begin = bg_unit * x;
ctx.moveTo(begin + bg_offset_x, bg_offset_y)
ctx.lineTo(begin + bg_offset_x, h)
mark_text(begin,begin+1,h-5)
})
ctx.stroke()
ctx.closePath();
}
var mark_text = function(text,x,y,color) {
if(x>w){
if(x>3e4){
console.warn("[reset:]prevent 'NS_ERROR_OUT_OF_MEMORY'");
reset();
return;
}
w = x+250;
}
ctx.fillStyle = color || '#eee';
ctx.fillText(text, x + bg_offset_x, y);
}
var mark_line = function(x,highlight) {
ctx.beginPath();
ctx.lineWidth = 1;
var top = (highlight ? h*.5 : h*.75) + bg_offset_y;
var color = highlight ? 'yellow' : 'red';
ctx.strokeStyle = color;
ctx.moveTo(x + bg_offset_x, top);
ctx.lineTo(x + bg_offset_x, h);
ctx.stroke()
ctx.closePath();
mark_text(x, x, top + 15, color);
plot_wrapper.scrollLeft = plot_wrapper.scrollWidth;
}
var lines = [];
var mark_all_lines = function() {
lines.forEach(function(line) {
mark_line(line.x,line.highlight)
})
}
var repaint = function() {
resize();
clear();
draw_bg();
mark_all_lines();
}
var delay_timer = null;
var add_line = function(x,highlight) {
lines.push({x:x,highlight:highlight});
if(!delay_timer)
delay_timer = setTimeout(function() {
delay_timer = clearTimeout(delay_timer);
repaint();
},100);
}
repaint();
mark_line(112);
mark_line(212,true);
window.addEventListener('scroll'
// ,trace
// ,throttle(50,trace)
,throttle(50,trace,true)
);
window.addEventListener('resize'
// ,trace
,throttle(50,trace,true)
);
// window.addEventListener('keydown',debounce(550,trace,true));
// window.addEventListener('keydown',debounce(550,trace));
var mm_throttle_types = {
debounce_head : debounce(150,trace),
debounce_tail : debounce(150,trace,true),
throttle_head : throttle(150,trace),
throttle_tail : throttle(150,trace,true)
}
var mousemove = $('#mousemove .play');
var mousemove_handler = null;
var set_mousemove_handler = function() {
mousemove.removeEventListener('mousemove',mousemove_handler)
mousemove_handler = mm_throttle_types[$('input[name=throttle-type]:checked').value];
mousemove.addEventListener('mousemove',mousemove_handler)
}
set_mousemove_handler();
[].slice.call($$('input[name=throttle-type]'))
.forEach(function(radio) {
radio.onchange = function() {
reset();
set_mousemove_handler();
};
})
/*$('#mousemove .play').addEventListener('mousemove'
// ,debounce(150,trace) // 头
// ,debounce(150,trace,true) // 尾
// ,throttle(150,trace)
// ,throttle(150,trace,true)
);*/
$('#log').addEventListener('click',log)
$('#reset').addEventListener('click',reset);
$('#autocomplete').addEventListener('keyup',debounce(250,function() {
$('#query').textContent = '[debounce]:'+this.value;
},true))
</script>
<!--local-->
<!-- <script src="throttle_test.js"></script> -->
</body>
</html>
@wushuang5112
Copy link

很好,不错!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment