lodash源码学习debounce,throttle
函数去抖(debounce)和函数节流(throttle)通常是用于优化浏览器中频繁触发的事件,具体内容可以看这篇文章http://www.cnblogs.com/fsjohnhuang/p/4147810.html
直接看lodash中对应方法的实现
_.debounce(func, [wait=0], [options={}])
//debounce.js var isObject = require('./isObject'),//是否是对象 now = require('./now'),//获取当前时间 toNumber = require('./toNumber');//转为为数字 var FUNC_ERROR_TEXT = 'Expected a function'; var nativeMax = Math.max,//原生最大值方法 nativeMin = Math.min;//原生最小值方法 /** * 函数去抖,也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。 * * @param {Function} func 需要去抖的函数. * @param {number} [wait=0] 延迟执行的时间. * @param {Object} [options={}] 选项对象. * @param {boolean} [options.leading=false] 指定是否在超时前调用. * @param {number} [options.maxWait] func延迟调用的最大时间. * @param {boolean} [options.trailing=true] 指定是否在超时后调用. * @returns {Function} 返回去抖之后的函数. * @example * * // Avoid costly calculations while the window size is in flux. * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); * * // Invoke `sendMail` when clicked, debouncing subsequent calls. * jQuery(element).on('click', _.debounce(sendMail, 300, { * 'leading': true, * 'trailing': false * })); * * // Ensure `batchLog` is invoked once after 1 second of debounced calls. * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); * var source = new EventSource('/stream'); * jQuery(source).on('message', debounced); * * // Cancel the trailing debounced invocation. * jQuery(window).on('popstate', debounced.cancel); */ function debounce(func, wait, options) { var lastArgs, //上次调用参数 lastThis, //上次调用this maxWait, //最大等待时间 result, //返回结果 timerId, //timerId lastCallTime, //上次调用debounced时间,即触发时间,不一定会调用func lastInvokeTime = 0, //上次调用func时间,即成功执行时间 leading = false, //超时之前 maxing = false, //是否传入最大超时时间 trailing = true; //超时之后 if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } wait = toNumber(wait) || 0; if (isObject(options)) { leading = !!options.leading; maxing = 'maxWait' in options; maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; trailing = 'trailing' in options ? !!options.trailing : trailing; } function invokeFunc(time) {//调用func,参数为当前时间 var args = lastArgs,//调用参数 thisArg = lastThis;//调用的this lastArgs = lastThis = undefined;//清除lastArgs和lastThis lastInvokeTime = time; //上次调用时间为当前时间 result = func.apply(thisArg, args);//调用func,并将结果返回 return result; } function leadingEdge(time) {//超时之前调用 lastInvokeTime = time;//设置上次调用时间为当前时间 timerId = setTimeout(timerExpired, wait); //开始timer return leading ? invokeFunc(time) : result;//如果leading为true,调用func,否则返回result } function remainingWait(time) {//设置还需要等待的时间 var timeSinceLastCall = time - lastCallTime,//距离上次触发的时间 timeSinceLastInvoke = time - lastInvokeTime,//距离上次调用func的时间 result = wait - timeSinceLastCall;//还需要等待的时间 return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result; } function shouldInvoke(time) {//是否应该被调用 var timeSinceLastCall = time - lastCallTime,//距离上次触发时间的时间 timeSinceLastInvoke = time - lastInvokeTime;//距离上次调用func的时间 return (lastCallTime === undefined || (timeSinceLastCall >= wait) || (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); } function timerExpired() {//刷新timer var time = now(); if (shouldInvoke(time)) {//如果可以调用,调用trailingEdge return trailingEdge(time); } timerId = setTimeout(timerExpired, remainingWait(time));//不调用则重置timerId } function trailingEdge(time) {//超时之后调用 timerId = undefined; // Only invoke if we have `lastArgs` which means `func` has been // debounced at least once. if (trailing && lastArgs) {//如果设置trailing为true,并且有lastArgs,调用func return invokeFunc(time); } lastArgs = lastThis = undefined;//清除lastArgs和lastThis return result;//否则返回result } function cancel() {//取消执行 if (timerId !== undefined) { clearTimeout(timerId); } lastInvokeTime = 0; lastArgs = lastCallTime = lastThis = timerId = undefined; } function flush() {//直接执行 return timerId === undefined ? result : trailingEdge(now()); } function debounced() { var time = now(), isInvoking = shouldInvoke(time);//判断是否可以调用 lastArgs = arguments;//得到参数 lastThis = this;//得到this对象 lastCallTime = time;//触发时间 if (isInvoking) { if (timerId === undefined) {//首次触发,调用leadingEdge return leadingEdge(lastCallTime); } if (maxing) { // 处理多次频繁的调用 timerId = setTimeout(timerExpired, wait);//设置定时器 return invokeFunc(lastCallTime); } } if (timerId === undefined) {//如果没有timer,设置定时器 timerId = setTimeout(timerExpired, wait); } return result;//返回result } debounced.cancel = cancel; debounced.flush = flush; return debounced; } module.exports = debounce;
_.throttle(func, [wait=0], [options={}])
//throttle.js var debounce = require('./debounce'),//debounce方法 isObject = require('./isObject');//判断是否为对象 var FUNC_ERROR_TEXT = 'Expected a function'; /** * 函数节流 * * @param {Function} func 需要处理的函数. * @param {number} [wait=0] 执行间隔. * @param {Object} [options={}] 选项对象. * @param {boolean} [options.leading=false] 指定是否在超时前调用. * @param {number} [options.maxWait] func延迟调用的最大时间. * @param {boolean} [options.trailing=true] 指定是否在超时后调用. * @returns {Function} 返回节流之后的函数. * @example * * // Avoid excessively updating the position while scrolling. * jQuery(window).on('scroll', _.throttle(updatePosition, 100)); * * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. * var throttled = _.throttle(renewToken, 300000, { 'trailing': false }); * jQuery(element).on('click', throttled); * * // Cancel the trailing throttled invocation. * jQuery(window).on('popstate', throttled.cancel); */ function throttle(func, wait, options) { var leading = true, trailing = true; if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } if (isObject(options)) { leading = 'leading' in options ? !!options.leading : leading; trailing = 'trailing' in options ? !!options.trailing : trailing; } return debounce(func, wait, { 'leading': leading, 'maxWait': wait, 'trailing': trailing }); } module.exports = throttle;
可以看到这两个方法基本上都差不多,区别在于throttle初始的时候设置了leading为true和maxWait,这样和debounce的区别在于,在第一次触发的时候throttle会直接调用,并且每隔wait的时间都会调用一次,而debounce第一次不会调用,并且只有当触发的间隔时间大于wait时才会调用,否则一直不会调用。