Created
October 23, 2011 04:55
-
-
Save ambar/1306893 to your computer and use it in GitHub Desktop.
throttle and debounce
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 频率控制 返回函数连续调用时,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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
很好,不错!!!