前端中debounce和throttle函数说明

前端开发中,debounce 和 throttle 函数经常会被使用,但是很多人分不清两者的区别,今天就来说明一下。

# debounce

我们首先来看 debounce (防抖动)的代码实现:

function debounce(fn: Function, time: number): Function {
  let timeout = 0;

  return (...args: any) => {
    window.clearTimeout(timeout);
    timeout = window.setTimeout(() => {
      fn(...args);
    }, time);
  };
}
1
2
3
4
5
6
7
8
9
10

从代码中可以看出,该函数在执行的时候会清除上次的定时器,然后设置一个新的定时器,等待一定时间之后执行。所以在短时间内调用多次的话,只会执行最后一次。

# throttle

throttle (节流)函数的代码实现:

function throttle(fn: Function, time: number): Function {
  let inThrottle: boolean;

  return (...args: any) => {
    if (!inThrottle) {
      inThrottle = true;
      fn(...args);
      setTimeout(() => (inThrottle = false), time);
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11

从代码中可以看出,函数在设置的阈值时间之内只会执行一次。

# 总结

debounce: 将频繁触发的事件合并为一次执行,适用场景例如输入名称进行搜索,使用 debounce 可以减少对服务器的请求,在用户输入完毕之后再进行请求。

throttle: 设置一个阈值,在阈值内函数只会执行一次,例如 resize 事件或者 scoll 事件,防止浏览器频繁执行,降低网页响应速度。

# 参考

The Difference Between Throttling and Debouncing (opens new window)

在浏览器上显示TIF图像

在实际的业务中,我们需要在浏览器中对 TIF 文件的某个区域进行选择,所以需要展示 TIF 文件在浏览器中。不过浏览器并不能直接支持 TIF 格式,所以我们需要做相应的处理。

# 寻找合适的解析库

图像处理是一个很复杂的事情,如果别人有实现就别自己造轮子了。这里我推荐UTIF.js (opens new window),这位作者实现了网页版图片编辑器。所以质量上还是很高的。

# 导入图片二进制数据

我们需要把图片导入成二进制数据,我们使用fetch来获取图片二进制数据,fetch 本身就可以返回二进制数据,大致代码如下:

  fetchImage() {
    fetch("bali.tif")
      .then(response => {
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        return response.arrayBuffer();
      })
      .then(data => {
        const ifds = UTIF.decode(data);
        UTIF.decodeImage(data, ifds[0]);
        const rgba = UTIF.toRGBA8(ifds[0]);
        const widh = ifds[0].width;
        const height = ifds[0].height;
        const canvas = document.createElement("canvas");
        canvas.width = widh;
        canvas.height = height;
        this.width = widh;
        this.height = height;
        const ctx = canvas.getContext("2d");
        const imgData = new ImageData(
          new Uint8ClampedArray(rgba.buffer),
          widh,
          height
        );
        ctx?.putImageData(imgData, 0, 0);
        canvas.toBlob(blob => {
          this.imgData = URL.createObjectURL(blob);
        });
      });
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

导入的二进制数据,使用UTIF去解码二进制数据。拿到图片的基本信息之后,创建相应的 canvas 画布,把图片数据加载进 canvas 之后可以创建ObjectURL就可以使用img tag 来显示。

# 总结

处理 TIF 文件本身并不是很复杂,在于找到合适的方法。完整的演示代码在vue-tif (opens new window)

# 参考

UTIF.js (opens new window)

vue-tif (opens new window)

简单多边形的判定

在实际工作中,需要在照片上选定一个范围,这个范围是个多边形,并且是个简单多边形,我们需要判定是否是个合法的简单多边形,主要判定的是任意两边不能交叉。

# 判断两条线段相交

我们可以借助向量的知识来判断两个线段是否相交。二维向量的叉乘(cross product)的几何意义是以两向量为邻边的平行四边形的面积。此外,定义两个向量 a, b。 当 aXb < 0, b 对应的线段,在 a 的顺时针方向。当 aXb = 0时, a 与 b 共线。当 aXb > 0,b 在 a 的逆时针方向。 如果两条线段相交,那必然一条线段的终点和起点,在另外一条线段的两侧。

# 判断多边形两边是否相交

简单并且暴力的方法是检测任意两边是否有交点,在复杂度不高的情况下可以使用这种方法。但是显然是有更优解的,目前比较有名的两个算法是 The Bentley-Ottmann AlgorithmThe Shamos-Hoey Algorithm。算法的细节请查看下方的参考,我就不详细描述了。

# 简单多边形判定的实现

export interface Point {
  x: number;
  y: number;
}

export interface Line {
  start: Point;
  end: Point;
}

function samePoint(p1: Point, p2: Point) {
  if (p1.x === p2.x && p1.y === p2.y) {
    return true;
  } else {
    return false;
  }
}

function signedArea(p1: Point, p2: Point, p3: Point) {
  return (p1.x - p3.x) * (p2.y - p3.y) > (p2.x - p3.x) * (p1.y - p3.y);
}

function intersectLine(l1: Line, l2: Line) {
  // consecutive edge return false
  if (
    samePoint(l1.start, l2.start) ||
    samePoint(l1.start, l2.end) ||
    samePoint(l1.end, l2.start) ||
    samePoint(l1.end, l2.end)
  ) {
    return false;
  }
  return (
    signedArea(l1.start, l2.start, l2.end) !==
      signedArea(l1.end, l2.start, l2.end) &&
    signedArea(l1.start, l1.end, l2.start) !==
      signedArea(l1.start, l1.end, l2.end)
  );
}

export function intersectPolygon(points: Array<Point>) {
  const len = points.length;
  for (let i = 0; i < len - 1; i++) {
    for (let j = i + 1; j < len; j++) {
      const l1: Line = {
        start: points[i],
        end: points[(i + 1) % len]
      };
      const l2: Line = {
        start: points[j],
        end: points[(j + 1) % len]
      };

      if (intersectLine(l1, l2)) {
        return true;
      }
    }
  }

  return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 总结

简单多边形判定的本质是任意两边是否相交,如果是邻边的话就直接跳过。在复杂度不高的情况下,可以直接使用遍历的方法来实现。

# 参考

Intersections of a Set of Segments (opens new window)

Line Segment Intersection Algorithm (opens new window)

shamos-hoey (opens new window)