每当看到发在 FCC 成都社区群里的技术文章,水歌都忍不住去指出它的不足。

今天评注的文章题为《一批提升你工作效率的 JavaScript 工具方法》,文中的 60 个方法不够简洁优雅,与最新 ECMAScript、DOM 标准有些差距,有些“复制粘贴老文章片段”的感觉。

接下来,我就按功能类别来对一些有必要优化的工具方法一一重构。

数据校验

完全基于正则表达式的检验规则其实可以不用封装成函数,全放在独立的模块中,导入后直接 /regexp/.test(data) 即可。

电邮地址

export const Email = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/
// 原文:/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/

「注解」

  • \w 即为 [a-zA-Z0-9_]
  • [] 表示一个字符范围,就是一个整体,无需 () 包围
  • Gmail 等服务商还支持形如 name.filter@gmail.com 这样的用户别名邮箱
  • (.[a-zA-Z0-9_-]{2,3}){1,2} 只适用于前些年常见的 .cn.com.cn 一类根域名,近几年新增的 .name.info.club.camp 等域名就失效了,形如 vip.xxmail.com 的多级域名也不适用

手机号码

其实以下只适用于中国大陆手机号,其它国家手机号似乎与固定电话号之间没有明显的区分。

export const Mobile = /^1[3-9]\d{9}$/;
// 原文:/^1[0-9]{10}$/

「注解」

  • \d 即为 [0-9]
  • 中国大陆手机号第二位目前没有 1、2

固话号码

中国大陆固定电话号码“区号 + 机号”始终为 11 位。

export const Phone = /^((0\d{2}-)?\d{8}|(0\d{3}-)?\d{7})$/;
// 原文:/^([0-9]{3,4}-)?[0-9]{7,8}$/

网址

export const URL = /^\w+:\/\/\S+$/;
// 原文:/^http[s]?:\/\/.*/

「注解」

  • URL 协议不仅包括 httphttps,还有 ftp(文件传输)、file(本机文件系统)、ed2k(电驴 2000)等各种各样的网络协议
  • URL 主机名、路径可以是 Unicode 中各种可见字符,但遇到空白符就结束

日期格式

判断是否为合法的日期格式除了用正则之外,还可利用 Date 构造函数内部的算法:

export const isDate = raw => !isNaN(+new Date(raw))

对于无法解析为日期的数据,date.toString() 会返回“Invalid Date”,date.getTime() 对应的返回值则是 NaN。而算数运算符会调用对象的 valueOf() 方法,date.valueOf() 的返回值又与 date.getTime() 相同。

汉字

“汉字”在计算机领域的学名叫中日韩统一表意文字(俗称 CJK),在 2017 年 6 月发布的 Unicode 10 标准中,它有了代码级明确的指代:

export const HanZi = /\p{Unified_Ideograph}/u;

【详情参考】

最佳实践

数据转换

阿拉伯数字转中文

ECMA-402 标准(ECMAScript 国际化 API)把各语言之间的数据格式转换算法都封装好了,我们引入 polyfill 就可以直接用:

export const toChineseNumber = raw =>
    new Intl.NumberFormat('zh-Hans-u-nu-hanidec').format(raw)

数据类型

判断一个值的类型,用比较构造函数名类名的方式兼容性比较差,因为线上环境通常是压缩后的代码,自定义的函数名、类名不再是原名,应用开发者一般也不会实现 Symbol.toStringTag getter 类成员,导致 Object.prototype.toString.call() 只会返回默认值 [object Object]

JavaScript

综上,我们应该利用 JavaScript 原型继承,来统一判断“值的类型归属”:

export const isType = (value, constructor) =>
    Object(value) instanceof constructor;

「注解」

  • Object 构造函数会返回所有基本值的包装对象

TypeScript

下面,我再给出一个 TypeScript 的实现,让类型推断更加准确:

export function isType(
    value: T, constructor: { new(...data: any[]): T }
): value is T {
    return Object(value) instanceof constructor;
}
import { isType } from './utility';

let test;

if (isType(test, Number)) console.log(test!.toFixed(2));

【在编辑器中体验 TS 类型提示】

浏览器检测

以下使用 globalThis 是为了兼容浏览器主线程、Web WorkerNode.jsDeno 等不同 JavaScript 运行时环境。

品牌

export const isBrowserVendor = (name, UA = globalThis.navigator?.userAgent || '') =>
    UA.toLowerCase().includes(name);

爬虫

export const isRobot = (UA = globalThis.navigator?.userAgent || '') =>
    /bot|spider|crawler/i.test(UA);

去除 HTML 标签

正则表达式

以下使用了 non-greedy(非贪婪模式)来提升性能,并规避正文中可能出现的示例代码没完全转译尖括号,导致删除错误。

export const removeHtmlTag = raw => raw.replace(/<[\s\S]+?>/g, '');

DOM API

下面再提供一种借助 DOM 引擎的实现:

const box = document.createElement('template');

export function removeHtmlTag(raw) {
    box.innerHTML = raw;

    return box.content.textContent;
}

URL 参数追加

URL()URLSearchParams() 在浏览器主线程、Web Worker、Node.js 10+、Deno 均全局可用。

export function appendQuery(path, data, base = globalThis.location.href) {
    const URI = new URL(path, base);
    const { searchParams } = URI;

    for (const key in data) searchParams.append(key, data[key]);

    return URI + '';
}

W3C、ECMA 标准

还有一些可以用新标准(部分为提案)直接实现的特性,集中罗列如下:

开源库

WebCell

水歌把日常开发中积累的各种工具方法,用 TypeScript 写成一个 Web 开源工具库 —— https://web-cell.dev/web-utility/ ,欢迎大家使用、改进!~

欢迎访问我的更多文章