Browse Source

refactor: avoid frequent setTimeout

Gerald 6 years ago
parent
commit
e447a72962
6 changed files with 109 additions and 14 deletions
  1. 27 13
      src/common/util.js
  2. 40 1
      test/common/index.test.js
  3. 1 0
      test/mock/base.js
  4. 3 0
      test/mock/index.js
  5. 1 0
      test/mock/polyfill.js
  6. 37 0
      test/mock/time.js

+ 27 - 13
src/common/util.js

@@ -20,28 +20,42 @@ export function memoize(func, resolver = toString) {
 }
 
 export function debounce(func, time) {
+  let startTime;
   let timer;
-  function run(thisObj, args) {
+  let callback;
+  function checkTime() {
     timer = null;
-    func.apply(thisObj, args);
+    if (performance.now() >= startTime) callback();
+    else checkTimer();
   }
-  return function debouncedFunction(...args) {
-    if (timer) clearTimeout(timer);
-    timer = setTimeout(run, time, this, args);
-  };
+  function checkTimer() {
+    if (!timer) {
+      const delta = startTime - performance.now();
+      timer = setTimeout(checkTime, delta);
+    }
+  }
+  function debouncedFunction(...args) {
+    startTime = performance.now() + time;
+    callback = () => {
+      callback = null;
+      func.apply(this, args);
+    };
+    checkTimer();
+  }
+  return debouncedFunction;
 }
 
 export function throttle(func, time) {
   let timer;
-  function run(thisObj, args) {
-    timer = null;
-    func.apply(thisObj, args);
-  }
-  return function throttledFunction(...args) {
+  function throttledFunction(...args) {
     if (!timer) {
-      timer = setTimeout(run, time, this, args);
+      func.apply(this, args);
+      timer = setTimeout(() => {
+        timer = null;
+      }, time);
     }
-  };
+  }
+  return throttledFunction;
 }
 
 export function noop() {}

+ 40 - 1
test/common/index.test.js

@@ -1,5 +1,8 @@
 import test from 'tape';
-import { isRemote, compareVersion } from '#/common';
+import {
+  isRemote, compareVersion, debounce, throttle,
+} from '#/common';
+import { mocker } from '../mock';
 
 test('isRemote', (t) => {
   t.notOk(isRemote());
@@ -23,3 +26,39 @@ test('compareVersion', (t) => {
   t.equal(compareVersion('1.10', '1.9'), 1);
   t.end();
 });
+
+test('debounce', (t) => {
+  const log = [];
+  const fn = debounce((i) => {
+    log.push(i);
+  }, 500);
+  for (let i = 0; i < 3; i += 1) {
+    fn(i);
+    mocker.clock.tick(200);
+  }
+  mocker.clock.tick(500);
+  for (let i = 0; i < 3; i += 1) {
+    fn(i);
+    mocker.clock.tick(600);
+  }
+  t.deepEqual(log, [2, 0, 1, 2]);
+  t.end();
+});
+
+test('throttle', (t) => {
+  const log = [];
+  const fn = throttle((i) => {
+    log.push(i);
+  }, 500);
+  for (let i = 0; i < 6; i += 1) {
+    fn(i);
+    mocker.clock.tick(200);
+  }
+  mocker.clock.tick(500);
+  for (let i = 0; i < 3; i += 1) {
+    fn(i);
+    mocker.clock.tick(600);
+  }
+  t.deepEqual(log, [0, 3, 0, 1, 2]);
+  t.end();
+});

+ 1 - 0
test/mock/base.js

@@ -0,0 +1 @@
+export const mocker = {};

+ 3 - 0
test/mock/index.js

@@ -1 +1,4 @@
 import './polyfill';
+import './time';
+
+export * from './base';

+ 1 - 0
test/mock/polyfill.js

@@ -22,4 +22,5 @@ const domProps = Object.getOwnPropertyDescriptors(new JSDOM('').window);
 for (const k of Object.keys(domProps)) {
   if (k.endsWith('Storage') || k in global) delete domProps[k];
 }
+delete domProps.performance;
 Object.defineProperties(global, domProps);

+ 37 - 0
test/mock/time.js

@@ -0,0 +1,37 @@
+import { mocker } from './base';
+
+let time = 0;
+let timers = [];
+
+function tick(delta) {
+  time += delta;
+  let scheduledTimers = [];
+  const filterTimer = ({ fn, ts, args }) => {
+    if (time >= ts) {
+      fn(...args);
+      return false;
+    }
+    return true;
+  };
+  while (timers.length) {
+    const processTimers = timers;
+    timers = [];
+    scheduledTimers = scheduledTimers.concat(processTimers.filter(filterTimer));
+  }
+  timers = scheduledTimers;
+}
+
+function now() {
+  return time;
+}
+
+function setTimeout(fn, delta, ...args) {
+  timers.push({ fn, ts: time + delta, args });
+  return timers.length;
+}
+
+global.performance = { now };
+global.Date.now = now;
+global.setTimeout = setTimeout;
+
+mocker.clock = { tick };