1 控制函数触发时间: 2 3 function myWait(func, wait, options) { 4 5 var context, args, result; 6 7 8 9 // setTimeout 的 handler 10 11 var timeout = null; 12 13 14 15 // 标记时间戳 16 17 // 上一次执行回调的时间戳 18 19 var previous = 0; 20 21 22 23 // 如果没有传入 options 参数 24 25 // 则将 options 参数置为空对象 26 27 if (!options) 28 29 options = {}; 30 31 32 33 var later = function() { 34 35 // 如果 options.leading === false 36 37 // 则每次触发回调后将 previous 置为 0 38 39 // 否则置为当前时间戳 40 41 previous = options.leading === false ? 0 :new Date(); 42 43 timeout = null; 44 45 result = func.apply(context, args); 46 47 // 这里的 timeout 变量一定是 null 了吧 48 49 // 是否没有必要进行判断? 50 51 if (!timeout) 52 53 context = args = null; 54 55 }; 56 57 58 59 // 以滚轮事件为例(scroll) 60 61 // 每次触发滚轮事件即执行这个返回的方法 62 63 // _.throttle 方法返回的函数 64 65 return function() { 66 67 // 记录当前时间戳 68 69 var now =new Date(); 70 71 72 73 // 第一次执行回调(此时 previous 为 0,之后 previous 值为上一次时间戳) 74 75 // 并且如果程序设定第一个回调不是立即执行的(options.leading === false) 76 77 // 则将 previous 值(表示上次执行的时间戳)设为 now 的时间戳(第一次触发时) 78 79 // 表示刚执行过,这次就不用执行了 80 81 if (!previous && options.leading === false) 82 83 previous = now; 84 85 // 距离下次触发 func 还需要等待的时间 86 87 var remaining = wait - (now - previous); 88 89 context = this; 90 91 args = arguments; 92 93 94 95 // 要么是到了间隔时间了,随即触发方法(remaining <= 0) 96 97 // 要么是没有传入 {leading: false},且第一次触发回调,即立即触发 98 99 // 此时 previous 为 0,wait - (now - previous) 也满足 <= 0 100 101 // 之后便会把 previous 值迅速置为 now 102 103 // ========= // 104 105 // remaining > wait,表示客户端系统时间被调整过 106 107 // 则马上执行 func 函数 108 109 // @see https://blog.coding.net/blog/the-difference-between-throttle-and-debounce-in-underscorejs 110 111 if (remaining <= 0 || remaining > wait) { 112 113 if (timeout) { 114 115 clearTimeout(timeout); 116 117 // 解除引用,防止内存泄露 118 119 timeout = null; 120 121 } 122 123 124 125 // 重置前一次触发的时间戳 126 127 previous = now; 128 129 130 131 // 触发方法 132 133 // result 为该方法返回值 134 135 result = func.apply(context, args); 136 137 // 引用置为空,防止内存泄露 138 139 // 感觉这里的 timeout 肯定是 null 啊?这个 if 判断没必要吧? 140 141 if (!timeout) 142 143 context = args = null; 144 145 } else if (!timeout && options.trailing !== false) { // 最后一次需要触发的情况 146 147 // 如果已经存在一个定时器,则不会进入该 if 分支 148 149 // 如果 {trailing: false},即最后一次不需要触发了,也不会进入这个分支 150 151 // 间隔 remaining milliseconds 后触发 later 方法 152 153 timeout = setTimeout(later, remaining); 154 155 } 156 157 158 159 // 回调返回值 160 161 return result; 162 163 }; 164 165 }; 166 167 168 169 控制触发频率: 170 171 function mybounce(func, wait, immediate) { 172 173 var timeout, args, context, timestamp, result; 174 175 176 177 var later = function() { 178 179 // 定时器设置的回调 later 方法的触发时间,和连续事件触发的最后一次时间戳的间隔 180 181 // 如果间隔为 wait(或者刚好大于 wait),则触发事件 182 183 var last = new Date() - timestamp; 184 185 186 187 // 时间间隔 last 在 [0, wait) 中 188 189 // 还没到触发的点,则继续设置定时器 190 191 // last 值应该不会小于 0 吧? 192 193 if (last < wait && last >= 0) { 194 195 timeout = setTimeout(later, wait - last); 196 197 } else { 198 199 // 到了可以触发的时间点 200 201 timeout = null; 202 203 // 可以触发了 204 205 // 并且不是设置为立即触发的 206 207 // 因为如果是立即触发(callNow),也会进入这个回调中 208 209 // 主要是为了将 timeout 值置为空,使之不影响下次连续事件的触发 210 211 // 如果不是立即执行,随即执行 func 方法 212 213 if (!immediate) { 214 215 // 执行 func 函数 216 217 result = func.apply(context, args); 218 219 // 这里的 timeout 一定是 null 了吧 220 221 // 感觉这个判断多余了 222 223 if (!timeout) 224 225 context = args = null; 226 227 } 228 229 } 230 231 }; 232 233 234 235 // 嗯,闭包返回的函数,是可以传入参数的 236 237 // 也是 DOM 事件所触发的回调函数 238 239 return function() { 240 241 // 可以指定 this 指向 242 243 context = this; 244 245 args = arguments; 246 247 248 249 // 每次触发函数,更新时间戳 250 251 // later 方法中取 last 值时用到该变量 252 253 // 判断距离上次触发事件是否已经过了 wait seconds 了 254 255 // 即我们需要距离最后一次事件触发 wait seconds 后触发这个回调方法 256 257 timestamp = new Date(); 258 259 260 261 // 立即触发需要满足两个条件 262 263 // immediate 参数为 true,并且 timeout 还没设置 264 265 // immediate 参数为 true 是显而易见的 266 267 // 如果去掉 !timeout 的条件,就会一直触发,而不是触发一次 268 269 // 因为第一次触发后已经设置了 timeout,所以根据 timeout 是否为空可以判断是否是首次触发 270 271 var callNow = immediate && !timeout; 272 273 274 275 // 设置 wait seconds 后触发 later 方法 276 277 // 无论是否 callNow(如果是 callNow,也进入 later 方法,去 later 方法中判断是否执行相应回调函数) 278 279 // 在某一段的连续触发中,只会在第一次触发时进入这个 if 分支中 280 281 if (!timeout) 282 283 // 设置了 timeout,所以以后不会进入这个 if 分支了 284 285 timeout = setTimeout(later, wait); 286 287 // 如果是立即触发 288 289 if (callNow) { 290 291 // func 可能是有返回值的 292 293 result = func.apply(context, args); 294 295 // 解除引用 296 297 context = args = null; 298 299 } 300 301 return result; 302 303 }; 304 305 }; 306 307 308 309 示例: 310 311 var x=0; 312 313 function add(){ 314 315 x++ 316 317 $(‘p‘).html(x) 318 319 } 320 321 $(‘.div‘).mouseenter(myWait(add,1000))