* 频率控制 返回函数连续调用时,action 执行频率限定为 次 / delay* @param delay {number} 延迟时间,单位毫秒* @param action {function} 请求关联函数,实际应用需要调用的函数* @return {function} 返回客户调用函数*/throttle(delay,action) 2. 简单实现var throttle = function(delay, action){ var last = 0return function(){ var curr = +new Date() if (curr - last > delay){action.apply(this, arguments)last = curr} }}下面我仔细解释一下这个节流函数。var COUNT = 0;function testFn() { console.log(COUNT++); }// 浏览器resize的时候// 1. 清除之前的计时器// 2. 添加一个计时器让真正的函数testFn延后100毫秒触发window.onresize = function () { var timer = null; clearTimeout(timer);timer = setTimeout(function() {testFn(); }, 100);};细心的同学会发现上面的代码其实是错误的,这是新手会犯的一个问题:setTimeout 函数返回值应该保存在一个相对全局变量里面,否则每次 resize 的时候都会产生一个新的计时器,这样就达不到我们发的效果了var timer = null;window.onresize = function () { clearTimeout(timer); timer = setTimeout(function() {testFn(); }, 100);};这时候代码就正常了,但是又多了一个新问题 —— 产生了一个全局变量 timer。这是我们不想见到的,如果这个页面还有别的功能也叫 timer 不同的代码之前就是产生冲突。为了解决这个问题我们要用 JavaScript 的一个语言特性:闭包 closures 。相关知识读者可以去 MDN 中了解,改造后的代码如下:/** * 函数节流方法 * @param Function fn 延时调用函数 * @param Number delay 延迟多长时间 * @return Function 延迟执行的方法 */var throttle = function (fn, delay) { var timer = null;return function () {clearTimeout(timer);timer = setTimeout(function() { fn();}, delay); }};window.onresize = throttle(testFn, 200, 1000);我们用一个闭包函数(throttle节流)把 timer 放在内部并且返回延时处理函数,这样以来 timer 变量对外是不可见的,但是内部延时函数触发时还可以访问到 timer 变量。var throttle = function (fn, delay) { var timer = null;return function () {clearTimeout(timer);timer = setTimeout(function() { fn();}, delay); }}; var f = throttle(testFn, 200);window.onresize = function () { f();};这里主要了解一点:throttle 被调用后返回的 function 才是真正的 onresize 触发时需要调用的函数/** * 函数节流方法 * @param Function fn 延时调用函数 * @param Number delay 延迟多长时间 * @param Number atleast 至少多长时间触发一次 * @return Function 延迟执行的方法 */var throttle = function (fn, delay, atleast) { var timer = null; var previous = null;return function () {var now = +new Date(); if ( !previous ) previous = now; if ( now - previous > atleast ) { fn(); // 重置上一次开始时间为本次结束时间 previous = now;} else { clearTimeout(timer); timer = setTimeout(function() {fn(); }, delay);} }};实践:<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>throttle</title></head><body> <div style="height:5000px"><div id="demo" style="position:fixed;"></div> </div> <script> var COUNT = 0, demo = document.getElementById("demo"); function testFn() {demo.innerHTML += "testFN 被调用了 " + ++COUNT + "次<br>";}var throttle = function (fn, delay, atleast) {var timer = null;var previous = null; return function () { var now = +new Date();if ( !previous ) previous = now; if ( atleast && now - previous > atleast ) {fn();// 重置上一次开始时间为本次结束时间previous = now;clearTimeout(timer); } else {clearTimeout(timer);timer = setTimeout(function() { fn(); previous = null;}, delay); }} }; window.onscroll = throttle(testFn, 200); // window.onscroll = throttle(testFn, 500, 1000); </script></body></html>我们用两个 case 来测试效果,分别是添加至少触发 atleast 参数和不添加:// case 1window.onscroll = throttle(testFn, 200);// case 2window.onscroll = throttle(testFn, 200, 500);case 1 的表现为:在页面滚动的过程(不能停止)中 testFN 不会被调用,直到停止的时候会调用一次,也就是说执行的是 throttle 里面 最后 一个 setTimeout ,效果如图:

case 2 的表现为:在页面滚动的过程(不能停止)中 testFN 第一次会延迟 500ms 执行(来自至少延迟逻辑),后来至少每隔 500ms 执行一次,效果如图

如上展示,我们要实现的效果已经介绍完毕并奉上了示例,望对有需要的朋友有所帮助。后续的一些辅助性优化读者可以自己琢磨,如:函数 this 指向,返回值保存等。总之仔仔细细理解一下这个过程感觉真好!