前端中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)

Web离线的解决方案

最近的工作是做嵌入在 iOS 程序内部的页面,其中有一个需求就是需要满足在离线的情况下显示页面。当然,现在主流的离线方式是使用 Service Worker 来完成离线需求。 但是 iOS 内置的 WKWebView 并不支持最新的 Service Worker(取决于 iOS 版本)最新版本已经支持,所以不得不想办法来解决。

# Application Cache

AppCache 是一个过时的技术,但是在 iOS 下勉强还能用。不过 Chrome 对这种过时的技术支持不是很好,单个缓存文件最大只支持 5MB。而且还有请求的 BUG:Accept header on GET request for appcache manifest (opens new window)。触发 AppCache 是在 html 标签中添加 manifest 属性。

<html manifest="manifest.appcache">
  ...
</html>
1
2
3

通过manifest.appcache文件来定义需要缓存的文件,不过载入manifest.appcache的页面会被作为Master entries缓存起来。因为 AppCache 出现的时候还是以静态网站为主,所以并不能缓存请求的 Ajax 数据。我们需要自己再手动实现缓存所有请求在localStorage,但是localStorage同样也有最大 5MB 的限制。所以也需要考虑相应的解决方案。不过,我最终采取的方式是通过嵌入iframe来实现触发 AppCache,这样做的好处是因为 SPA 应用是自己来控制路由的,所以导致每个路径都会保存一份Master entries。但是通过 iframe 的话,我们的Master entries永远只有一份。并且当前页面的所有资源也被顺利缓存,因为 SPA 无论访问哪个路径返回的都是相同的index.html。由 JS 来控制路由并加载相应的组件。

这里要补充的一点是,针对 https 的 Application Cache 是无法完成跨域请求的,所以请慎重。

# WorkBox

Service Worker 是现在主流的缓存技术,会帮你缓存所有的静态文件和数据请求。但是对于 SPA 项目,我们不可能自己手动书写缓存清单。所以还是借助现有的开源解决方案,这里最出名的解决方案是谷歌的 Workbox。通过使用 Webpack 插件和简单的配置,我们便可以做到缓存所有的静态资源和数据。Service Worker 有很多种缓存策略可以选择,例如 Cache First 和 NetWork First。但是 AppCache 每次都会优先使用缓存,然后再去更新最新的文件。所以我们不得不在发生更新的时候去重新加载页面。

# 总结

最终的解决方案是优先使用 Service Worker,当不支持 Service Worker 的时候再回退到 AppCache。但是想要从 AppCache 升级到 Service Worker 的时候,必须清除所有 AppCache 的所有数据。浏览器并没有提供相应的接口,我们目前采用的方式是手动删除 AppCache 储存的数据库来完成这一需求。

参考:

Using the application cache (opens new window)

HTML5 Offline Application Cache (opens new window)

Application Cache plugin for Webpack (opens new window)

Workbox webpack Plugins (opens new window)

Appcache Facts (opens new window)

网站开发中的Modal问题

最近在开发的网站基本上全是使用弹出的对话框(Modal)来进行 UI 交互的,所以对于 Modal 的处理也算积攒了一点经验。便想写下来供自己以后参考和学习。说实话,在目前响应式布局的主流开发方式下,Modal 非常不适合作为一个良好的交互方式。因为对于手机触屏用户非常不友好。当然,我开发的这个网站也没有考虑手机用户。当前的主流方式还是采用 SPA,JS 软路由切换页面来交互才是正解。

弹出对话框的方式基本是把对话框的z-index设置的比当前页面元素高,然后使用opacity: 0.5来半透明进行遮罩。但是,需要使用 Modal 的内容最好放在 body 的下层,而不是嵌套了好多层的某个 div 里面,因为子元素的z-index是不可能大于父元素的,会导致在某些情况下,其他元素比当前的 Modalz-index更高。

很多情况下,弹出的 Modal 内容过多导致会产生滚动条。这时如果不处理好会导致出现双重滚动条。一条是页面本身的内容过多产生的滚动条,还有一条是 Modal 自身的。双重滚动条还有一个问题是当你在 Modal 里面进行滚动的时候,页面内容本身也会被滚动,这会导致关闭 Modal 的时候发现页面的位置已不是打开的位置了,用户体验非常不好。这时候有两种解决方案。

第一个方案是页面本身采用position:fixed进行固定,并用 JS 记住滚动位置,但必须保持页面本身和 Modal 是并列关系。例:

<body>
  <div class="content"></div>
  <div class="modal"></div>
</body>
1
2
3
4

这样,Modal 采用position:absolute定位,当内容过长就会自动出现滚动条。但是当关闭 Modal 的时候,必须把网页内容的position:fixed属性去除,并用 JS 滚动到当初打开 Modal 的位置。如果 Modal 是透明的,那就必须在打开 Modal 的时候设置网页内容的topleft属性来保持位置不变。

第二个方案是,网页内容采用overflow: hidden来隐藏滚动条。Modal 采用position:fixed方案进行定位,但是这时候 Modal 不得不设置overflow: auto来进行滚动。 第二个方案对于网页内容和 Modal 的位置并没有特殊的要求,比较灵活,而且不需要 JS 的介入。

# 浏览器重绘

在一个方案中,由于网页内容被设置成了position:fixed,滚动条自然消失。所以滚动位置回到了浏览器的最上面才对。但是有时候遇到打开Modal的时候发现 Modal 打开之后滚动条不在最上方,这时候的原因是因为我们虽然设置了 CSS 进行了变更,但是浏览器没有进行重新绘制,我们可以使用会导致浏览器重绘的 JS API 来让浏览器更新滚动条信息,使得打开的 Modal 处于浏览器的最上方。

参考:

Force reflow (opens new window)