web

vue实现datepicker组件

前端组件化的今天,选择日期组件算是最复杂的一个了吧。我们需要考虑日期显示的多语言,弹框的显示位置,以及日期月份的计算。

日期相关计算 ​

计算每个月的天数,这个是不变的。一三五七八十腊,三十一天永不差;四六九冬三十整,惟有二月二十八,闰年还要把一日加。

ts
function getDayCountOfMonth(year: number, month: number) {
  if (month === 3 || month === 5 || month === 8 || month === 10) {
    return 30;
  }

  if (month === 1) {
    if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
      return 29;
    } else {
      return 28;
    }
  }

  return 31;
}

计算每个月的 1 号是周几:

ts
function getFirstDayOfMonth(date: Date) {
  const temp = new Date(date.getTime());
  temp.setDate(1);
  return temp.getDay();
}

其他计算函数可以去看完整代码date.ts

弹窗定位相关 ​

点击日期输入框,需要弹出下拉菜单,下拉菜单位置的计算并不简单,需要考虑到浏览器的窗口,如果输入框在下方,需要在上方弹出,如果输入框在上方,需要在下方弹出。 并且用户滑动到可视范围以外的话,还要可以隐藏。所以我们这里引用了一个比较成熟的第三方库来解决这个问题。 popperjs 这个库可以帮我们解决上面的问题,节约开发的成本。

多语言支持 ​

日期选择组件,是为数不多需要做多语言支持的组件。因为我们要显示月份和每周的名字。就不得不做多语言处理了。目前我做的 demo 里面包含了三种语言的支持。

总结 ​

日期选择组件,常见的需求有单日期选择和日期范围选择。优秀的组件需要同时满足这两个需求。并且尽量保证代码的简洁性。为此,我在 Github 写了一个 demo 库,希望可以给大家参考。

vue-datepicker

前端中debounce和throttle函数说明

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

debounce ​

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

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

  return (...args: any) => {
    window.clearTimeout(timeout);
    timeout = window.setTimeout(() => {
      fn(...args);
    }, time);
  };
}

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

throttle ​

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

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

  return (...args: any) => {
    if (!inThrottle) {
      inThrottle = true;
      fn(...args);
      setTimeout(() => (inThrottle = false), time);
    }
  };
}

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

总结 ​

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

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

参考 ​

The Difference Between Throttling and Debouncing

Web离线的解决方案

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

Application Cache ​

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

html
<html manifest="manifest.appcache">
  ...
</html>

通过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

HTML5 Offline Application Cache

Application Cache plugin for Webpack

Workbox webpack Plugins

Appcache Facts