rx-util

rxliuli 在浏览器上使用的 js 工具集

当前为 2019-09-05 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/382120/730950/rx-util.js

  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  3. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  4. (global = global || self, factory(global.rx = {}));
  5. }(this, function (exports) { 'use strict';
  6.  
  7. /**
  8. * 在浏览器上下载二进制资源
  9. * @param blob 要下载的二进制资源
  10. * @param filename 文件名
  11. */
  12. function download(blob, filename = 'unknown') {
  13. // 创建隐藏的可下载链接
  14. const eleLink = document.createElement('a');
  15. eleLink.download = filename;
  16. eleLink.style.display = 'none';
  17. // 为 link 赋值
  18. eleLink.href = URL.createObjectURL(blob);
  19. // 触发点击
  20. document.body.appendChild(eleLink);
  21. eleLink.click();
  22. // 然后移除
  23. document.body.removeChild(eleLink);
  24. }
  25.  
  26. /*! *****************************************************************************
  27. Copyright (c) Microsoft Corporation. All rights reserved.
  28. Licensed under the Apache License, Version 2.0 (the "License"); you may not use
  29. this file except in compliance with the License. You may obtain a copy of the
  30. License at http://www.apache.org/licenses/LICENSE-2.0
  31.  
  32. THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  33. KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
  34. WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
  35. MERCHANTABLITY OR NON-INFRINGEMENT.
  36.  
  37. See the Apache Version 2.0 License for specific language governing permissions
  38. and limitations under the License.
  39. ***************************************************************************** */
  40.  
  41. function __awaiter(thisArg, _arguments, P, generator) {
  42. return new (P || (P = Promise))(function (resolve, reject) {
  43. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  44. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  45. function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
  46. step((generator = generator.apply(thisArg, _arguments || [])).next());
  47. });
  48. }
  49.  
  50. /**
  51. * 将字符串转化为 Blob 类型
  52. * @param str 字符串
  53. * @returns Blob 数据
  54. */
  55. function strToBlob(str) {
  56. return new Blob([str], {
  57. type: 'text/plain',
  58. });
  59. }
  60.  
  61. /**
  62. * 在浏览器上下载文本内容
  63. * @param str 字符串内容
  64. * @param filename 下载文件名,没有则默认为链接中的文件名
  65. */
  66. function downloadString(str, filename = 'unknown.txt') {
  67. return __awaiter(this, void 0, void 0, function* () {
  68. download(strToBlob(str), filename);
  69. });
  70. }
  71.  
  72. /**
  73. * 根据 url 下载二进制资源
  74. * @param url 下载请求信息
  75. * @param filename 下载文件名,没有则默认为链接中的文件名
  76. */
  77. function downloadUrl(url, filename = url.substr(url.lastIndexOf('/'))) {
  78. return __awaiter(this, void 0, void 0, function* () {
  79. try {
  80. const res = yield fetch(url);
  81. const blob = yield res.blob();
  82. download(blob, filename);
  83. }
  84. catch (error) {
  85. throw error;
  86. }
  87. });
  88. }
  89.  
  90. /**
  91. * 获取 cookie 键值映射 Map
  92. * @returns cookie 键值映射 Map
  93. */
  94. function getCookies() {
  95. return document.cookie.split(';').reduce((res, str) => {
  96. const [k, v] = str.split('=');
  97. res.set(k, v);
  98. return res;
  99. }, new Map());
  100. }
  101.  
  102. /**
  103. * 将 url 中的内容加载到元素上
  104. * 注:domSelector 必须有 src 属性用以将加载完成的资源赋值给其,加载默认是异步的
  105. * @param url url 资源
  106. * @param dom dom 元素
  107. * @param init 初始化参数, 实为 fetch() 的参数以及一些自定义的参数,默认 {}
  108. * 关于 fetch 具体可以参考 <https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch>
  109. */
  110. function loadResource(url, dom, init = {}) {
  111. return __awaiter(this, void 0, void 0, function* () {
  112. const res = yield fetch(url, init);
  113. const blob = yield res.blob();
  114. // 生成一个本地的 url 并赋值给 src 属性
  115. dom.src = window.URL.createObjectURL(blob);
  116. });
  117. }
  118.  
  119. /**
  120. * 协议与默认端口映射表
  121. */
  122. const protocolPortMap = new Map()
  123. .set('http', 80)
  124. .set('https', 443)
  125. .set('ssh', 22)
  126. .set('ftp', 21);
  127. /**
  128. * 解析 url 字符串
  129. * @param url url 字符串,不能为空
  130. * @returns url 对象
  131. */
  132. function parseUrl(url) {
  133. if (!url) {
  134. throw new Error('Url cannot be empty');
  135. }
  136. const regexp = new RegExp('^((\\w+)://([\\w\\.]*)(:(\\d+))?)(.*)');
  137. const temps = regexp.exec(url);
  138. if (temps === null) {
  139. return null;
  140. }
  141. const website = temps[1];
  142. const protocol = temps[2];
  143. const domain = temps[3];
  144. const portStr = temps[5];
  145. const href = temps[6];
  146. // 截取域名之后的内容
  147. const temp = url.substr(website.length);
  148. const markIndex = temp.indexOf('?');
  149. // 如果没有携带参数则直接返回
  150. if (markIndex === -1) {
  151. const accessPath = temp;
  152. return {
  153. url,
  154. website,
  155. protocol,
  156. domain,
  157. // tslint:disable-next-line:radix
  158. port: parseInt(portStr),
  159. href,
  160. accessPath,
  161. params: new Map(),
  162. };
  163. }
  164. let accessPath = temp.substr(0, markIndex);
  165. if (accessPath.endsWith('/')) {
  166. accessPath = accessPath.substring(0, accessPath.length - 1);
  167. }
  168. const port = portStr || protocolPortMap.get(protocol) || 0;
  169. // 解析参数列表
  170. const params = temp
  171. .substr(markIndex + 1)
  172. .split('&')
  173. .map(str => str.split('='))
  174. .filter(arr => arr[0] !== '')
  175. .reduce((params, arr) => {
  176. const k = decodeURIComponent(arr[0]);
  177. const v = decodeURIComponent(arr.length === 1 ? '' : arr[1]);
  178. // 如果已经存在了就认为是数组参数
  179. const vs = params.get(k);
  180. if (vs === undefined) {
  181. params.set(k, v);
  182. }
  183. else {
  184. if (!Array.isArray(vs)) {
  185. params.set(k, [vs]);
  186. }
  187. if (params.get(k).length !== undefined) {
  188. params.get(k).push(v);
  189. }
  190. }
  191. return params;
  192. }, new Map());
  193. return {
  194. url,
  195. website,
  196. protocol,
  197. domain,
  198. port,
  199. href,
  200. accessPath,
  201. params,
  202. };
  203. }
  204.  
  205. /**
  206. * 读取文件类型
  207. */
  208. var ReadType;
  209. (function (ReadType) {
  210. /**
  211. * 以 data url 读取
  212. */
  213. ReadType["DataURL"] = "readAsDataURL";
  214. /**
  215. * 以文本读取
  216. */
  217. ReadType["Text"] = "readAsText";
  218. /**
  219. * 以二进制文件读取
  220. */
  221. ReadType["BinaryString"] = "readAsBinaryString";
  222. /**
  223. * 以 ArrayBuffer 读取
  224. */
  225. ReadType["ArrayBuffer"] = "readAsArrayBuffer";
  226. })(ReadType || (ReadType = {}));
  227. /**
  228. * 读取本地浏览器选择的文件
  229. * @param file 选择的文件
  230. * @param option 可选项参数
  231. * @param option.type 读取的类型,默认按照二进制 url 读取
  232. * @param option.encoding 读取的编码格式,默认为 UTF-8
  233. * @returns 返回了读取到的内容(异步)
  234. */
  235. function readLocal(file, { type = readLocal.DataURL, encoding = 'UTF-8', } = {}) {
  236. return new Promise((resolve, reject) => {
  237. if (!file) {
  238. reject(new Error('file not exists'));
  239. }
  240. const fr = new FileReader();
  241. fr.onload = () => {
  242. resolve(fr.result);
  243. };
  244. fr.onerror = error => {
  245. reject(error);
  246. };
  247. new Map()
  248. .set(ReadType.DataURL, () => fr.readAsDataURL(file))
  249. .set(ReadType.Text, () => fr.readAsText(file, encoding))
  250. .set(ReadType.BinaryString, () => fr.readAsBinaryString(file))
  251. .set(ReadType.ArrayBuffer, () => fr.readAsArrayBuffer(file))
  252. .get(type)();
  253. });
  254. }
  255. /**
  256. * 以 data url 读取
  257. * @deprecated 已废弃,请使用枚举类 ReadType
  258. */
  259. readLocal.DataURL = ReadType.DataURL;
  260. /**
  261. * 以文本读取
  262. * @deprecated 已废弃,请使用枚举类 ReadType
  263. */
  264. readLocal.Text = ReadType.Text;
  265. /**
  266. * 以二进制文件读取
  267. * @deprecated 已废弃,请使用枚举类 ReadType
  268. */
  269. readLocal.BinaryString = ReadType.BinaryString;
  270. /**
  271. * 以 ArrayBuffer 读取
  272. * @deprecated 已废弃,请使用枚举类 ReadType
  273. */
  274. readLocal.ArrayBuffer = ReadType.ArrayBuffer;
  275.  
  276. /**
  277. * 为 js 中的 Date 对象原型添加 format 格式化方法
  278. * @param date 要进行格式化的日期
  279. * @param fmt 日期的格式
  280. * @returns 格式化得到的结果
  281. */
  282. function dateFormat(date, fmt) {
  283. const o = {
  284. 'y+': date.getFullYear(),
  285. 'M+': date.getMonth() + 1,
  286. 'd+': date.getDate(),
  287. 'h+': date.getHours(),
  288. 'm+': date.getMinutes(),
  289. 's+': date.getSeconds(),
  290. 'q+': Math.floor((date.getMonth() + 3) / 3),
  291. 'S+': date.getMilliseconds(),
  292. };
  293. for (const k in o) {
  294. if (!new RegExp('(' + k + ')').test(fmt)) {
  295. continue;
  296. }
  297. if (k === 'y+') {
  298. fmt = fmt.replace(RegExp.$1, ('' + o[k]).substr(4 - RegExp.$1.length));
  299. }
  300. else if (k === 'S+') {
  301. let lens = RegExp.$1.length;
  302. lens = lens === 1 ? 3 : lens;
  303. fmt = fmt.replace(RegExp.$1, ('00' + o[k]).substr(('' + o[k]).length - 1, lens));
  304. }
  305. else {
  306. const v = Reflect.get(o, k);
  307. fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? v : ('00' + v).substr(('' + v).length));
  308. }
  309. }
  310. return fmt;
  311. }
  312.  
  313. /**
  314. * 默认的日期格式
  315. * 不加 Z 为本地日期时间,避免出现时区的问题
  316. */
  317. const deteFormatter = 'yyyy-MM-ddThh:mm:ss.SSS';
  318. /**
  319. * 将参数 key 与 value 进行 url 编码
  320. * @param k 参数的名字
  321. * @param v 参数的值
  322. * @returns 编码后的字符串
  323. */
  324. const encode = (k, v) => encodeURIComponent(k) + '=' + encodeURIComponent(v);
  325. /**
  326. * 拼接参数字符串
  327. * @param params 参数对象
  328. * @returns 拼接后的字符串
  329. */
  330. function spliceParams(params = {}) {
  331. return Array.from(Object.entries(params)).reduce((res, [k, v]) => {
  332. if (v === undefined || v === null) {
  333. return res;
  334. }
  335. else if (v instanceof Date) {
  336. res += encode(k, dateFormat(v, deteFormatter));
  337. }
  338. else if (v instanceof Array) {
  339. res += v
  340. .map(item => encode(k, item instanceof Date ? dateFormat(item, deteFormatter) : item))
  341. .join('&');
  342. }
  343. else {
  344. res += encode(k, v);
  345. }
  346. return (res += '&');
  347. }, '');
  348. }
  349.  
  350. /**
  351. * 等待指定的时间/等待指定表达式成立
  352. * 如果未指定等待条件则立刻执行
  353. * 注: 此实现在 nodejs 10- 会存在宏任务与微任务的问题,切记 async-await 本质上还是 Promise 的语法糖,实际上并非真正的同步函数!!!即便在浏览器,也不要依赖于这种特性。
  354. * @param param 等待时间/等待条件
  355. * @returns Promise 对象
  356. */
  357. function wait(param) {
  358. return new Promise(resolve => {
  359. if (typeof param === 'number') {
  360. setTimeout(resolve, param);
  361. }
  362. else if (typeof param === 'function') {
  363. const timer = setInterval(() => {
  364. if (param()) {
  365. clearInterval(timer);
  366. resolve();
  367. }
  368. }, 100);
  369. }
  370. else {
  371. resolve();
  372. }
  373. });
  374. }
  375.  
  376. /**
  377. * 为 fetch 请求添加超时选项
  378. * 注:超时选项并非真正意义上的超时即取消请求,请求依旧正常执行完成,但会提前返回 reject 结果
  379. * @param fetchPromise fetch 请求的 Promise
  380. * @param timeout 超时时间
  381. * @returns 如果超时就提前返回 reject, 否则正常返回 fetch 结果
  382. */
  383. function fetchTimeout(fetchPromise, timeout) {
  384. return Promise.race([
  385. fetchPromise,
  386. wait(timeout).then(() => {
  387. throw new Error('timeout');
  388. }),
  389. ]);
  390. }
  391.  
  392. /**
  393. * 将字符串转为字符流
  394. * @param str 字符串
  395. * @returns 字符流对象
  396. */
  397. function strToArrayBuffer(str) {
  398. const buf = new ArrayBuffer(str.length);
  399. const view = new Uint8Array(buf);
  400. for (let i = 0; i < str.length; ++i) {
  401. view[i] = str.charCodeAt(i) & 0xff;
  402. }
  403. return buf;
  404. }
  405.  
  406. /**
  407. * 限制并发请求数量的 fetch 封装
  408. * @class FetchLimiting
  409. * @example
  410. * const fetchLimiting = new FetchLimiting()
  411. * fetchLimiting._fetch('/')
  412. * .then(res => res.json())
  413. * .then(json => console.log(json))
  414. */
  415. class FetchLimiting {
  416. /**
  417. * 构造函数
  418. * @param option 可选配置项
  419. * @param option.timeout 超时毫秒数
  420. * @param option.limit 最大并发数限制
  421. */
  422. constructor({ timeout = 10000, limit = 10, } = {}) {
  423. /**
  424. * @field timeout 超时毫秒数
  425. */
  426. this.timeout = timeout;
  427. /**
  428. * @field limit 最大并发数限制
  429. */
  430. this.limit = limit;
  431. /**
  432. * @field execCount 当前正在执行异步的数量
  433. */
  434. this.execCount = 0;
  435. /**
  436. * @field waitArr 等待的队列
  437. * @type {Array.<IArguments>}
  438. */
  439. this.waitArr = [];
  440. }
  441. /**
  442. * 执行一个请求
  443. * 如果到达最大并发限制时就进行等待
  444. * @param url 请求 url 信息
  445. * @param init 请求的其他可选项,默认为 undefined
  446. * @returns 如果超时就提前返回 reject, 否则正常返回 fetch 结果
  447. */
  448. fetch(input, init) {
  449. return __awaiter(this, void 0, void 0, function* () {
  450. const _innerFetch = () => __awaiter(this, void 0, void 0, function* () {
  451. this.execCount++;
  452. const args = this.waitArr.shift();
  453. try {
  454. // 这里的 args 实际上就是 arguments 对象,即上面的 url 和 init
  455. return yield fetchTimeout(fetch(args[0], args[1]), this.timeout);
  456. }
  457. finally {
  458. this.execCount--;
  459. }
  460. });
  461. this.waitArr.push([input, init]);
  462. yield wait(() => this.execCount < this.limit);
  463. // 尝试启动等待队列
  464. return _innerFetch();
  465. });
  466. }
  467. }
  468.  
  469. /**
  470. * 将一个 Iterator 迭代器转换为一个 Array
  471. * @param iterator Iterator 迭代器
  472. * @return Iterator 中每一项元素转换而得到的 Array
  473. * @deprecated 已废弃,请使用 ES6 原生函数 {@see Array.from} 替代
  474. */
  475. function asIterator(iterator) {
  476. const arr = [];
  477. while (true) {
  478. const next = iterator.next();
  479. if (next.done) {
  480. break;
  481. }
  482. arr.push(next.value);
  483. }
  484. return arr;
  485. }
  486.  
  487. /**
  488. * 将数组异步压平一层
  489. * @param arr 数组
  490. * @param fn 映射函数,将一个元素映射为一个数组
  491. * @returns 压平一层的数组
  492. * @deprecated 已废弃,请使用更强大的异步数组 {@link AsyncArray}
  493. */
  494. function asyncFlatMap(arr, fn) {
  495. return __awaiter(this, void 0, void 0, function* () {
  496. const res = [];
  497. for (let i = 0; i < arr.length; i++) {
  498. res.push(...(yield fn(arr[i], i, arr)));
  499. }
  500. return res;
  501. });
  502. }
  503.  
  504. /**
  505. * 自行实现 flatMap,将数组压平一层
  506. * @param arr 数组
  507. * @param fn 映射方法,将一个元素映射为一个数组
  508. * @returns 压平一层的数组
  509. */
  510. function flatMap(arr, fn = v => Array.from(v)) {
  511. return arr.reduce((res, v, i, arr) => {
  512. res.push(...fn(v, i, arr));
  513. return res;
  514. }, new Array());
  515. }
  516.  
  517. function groupBy(arr, kFn,
  518. /**
  519. * 默认的值处理函数
  520. * @param res 最终 V 集合
  521. * @param item 当前迭代的元素
  522. * @returns 将当前元素合并后的最终 V 集合
  523. */
  524. vFn = ((res, item) => {
  525. res.push(item);
  526. return res;
  527. }), init = () => []) {
  528. // 将元素按照分组条件进行分组得到一个 条件 -> 数组 的对象
  529. return arr.reduce((res, item, index, arr) => {
  530. const k = kFn(item, index, arr);
  531. // 如果已经有这个键了就直接追加, 否则先将之初始化再追加元素
  532. if (!res.has(k)) {
  533. res.set(k, init());
  534. }
  535. res.set(k, vFn(res.get(k), item, index, arr));
  536. return res;
  537. }, new Map());
  538. }
  539.  
  540. /**
  541. * 创建一个等差数列数组
  542. * @param start 开始(包含)
  543. * @param end 结束(不包含)
  544. * @param sep 步长,默认为 1
  545. * @returns 等差数列数组
  546. */
  547. function range(start, end, sep = 1) {
  548. const arr = [];
  549. for (let i = start; i < end; i += sep) {
  550. arr.push(i);
  551. }
  552. return arr;
  553. }
  554.  
  555. /**
  556. * 返回第一个参数的函数
  557. * 注: 一般可以当作返回参数自身的函数,如果你只关注第一个参数的话
  558. * @param obj 任何对象
  559. * @typeparam T 传入参数的类型
  560. * @typeparam R 返回结果的类型,默认为 T,只是为了兼容该函数当参数被传递时可能出现需要类型不一致的问题
  561. * @returns 传入的第一个参数
  562. */
  563. function returnItself(obj) {
  564. return obj;
  565. }
  566.  
  567. /**
  568. * 将数组转化为一个 Object 对象
  569. * @deprecated 已废弃,请使用更好的 {@link arrayToMap} 替代
  570. * @param arr 需要进行转换的数组
  571. * @param kFn 生成对象属性名的函数
  572. * @param vFn 生成对象属性值的函数,默认为数组中的迭代元素
  573. * @returns 转化得到的对象
  574. */
  575. function toObject(arr, kFn, vFn = returnItself) {
  576. return arr.reduce((res, item, i, arr) => {
  577. const k = kFn(item, i, arr);
  578. if (!Reflect.has(res, k)) {
  579. Reflect.set(res, k, vFn(item, i, arr));
  580. }
  581. return res;
  582. }, {});
  583. }
  584.  
  585. /**
  586. * js 的数组去重方法
  587. * @param arr 要进行去重的数组
  588. * @param kFn 唯一标识元素的方法,默认使用 {@link returnItself}
  589. * @returns 进行去重操作之后得到的新的数组 (原数组并未改变)
  590. */
  591. function uniqueBy(arr, kFn = returnItself) {
  592. const set = new Set();
  593. return arr.filter((v, ...args) => {
  594. const k = kFn(v, ...args);
  595. if (set.has(k)) {
  596. return false;
  597. }
  598. set.add(k);
  599. return true;
  600. });
  601. }
  602.  
  603. /**
  604. * 将数组映射为 Map
  605. * @param arr 数组
  606. * @param k 产生 Map 元素唯一标识的函数,或者对象元素中的一个属性名
  607. * @param v 产生 Map 值的函数,默认为返回数组的元素,或者对象元素中的一个属性名
  608. * @returns 映射产生的 map 集合
  609. */
  610. function arrayToMap(arr, k, v = returnItself) {
  611. const kFn = k instanceof Function ? k : (item) => Reflect.get(item, k);
  612. const vFn = v instanceof Function ? v : (item) => Reflect.get(item, v);
  613. return arr.reduce((res, item, index, arr) => res.set(kFn(item, index, arr), vFn(item, index, arr)), new Map());
  614. }
  615.  
  616. /**
  617. * 填充字符串到指定长度
  618. * @param item 填充的字符串
  619. * @param len 填充的长度
  620. * @returns 填充完成的字符串
  621. * @deprecated 已废弃,请使用 ES6 {@link String.prototype.repeat} 函数
  622. * 具体请参考 MDN {@url(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/repeat)}
  623. */
  624. function fill(item, len) {
  625. if (len <= 0) {
  626. return '';
  627. }
  628. return item + fill(item, len - 1);
  629. }
  630.  
  631. /**
  632. * 日期格式化类
  633. */
  634. class DateFormat {
  635. /**
  636. * 构造函数
  637. * @param name 日期格式的名称
  638. * @param format 日期的格式值
  639. * @param value 格式化得到的值
  640. * @param index 需要替换位置的索引
  641. */
  642. constructor(name, format, value, index) {
  643. this.name = name;
  644. this.format = format;
  645. this.value = value;
  646. this.index = index;
  647. }
  648. }
  649. /**
  650. * 日期时间的正则表达式
  651. */
  652. const dateFormats = new Map()
  653. .set('year', 'y{4}|y{2}')
  654. .set('month', 'M{1,2}')
  655. .set('day', 'd{1,2}')
  656. .set('hour', 'h{1,2}')
  657. .set('minute', 'm{1,2}')
  658. .set('second', 's{1,2}')
  659. .set('millieSecond', 'S{1,3}');
  660. /**
  661. * 如果没有格式化某项的话则设置为默认时间
  662. */
  663. const defaultDateValues = new Map()
  664. .set('month', '01')
  665. .set('day', '01')
  666. .set('hour', '00')
  667. .set('minute', '00')
  668. .set('second', '00')
  669. .set('millieSecond', '000');
  670. /**
  671. * 解析字符串为 Date 对象
  672. * @param str 日期字符串
  673. * @param fmt 日期字符串的格式,目前仅支持使用 y(年),M(月),d(日),h(时),m(分),s(秒),S(毫秒)
  674. * @returns 解析得到的 Date 对象
  675. */
  676. function dateParse(str, fmt) {
  677. const now = new Date();
  678. defaultDateValues.set('year', now.getFullYear().toString());
  679. // 保存对传入的日期字符串进行格式化的全部信息数组列表
  680. const dateUnits = [];
  681. for (const [fmtName, regex] of dateFormats) {
  682. const regExp = new RegExp(regex);
  683. if (regExp.test(fmt)) {
  684. const matchStr = regExp.exec(fmt)[0];
  685. const regexStr = fill('`', matchStr.length);
  686. const index = fmt.indexOf(matchStr);
  687. fmt = fmt.replace(matchStr, regexStr);
  688. dateUnits.push(new DateFormat(fmtName, fill('\\d', matchStr.length), null, index));
  689. }
  690. else {
  691. dateUnits.push(new DateFormat(fmtName, null, defaultDateValues.get(fmtName), -1));
  692. }
  693. }
  694. // 进行验证是否真的是符合传入格式的字符串
  695. fmt = fmt.replace(new RegExp('`', 'g'), '\\d');
  696. if (!new RegExp(`^${fmt}$`).test(str)) {
  697. return null;
  698. }
  699. // 进行一次排序, 依次对字符串进行截取
  700. dateUnits
  701. // 过滤掉没有得到格式化的对象
  702. .filter(({ format }) => format)
  703. // 按照字符串中日期片段的索引进行排序
  704. .sort(function (a, b) {
  705. return a.index - b.index;
  706. })
  707. // 获取到匹配的日期片段的值
  708. .map(format => {
  709. const matchDateUnit = new RegExp(format.format).exec(str);
  710. if (matchDateUnit !== null && matchDateUnit.length > 0) {
  711. str = str.replace(matchDateUnit[0], '');
  712. format.value = matchDateUnit[0];
  713. }
  714. return format;
  715. })
  716. // 覆写到 dateStr 上面
  717. .forEach(({ format }, i) => {
  718. const matchDateUnit = new RegExp(format).exec(str);
  719. if (matchDateUnit !== null && matchDateUnit.length > 0) {
  720. str = str.replace(matchDateUnit[0], '');
  721. dateUnits[i].value = matchDateUnit[0];
  722. }
  723. });
  724. // 将截取完成的信息封装成对象并格式化标准的日期字符串
  725. const map = arrayToMap(dateUnits, item => item.name, item => item.value);
  726. if (map.get('year').length === 2) {
  727. map.set('year', defaultDateValues
  728. .get('year')
  729. .substr(0, 2)
  730. .concat(map.get('year')));
  731. }
  732. // 注意:此处使用的是本地时间而非 UTC 时间
  733. const date = `${map.get('year')}-${map.get('month')}-${map.get('day')}T${map.get('hour')}:${map.get('minute')}:${map.get('second')}.${map.get('millieSecond')}`;
  734. return new Date(date);
  735. }
  736.  
  737. /**
  738. * 解析字符串为 Date 对象
  739. * @deprecated 已弃用,请使用可读性更好的 {@link dateParse} 代替
  740. * @param dateStr 日期字符串
  741. * @param fmt 日期字符串的格式
  742. * 目前仅支持使用 y(年),M(月),d(日),h(时),m(分),s(秒),S(毫秒)
  743. * @returns 解析得到的 Date 对象
  744. */
  745. function strToDate(dateStr, fmt) {
  746. return dateParse(dateStr, fmt);
  747. }
  748.  
  749. /**
  750. * 复制一段文本内容
  751. * @param text 要进行复制的文本
  752. * @returns 是否复制成功
  753. */
  754. function copyText(text) {
  755. const input = document.createElement('input');
  756. document.body.appendChild(input);
  757. input.setAttribute('value', text);
  758. input.select();
  759. const res = document.execCommand('copy');
  760. document.body.removeChild(input);
  761. return res;
  762. }
  763.  
  764. /**
  765. * 根据 html 字符串创建 Element 元素
  766. * @param str html 字符串
  767. * @returns 创建的 Element 元素
  768. */
  769. function createElByString(str) {
  770. const root = document.createElement('div');
  771. root.innerHTML = str;
  772. return root.querySelector('*');
  773. }
  774.  
  775. /**
  776. * 获取输入框中光标所在位置
  777. * @param {HTMLFormElement} el 需要获取的输入框元素
  778. * @returns 光标所在位置的下标
  779. */
  780. function getCursorPosition(el) {
  781. return el.selectionStart;
  782. }
  783.  
  784. /**
  785. * 获取输入框中光标所在位置
  786. * @param {HTMLFormElement} el 需要获取的输入框元素
  787. * @returns 光标所在位置的下标
  788. * @deprecated 已废弃,请使用正确更名后的 {@link getCursorPosition} 函数
  789. */
  790. function getCusorPostion(el) {
  791. return getCursorPosition(el);
  792. }
  793.  
  794. /**
  795. * 设置输入框中选中的文本/光标所在位置
  796. * @param el 需要设置的输入框元素
  797. * @param start 光标所在位置的下标
  798. * @param end 结束位置,默认为输入框结束
  799. */
  800. function setCursorPosition(el, start, end = start) {
  801. el.focus();
  802. el.setSelectionRange(start, end);
  803. }
  804.  
  805. /**
  806. * 在指定位置后插入文本
  807. * @param el 需要设置的输入框元素
  808. * @param text 要插入的值
  809. * @param start 开始位置,默认为当前光标处
  810. */
  811. function insertText(el, text, start = getCursorPosition(el)) {
  812. const value = el.value;
  813. el.value = value.substr(0, start) + text + value.substr(start);
  814. setCursorPosition(el, start + text.length);
  815. }
  816.  
  817. /**
  818. * 判断一个对象是否是无效的
  819. * 无效的值仅包含 null/undefined
  820. * @param object 任何一个对象
  821. * @returns 是否无效的值
  822. */
  823. function isNullOrUndefined(object) {
  824. return object === undefined || object === null;
  825. }
  826.  
  827. /**
  828. * 字符串安全的转换为小写
  829. * @param str 字符串
  830. * @returns 转换后得到的全小写字符串
  831. */
  832. function toLowerCase(str) {
  833. if (isNullOrUndefined(str) || typeof str !== 'string') {
  834. return str;
  835. }
  836. return str.toLowerCase();
  837. }
  838.  
  839. /**
  840. * 判断指定元素是否是可编辑元素
  841. * 注:可编辑元素并不一定能够进行编辑,例如只读的 input 元素
  842. * @param el 需要进行判断的元素
  843. * @returns 是否为可编辑元素
  844. */
  845. function isEditable(el) {
  846. const inputEls = ['input', 'date', 'datetime', 'select', 'textarea'];
  847. return (
  848. // 此处需要判断是否存在属性 isContentEditable
  849. // @ts-ignore
  850. el && (el.isContentEditable || inputEls.includes(toLowerCase(el.tagName))));
  851. }
  852.  
  853. let lastFocusEl;
  854. document.addEventListener('focus', event => {
  855. lastFocusEl = event.target;
  856. }, true);
  857. document.addEventListener('blur', () => {
  858. lastFocusEl = null;
  859. }, true);
  860. /**
  861. * 获取到最后一个获得焦点的元素
  862. * @returns 最后一个获取到焦点的元素
  863. */
  864. function lastFocus() {
  865. return lastFocusEl;
  866. }
  867.  
  868. /**
  869. * 直接删除指定元素
  870. * @param el 需要删除的元素
  871. * @returns 返回被删除的元素
  872. */
  873. function removeEl(el) {
  874. const parent = el.parentElement;
  875. if (parent == null) {
  876. return null;
  877. }
  878. return parent.removeChild(el);
  879. }
  880.  
  881. /**
  882. * 在指定范围内删除文本
  883. * @param el 需要设置的输入框元素
  884. * @param start 开始位置,默认为当前选中开始位置
  885. * @param end 结束位置,默认为当前选中结束位置
  886. */
  887. function removeText(el, start = el.selectionStart, end = el.selectionEnd) {
  888. // 删除之前必须要 [记住] 当前光标的位置
  889. const index = getCursorPosition(el);
  890. const value = el.value;
  891. el.value = value.substr(0, start) + value.substr(end, value.length);
  892. setCursorPosition(el, index);
  893. }
  894.  
  895. /**
  896. * 设置输入框中选中的文本/光标所在位置
  897. * @param el 需要设置的输入框元素
  898. * @param start 光标所在位置的下标
  899. * @param end 结束位置,默认为输入框结束
  900. * @deprecated 已废弃,请使用正确更名后的 {@link setCursorPosition} 函数
  901. */
  902. function setCusorPostion(el, start, end = start) {
  903. return setCursorPosition(el, start, end);
  904. }
  905.  
  906. /**
  907. * 用来保存监听到的事件信息
  908. */
  909. class Event {
  910. constructor(el, type, listener, options) {
  911. this.el = el;
  912. this.type = type;
  913. this.listener = listener;
  914. this.options = options;
  915. }
  916. }
  917. /**
  918. * 监听 event 的添加
  919. * 注:必须及早添加
  920. */
  921. function watchEventListener() {
  922. /**
  923. * 监听所有的 addEventListener, removeEventListener 事件
  924. */
  925. const documentAddEventListener = document.addEventListener;
  926. const eventTargetAddEventListener = EventTarget.prototype.addEventListener;
  927. const documentRemoveEventListener = document.removeEventListener;
  928. const eventTargetRemoveEventListener = EventTarget.prototype.removeEventListener;
  929. let events = [];
  930. /**
  931. * 自定义的添加事件监听函数
  932. * @param type 事件类型
  933. * @param listener 事件监听函数
  934. * @param useCapture 是否需要捕获事件冒泡,默认为 false
  935. */
  936. function addEventListener(type, listener, useCapture) {
  937. // @ts-ignore
  938. const _this = this;
  939. const $addEventListener = _this === document
  940. ? documentAddEventListener
  941. : eventTargetAddEventListener;
  942. events.push(new Event(_this, type, listener, useCapture));
  943. // @ts-ignore
  944. $addEventListener.apply(this, type, listener, useCapture);
  945. }
  946. /**
  947. * 自定义的根据类型删除事件函数
  948. * 该方法会删除这个类型下面全部的监听函数,不管数量
  949. * @param type 事件类型
  950. */
  951. // @ts-ignore
  952. function removeEventListenerByType(type) {
  953. // @ts-ignore
  954. const _this = this;
  955. const $removeEventListener = _this === document
  956. ? documentRemoveEventListener
  957. : eventTargetRemoveEventListener;
  958. const map = groupBy(events, e => e.el === _this && e.type === type);
  959. const removeArr = map.get(true);
  960. removeArr.forEach(e => {
  961. // @ts-ignore
  962. $removeEventListener.apply(e.el, [e.type, e.listener, e.useCapture]);
  963. });
  964. // @ts-ignore
  965. events = map.get(false);
  966. }
  967. // @ts-ignore
  968. document.addEventListener = EventTarget.prototype.addEventListener = addEventListener;
  969. // 此处是为了新增函数 removeEventListenerByType
  970. // @ts-ignore
  971. document.removeEventListenerByType = EventTarget.prototype.removeEventListenerByType = removeEventListenerByType;
  972. }
  973.  
  974. /**
  975. * 将任意对象转换为 String
  976. * 主要避免原生 Object toString 遇到某些空值的时候抛异常的问题
  977. * @param object 任意对象
  978. * @returns 字符串
  979. */
  980. function toString$1(object) {
  981. if (isNullOrUndefined(object)) {
  982. return '';
  983. }
  984. if (object instanceof Date) {
  985. return object.toISOString();
  986. }
  987. return object.toString();
  988. }
  989.  
  990. /**
  991. * FormData 批量添加方法
  992. * 注:该方法不会覆盖掉原本的属性
  993. * @param fd FormData 对象
  994. * @param obj 键值对对象
  995. * @returns 添加完成后的 FormData 对象
  996. */
  997. function appends(fd, obj) {
  998. for (const k in obj) {
  999. const v = obj[k];
  1000. fd.append(k, toString$1(v));
  1001. }
  1002. return fd;
  1003. }
  1004.  
  1005. /**
  1006. * FormData 批量删除方法
  1007. * @param fd FormData 对象
  1008. * @param keys 删除的 key 列表
  1009. * @returns 返回删除后的 FormData 对象
  1010. */
  1011. function deletes(fd, keys) {
  1012. keys.forEach(key => fd.delete(key));
  1013. return fd;
  1014. }
  1015.  
  1016. /**
  1017. * FormData 批量设置方法
  1018. * 注:该方法会覆盖掉原本的属性
  1019. * @param fd 表单对象
  1020. * @param obj 键值对对象
  1021. * @returns 设置完成后的 FormData 对象
  1022. */
  1023. function sets(fd, obj) {
  1024. for (const k in obj) {
  1025. fd.set(k, obj[k]);
  1026. }
  1027. return fd;
  1028. }
  1029.  
  1030. /**
  1031. * FormData 转换为包含所有键值数组的二维数组函数
  1032. *
  1033. * @param fd 需要转换的 FormData 对象
  1034. * @returns 转换后的数组
  1035. * @deprecated 已被原生函数 Array.from 取代
  1036. */
  1037. function formDataToArray(fd) {
  1038. // @ts-ignore
  1039. return Array.from(fd);
  1040. }
  1041.  
  1042. /**
  1043. * 将参数对象转换为 FormData,只转换一层
  1044. * @param data 参数对象
  1045. * @return {FormData} 转换后的表单对象
  1046. */
  1047. function objToFormData(data) {
  1048. return Object.entries(data).reduce((res, [k, v]) => {
  1049. if (v instanceof Blob) {
  1050. res.append(k, v);
  1051. }
  1052. else {
  1053. res.append(k, v && v.toString());
  1054. }
  1055. return res;
  1056. }, new FormData());
  1057. }
  1058.  
  1059. /**
  1060. * 函数去抖
  1061. * 去抖 (debounce) 去抖就是对于一定时间段的连续的函数调用,只让其执行一次
  1062. * 注: 包装后的函数如果两次操作间隔小于 delay 则不会被执行, 如果一直在操作就会一直不执行, 直到操作停止的时间大于 delay 最小间隔时间才会执行一次, 不管任何时间调用都需要停止操作等待最小延迟时间
  1063. * 应用场景主要在那些连续的操作, 例如页面滚动监听, 包装后的函数只会执行最后一次
  1064. * 注: 该函数第一次调用一定不会执行,第一次一定拿不到缓存值,后面的连续调用都会拿到上一次的缓存值。如果需要在第一次调用获取到的缓存值,则需要传入第三个参数 {@param init},默认为 {@code undefined} 的可选参数
  1065. * 注: 返回函数结果的高阶函数需要使用 {@see Proxy} 实现,以避免原函数原型链上的信息丢失
  1066. *
  1067. * @param delay 最小延迟时间,单位为 ms
  1068. * @param action 真正需要执行的操作
  1069. * @param init 初始的缓存值,不填默认为 {@see undefined}
  1070. * @return 包装后有去抖功能的函数。该函数是异步的,与需要包装的函数 {@see action} 是否异步没有太大关联
  1071. */
  1072. function debounce(delay, action, init = null) {
  1073. let flag;
  1074. let result = init;
  1075. return new Proxy(action, {
  1076. apply(_, _this, args) {
  1077. return new Promise(resolve => {
  1078. if (flag)
  1079. clearTimeout(flag);
  1080. flag = setTimeout(() => resolve((result = Reflect.apply(_, _this, args))), delay);
  1081. setTimeout(() => resolve(result), delay);
  1082. });
  1083. },
  1084. });
  1085. }
  1086.  
  1087. /**
  1088. * 安全执行某个函数
  1089. * @param fn 需要执行的函数
  1090. * @param defaultVal 发生异常后的默认返回值,默认为 null
  1091. * @param args 可选的函数参数
  1092. * @returns 函数执行的结果,或者其默认值
  1093. */
  1094. function safeExec(fn, defaultVal = null, ...args) {
  1095. try {
  1096. return fn(...args);
  1097. }
  1098. catch (err) {
  1099. return defaultVal;
  1100. }
  1101. }
  1102.  
  1103. /**
  1104. * 使用 Proxy 实现通用的单例模式
  1105. * @param clazz 需要包装为单例的类型
  1106. * @returns 包装后的单例模式类,使用 {@code new} 创建将只在第一次有效
  1107. */
  1108. function singleModel(clazz) {
  1109. let instance;
  1110. return new Proxy(clazz, {
  1111. construct(target, args, newTarget) {
  1112. if (instance === undefined) {
  1113. instance = Reflect.construct(target, args, newTarget);
  1114. }
  1115. return instance;
  1116. },
  1117. });
  1118. }
  1119.  
  1120. /**
  1121. * 状态机
  1122. * 用于避免使用 if-else 的一种方式
  1123. * @typeparam K 状态的类型,默认为 any
  1124. * @typeparam V 构造函数返回值的类型,一般为实现子类的基类,默认为 any
  1125. * @deprecated 该类将在下个大版本进行重构,使用函数而非类作为基本单元
  1126. */
  1127. class StateMachine {
  1128. constructor() {
  1129. this.classMap = new Map();
  1130. }
  1131. /**
  1132. * 获取到一个状态工厂
  1133. * @deprecated 已废弃,请直接创建一个 StateMachine 实例
  1134. */
  1135. static getFactory() {
  1136. /**
  1137. * 状态注册器
  1138. * 更好的有限状态机,分离子类与构建的关系,无论子类如何增删该都不影响基类及工厂类
  1139. */
  1140. return new StateMachine();
  1141. }
  1142. /**
  1143. * 注册一个 class,创建子类时调用,用于记录每一个 [状态 => 子类] 对应
  1144. * 注: 此处不再默认使用单例模式,如果需要,请自行对 class 进行包装
  1145. * @param state 作为键的状态
  1146. * @param clazz 对应的子类型
  1147. * @returns 返回 clazz 本身
  1148. */
  1149. register(state, clazz) {
  1150. this.classMap.set(state, clazz);
  1151. return clazz;
  1152. }
  1153. /**
  1154. * 获取一个标签子类对象
  1155. * @param state 状态索引
  1156. * @param args 构造函数的参数
  1157. * @returns 子类对象
  1158. */
  1159. getInstance(state, ...args) {
  1160. const Class = this.classMap.get(state);
  1161. if (!Class) {
  1162. return null;
  1163. }
  1164. // 构造函数的参数
  1165. return new Class(...args);
  1166. }
  1167. /**
  1168. * 允许使用 for-of 遍历整个状态机
  1169. */
  1170. *[Symbol.iterator]() {
  1171. for (const kv of this.classMap.entries()) {
  1172. yield kv;
  1173. }
  1174. }
  1175. }
  1176.  
  1177. /**
  1178. * 函数节流
  1179. * 节流 (throttle) 让一个函数不要执行的太频繁,减少执行过快的调用,叫节流
  1180. * 类似于上面而又不同于上面的函数去抖, 包装后函数在上一次操作执行过去了最小间隔时间后会直接执行, 否则会忽略该次操作
  1181. * 与上面函数去抖的明显区别在连续操作时会按照最小间隔时间循环执行操作, 而非仅执行最后一次操作
  1182. * 注: 该函数第一次调用一定会执行,不需要担心第一次拿不到缓存值,后面的连续调用都会拿到上一次的缓存值
  1183. * 注: 返回函数结果的高阶函数需要使用 {@see Proxy} 实现,以避免原函数原型链上的信息丢失
  1184. *
  1185. * @param delay 最小间隔时间,单位为 ms
  1186. * @param action 真正需要执行的操作
  1187. * @return {Function} 包装后有节流功能的函数。该函数是异步的,与需要包装的函数 {@link action} 是否异步没有太大关联
  1188. */
  1189. function throttle(delay, action) {
  1190. let last = 0;
  1191. let result;
  1192. return new Proxy(action, {
  1193. apply(target, thisArg, args) {
  1194. return new Promise(resolve => {
  1195. const curr = Date.now();
  1196. if (curr - last > delay) {
  1197. result = Reflect.apply(target, thisArg, args);
  1198. last = curr;
  1199. resolve(result);
  1200. return;
  1201. }
  1202. resolve(result);
  1203. });
  1204. },
  1205. });
  1206. }
  1207.  
  1208. /**
  1209. * 兼容异步函数的返回值
  1210. * @param res 返回值
  1211. * @param callback 同步/异步结果的回调函数
  1212. * @typeparam T 处理参数的类型,如果是 Promise 类型,则取出其泛型类型
  1213. * @typeparam Param 处理参数具体的类型,如果是 Promise 类型,则指定为原类型
  1214. * @typeparam R 返回值具体的类型,如果是 Promise 类型,则指定为 Promise 类型,否则为原类型
  1215. * @returns 处理后的结果,如果是同步的,则返回结果是同步的,否则为异步的
  1216. */
  1217. function compatibleAsync(res, callback) {
  1218. return (res instanceof Promise
  1219. ? res.then(callback)
  1220. : callback(res));
  1221. }
  1222.  
  1223. /**
  1224. * 测试函数的执行时间
  1225. * 注:如果函数返回 Promise,则该函数也会返回 Promise,否则直接返回执行时间
  1226. * @param fn 需要测试的函数
  1227. * @returns 执行的毫秒数
  1228. */
  1229. function timing(fn) {
  1230. const begin = performance.now();
  1231. const res = fn();
  1232. return compatibleAsync(res, () => performance.now() - begin);
  1233. }
  1234.  
  1235. /**
  1236. * 轮询等待指定资源加载完毕再执行操作
  1237. * 使用 Promises 实现,可以使用 ES7 的 {@see async} 和 {@see await} 调用
  1238. * @param fn 判断必须的资源是否存在的方法
  1239. * @param option 可配置项
  1240. * @returns Promise 对象
  1241. */
  1242. function waitResource(fn, { interval = 100, max = 10 } = {}) {
  1243. let current = 0;
  1244. return new Promise((resolve, reject) => {
  1245. const timer = setInterval(() => {
  1246. if (fn()) {
  1247. clearInterval(timer);
  1248. resolve();
  1249. }
  1250. current++;
  1251. if (current >= max) {
  1252. clearInterval(timer);
  1253. reject(new Error('waitResource call timeout'));
  1254. }
  1255. }, interval);
  1256. });
  1257. }
  1258.  
  1259. /**
  1260. * 监视指定函数返回值的变化
  1261. * @param fn 需要监视的函数
  1262. * @param callback 回调函数
  1263. * @param interval 每次检查的间隔时间,默认为 100ms
  1264. * @returns 关闭这个监视函数
  1265. */
  1266. function watch(fn, callback, interval = 100) {
  1267. let oldVal = fn();
  1268. const timer = setInterval(() => {
  1269. const newVal = fn();
  1270. if (oldVal !== newVal) {
  1271. callback(newVal, oldVal);
  1272. oldVal = newVal;
  1273. }
  1274. }, interval);
  1275. return () => clearInterval(timer);
  1276. }
  1277.  
  1278. /**
  1279. * 深度监听指定对象属性的变化
  1280. * 注:指定对象不能是原始类型,即不可变类型,而且对象本身的引用不能改变,最好使用 const 进行声明
  1281. * @param object 需要监视的对象
  1282. * @param callback 当代理对象发生改变时的回调函数,回调函数有三个参数,分别是对象,修改的 key,修改的 v
  1283. * @returns 返回源对象的一个代理
  1284. */
  1285. function watchObject(object, callback) {
  1286. const handler = {
  1287. get(target, k) {
  1288. try {
  1289. // 注意: 这里很关键,它为对象的字段也添加了代理
  1290. return new Proxy(Reflect.get(target, k), handler);
  1291. }
  1292. catch (err) {
  1293. return Reflect.get(target, k);
  1294. }
  1295. },
  1296. set(target, k, v) {
  1297. callback(target, k, v);
  1298. return Reflect.set(target, k, v);
  1299. },
  1300. };
  1301. return new Proxy(object, handler);
  1302. }
  1303.  
  1304. /**
  1305. * 字符串格式化
  1306. *
  1307. * @param str 要进行格式化的值
  1308. * @param args 格式化参数值,替换字符串中的 {} 的值
  1309. * @returns 替换完成的字符串
  1310. * @deprecated 已废弃,请使用 ES6 模板字符串 {@url(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings)}
  1311. */
  1312. function format(str, args) {
  1313. return Object.keys(args).reduce((res, k) => res.replace(new RegExp(`{${k}}`, 'g'), toString$1(args[k])), str);
  1314. }
  1315.  
  1316. /**
  1317. * 判断数字是否在指定区间之中
  1318. * @param num 指定数字
  1319. * @param min 最小值
  1320. * @param max 最大值(不包含)
  1321. */
  1322. function isRange(num, min, max) {
  1323. return num >= min && num < max;
  1324. }
  1325.  
  1326. /**
  1327. * 判断是否为小数的正则表达式
  1328. */
  1329. const FloatRule = /^(-?\d+)(.\d+)?$/;
  1330. /**
  1331. * 判断是否为整数的正则表达式
  1332. */
  1333. const IntegerRule = /^-?\d+$/;
  1334. /**
  1335. * 判断是否为邮箱的正则表达式
  1336. */
  1337. const EmailRule = /^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z]+$/;
  1338. /**
  1339. * 判断是否为 ipv4 地址的正则表达式
  1340. */
  1341. const Ipv4Rule = /^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$/;
  1342. /**
  1343. * 判断是否为固定电话的正则表达式
  1344. */
  1345. const TelephoneRule = /^0[1-9][0-9]{1,2}-[2-8][0-9]{6,7}$/;
  1346. /**
  1347. * 判断是否为移动电话的正则表达式
  1348. */
  1349. const MobileRule = /^(((13[0-9]{1})|15[0-9]{1}|18[0-9]{1}|)+\d{8})$/;
  1350. /**
  1351. * 判断是否为域名的正则表达式
  1352. */
  1353. const DomainRule = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
  1354. /**
  1355. * 判断是否为邮政编码的正则表达式
  1356. */
  1357. const PostcodeRule = /^\d{6}$/;
  1358. /**
  1359. * 字符串校验
  1360. * TODO 使用 any 可能是个严重的错误。。。
  1361. */
  1362. class StringValidator {
  1363. /**
  1364. * 判断一个字符串是否为空字符串
  1365. * @param str 字符串
  1366. * @returns 是否为空字符串
  1367. */
  1368. static isEmpty(str) {
  1369. return isNullOrUndefined(str) || str === '';
  1370. }
  1371. /**
  1372. * 判断一个字符串是否为空白的字符串
  1373. * @param str 字符串
  1374. * @returns 是否为空字符串
  1375. */
  1376. static isBlank(str) {
  1377. return StringValidator.isEmpty(str) || str.trim() === '';
  1378. }
  1379. /**
  1380. * 判断字符串是否位小数
  1381. * @param str 需要进行判断的字符串
  1382. * @returns 是否为小数
  1383. */
  1384. static isFloat(str) {
  1385. if (isNullOrUndefined(str)) {
  1386. return false;
  1387. }
  1388. return FloatRule.test(str);
  1389. }
  1390. /**
  1391. * 判断字符串是否位整数
  1392. * @param str 需要进行判断的字符串
  1393. * @returns 是否为小数
  1394. */
  1395. static isInteger(str) {
  1396. return !isNullOrUndefined(str) && IntegerRule.test(str);
  1397. }
  1398. /**
  1399. * 判断邮箱的格式是否正确
  1400. * @param str 邮箱字符串
  1401. * @returns 是否是邮箱
  1402. */
  1403. static isEmail(str) {
  1404. return !isNullOrUndefined(str) && EmailRule.test(str);
  1405. }
  1406. /**
  1407. * 判断 ipv4 地址的格式是否正确
  1408. * @param str ipv4 字符串
  1409. * @returns 是否是 ipv4 地址
  1410. */
  1411. static isIpv4(str) {
  1412. return !isNullOrUndefined(str) && Ipv4Rule.test(str);
  1413. }
  1414. /**
  1415. * 判断字符串是否为正确的端口号
  1416. * 正确的端口号是 1-65535
  1417. * @param str 字符串
  1418. * @returns 是否为端口号
  1419. */
  1420. static isPort(str) {
  1421. // tslint:disable-next-line:radix
  1422. return StringValidator.isInteger(str) && isRange(parseInt(str), 1, 65535);
  1423. }
  1424. /**
  1425. * 判断是否为固定电话
  1426. * @param str 字符串
  1427. * @returns 是否为固定电话
  1428. */
  1429. static isTelephone(str) {
  1430. return !isNullOrUndefined(str) && TelephoneRule.test(str);
  1431. }
  1432. /**
  1433. * 判断是否为移动电话
  1434. * @param str 字符串
  1435. * @returns 是否为移动电话
  1436. */
  1437. static isMobile(str) {
  1438. return !isNullOrUndefined(str) && MobileRule.test(str);
  1439. }
  1440. /**
  1441. * 判断是否为域名
  1442. * @param str 字符串
  1443. * @returns 是否为域名
  1444. */
  1445. static isDomain(str) {
  1446. return !isNullOrUndefined(str) && DomainRule.test(str);
  1447. }
  1448. /**
  1449. * 判断是否为邮政编码
  1450. * @param str 字符串
  1451. * @returns 是否为邮政编码
  1452. */
  1453. static isPostcode(str) {
  1454. return !isNullOrUndefined(str) && PostcodeRule.test(str);
  1455. }
  1456. }
  1457. /**
  1458. * 导出一个字符串校验的对象
  1459. * @deprecated 已废弃,请直接使用类的静态函数
  1460. */
  1461. const stringValidator = StringValidator;
  1462.  
  1463. /**
  1464. * 判断字符串是否位小数
  1465. * @param str 需要进行判断的字符串
  1466. * @returns 是否为小数
  1467. * @deprecated 已废弃,请使用 {@link stringValidator#isFloat}
  1468. */
  1469. function isFloat(str) {
  1470. return stringValidator.isFloat(str);
  1471. }
  1472.  
  1473. /**
  1474. * 判断字符串是否位整数
  1475. * @param str 需要进行判断的字符串
  1476. * @returns 是否为小数
  1477. * @deprecated 已废弃,请使用 {@link stringValidator#isInteger}
  1478. */
  1479. function isNumber(str) {
  1480. return stringValidator.isInteger(str);
  1481. }
  1482.  
  1483. /**
  1484. * 字符串安全的转换为大写
  1485. * @param str 字符串
  1486. * @returns 转换后得到的全大写字符串
  1487. */
  1488. function toUpperCase(str) {
  1489. if (isNullOrUndefined(str) || typeof str !== 'string') {
  1490. return str;
  1491. }
  1492. return str.toUpperCase();
  1493. }
  1494.  
  1495. /**
  1496. * 将空白字符串转换为 null
  1497. *
  1498. * @param str 将空字符串转换为 {@code null}
  1499. * @returns 可能为 {@code null}
  1500. */
  1501. function blankToNull(str) {
  1502. return stringValidator.isBlank(str) ? null : str;
  1503. }
  1504.  
  1505. /**
  1506. * 置空对象所有空白的属性
  1507. * @param obj 对象
  1508. * @returns 将所有的空白属性全部转换为 null 的新对象
  1509. */
  1510. function blankToNullField(obj) {
  1511. return Reflect.ownKeys(obj).reduce((res, k) => {
  1512. const v = Reflect.get(obj, k);
  1513. Reflect.set(res, k, typeof v === 'string' ? blankToNull(v) : v);
  1514. return res;
  1515. }, {});
  1516. }
  1517.  
  1518. /**
  1519. * 将对象的所有属性置空
  1520. * @param obj 需要置空属性的对象
  1521. * @returns 返回一个新的对象
  1522. */
  1523. function emptyAllField(obj) {
  1524. return Reflect.ownKeys(obj).reduce((res, k) => {
  1525. Reflect.set(res, k, null);
  1526. return res;
  1527. }, {});
  1528. }
  1529.  
  1530. /**
  1531. * 排除对象中的指定字段
  1532. * 注: 此处将获得一个浅拷贝对象
  1533. * @param obj 排除对象
  1534. * @param fields 要排除的多个字段
  1535. * @returns 排除完指定字段得到的新的对象
  1536. */
  1537. function excludeFields(obj, ...fields) {
  1538. const set = new Set(fields);
  1539. return Reflect.ownKeys(obj).reduce((res, k) => {
  1540. if (!set.has(k)) {
  1541. Reflect.set(res, k, Reflect.get(obj, k));
  1542. }
  1543. return res;
  1544. }, {});
  1545. }
  1546.  
  1547. /**
  1548. * 将 Map 转换为 Object 对象
  1549. * @param map Map 键值表
  1550. * @returns 转换得到的 Object 对象
  1551. */
  1552. function mapToObject(map) {
  1553. const res = {};
  1554. for (const [k, v] of map) {
  1555. Reflect.set(res, k, v);
  1556. }
  1557. return res;
  1558. }
  1559.  
  1560. /**
  1561. * 生成一个随机的数字
  1562. * 如果没有参数,则会抛出异常
  1563. * @param num1 第一个参数,如果只有一个参数,则认为是最大值,最小值为 0
  1564. * @param num2 第二个参数,如果该参数存在,则认为第二个是最大值,忽略剩余的参数
  1565. * @returns 生成的随机整数
  1566. */
  1567. function randomInt(num1, num2) {
  1568. const min = num2 ? num1 : 0;
  1569. const max = num2 ? num2 : num1;
  1570. return min + Math.floor(Math.random() * (max - min));
  1571. }
  1572.  
  1573. /**
  1574. * 日期固定时间点
  1575. */
  1576. class DateConstants {
  1577. /**
  1578. * 获取指定日期一天的开始时间
  1579. * @param date 指定的时间,默认为当前日期
  1580. * @returns 一天的开始时间
  1581. */
  1582. static dayStart(date = new Date()) {
  1583. return new Date(`${dateFormat(date, 'yyyy-MM-dd')}T00:00:00.000`);
  1584. }
  1585. /**
  1586. * 获取指定日期一天的结束时间
  1587. * @param date 指定的时间,默认为当前日期
  1588. * @returns 一天的结束时间
  1589. */
  1590. static dayEnd(date = new Date()) {
  1591. return new Date(`${dateFormat(date, 'yyyy-MM-dd')}T23:59:59.999`);
  1592. }
  1593. /**
  1594. * 获取指定日期所在年份的新年开始时间
  1595. * @param date 指定的时间,默认为当前日期
  1596. * @returns 新年开始时间
  1597. */
  1598. static yearStart(date = new Date()) {
  1599. return new Date(`${date.getFullYear()}-01-01T00:00:00.000`);
  1600. }
  1601. /**
  1602. * 获取指定日期所在年份的旧年结束时间
  1603. * @param date 指定的时间,默认为当前日期
  1604. * @returns 旧年结束时间
  1605. */
  1606. static yearEnd(date = new Date()) {
  1607. return new Date(`${date.getFullYear()}-12-31T23:59:59.999`);
  1608. }
  1609. }
  1610. /**
  1611. * 导出一个日期固定时间点的对象
  1612. * @deprecated 已废弃,请直接使用类的静态函数
  1613. */
  1614. const dateConstants = DateConstants;
  1615.  
  1616. /**
  1617. * 一天标准的毫秒数
  1618. */
  1619. const DAY_UNIT_TIME = 1000 * 60 * 60 * 24;
  1620. /**
  1621. * 日期增强
  1622. */
  1623. class DateEnhance {
  1624. /**
  1625. * 构造函数
  1626. * @param date 要增强的日期
  1627. */
  1628. constructor(date) {
  1629. this.date = date;
  1630. }
  1631. /**
  1632. * 获取到年份
  1633. * @returns
  1634. */
  1635. year() {
  1636. return this.date.getFullYear();
  1637. }
  1638. /**
  1639. * 获取月份
  1640. * @returns
  1641. * @deprecated 已废弃,请使用 {@link this#monthOfYear} 函数
  1642. */
  1643. month() {
  1644. return this.date.getMonth();
  1645. }
  1646. /**
  1647. * 获取今年的第几个月份
  1648. * 和 {@link this#month} 不同的是不再从 0 计算月份
  1649. */
  1650. monthOfYear() {
  1651. return this.date.getMonth() + 1;
  1652. }
  1653. /**
  1654. * 获取一年内的第多少天
  1655. * 注: 这个天数指定的在第几天而非过去了多少天,例如 2018-01-10 的结果会是 10
  1656. * @returns
  1657. */
  1658. dayOfYear() {
  1659. return Math.ceil((this.date.getTime() - dateConstants.yearStart(this.date).getTime()) /
  1660. DAY_UNIT_TIME);
  1661. }
  1662. /**
  1663. * 获取一个月内的第多少天
  1664. * 注: 这个天数指的是在第几天而非过去了多少天,例如 2018-01-10 的结果会是 10
  1665. * @returns
  1666. */
  1667. dayOfMonth() {
  1668. return this.date.getDate();
  1669. }
  1670. /**
  1671. * 获取一个星期内的第多少天
  1672. * @returns
  1673. */
  1674. dayOfWeek() {
  1675. return this.date.getDay();
  1676. }
  1677. /**
  1678. * 获取一年内的第多少星期
  1679. * 注: 这个星期指定的在第几天而非过去了多少天,例如 2018-01-10 的结果会是 10
  1680. * @returns
  1681. */
  1682. weekOfYear() {
  1683. return Math.ceil(this.dayOfYear() / 7);
  1684. }
  1685. /**
  1686. * 获取一个月内的第多少星期
  1687. * @returns
  1688. */
  1689. weekOfMonth() {
  1690. return Math.ceil(this.dayOfMonth() / 7);
  1691. }
  1692. /**
  1693. * 获取季度
  1694. * @returns
  1695. */
  1696. quarter() {
  1697. const month = this.month();
  1698. if (isRange(month, 0, 3)) {
  1699. return 1;
  1700. }
  1701. else if (isRange(month, 3, 6)) {
  1702. return 2;
  1703. }
  1704. else if (isRange(month, 6, 9)) {
  1705. return 3;
  1706. }
  1707. else {
  1708. return 4;
  1709. }
  1710. }
  1711. /**
  1712. * 获取小时
  1713. * @returns
  1714. */
  1715. hour() {
  1716. return this.date.getHours();
  1717. }
  1718. /**
  1719. * 获取分钟
  1720. * @returns
  1721. */
  1722. minute() {
  1723. return this.date.getMinutes();
  1724. }
  1725. /**
  1726. * 获取秒
  1727. * @returns
  1728. */
  1729. second() {
  1730. return this.date.getSeconds();
  1731. }
  1732. /**
  1733. * 获取毫秒
  1734. * @returns
  1735. */
  1736. milliSecond() {
  1737. return this.date.getMilliseconds();
  1738. }
  1739. }
  1740. /**
  1741. * 获取一个增强的日期
  1742. * @param date 要增强的日期
  1743. * @returns 增强日期
  1744. */
  1745. function dateEnhance(date) {
  1746. return new DateEnhance(date);
  1747. }
  1748.  
  1749. /**
  1750. * 获取一年内的第多少星期
  1751. * @param date 日期
  1752. * @returns 这个日期第多少个星期
  1753. * @deprecated 不推荐使用,请使用 {@see dateEnhance} 代替
  1754. */
  1755. function getYearWeek(date) {
  1756. return dateEnhance(date).weekOfYear();
  1757. }
  1758.  
  1759. /**
  1760. * 时间日期间隔
  1761. */
  1762. class DateBetween {
  1763. /**
  1764. * 构造函数
  1765. * @param start 开始时间
  1766. * @param end 结束时间
  1767. */
  1768. constructor(start, end) {
  1769. this.start = start;
  1770. this.end = end;
  1771. }
  1772. /**
  1773. * 获取毫秒差值
  1774. * @returns 毫秒差值
  1775. */
  1776. milliSecond() {
  1777. return this.end.getTime() - this.start.getTime();
  1778. }
  1779. /**
  1780. * 获取秒差值
  1781. * @returns 秒差值
  1782. */
  1783. second() {
  1784. return Math.floor(this.milliSecond() / 1000);
  1785. }
  1786. /**
  1787. * 获取分钟差值
  1788. * @returns 分钟差值
  1789. */
  1790. minute() {
  1791. return Math.floor(this.second() / 60);
  1792. }
  1793. /**
  1794. * 获取小时差值
  1795. * @returns 小时差值
  1796. */
  1797. hour() {
  1798. return Math.floor(this.minute() / 60);
  1799. }
  1800. /**
  1801. * 获取天数差值
  1802. * @returns 天数差值
  1803. */
  1804. day() {
  1805. return Math.floor(this.hour() / 24);
  1806. }
  1807. /**
  1808. * 获取月份差值
  1809. * 注: 此处获取的差值是按月计算的,即 2018-12-31 => 2019-01-01 也被认为相差一个月
  1810. * @returns 月份差值
  1811. */
  1812. month() {
  1813. const year = this.year();
  1814. const month = this.end.getMonth() - this.start.getMonth();
  1815. return year * 12 + month;
  1816. }
  1817. /**
  1818. * 获取年份差值
  1819. * 注: 此处获取的差值是按年计算的,即 2018-12-31 => 2019-01-01 也被认为相差一年
  1820. * @returns 年份差值
  1821. */
  1822. year() {
  1823. return this.end.getFullYear() - this.start.getFullYear();
  1824. }
  1825. }
  1826. /**
  1827. * 获取两个时间的差值
  1828. * @param start 开始时间
  1829. * @param end 结束时间
  1830. * @returns 差值对象
  1831. */
  1832. function dateBetween(start, end) {
  1833. return new DateBetween(start, end);
  1834. }
  1835.  
  1836. /**
  1837. * 返回合理参数本身的函数
  1838. * 1. 如果没有参数则返回 undefined
  1839. * 2. 如果只有一个参数则返回参数本身
  1840. * 3. 如果有两个以上的参数则返回参数列表
  1841. * @param args 任何对象
  1842. * @returns 传入的参数
  1843. */
  1844. function returnReasonableItself(...args) {
  1845. const len = args.length;
  1846. if (len === 0) {
  1847. return null;
  1848. }
  1849. if (len === 1) {
  1850. return args[0];
  1851. }
  1852. return args;
  1853. }
  1854.  
  1855. /**
  1856. * 从数组中移除指定的元素
  1857. * 注: 时间复杂度为 1~3On
  1858. * @param arr 需要被过滤的数组
  1859. * @param deleteItems 要过滤的元素数组
  1860. * @param kFn 每个元素的唯一键函数
  1861. */
  1862. function filterItems(arr, deleteItems, kFn = returnItself) {
  1863. const kSet = new Set(deleteItems.map(kFn));
  1864. return arr.filter((v, i, arr) => !kSet.has(kFn(v, i, arr)));
  1865. }
  1866.  
  1867. /**
  1868. * 比较两个数组的差异
  1869. * @param left 第一个数组
  1870. * @param right 第二个数组
  1871. * @param kFn 每个元素的唯一标识产生函数
  1872. * @returns 比较的差异结果
  1873. */
  1874. function diffBy(left, right, kFn = returnItself) {
  1875. // 首先得到两个 kSet 集合用于过滤
  1876. const kThanSet = new Set(left.map(kFn));
  1877. const kThatSet = new Set(right.map(kFn));
  1878. const leftUnique = left.filter((v, ...args) => !kThatSet.has(kFn(v, ...args)));
  1879. const rightUnique = right.filter((v, ...args) => !kThanSet.has(kFn(v, ...args)));
  1880. const kLeftSet = new Set(leftUnique.map(kFn));
  1881. const common = left.filter((v, ...args) => !kLeftSet.has(kFn(v, ...args)));
  1882. return { left: leftUnique, right: rightUnique, common };
  1883. }
  1884.  
  1885. /**
  1886. * 比较两个数组的差异
  1887. * @deprecated 已废弃,请使用更简洁的 {@link diffBy}
  1888. */
  1889. const arrayDiffBy = diffBy;
  1890.  
  1891. /**
  1892. * 使用 Generator 实现一个从 0 开始的无限自增序列
  1893. */
  1894. function* autoIncrementGenerator() {
  1895. for (let i = 0;; i++) {
  1896. /**
  1897. * @returns 每次获取都返回循环中的当前迭代变量,然后暂停于此处
  1898. */
  1899. yield i;
  1900. }
  1901. }
  1902. /**
  1903. * 生成器对象
  1904. */
  1905. const generator = autoIncrementGenerator();
  1906. /**
  1907. * 获取自增长序列的最新值
  1908. * @returns 最新值
  1909. */
  1910. function autoIncrement() {
  1911. return generator.next().value;
  1912. }
  1913.  
  1914. /**
  1915. * 转换接口
  1916. * @interface
  1917. */
  1918. class IConverter {
  1919. /**
  1920. * 将字符串解析为字符串列表
  1921. *
  1922. * @param str 字符串
  1923. * @return {Array.<String>} 字符串列表
  1924. * @abstract
  1925. */
  1926. from(str) {
  1927. throw new Error('子类必须重写 from 函数');
  1928. }
  1929. /**
  1930. * 将字符串列表构造为字符串
  1931. *
  1932. * @param list 字符串列表
  1933. * @return {String} 字符串
  1934. * @abstract
  1935. */
  1936. to(list) {
  1937. throw new Error('子类必须重写 to 函数');
  1938. }
  1939. }
  1940.  
  1941. /**
  1942. * 驼峰风格解析
  1943. */
  1944. class CamelOrPascalFrom extends IConverter {
  1945. /**
  1946. * 将字符串解析为字符串列表
  1947. *
  1948. * @param str 字符串
  1949. * @return {Array.<String>} 字符串列表
  1950. * @override
  1951. */
  1952. from(str) {
  1953. const result = [];
  1954. const len = str.length;
  1955. let old = 0;
  1956. for (let i = 0; i < len; i++) {
  1957. const c = str.charAt(i);
  1958. if (c >= 'A' && c <= 'Z') {
  1959. if (i !== 0) {
  1960. result.push(str.substring(old, i));
  1961. }
  1962. old = i;
  1963. }
  1964. }
  1965. if (old !== str.length) {
  1966. result.push(str.substring(old, str.length));
  1967. }
  1968. return result;
  1969. }
  1970. }
  1971.  
  1972. /**
  1973. * 小写开头的驼峰转换器
  1974. *
  1975. */
  1976. class CamelConverter extends CamelOrPascalFrom {
  1977. /**
  1978. * 将字符串列表构造为字符串
  1979. *
  1980. * @param list 字符串列表
  1981. * @return {String} 字符串
  1982. * @override
  1983. */
  1984. to(list) {
  1985. return list.reduce((res, s, i) => {
  1986. const str = toLowerCase(s);
  1987. return (res +=
  1988. (i === 0 ? toLowerCase : toUpperCase)(str.substring(0, 1)) +
  1989. str.substring(1));
  1990. }, '');
  1991. }
  1992. }
  1993.  
  1994. /**
  1995. * 大写开头的驼峰转换器
  1996. */
  1997. class PascalConverter extends CamelOrPascalFrom {
  1998. /**
  1999. * 将字符串列表构造为字符串
  2000. *
  2001. * @param list 字符串列表
  2002. * @return {String} 字符串
  2003. * @override
  2004. */
  2005. to(list) {
  2006. return list.reduce((res, s) => {
  2007. const str = toLowerCase(s);
  2008. return (res += toUpperCase(str.substring(0, 1)) + str.substring(1));
  2009. }, '');
  2010. }
  2011. }
  2012.  
  2013. /**
  2014. * 下划线风格解析
  2015. */
  2016. class SnakeOrScreamingSnakeFrom extends IConverter {
  2017. /**
  2018. * 将字符串解析为字符串列表
  2019. *
  2020. * @param str 字符串
  2021. * @return {Array.<String>} 字符串列表
  2022. * @override
  2023. */
  2024. from(str) {
  2025. return str.split('_');
  2026. }
  2027. }
  2028.  
  2029. /**
  2030. * 小写下划线的转换器
  2031. */
  2032. class SnakeConverter extends SnakeOrScreamingSnakeFrom {
  2033. /**
  2034. * 将字符串列表构造为字符串
  2035. *
  2036. * @param list 字符串列表
  2037. * @return {String} 字符串
  2038. * @override
  2039. */
  2040. to(list) {
  2041. return list.map(toLowerCase).join('_');
  2042. }
  2043. }
  2044.  
  2045. /**
  2046. * 大写下划线的转换器
  2047. */
  2048. class ScreamingSnakeConverter extends SnakeOrScreamingSnakeFrom {
  2049. /**
  2050. * 将字符串列表构造为字符串
  2051. *
  2052. * @param list 字符串列表
  2053. * @return {String} 字符串
  2054. * @override
  2055. */
  2056. to(list) {
  2057. return list.map(toUpperCase).join('_');
  2058. }
  2059. }
  2060.  
  2061. /**
  2062. * @enum {Symbol} 字符串风格常量对象
  2063. */
  2064. (function (StringStyleType) {
  2065. /**
  2066. * 小写驼峰
  2067. */
  2068. StringStyleType[StringStyleType["Camel"] = 1] = "Camel";
  2069. /**
  2070. * 大写驼峰
  2071. */
  2072. StringStyleType[StringStyleType["Pascal"] = 2] = "Pascal";
  2073. /**
  2074. * 小写下划线
  2075. */
  2076. StringStyleType[StringStyleType["Snake"] = 3] = "Snake";
  2077. /**
  2078. * 大写下划线
  2079. */
  2080. StringStyleType[StringStyleType["ScreamingSnake"] = 4] = "ScreamingSnake";
  2081. })(exports.StringStyleType || (exports.StringStyleType = {}));
  2082.  
  2083. /**
  2084. * 转换器工厂
  2085. */
  2086. class ConverterFactory {
  2087. /**
  2088. * 获取一个转换器实例
  2089. *
  2090. * @param styleType 转换风格,使用了 {@link stringStyleType} 定义的常量对象
  2091. * @return {IConverter} 转换器对象
  2092. * @throws 如果获取未定义过的转换器,则会抛出异常
  2093. */
  2094. static getInstance(styleType) {
  2095. switch (styleType) {
  2096. case exports.StringStyleType.Camel:
  2097. return new CamelConverter();
  2098. case exports.StringStyleType.Pascal:
  2099. return new PascalConverter();
  2100. case exports.StringStyleType.Snake:
  2101. return new SnakeConverter();
  2102. case exports.StringStyleType.ScreamingSnake:
  2103. return new ScreamingSnakeConverter();
  2104. default:
  2105. throw new Error('No corresponding converter found');
  2106. }
  2107. }
  2108. }
  2109.  
  2110. /**
  2111. * 字符串风格转换器
  2112. * 请不要直接使用构造函数创建,而是用 {@link StringStyleUtil.getConverter} 来获得一个转换器
  2113. * @private
  2114. */
  2115. class StringStyleConverter {
  2116. /**
  2117. * 构造一个字符串任意风格转换器
  2118. * @param from 转换字符串的风格
  2119. * @param to 需要转换的风格
  2120. * @private
  2121. */
  2122. constructor(from, to) {
  2123. /**
  2124. * @field 解析字符串风格的转换器
  2125. * @type {IConverter}
  2126. * @private
  2127. */
  2128. this.fromConverter = ConverterFactory.getInstance(from);
  2129. /**
  2130. * @field 构造字符串风格的转换器
  2131. * @type {IConverter}
  2132. * @private
  2133. */
  2134. this.toConverter = ConverterFactory.getInstance(to);
  2135. }
  2136. /**
  2137. * 转换字符串的风格
  2138. *
  2139. * @param str 要转换的字符串
  2140. * @return {String} 转换得到的字符串
  2141. */
  2142. convert(str) {
  2143. if (stringValidator.isEmpty(str)) {
  2144. return str;
  2145. }
  2146. return this.toConverter.to(this.fromConverter.from(str));
  2147. }
  2148. }
  2149.  
  2150. /**
  2151. * 包装一个函数为指定参数只执行一次的函数
  2152. * @param fn 需要包装的函数
  2153. * @param identity 参数转换的函数,参数为需要包装函数的参数
  2154. * @returns 需要被包装的函数
  2155. */
  2156. function onceOfSameParam(fn, identity = fn.toString()) {
  2157. const generateKey = (args) => `onceOfSameParam-${identity}-${JSON.stringify(args)}`;
  2158. const cacheMap = new Map();
  2159. return new Proxy(fn, {
  2160. apply(_, _this, args) {
  2161. const key = generateKey(args);
  2162. const old = cacheMap.get(key);
  2163. if (old !== undefined) {
  2164. return old;
  2165. }
  2166. const res = Reflect.apply(_, _this, args);
  2167. return compatibleAsync(res, res => {
  2168. cacheMap.set(key, res);
  2169. return res;
  2170. });
  2171. },
  2172. });
  2173. }
  2174.  
  2175. /**
  2176. * 包装获取字符串风格转换器
  2177. * 此处采用了单例模式,每种转换器只会有一个
  2178. *
  2179. * @param from 解析风格
  2180. * @param to 转换风格
  2181. * @return {StringStyleConverter} 转换器的实例
  2182. */
  2183. const _getConverter = onceOfSameParam(
  2184. /**
  2185. * @param from 解析风格
  2186. * @param to 转换风格
  2187. * @return {StringStyleConverter} 转换器的实例
  2188. */
  2189. (from, to) => new StringStyleConverter(from, to));
  2190. /**
  2191. * 字符串风格转换工具类
  2192. */
  2193. class StringStyleUtil {
  2194. /**
  2195. * 获取一个转换器的实例
  2196. * 该函数获取的转换器可以任意复用,请优先使用函数
  2197. * @param from 解析风格
  2198. * @param to 转换风格
  2199. * @return {StringStyleConverter} 转换器的实例
  2200. */
  2201. static getConverter(from, to) {
  2202. return _getConverter(from, to);
  2203. }
  2204. /**
  2205. * 直接转换字符串的风格
  2206. * 请优先使用可以复用的 {@link StringStyleUtil.getConverter} 函数
  2207. * @param from 解析风格
  2208. * @param to 转换风格
  2209. * @param str 要转换的字符串
  2210. * @return {String} 转换得到的字符串
  2211. */
  2212. static convert(from, to, str) {
  2213. return StringStyleUtil.getConverter(from, to).convert(str);
  2214. }
  2215. }
  2216.  
  2217. /**
  2218. * 获取对象中所有的属性,包括 ES6 新增的 Symbol 类型的属性
  2219. * @param obj 任何对象
  2220. * @returns 属性数组
  2221. * @deprecated 已废弃,请使用 ES6 {@see Reflect.ownKeys} 代替
  2222. * 具体参考 {@url(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys)}
  2223. */
  2224. function getObjectKeys(obj) {
  2225. if (isNullOrUndefined(obj)) {
  2226. return [];
  2227. }
  2228. return Reflect.ownKeys(obj);
  2229. }
  2230.  
  2231. /**
  2232. * 获取对象中所有的属性值,包括 ES6 新增的 Symbol 类型的属性
  2233. * @param obj 任何对象
  2234. * @returns 属性值数组
  2235. */
  2236. function getObjectValues(obj) {
  2237. return getObjectKeys(obj).map(k => Reflect.get(obj, k));
  2238. }
  2239.  
  2240. /**
  2241. * 递归使对象不可变
  2242. * @param obj 任何非空对象
  2243. * @returns 新的不可变对象
  2244. */
  2245. function deepFreeze(obj) {
  2246. // 数组和对象分别处理
  2247. if (obj instanceof Array) {
  2248. obj.forEach(v => {
  2249. if (typeof v === 'object') {
  2250. deepFreeze(v);
  2251. }
  2252. });
  2253. }
  2254. else if (obj instanceof Object) {
  2255. getObjectValues(obj).forEach(v => {
  2256. if (typeof v === 'object') {
  2257. deepFreeze(v);
  2258. }
  2259. });
  2260. }
  2261. return Object.freeze(obj);
  2262. }
  2263.  
  2264. /**
  2265. * 包装对象,使其成为可以任意深度调用而不会出现 undefined 调用的问题
  2266. * 注意: 该函数不能进行递归调用({@link JSON.stringfy}),一定会造成堆栈溢出的问题(RangeError: Maximum call stack size exceeded)
  2267. * @param obj 任意一个 Object 对象
  2268. * @param [defaultValue] 默认值,默认为 {}
  2269. * @returns 包装后的对象
  2270. */
  2271. function deepProxy(obj, defaultValue = {}) {
  2272. const handler = {
  2273. get(target, k) {
  2274. let v = Reflect.get(target, k);
  2275. if (isNullOrUndefined(v)) {
  2276. v = defaultValue;
  2277. }
  2278. if (typeof v !== 'object') {
  2279. return v;
  2280. }
  2281. return new Proxy(v, handler);
  2282. },
  2283. };
  2284. return new Proxy(obj, handler);
  2285. }
  2286.  
  2287. /**
  2288. * 将函数包装为柯里化函数
  2289. * 注: 该函数模仿了 Lodash 的 curry 函数
  2290. * @param fn 需要包装的函数
  2291. * @param {...any} args 应用的部分参数
  2292. * @returns 包装后的函数
  2293. */
  2294. function curry(fn, ...args) {
  2295. const realArgs = args.filter(arg => arg !== curry._);
  2296. // 如果函数参数足够则调用传入的函数
  2297. if (realArgs.length >= fn.length) {
  2298. return fn(...realArgs);
  2299. }
  2300. /**
  2301. * 最终返回的函数
  2302. * @param otherArgs 接受任意参数
  2303. * @returns 返回一个函数,或者函数调用完成返回结果
  2304. */
  2305. function innerFn(...otherArgs) {
  2306. // 记录需要移除补到前面的参数
  2307. const removeIndexSet = new Set();
  2308. let i = 0;
  2309. const newArgs = args.map(arg => {
  2310. if (arg !== curry._ ||
  2311. otherArgs[i] === undefined ||
  2312. otherArgs[i] === curry._) {
  2313. return arg;
  2314. }
  2315. removeIndexSet.add(i);
  2316. // 每次补偿前面的 curry._ 参数计数器 +1
  2317. return otherArgs[i++];
  2318. });
  2319. const newOtherArgs = otherArgs.filter((_v, i) => !removeIndexSet.has(i));
  2320. return curry(fn, ...newArgs, ...newOtherArgs);
  2321. }
  2322. // 定义柯里化函数的剩余参数长度,便于在其他地方进行部分参数应用
  2323. // 注: 不使用 length 属性的原因是 length 属性
  2324. innerFn._length = fn.length - args.filter(arg => arg !== curry._).length;
  2325. // 自定义 toString 函数便于调试
  2326. innerFn.toString = () => `name: ${fn.name}, args: [${args.map(o => o.toString()).join(', ')}]`;
  2327. innerFn._curry = true;
  2328. return innerFn;
  2329. }
  2330. /**
  2331. * 柯里化的占位符,需要应用后面的参数时使用
  2332. * 例如 {@link curry(fn)(curry._, 1)} 意味着函数 fn 的第二个参数将被确定为 1
  2333. */
  2334. curry._ = Symbol('_');
  2335.  
  2336. /**
  2337. * 快速根据指定函数对数组进行排序
  2338. * 注: 使用递归实现,对于超大数组(其实前端的数组不可能特别大吧?#笑)可能造成堆栈溢出
  2339. * @param arr 需要排序的数组
  2340. * @param kFn 对数组中每个元素都产生可比较的值的函数,默认返回自身进行比较
  2341. * @returns 排序后的新数组
  2342. */
  2343. function sortBy(arr, kFn = returnItself) {
  2344. // TODO 此处为了让 typedoc 能生成文档而不得不加上类型
  2345. const newArr = arr.map((v, i) => [v, i]);
  2346. function _sort(arr, fn) {
  2347. // 边界条件,如果传入数组的值
  2348. if (arr.length <= 1) {
  2349. return arr;
  2350. }
  2351. // 根据中间值对数组分治为两个数组
  2352. const medianIndex = Math.floor(arr.length / 2);
  2353. const medianValue = arr[medianIndex];
  2354. const left = [];
  2355. const right = [];
  2356. for (let i = 0, len = arr.length; i < len; i++) {
  2357. if (i === medianIndex) {
  2358. continue;
  2359. }
  2360. const v = arr[i];
  2361. if (fn(v, medianValue) <= 0) {
  2362. left.push(v);
  2363. }
  2364. else {
  2365. right.push(v);
  2366. }
  2367. }
  2368. return _sort(left, fn)
  2369. .concat([medianValue])
  2370. .concat(_sort(right, fn));
  2371. }
  2372. return _sort(newArr, ([t1, i1], [t2, i2]) => {
  2373. const k1 = kFn(t1, i1, arr);
  2374. const k2 = kFn(t2, i2, arr);
  2375. if (k1 === k2) {
  2376. return 0;
  2377. }
  2378. else if (k1 < k2) {
  2379. return -1;
  2380. }
  2381. else {
  2382. return 1;
  2383. }
  2384. }).map(([_v, i]) => arr[i]);
  2385. }
  2386.  
  2387. /**
  2388. * 日期格式化器
  2389. * 包含格式化为字符串和解析字符串为日期的函数
  2390. */
  2391. class DateFormatter {
  2392. /**
  2393. * 构造函数
  2394. * @param fmt 日期时间格式
  2395. */
  2396. constructor(fmt) {
  2397. this.fmt = fmt;
  2398. }
  2399. /**
  2400. * 格式化
  2401. * @param date 需要格式化的日期
  2402. * @returns 格式化的字符串
  2403. */
  2404. format(date) {
  2405. if (isNullOrUndefined(date)) {
  2406. return '';
  2407. }
  2408. return dateFormat(date, this.fmt);
  2409. }
  2410. /**
  2411. * 解析字符串为日期对象
  2412. * @param str 字符串
  2413. * @returns 解析得到的日期
  2414. */
  2415. parse(str) {
  2416. if (stringValidator.isEmpty(str)) {
  2417. return null;
  2418. }
  2419. return dateParse(str, this.fmt);
  2420. }
  2421. /**
  2422. * 将日期时间字符串转换为前端指定格式的字符串
  2423. * 主要适用场景是前端接收到后端的日期时间一般是一个字符串,然而需要自定义格式的时候还必须先创建 {@link Date} 对象才能格式化,略微繁琐,故使用该函数
  2424. * @param str 字符串
  2425. * @param parseFmt 解析的日期时间格式。默认直接使用 {@link new Date()} 创建
  2426. * @returns 转换后得到的字符串
  2427. */
  2428. strFormat(str, parseFmt) {
  2429. if (stringValidator.isEmpty(str)) {
  2430. return '';
  2431. }
  2432. const date = parseFmt ? dateParse(str, parseFmt) : new Date(str);
  2433. return dateFormat(date, this.fmt);
  2434. }
  2435. }
  2436. /**
  2437. * 日期格式化器
  2438. */
  2439. DateFormatter.dateFormatter = new DateFormatter('yyyy-MM-dd');
  2440. /**
  2441. * 时间格式化器
  2442. */
  2443. DateFormatter.timeFormatter = new DateFormatter('hh:mm:ss');
  2444. /**
  2445. * 日期时间格式化器
  2446. */
  2447. DateFormatter.dateTimeFormatter = new DateFormatter('yyyy-MM-dd hh:mm:ss');
  2448.  
  2449. /**
  2450. * 查询符合条件的元素的下标
  2451. * @param arr 查询的数组
  2452. * @param fn 谓词
  2453. * @param num 查询的第几个符合条件的元素,默认为 1,和默认的 findIndex 行为保持一致
  2454. * @returns 符合条件的元素的下标,如果没有则返回 -1
  2455. */
  2456. function findIndex(arr, fn, num = 1) {
  2457. let k = 0;
  2458. for (let i = 0, len = arr.length; i < len; i++) {
  2459. if (fn.call(arr, arr[i], i, arr) && ++k >= num) {
  2460. return i;
  2461. }
  2462. }
  2463. return -1;
  2464. }
  2465.  
  2466. /**
  2467. * 连接两个函数并自动柯里化
  2468. * 注: 该函数依赖于 length,所以不支持默认参数以及不定参数
  2469. * @param fn1 第一个函数
  2470. * @param fn2 第二个函数
  2471. * @returns 连接后的函数
  2472. */
  2473. const _compose = (fn1, fn2) => {
  2474. return function (...args) {
  2475. const i = findIndex(args, v => v !== curry._, fn1._length || fn1.length);
  2476. const res = curry(fn1, ...args);
  2477. // 如果这个函数的参数不足,则返回它
  2478. if (i === -1) {
  2479. return _compose(res, fn2);
  2480. }
  2481. // 否则将结果以及多余的参数应用到下一个函数上
  2482. return curry(fn2, res, ...args.slice(i + 1));
  2483. };
  2484. };
  2485. /**
  2486. * 将多个函数组合起来
  2487. * 前面函数的返回值将变成后面函数的第一个参数,如果到了最后一个函数执行完成,则直接返回
  2488. * 注: 该函数是自动柯里化,将对所有传入的函数进行柯里化处理
  2489. * 注: 该函数支持一次调用传入全部函数的参数
  2490. * @param fns 多个需要连接函数
  2491. * @returns 连接后的柯里化函数
  2492. * TODO 这里需要进行类型优化
  2493. */
  2494. function compose(...fns) {
  2495. return fns.reduceRight((fn1, fn2) => _compose(fn2, fn1));
  2496. }
  2497.  
  2498. /**
  2499. * 递归排除对象中的指定字段
  2500. * @param obj 需要排除的对象
  2501. * @param {...obj} fields 需要排除的字段
  2502. */
  2503. function excludeFieldsDeep(obj, ...fields) {
  2504. return Reflect.ownKeys(obj).reduce((res, k) => {
  2505. const v = Reflect.get(res, k);
  2506. if (v instanceof Object) {
  2507. Reflect.set(obj, k, excludeFieldsDeep(v, ...fields));
  2508. }
  2509. return res;
  2510. }, obj instanceof Array ? obj : excludeFields(obj, ...fields));
  2511. }
  2512.  
  2513. /**
  2514. * 缓存的值
  2515. */
  2516. class CacheVal {
  2517. /**
  2518. * 构造函数
  2519. * @param options 缓存值对象
  2520. * @param options.key 缓存的键原始值
  2521. * @param options.val 缓存的值
  2522. * @param options.cacheOption 缓存的选项
  2523. */
  2524. constructor(options = {}) {
  2525. Object.assign(this, options);
  2526. }
  2527. }
  2528.  
  2529. /**
  2530. * 无限的超时时间
  2531. * TODO 此处暂时使用字符串作为一种折衷方法,因为 Symbol 无法被序列化为 JSON,反向序列化也是不可能的
  2532. */
  2533. const TimeoutInfinite = 'TimeoutInfinite';
  2534.  
  2535. /**
  2536. * 内部使用的函数
  2537. * 注: 如果谓词中包含任意一个异步(返回 Promise)函数,则整个返回结果将变成异步的,否则默认为同步操作.
  2538. * @param fns 谓词数组
  2539. * @param args 谓词应用的参数列表
  2540. * @param condition 临界条件
  2541. * @returns 返回结果
  2542. */
  2543. function _inner(fns, args, condition) {
  2544. const fn = fns[0];
  2545. const res = fn(...args);
  2546. function _call(res) {
  2547. if (condition(res)) {
  2548. return res;
  2549. }
  2550. const others = fns.slice(1);
  2551. if (others.length === 0) {
  2552. return res;
  2553. }
  2554. return _inner(others, args, condition);
  2555. }
  2556. return compatibleAsync(res, _call);
  2557. }
  2558. /**
  2559. * 连接谓词函数
  2560. */
  2561. class CombinedPredicate {
  2562. /**
  2563. * 使用 && 进行连接
  2564. * @param fns 连接任意多个谓词
  2565. * @returns 连接后的新谓词
  2566. */
  2567. static and(...fns) {
  2568. return function (...args) {
  2569. return _inner(fns, args, res => !res);
  2570. };
  2571. }
  2572. /**
  2573. * 使用 || 进行连接
  2574. * @param fns 连接任意多个谓词
  2575. * @returns 连接后的新谓词
  2576. */
  2577. static or(...fns) {
  2578. return function (...args) {
  2579. return _inner(fns, args, res => res);
  2580. };
  2581. }
  2582. /**
  2583. * 对谓词进行取反
  2584. * @param fn 谓词
  2585. * @returns 取反后的谓词
  2586. */
  2587. static not(fn) {
  2588. return new Proxy(fn, {
  2589. apply(_, _this, args) {
  2590. return compatibleAsync(Reflect.apply(_, this, args), res => !res);
  2591. },
  2592. });
  2593. }
  2594. }
  2595. const and = CombinedPredicate.and;
  2596. const or = CombinedPredicate.or;
  2597. const not = CombinedPredicate.not;
  2598.  
  2599. /**
  2600. * 使用 LocalStorage 实现的缓存
  2601. * 1. get: 根据 key 获取
  2602. * 2. set: 根据 key value 设置,会覆盖
  2603. * 3. touch: 获取并刷新超时时间
  2604. * 4. add: 根据 key value 添加,不会覆盖
  2605. * 5. del: 根据 key 删除
  2606. * 6. clearExpired: 清除所有过期的缓存
  2607. */
  2608. class LocalStorageCache {
  2609. /**
  2610. * 构造函数
  2611. * @param cacheOption 全局缓存选项
  2612. */
  2613. constructor({ timeout = TimeoutInfinite, serialize = JSON.stringify, deserialize = JSON.parse, } = {}) {
  2614. // 这里必须强制转换,因为 timeStart 在全局选项中是不可能存在的
  2615. this.cacheOption = {
  2616. timeout,
  2617. serialize,
  2618. deserialize,
  2619. };
  2620. /**
  2621. * 缓存对象,默认使用 localStorage
  2622. */
  2623. this.localStorage = window.localStorage;
  2624. // 创建后将异步清空所有过期的缓存
  2625. this.clearExpired();
  2626. }
  2627. /**
  2628. * 清空所有过期的 key
  2629. * 注: 该函数是异步执行的
  2630. */
  2631. clearExpired() {
  2632. return __awaiter(this, void 0, void 0, function* () {
  2633. const local = this.localStorage;
  2634. const getKeys = () => {
  2635. const len = local.length;
  2636. const res = [];
  2637. for (let i = 0; i < len; i++) {
  2638. res.push(local.key(i));
  2639. }
  2640. return res;
  2641. };
  2642. getKeys()
  2643. .filter(not(isNullOrUndefined))
  2644. .map(key => safeExec(JSON.parse, null, local.getItem(key)))
  2645. .filter(cacheVal => !isNullOrUndefined(cacheVal) &&
  2646. isNullOrUndefined(cacheVal.cacheOption))
  2647. .filter(({ cacheOption }) => {
  2648. const { timeStart, timeout } = cacheOption;
  2649. return timeout !== TimeoutInfinite && Date.now() - timeStart > timeout;
  2650. })
  2651. .forEach(({ key }) => local.removeItem(key));
  2652. });
  2653. }
  2654. /**
  2655. * 根据 key + value 添加
  2656. * 如果不存在则添加,否则忽略
  2657. * @param key 缓存的 key
  2658. * @param val 缓存的 value
  2659. * @param cacheOption 缓存的选项,默认为无限时间
  2660. * @override
  2661. */
  2662. add(key, val, timeout) {
  2663. const result = this.get(key);
  2664. if (result !== null) {
  2665. return;
  2666. }
  2667. this.set(key, val, timeout);
  2668. }
  2669. /**
  2670. * 根据指定的 key 删除
  2671. * 如果存在则删除,否则忽略
  2672. * @param key 删除的 key
  2673. * @override
  2674. */
  2675. del(key) {
  2676. this.localStorage.removeItem(key);
  2677. }
  2678. /**
  2679. * 根据指定的 key 修改
  2680. * 不管是否存在都会设置
  2681. * @param key 修改的 key
  2682. * @param val 修改的 value
  2683. * @param timeout 修改的选项
  2684. * @override
  2685. */
  2686. set(key, val, timeout) {
  2687. this.localStorage.setItem(key, JSON.stringify(new CacheVal({
  2688. key,
  2689. val: this.cacheOption.serialize(val),
  2690. // 我们不需要缓存序列化/反序列化策略(实际上也无法缓存)
  2691. cacheOption: {
  2692. timeStart: Date.now(),
  2693. timeout: timeout || this.cacheOption.timeout,
  2694. },
  2695. })));
  2696. }
  2697. /**
  2698. * 根据 key 获取
  2699. * 如果存在则获取,否则忽略
  2700. * @param key 指定的 key
  2701. * @param timeout 获取的选项
  2702. * @returns 获取到的缓存值
  2703. * @override
  2704. */
  2705. get(key) {
  2706. const str = this.localStorage.getItem(key);
  2707. const cacheVal = safeExec(JSON.parse, null, str);
  2708. if (isNullOrUndefined(cacheVal) ||
  2709. isNullOrUndefined(cacheVal.cacheOption)) {
  2710. return null;
  2711. }
  2712. const [timeStart, timeout, deserialize] = [
  2713. cacheVal.cacheOption.timeStart,
  2714. cacheVal.cacheOption.timeout,
  2715. this.cacheOption.deserialize,
  2716. ];
  2717. // 如果超时则删除并返回 null
  2718. if (timeout !== TimeoutInfinite && Date.now() - timeStart > timeout) {
  2719. this.del(key);
  2720. return null;
  2721. }
  2722. try {
  2723. return deserialize(cacheVal.val);
  2724. }
  2725. catch (e) {
  2726. this.del(key);
  2727. return null;
  2728. }
  2729. }
  2730. /**
  2731. * 根据 key 获取并刷新超时时间
  2732. * @param key 指定的 key
  2733. * @param cacheOption 获取的选项
  2734. * @returns 获取到的缓存值
  2735. * @override
  2736. */
  2737. touch(key) {
  2738. const str = this.localStorage.getItem(key);
  2739. /**
  2740. * @type {CacheVal}
  2741. */
  2742. const cacheVal = safeExec(JSON.parse, null, str);
  2743. if (isNullOrUndefined(cacheVal) ||
  2744. isNullOrUndefined(cacheVal.cacheOption)) {
  2745. return null;
  2746. }
  2747. const [timeStart, timeout, deserialize] = [
  2748. cacheVal.cacheOption.timeStart,
  2749. cacheVal.cacheOption.timeout,
  2750. this.cacheOption.deserialize,
  2751. ];
  2752. // 如果超时则删除并返回 null
  2753. if (timeout !== TimeoutInfinite && Date.now() - timeStart > timeout) {
  2754. this.del(key);
  2755. return null;
  2756. }
  2757. try {
  2758. const result = deserialize(cacheVal.val);
  2759. this.set(key, result, { timeStart: Date.now(), timeout });
  2760. return result;
  2761. }
  2762. catch (e) {
  2763. this.del(key);
  2764. return null;
  2765. }
  2766. }
  2767. }
  2768.  
  2769. /**
  2770. * 默认使用的 {@link ICache} 接口的缓存实现
  2771. */
  2772. const cache = new LocalStorageCache();
  2773. /**
  2774. * 缓存工具类
  2775. * 主要实现缓存高阶函数的封装
  2776. */
  2777. class CacheUtil {
  2778. /**
  2779. * 将指定函数包装为只调用一次为缓存函数
  2780. * @param fn 需要包装的函数
  2781. * @param options 缓存选项对象。可选项
  2782. * @param options.identity 缓存标识。默认为函数 {@link toString},但有时候不太可行(继承自基类的函数)
  2783. * @param options.timeout 缓存时间。默认为无限
  2784. * @returns 包装后的函数
  2785. */
  2786. static once(fn, { identity = fn.toString(), timeout } = {}) {
  2787. const generateKey = () => `CacheUtil.onceOfSameParam-${identity}`;
  2788. const innerFn = new Proxy(fn, {
  2789. apply(_, _this, args) {
  2790. const key = generateKey();
  2791. const val = cache.get(key);
  2792. if (val !== null) {
  2793. return val;
  2794. }
  2795. return compatibleAsync(Reflect.apply(_, _this, args), res => {
  2796. cache.set(key, res, timeout);
  2797. return res;
  2798. });
  2799. },
  2800. });
  2801. return Object.assign(innerFn, {
  2802. origin: fn,
  2803. clear() {
  2804. cache.del(generateKey());
  2805. },
  2806. });
  2807. }
  2808. /**
  2809. * 包裹函数为缓存函数
  2810. * @param fn 一个接受一些参数并返回结果的函数
  2811. * @param options 缓存选项对象。可选项
  2812. * @param options.identity 缓存标识。默认为函数 {@link toString},但有时候不太可行(继承自基类的函数)
  2813. * @param options.timeout 缓存时间。默认为无限
  2814. * @returns 带有缓存功能的函数
  2815. */
  2816. static onceOfSameParam(fn, { identity = fn.toString(), timeout } = {}) {
  2817. const generateKey = (args) => `CacheUtil.onceOfSameParam-${identity}-${JSON.stringify(args)}`;
  2818. const innerFn = new Proxy(fn, {
  2819. apply(_, _this, args) {
  2820. const key = generateKey(args);
  2821. const val = cache.get(key);
  2822. if (val !== null) {
  2823. return val;
  2824. }
  2825. return compatibleAsync(Reflect.apply(_, _this, args), res => {
  2826. cache.set(key, res, timeout);
  2827. return res;
  2828. });
  2829. },
  2830. });
  2831. return Object.assign(innerFn, {
  2832. origin: fn,
  2833. clear(...args) {
  2834. cache.del(generateKey(args));
  2835. },
  2836. });
  2837. }
  2838. }
  2839. /**
  2840. * 导出一个默认的缓存工具对象
  2841. * @deprecated 已废弃,请直接使用类的静态函数
  2842. */
  2843. const cacheUtil = CacheUtil;
  2844.  
  2845. /**
  2846. * 一个空的函数
  2847. * @param args 接受任何参数
  2848. */
  2849. function emptyFunc(...args) { }
  2850.  
  2851. /**
  2852. * 禁止他人调试网站相关方法的集合对象
  2853. */
  2854. class AntiDebug {
  2855. /**
  2856. * 不停循环 debugger 防止有人调试代码
  2857. * @returns 取消函数
  2858. */
  2859. static cyclingDebugger() {
  2860. const res = setInterval(() => {
  2861. debugger;
  2862. }, 100);
  2863. return () => clearInterval(res);
  2864. }
  2865. /**
  2866. * 检查是否正在 debugger 并调用回调函数
  2867. * @param fn 回调函数,默认为重载页面
  2868. * @returns 取消函数
  2869. */
  2870. static checkDebug(fn = () => window.location.reload()) {
  2871. const res = setInterval(() => {
  2872. const diff = timing(() => {
  2873. for (let i = 0; i < 1000; i++) {
  2874. console.log(i);
  2875. console.clear();
  2876. }
  2877. });
  2878. if (diff > 500) {
  2879. console.log(diff);
  2880. fn();
  2881. }
  2882. }, 1000);
  2883. return () => clearInterval(res);
  2884. }
  2885. /**
  2886. * 禁用控制台调试输出
  2887. * @returns 取消函数
  2888. */
  2889. static disableConsoleOutput() {
  2890. if (!window.console) {
  2891. return emptyFunc;
  2892. }
  2893. const map = arrayToMap(Object.keys(console), returnItself, k => {
  2894. // @ts-ignore
  2895. const temp = console[k];
  2896. // @ts-ignore
  2897. console[k] = emptyFunc;
  2898. return temp;
  2899. });
  2900. return () => {
  2901. for (const [k, v] of map) {
  2902. // @ts-ignore
  2903. console[k] = v;
  2904. }
  2905. };
  2906. }
  2907. }
  2908. /**
  2909. * 禁止他人调试网站相关方法的集合对象
  2910. * @deprecated 已废弃,请直接使用类的静态函数
  2911. */
  2912. const antiDebug = AntiDebug;
  2913.  
  2914. /**
  2915. * 判断一个字符串是否为空白的字符串
  2916. * @param str 字符串
  2917. * @returns 是否为空字符串
  2918. * @deprecated 已废弃,请使用 {@link stringValidator#isBlank}
  2919. */
  2920. function isBlank(str) {
  2921. return stringValidator.isBlank(str);
  2922. }
  2923.  
  2924. /**
  2925. * 判断一个字符串是否为空字符串
  2926. * @param str 字符串
  2927. * @returns 是否为空字符串
  2928. * @deprecated 已废弃,请使用 {@link stringValidator#isEmpty}
  2929. */
  2930. function isEmpty(str) {
  2931. return stringValidator.isEmpty(str);
  2932. }
  2933.  
  2934. /**
  2935. * 加载一个远程脚本文件
  2936. * @param src 远程脚本路径
  2937. * @returns 等待异步加载脚本完成
  2938. */
  2939. function loadScript(src) {
  2940. return new Promise((resolve, reject) => {
  2941. const script = document.createElement('script');
  2942. script.src = src;
  2943. script.addEventListener('load', () => resolve());
  2944. script.addEventListener('error', reject);
  2945. document.body.appendChild(script);
  2946. });
  2947. }
  2948.  
  2949. /**
  2950. * 将一个谓词函数取反
  2951. * 如果是同步函数,则返回的函数也是同步的,否则返回的是取反后的异步函数
  2952. * @param fn 要取反的函数
  2953. * @returns 取反得到的函数
  2954. * @deprecated 已废弃,请使用 {@link CombinedPredicate.not} 进行为此取反
  2955. */
  2956. function deny(fn) {
  2957. return CombinedPredicate.not(fn);
  2958. }
  2959.  
  2960. /**
  2961. * 数组校验器
  2962. */
  2963. class ArrayValidator {
  2964. /**
  2965. * 是否为空数组
  2966. * @param array 空数组
  2967. * @returns 是否为空数组
  2968. */
  2969. static isEmpty(array) {
  2970. return (isNullOrUndefined(array) ||
  2971. !(array instanceof Array) ||
  2972. array.length === 0);
  2973. }
  2974. }
  2975. /**
  2976. * 导出一个默认的数组校验对象
  2977. * @deprecated 已废弃,请直接使用类的静态函数
  2978. */
  2979. const arrayValidator = ArrayValidator;
  2980.  
  2981. /**
  2982. * 路径工具
  2983. */
  2984. class PathUtil {
  2985. /**
  2986. * 拼接多个路径
  2987. *
  2988. * @param paths 路径数组
  2989. * @return {String} 拼接完成的路径
  2990. */
  2991. static join(...paths) {
  2992. return paths.reduce(PathUtil._join);
  2993. }
  2994. /**
  2995. * 拼接两个路径
  2996. *
  2997. * @param pathStart 开始路径
  2998. * @param pathEnd 结束路径
  2999. * @return {String} 拼接完成的两个路径
  3000. */
  3001. static _join(pathStart, pathEnd) {
  3002. if (pathStart.endsWith(PathUtil.Separator)) {
  3003. return (pathStart + pathEnd).replace(PathUtil.Separator + PathUtil.Separator, PathUtil.Separator);
  3004. }
  3005. if (pathEnd.startsWith(PathUtil.Separator)) {
  3006. return pathStart + pathEnd;
  3007. }
  3008. return pathStart + PathUtil.Separator + pathEnd;
  3009. }
  3010. }
  3011. /**
  3012. * 路径分隔符
  3013. */
  3014. PathUtil.Separator = '/';
  3015. /**
  3016. * 导出一个路径工具类
  3017. * @deprecated 已废弃,请直接使用类的静态函数
  3018. */
  3019. const pathUtil = PathUtil;
  3020.  
  3021. /**
  3022. * 自定义的日志类
  3023. * 主要便于在开发环境下正常显示调试信息,在生产环境则默认关闭它
  3024. */
  3025. class Logger {
  3026. /**
  3027. * 构造函数
  3028. * @param options 可选项
  3029. * @param options.enable 是否开启日志
  3030. */
  3031. constructor({ enable = true } = {}) {
  3032. this.debug = console.debug;
  3033. this.error = console.error;
  3034. this.info = console.info;
  3035. this.log = console.log;
  3036. this.warn = console.warn;
  3037. this.dir = console.dir;
  3038. this.dirxml = console.dirxml;
  3039. this.table = console.table;
  3040. this.trace = console.trace;
  3041. this.group = console.group;
  3042. this.groupCollapsed = console.groupCollapsed;
  3043. this.groupEnd = console.groupEnd;
  3044. this.clear = console.clear;
  3045. this.count = console.count;
  3046. this.assert = console.assert;
  3047. this.profile = console.profile;
  3048. this.profileEnd = console.profileEnd;
  3049. this.time = console.time;
  3050. this.timeEnd = console.timeEnd;
  3051. this.timeStamp = console.timeStamp;
  3052. this._enable = enable;
  3053. }
  3054. /**
  3055. * 设置 enable 的 setter 属性,在改变时合并对应的子类对象实现
  3056. * @param enable 是否开启
  3057. */
  3058. set enable(enable) {
  3059. /**
  3060. * @field 是否开启全局控制台,该属性只写
  3061. */
  3062. this._enable = enable;
  3063. Object.keys(console).forEach(
  3064. // @ts-ignore
  3065. k => (this[k] = enable ? console[k] : emptyFunc));
  3066. }
  3067. }
  3068. /**
  3069. * 导出一个全局可用的 Logger 对象
  3070. * 使用 enable 属性控制是否开启日志输出,默认为 true
  3071. */
  3072. const logger = new Logger();
  3073.  
  3074. /**
  3075. * 将 Object 对象 转换为 Map
  3076. * @param obj Object 对象
  3077. * @returns 转换得到的 Map 键值表
  3078. */
  3079. function objectToMap(obj) {
  3080. return Reflect.ownKeys(obj).reduce((map, k) => map.set(k, Reflect.get(obj, k)), new Map());
  3081. }
  3082.  
  3083. /**
  3084. * 将列表转换为树节点
  3085. * 注: 该函数默认树的根节点只有一个,如果有多个,则返回一个数组
  3086. * @param list 树节点列表
  3087. * @param options 其他选项
  3088. * @returns 树节点,或是树节点列表
  3089. */
  3090. function listToTree(list, { bridge = returnItself, isRoot = node => !node.parentId, } = {}) {
  3091. const arr = [];
  3092. const res = list.reduce((root, _sub) => {
  3093. const sub = bridge(_sub);
  3094. if (isRoot(sub)) {
  3095. root.push(sub);
  3096. return root;
  3097. }
  3098. for (const _parent of list) {
  3099. const parent = bridge(_parent);
  3100. if (sub.parentId === parent.id) {
  3101. parent.child = parent.child || [];
  3102. parent.child.push(sub);
  3103. return root;
  3104. }
  3105. }
  3106. return root;
  3107. }, arr);
  3108. // 根据顶级节点的数量决定如何返回
  3109. const len = res.length;
  3110. if (len === 0)
  3111. return {};
  3112. if (len === 1)
  3113. return res[0];
  3114. return res;
  3115. }
  3116.  
  3117. /**
  3118. * 桥接对象不存在的字段
  3119. * @param map 代理的字段映射 Map
  3120. * @returns 转换一个对象为代理对象
  3121. */
  3122. function bridge(map) {
  3123. /**
  3124. * 为对象添加代理的函数
  3125. * @param obj 任何对象
  3126. * @returns 代理后的对象
  3127. */
  3128. return function (obj) {
  3129. return new Proxy(obj, {
  3130. get(_, k) {
  3131. if (Reflect.has(map, k)) {
  3132. return Reflect.get(_, Reflect.get(map, k));
  3133. }
  3134. return Reflect.get(_, k);
  3135. },
  3136. set(_, k, v) {
  3137. if (Reflect.has(map, k)) {
  3138. Reflect.set(_, Reflect.get(map, k), v);
  3139. return true;
  3140. }
  3141. Reflect.set(_, k, v);
  3142. return true;
  3143. },
  3144. });
  3145. };
  3146. }
  3147.  
  3148. /**
  3149. * 遍历并映射一棵树的每个节点
  3150. * @param root 树节点
  3151. * @param options 其他选项
  3152. * @returns 递归遍历后的树节点
  3153. */
  3154. function treeMapping(root, { before = returnItself, after = returnItself, paramFn = (node, ...args) => [], } = {}) {
  3155. /**
  3156. * 遍历一颗完整的树
  3157. * @param node 要遍历的树节点
  3158. * @param args 每次递归遍历时的参数
  3159. */
  3160. function _treeMapping(node, ...args) {
  3161. // 之前的操作
  3162. const _node = before(node, ...args);
  3163. const _child = _node.child;
  3164. if (!arrayValidator.isEmpty(_child)) {
  3165. _node.child = _child.map(v =>
  3166. // 产生一个参数
  3167. _treeMapping(v, ...paramFn(_node, ...args)));
  3168. }
  3169. // 之后的操作
  3170. return after(_node, ...args);
  3171. }
  3172. return _treeMapping(root);
  3173. }
  3174.  
  3175. /**
  3176. * 将树节点转为树节点列表
  3177. * 这里使用了循环进行遍历,而非传统的递归方式
  3178. * @param root 树节点
  3179. * @param options 其他选项
  3180. * @returns 树节点列表
  3181. */
  3182. function treeToList(root, { calcPath = false, bridge = returnItself, } = {}) {
  3183. const res = [];
  3184. const temp = bridge(root);
  3185. if (calcPath) {
  3186. temp.path = temp.id + '';
  3187. }
  3188. // 利用队列缓存所有未处理的节点
  3189. const queue = [temp];
  3190. // 使用 Set 防止可能的重复引用
  3191. const filterSet = new Set();
  3192. // 使用 lastIdMap 避免重复添加
  3193. const lastIdMap = new Map();
  3194. for (let value; queue.length > 0;) {
  3195. const first = queue.shift();
  3196. value = bridge(first);
  3197. // 判断重复
  3198. if (value === undefined || filterSet.has(first)) {
  3199. continue;
  3200. }
  3201. filterSet.add(first);
  3202. res.push(value);
  3203. const child = value.child;
  3204. if (ArrayValidator.isEmpty(child)) {
  3205. continue;
  3206. }
  3207. const childNonIllegal = child.filter(v => !isNullOrUndefined(v) || filterSet.has(v));
  3208. // TODO 这里和上面的代码明显重复,待优化。。。
  3209. queue.push(...(calcPath
  3210. ? childNonIllegal.map(v => {
  3211. const _v = bridge(v);
  3212. // 如果最后一个的 id 等于自身,说明已经被添加过了
  3213. if (lastIdMap.get(_v.id) === _v.id) {
  3214. return _v;
  3215. }
  3216. // 使用父节点绝对路径 + 当前 id
  3217. _v.path = value.path + ',' + _v.id;
  3218. lastIdMap.set(_v.id, _v.id);
  3219. return _v;
  3220. })
  3221. : childNonIllegal));
  3222. }
  3223. return res;
  3224. }
  3225.  
  3226. /**
  3227. * 树节点桥接工具类
  3228. * 主要实现了桥接 {@field bridge} {@field bridgeTree} 和 {@field bridgeList} 三个函数,事实上桥接之后再转换相当于做了两遍,但就目前而言暂且只能如此了
  3229. */
  3230. class NodeBridgeUtil {
  3231. /**
  3232. * 桥接对象为标准的树结构
  3233. * @param nodeBridge 桥接对象
  3234. * @returns 代理函数
  3235. */
  3236. static bridge({ id = 'id', parentId = 'parentId', child = 'child', path = 'path', } = {}) {
  3237. return bridge({
  3238. id,
  3239. parentId,
  3240. child,
  3241. path,
  3242. });
  3243. }
  3244. /**
  3245. * 桥接一棵完整的树
  3246. * @param tree 树节点
  3247. * @param nodeBridge 桥接对象
  3248. * @returns 代理后的树对象
  3249. */
  3250. static bridgeTree(tree, nodeBridge) {
  3251. return treeMapping(tree, {
  3252. before: this.bridge(nodeBridge),
  3253. });
  3254. }
  3255. /**
  3256. * 桥接一个树节点列表
  3257. * @param list 树节点列表
  3258. * @param nodeBridge 桥接对象
  3259. * @returns 代理后的树节点列表
  3260. */
  3261. static bridgeList(list, nodeBridge) {
  3262. return list.map(this.bridge(nodeBridge));
  3263. }
  3264. }
  3265. /**
  3266. * 导出一个 NodeBridgeUtil 的实例
  3267. * @deprecated 已废弃,请直接使用类的静态函数
  3268. */
  3269. const nodeBridgeUtil = NodeBridgeUtil;
  3270.  
  3271. /**
  3272. * 获取对象中所有的属性及对应的值,包括 ES6 新增的 Symbol 类型的属性
  3273. * @param obj 任何对象
  3274. * @returns 属性及其对应值的二维数组
  3275. */
  3276. function getObjectEntries(obj) {
  3277. const mFn = k => [
  3278. k,
  3279. Reflect.get(obj, k),
  3280. ];
  3281. return Reflect.ownKeys(obj).map(mFn);
  3282. }
  3283.  
  3284. /**
  3285. * 比较两个浮点数是否相等
  3286. * 具体实现采用差值取绝对值并与 {@link Number.EPSILON} 比较的方式,如果小于浮点数最小差值,则认为它们是 [相等] 的
  3287. * @param num1 第一个浮点数
  3288. * @param num2 第二个浮点数
  3289. * @returns 两数是否相等
  3290. */
  3291. function floatEquals(num1, num2) {
  3292. return Math.abs(num1 - num2) < Number.EPSILON;
  3293. }
  3294.  
  3295. /**
  3296. * 合并多个对象的属性
  3297. * 1. 该合并的方式为浅层合并,只会合并一层的对象
  3298. * 2. 默认忽略值为 undefined/null 的属性
  3299. * @param {...Object} sources 任意数量的对象
  3300. * @returns 合并后的对象
  3301. */
  3302. function assign(target, ...sources) {
  3303. return [target, ...sources].reduce((res, source) => {
  3304. if (isNullOrUndefined(source)) {
  3305. return res;
  3306. }
  3307. return Reflect.ownKeys(source).reduce((res, k) => {
  3308. const v = Reflect.get(source, k);
  3309. if (isNullOrUndefined(v)) {
  3310. return res;
  3311. }
  3312. Reflect.set(res, k, v);
  3313. return res;
  3314. }, res);
  3315. }, {});
  3316. }
  3317.  
  3318. /**
  3319. * 根据不同的源对象获取不同的正则匹配,代表不需要拷贝的属性
  3320. * @param source 源对象
  3321. * @returns 匹配内部属性的正则表达式
  3322. */
  3323. function getInnerFieldRule(source) {
  3324. if (source instanceof Function) {
  3325. return /^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/;
  3326. }
  3327. else {
  3328. return /^(?:toString|length)$/;
  3329. }
  3330. }
  3331. /**
  3332. * 拷贝对象的属性到目标对象上
  3333. * @param target 目标对象
  3334. * @param source 源对象
  3335. * @returns 返回 {@param target} 目标对象
  3336. */
  3337. function _copyProps(target, source) {
  3338. const innerField = getInnerFieldRule(source);
  3339. Reflect.ownKeys(source).forEach(prop => {
  3340. if (typeof prop === 'string' && innerField.test(prop)) {
  3341. return;
  3342. }
  3343. Reflect.set(target, prop, Reflect.get(source, prop));
  3344. });
  3345. return target;
  3346. }
  3347. /**
  3348. * 混合多个类
  3349. * @param {...Class} mixins 需要混合的多个类及其构造函数参数映射函数的 Map 集合
  3350. * @returns 返回一个混合后的类,构造函数将的参数
  3351. */
  3352. function aggregation(mixins) {
  3353. const arr = Array.from(mixins);
  3354. class Aggregate {
  3355. /**
  3356. * @param args 任意数量的参数
  3357. */
  3358. constructor(...args) {
  3359. arr.forEach(([Mixin, fn = returnItself]) => _copyProps(this, new Mixin(...fn(args))));
  3360. }
  3361. }
  3362. arr.forEach(([Mixin]) => {
  3363. _copyProps(Aggregate.prototype, Mixin.prototype);
  3364. _copyProps(Aggregate, Mixin);
  3365. });
  3366. return Aggregate;
  3367. }
  3368.  
  3369. /**
  3370. * 包装一个异步函数为有限制并发功能的函数
  3371. * @param fn 异步函数
  3372. * @param options 可选参数
  3373. * @param options.limit 并发限制数量,默认为 1
  3374. * @returns 返回被包装后的限制并发功能的函数
  3375. */
  3376. function asyncLimiting(fn, { limit = 1 } = {}) {
  3377. // 当前正在执行异步的数量
  3378. let execCount = 0;
  3379. // waitArr 等待的队列
  3380. const takeQueue = [];
  3381. // 是否增加了 execCount 的标记
  3382. let flag = false;
  3383. return new Proxy(fn, {
  3384. apply(_, _this, args) {
  3385. return __awaiter(this, void 0, void 0, function* () {
  3386. const _takeRun = () => __awaiter(this, void 0, void 0, function* () {
  3387. if (!flag) {
  3388. execCount++;
  3389. flag = false;
  3390. }
  3391. const tempArgs = takeQueue.shift();
  3392. try {
  3393. return yield Reflect.apply(_, _this, tempArgs);
  3394. }
  3395. finally {
  3396. execCount--;
  3397. }
  3398. });
  3399. takeQueue.push(args);
  3400. yield wait(() => {
  3401. const result = execCount < limit;
  3402. // 如果等待结束则必须立刻增加 execCount,避免微任务与宏任务调度可能产生错误
  3403. if (result) {
  3404. flag = true;
  3405. execCount++;
  3406. }
  3407. return result;
  3408. });
  3409. return _takeRun();
  3410. });
  3411. },
  3412. });
  3413. }
  3414.  
  3415. /**
  3416. * 默认的超时时间,可以认为是无限
  3417. */
  3418. const TimeoutInfinity = () => false;
  3419. /**
  3420. * 创建一个 Lock 对象,用于锁住当前的当前的异步流程
  3421. */
  3422. class Locker {
  3423. /**
  3424. * @param options 可选项
  3425. * @param options.limit 限制并发数量,默认为 1
  3426. * @param options.timeout 超时时间,默认为无限
  3427. */
  3428. constructor({ limit = 1, timeout } = {}) {
  3429. this.limit = limit;
  3430. this.timeout = timeout || TimeoutInfinity;
  3431. }
  3432. /**
  3433. * 当前是否锁住了
  3434. * @returns 是否锁住了
  3435. */
  3436. isLocked() {
  3437. return this.limit <= 0;
  3438. }
  3439. /**
  3440. * 添加异步锁
  3441. * @param timeout 超时时间,默认为全局 timeout
  3442. * @returns 进行等待
  3443. */
  3444. lock(timeout = this.timeout) {
  3445. return __awaiter(this, void 0, void 0, function* () {
  3446. if (this.isLocked()) {
  3447. /**
  3448. * @type {Number|Function}
  3449. */
  3450. yield Promise.race([
  3451. wait(() => {
  3452. const result = !this.isLocked();
  3453. if (result) {
  3454. this.limit--;
  3455. }
  3456. return result;
  3457. }),
  3458. wait(timeout),
  3459. ]);
  3460. }
  3461. else {
  3462. this.limit--;
  3463. }
  3464. });
  3465. }
  3466. /**
  3467. * 删除异步锁
  3468. */
  3469. unlock() {
  3470. this.limit++;
  3471. }
  3472. }
  3473.  
  3474. /**
  3475. * 包装一个函数为有错误重试功能的函数
  3476. * 注: 如果发生错误,最终将抛出最后一次调用的异常
  3477. * @param fn 需要被包装的函数
  3478. * @param num 调用的次数。默认为 1
  3479. * @param errorCheck 检查返回结果是否需要重试的函数。默认只要 resolve() 就返回 true
  3480. * @returns 包装后的有错误重试功能的函数
  3481. */
  3482. function trySometime(fn, num = 1, errorCheck = res => true) {
  3483. return new Proxy(fn, {
  3484. apply(target, thisArg, args) {
  3485. return __awaiter(this, void 0, void 0, function* () {
  3486. let err;
  3487. for (let i = 0; i < num; i++) {
  3488. try {
  3489. // 等待结果出来
  3490. const res = yield Reflect.apply(target, thisArg, args);
  3491. // 如果没问题就直接返回了
  3492. if (errorCheck(res) === true) {
  3493. return res;
  3494. }
  3495. // 否则抛出异常以进行下一次重试
  3496. throw res;
  3497. }
  3498. catch (error) {
  3499. err = error;
  3500. }
  3501. }
  3502. throw err;
  3503. });
  3504. },
  3505. });
  3506. }
  3507.  
  3508. /**
  3509. * 包装一个函数为有错误重试功能的函数
  3510. * 注意: 该函数是并行运行,所以一旦调用,就会同时调用 n 次,不管之前有没有失败。。。此函数不适合包装有副作用的操作,例如修改用户信息,请使用 {@link trySometime} 替代
  3511. * @param fn 需要被包装的函数
  3512. * @param num 调用的次数。默认为 1
  3513. * @param errorCheck 检查返回结果是否需要重试的函数。默认只要 resolve() 就返回 true
  3514. * @returns 包装后的有错误重试功能的函数
  3515. */
  3516. function trySometimeParallel(fn, num = 1, errorCheck = res => true) {
  3517. return new Proxy(fn, {
  3518. apply(target, thisArg, args) {
  3519. return __awaiter(this, void 0, void 0, function* () {
  3520. return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
  3521. let err;
  3522. try {
  3523. yield Promise.all(range(0, num).map(() => __awaiter(this, void 0, void 0, function* () {
  3524. try {
  3525. const res = yield Reflect.apply(target, thisArg, args);
  3526. if (errorCheck(res) === true) {
  3527. resolve(res);
  3528. }
  3529. throw res;
  3530. }
  3531. catch (error) {
  3532. err = error;
  3533. }
  3534. })));
  3535. }
  3536. catch (error) {
  3537. console.log(error);
  3538. }
  3539. reject(err);
  3540. }));
  3541. });
  3542. },
  3543. });
  3544. }
  3545.  
  3546. /**
  3547. * 深度比较两个对象是否相等
  3548. * @param x 任何对象
  3549. * @param y 任何对象
  3550. * @returns 是否相等
  3551. */
  3552. function compare(x, y) {
  3553. if ((typeof x === 'number' || x instanceof Number) &&
  3554. (typeof y === 'number' || y instanceof Number)) {
  3555. const _x = +x;
  3556. const _y = +y;
  3557. // 如果都是 NaN 则直接返回 true
  3558. if (isNaN(_x) && isNaN(_y)) {
  3559. return true;
  3560. }
  3561. // 如果是 -0/+0 则返回 false
  3562. if (_x === _y) {
  3563. return 1 / _x === 1 / _y;
  3564. }
  3565. // 如果均为数字且两数之差的绝对值小于浮点数的最小精度(此举主要是为了避免浮点数的精度丢失)
  3566. if (Math.abs(_x - _y) < Number.EPSILON) {
  3567. return true;
  3568. }
  3569. }
  3570. // 如果恒等表达式成立则直接返回 true
  3571. if (x === y) {
  3572. return true;
  3573. }
  3574. // 比较正则和字符串
  3575. if ((x instanceof RegExp && y instanceof RegExp) ||
  3576. ((typeof x === 'string' || x instanceof String) &&
  3577. (typeof y === 'string' || y instanceof String))) {
  3578. return x.toString() === y.toString();
  3579. }
  3580. // 如果都是时间则比较它们的时间戳
  3581. if (x instanceof Date && y instanceof Date) {
  3582. return x.getTime() === y.getTime();
  3583. }
  3584. // 如果两者有一个不是 Object 类型的话则直接返回 false
  3585. // 注: 此处直接返回 false 是因为特殊原生类型的都在上面处理过了
  3586. // 注: Array 可以按照 Object 的逻辑进行处理
  3587. if (!(x instanceof Object && y instanceof Object)) {
  3588. return false;
  3589. }
  3590. // 比较它们的原型
  3591. if (x.prototype !== y.prototype) {
  3592. return false;
  3593. }
  3594. // 比较构造函数
  3595. if (x.constructor !== y.constructor) {
  3596. return false;
  3597. }
  3598. // 比较 y 中的属性是否全部都在 x 中
  3599. for (const p of Reflect.ownKeys(y)) {
  3600. if (!Reflect.has(x, p)) {
  3601. return false;
  3602. }
  3603. }
  3604. // 比较 x 中的属性是否全部都在 y 中
  3605. for (const p of Reflect.ownKeys(x)) {
  3606. if (!Reflect.has(y, p)) {
  3607. return false;
  3608. }
  3609. // 比较每个元素的类型,如果不同则直接返回 false
  3610. if (typeof y[p] !== typeof x[p]) {
  3611. return false;
  3612. }
  3613. // 递归比较每个元素
  3614. if (!compare(x[p], y[p])) {
  3615. return false;
  3616. }
  3617. }
  3618. // 全部比较完成仍然没有结果就返回 true
  3619. return true;
  3620. }
  3621.  
  3622. /**
  3623. * 阻塞主线程指定时间
  3624. * 注: 和 {@see wait} 不同,该函数会真的阻塞住主线程,即这段时间内其他的代码都无法执行,例如用户的点击事件!
  3625. * @param time 阻塞毫秒数
  3626. */
  3627. function sleep(time) {
  3628. const end = performance.now() + time;
  3629. while (performance.now() <= end) { }
  3630. }
  3631.  
  3632. /**
  3633. * 操作类型
  3634. */
  3635. var ActionType;
  3636. (function (ActionType) {
  3637. ActionType["forEach"] = "forEach";
  3638. ActionType["filter"] = "filter";
  3639. ActionType["map"] = "map";
  3640. ActionType["flatMap"] = "flatMap";
  3641. ActionType["sort"] = "sort";
  3642. ActionType["reduce"] = "reduce";
  3643. ActionType["reduceRight"] = "reduceRight";
  3644. ActionType["findIndex"] = "findIndex";
  3645. ActionType["find"] = "find";
  3646. ActionType["every"] = "every";
  3647. ActionType["some"] = "some";
  3648. ActionType["parallel"] = "parallel";
  3649. ActionType["serial"] = "serial";
  3650. })(ActionType || (ActionType = {}));
  3651. /**
  3652. * 保存高阶函数传入的异步操作
  3653. * @field 异步操作的类型
  3654. * @field 异步操作
  3655. */
  3656. class Action {
  3657. constructor(type, args) {
  3658. this.type = type;
  3659. this.args = args;
  3660. this.type = type;
  3661. this.args = args;
  3662. }
  3663. }
  3664. Action.Type = ActionType;
  3665. /**
  3666. * 抽象异步数组,实现了一些公共的函数
  3667. */
  3668. class InnerBaseAsyncArray {
  3669. /**
  3670. * 构造函数
  3671. * @param args 数组初始元素
  3672. */
  3673. constructor(args = []) {
  3674. this._arr = args;
  3675. }
  3676. /**
  3677. * 将整个数组排序
  3678. * @param fn 比较函数
  3679. * @returns 排序后的数组
  3680. */
  3681. sort(fn) {
  3682. return __awaiter(this, void 0, void 0, function* () {
  3683. if (fn === undefined) {
  3684. return new InnerAsyncArray(this._arr.sort());
  3685. }
  3686. // TODO 此处为了让 type-doc 能生成文档而不得不加上类型
  3687. const arr = this._arr.map((v, i) => [v, i]);
  3688. function _sort(arr, fn) {
  3689. return __awaiter(this, void 0, void 0, function* () {
  3690. // 边界条件,如果传入数组的值
  3691. if (arr.length <= 1) {
  3692. return arr;
  3693. }
  3694. // 根据中间值对数组分治为两个数组
  3695. const medianIndex = Math.floor(arr.length / 2);
  3696. const medianValue = arr[medianIndex];
  3697. const left = [];
  3698. const right = [];
  3699. for (let i = 0, len = arr.length; i < len; i++) {
  3700. if (i === medianIndex) {
  3701. continue;
  3702. }
  3703. const v = arr[i];
  3704. if ((yield fn(v, medianValue)) <= 0) {
  3705. left.push(v);
  3706. }
  3707. else {
  3708. right.push(v);
  3709. }
  3710. }
  3711. return (yield _sort(left, fn))
  3712. .concat([medianValue])
  3713. .concat(yield _sort(right, fn));
  3714. });
  3715. }
  3716. return new InnerAsyncArray(yield (yield _sort(arr, ([t1], [t2]) => fn(t1, t2))).map(([_v, i]) => this._arr[i]));
  3717. });
  3718. }
  3719. /**
  3720. * 异步的 find
  3721. * @param fn 异步查询函数
  3722. * @returns 查询到的第一个值
  3723. */
  3724. find(fn) {
  3725. return __awaiter(this, void 0, void 0, function* () {
  3726. const i = yield this.findIndex(fn);
  3727. return i === -1 ? null : this._arr[i];
  3728. });
  3729. }
  3730. /**
  3731. * 异步的 every
  3732. * @param fn 异步匹配函数
  3733. * @returns 是否全部匹配
  3734. */
  3735. every(fn) {
  3736. return __awaiter(this, void 0, void 0, function* () {
  3737. return (yield this.findIndex(CombinedPredicate.not(fn))) === -1;
  3738. });
  3739. }
  3740. /**
  3741. * 异步的 some
  3742. * @param fn 异步匹配函数
  3743. * @returns 是否有任意一个匹配
  3744. */
  3745. some(fn) {
  3746. return __awaiter(this, void 0, void 0, function* () {
  3747. return (yield this.findIndex(fn)) !== -1;
  3748. });
  3749. }
  3750. /**
  3751. * 转换为并发异步数组
  3752. */
  3753. parallel() {
  3754. return new InnerAsyncArrayParallel(this._arr);
  3755. }
  3756. /**
  3757. * 转换为顺序异步数组
  3758. */
  3759. serial() {
  3760. return new InnerAsyncArray(this._arr);
  3761. }
  3762. /**
  3763. * 获取内部数组的值,将返回一个浅复制的数组
  3764. */
  3765. value() {
  3766. return this._arr.slice();
  3767. }
  3768. }
  3769. /**
  3770. * 串行的异步数组
  3771. */
  3772. class InnerAsyncArray extends InnerBaseAsyncArray {
  3773. constructor(args) {
  3774. super(args);
  3775. }
  3776. forEach(fn) {
  3777. return __awaiter(this, void 0, void 0, function* () {
  3778. for (let i = 0, len = this._arr.length; i < len; i++) {
  3779. yield fn.call(this, this._arr[i], i, this);
  3780. }
  3781. });
  3782. }
  3783. filter(fn) {
  3784. return __awaiter(this, void 0, void 0, function* () {
  3785. const res = new InnerAsyncArray();
  3786. for (let i = 0, len = this._arr.length; i < len; i++) {
  3787. if (yield fn.call(this, this._arr[i], i, this)) {
  3788. res._arr.push(this._arr[i]);
  3789. }
  3790. }
  3791. return res;
  3792. });
  3793. }
  3794. map(fn) {
  3795. return __awaiter(this, void 0, void 0, function* () {
  3796. const res = new InnerAsyncArray();
  3797. for (let i = 0, len = this._arr.length; i < len; i++) {
  3798. res._arr.push(yield fn.call(this, this._arr[i], i, this));
  3799. }
  3800. return res;
  3801. });
  3802. }
  3803. flatMap(fn) {
  3804. return __awaiter(this, void 0, void 0, function* () {
  3805. const res = new InnerAsyncArray();
  3806. for (let i = 0, len = this._arr.length; i < len; i++) {
  3807. res._arr.push(...(yield fn.call(this, this._arr[i], i, this)));
  3808. }
  3809. return res;
  3810. });
  3811. }
  3812. reduce(fn, res) {
  3813. return __awaiter(this, void 0, void 0, function* () {
  3814. for (let i = 0, len = this._arr.length; i < len; i++) {
  3815. if (res) {
  3816. res = yield fn.call(this, res, this._arr[i], i, this);
  3817. }
  3818. else {
  3819. res = this._arr[i];
  3820. }
  3821. }
  3822. return res;
  3823. });
  3824. }
  3825. reduceRight(fn, res) {
  3826. return __awaiter(this, void 0, void 0, function* () {
  3827. for (let i = this._arr.length - 1; i >= 0; i--) {
  3828. if (res) {
  3829. res = yield fn.apply(this, [res, this._arr[i], i, this]);
  3830. }
  3831. else {
  3832. res = this._arr[i];
  3833. }
  3834. }
  3835. return res;
  3836. });
  3837. }
  3838. findIndex(fn) {
  3839. return __awaiter(this, void 0, void 0, function* () {
  3840. for (let i = 0, len = this._arr.length; i < len; i++) {
  3841. const res = yield fn.call(this, this._arr[i], i, this);
  3842. if (res) {
  3843. return i;
  3844. }
  3845. }
  3846. return -1;
  3847. });
  3848. }
  3849. }
  3850. /**
  3851. * 并发的异步数组
  3852. */
  3853. class InnerAsyncArrayParallel extends InnerBaseAsyncArray {
  3854. constructor(args) {
  3855. super(args);
  3856. }
  3857. forEach(fn) {
  3858. return __awaiter(this, void 0, void 0, function* () {
  3859. yield this._all(fn);
  3860. });
  3861. }
  3862. filter(fn) {
  3863. return __awaiter(this, void 0, void 0, function* () {
  3864. const res = yield this._all(fn);
  3865. const result = new InnerAsyncArrayParallel();
  3866. for (let i = 0, len = res.length; i < len; i++) {
  3867. if (res[i]) {
  3868. result._arr.push(this._arr[i]);
  3869. }
  3870. }
  3871. return result;
  3872. });
  3873. }
  3874. map(fn) {
  3875. return __awaiter(this, void 0, void 0, function* () {
  3876. return new InnerAsyncArrayParallel(yield this._all(fn));
  3877. });
  3878. }
  3879. flatMap(fn) {
  3880. return __awaiter(this, void 0, void 0, function* () {
  3881. const res = yield this._all(fn);
  3882. return new InnerAsyncArrayParallel(res.flat());
  3883. });
  3884. }
  3885. sort(fn) {
  3886. throw new Error('Method not implemented.');
  3887. }
  3888. reduce(fn, res) {
  3889. return __awaiter(this, void 0, void 0, function* () {
  3890. for (let i = 0, len = this._arr.length; i < len; i++) {
  3891. if (res) {
  3892. res = yield fn.call(this, res, this._arr[i], i, this);
  3893. }
  3894. else {
  3895. res = this._arr[i];
  3896. }
  3897. }
  3898. return res;
  3899. });
  3900. }
  3901. reduceRight(fn, res) {
  3902. return __awaiter(this, void 0, void 0, function* () {
  3903. for (let i = this._arr.length - 1; i >= 0; i--) {
  3904. if (res) {
  3905. res = yield fn.apply(this, [res, this._arr[i], i, this]);
  3906. }
  3907. else {
  3908. res = this._arr[i];
  3909. }
  3910. }
  3911. return res;
  3912. });
  3913. }
  3914. findIndex(fn) {
  3915. return __awaiter(this, void 0, void 0, function* () {
  3916. return (yield this._all(fn)).findIndex(returnItself);
  3917. });
  3918. }
  3919. _all(fn) {
  3920. return __awaiter(this, void 0, void 0, function* () {
  3921. return yield Promise.all(this._arr.map((v, i) => fn.apply(this, [v, i, this])));
  3922. });
  3923. }
  3924. }
  3925. /**
  3926. * 异步数组
  3927. */
  3928. class AsyncArray {
  3929. /**
  3930. * 构造函数
  3931. * @param args 任意个参数
  3932. */
  3933. constructor(...args) {
  3934. /**
  3935. * 内部数组的长度,用于让 {@link AsyncArray} 的实例能作为 {@link Array.from} 的参数
  3936. */
  3937. this.length = 0;
  3938. this._arr = Array.from(args);
  3939. /**
  3940. * @field 保存异步任务
  3941. * @type {Action[]}
  3942. */
  3943. this._tasks = [];
  3944. }
  3945. /**
  3946. * 为内置数组赋值
  3947. * 此处自动重新计算 length 属性
  3948. */
  3949. set _arr(arr) {
  3950. this.__arr = arr;
  3951. this.length = this.__arr.length;
  3952. }
  3953. get _arr() {
  3954. return this.__arr;
  3955. }
  3956. /**
  3957. * 提供一个函数方便根据已有的数组或类数组(Set/Map)创建 {@link AsyncArray}
  3958. * @param arr 一个可迭代元素
  3959. * @returns 创建一个新的异步数组包装
  3960. */
  3961. static from(arr) {
  3962. const result = new AsyncArray();
  3963. if (isNullOrUndefined(arr)) {
  3964. return result;
  3965. }
  3966. result._arr = Array.from(arr);
  3967. return result;
  3968. }
  3969. filter(fn) {
  3970. return this._addTask(new Action(Action.Type.filter, [fn]));
  3971. }
  3972. map(fn) {
  3973. return this._addTask(new Action(Action.Type.map, [fn]));
  3974. }
  3975. flatMap(fn) {
  3976. return this._addTask(new Action(Action.Type.flatMap, [fn]));
  3977. }
  3978. sort(fn) {
  3979. return this._addTask(new Action(Action.Type.sort, [fn]));
  3980. }
  3981. parallel() {
  3982. return this._addTask(new Action(Action.Type.parallel, []));
  3983. }
  3984. serial() {
  3985. return this._addTask(new Action(Action.Type.serial, []));
  3986. }
  3987. forEach(fn) {
  3988. return this._addTask(new Action(Action.Type.forEach, [fn])).then();
  3989. }
  3990. some(fn) {
  3991. return this._addTask(new Action(Action.Type.some, [fn])).then();
  3992. }
  3993. every(fn) {
  3994. return this._addTask(new Action(Action.Type.every, [fn])).then();
  3995. }
  3996. find(fn) {
  3997. return this._addTask(new Action(Action.Type.find, [fn])).then();
  3998. }
  3999. findIndex(fn) {
  4000. return this._addTask(new Action(Action.Type.findIndex, [fn])).then();
  4001. }
  4002. reduce(fn, res) {
  4003. return this._addTask(new Action(Action.Type.reduce, [fn, res])).then();
  4004. }
  4005. reduceRight(fn, res) {
  4006. return this._addTask(new Action(Action.Type.reduceRight, [fn, res])).then();
  4007. }
  4008. /**
  4009. * 终结整个链式操作并返回结果,可以使用 await 等待当前实例开始计算
  4010. */
  4011. then(onfulfilled, onrejected) {
  4012. return __awaiter(this, void 0, void 0, function* () {
  4013. try {
  4014. let asyncArray = new InnerAsyncArray(this._arr);
  4015. let result = this._arr;
  4016. for (const task of this._tasks) {
  4017. asyncArray = yield Reflect.apply(Reflect.get(asyncArray, task.type), asyncArray, task.args);
  4018. if (asyncArray instanceof InnerBaseAsyncArray) {
  4019. result = asyncArray.value();
  4020. }
  4021. else {
  4022. if (!isNullOrUndefined(onfulfilled)) {
  4023. onfulfilled(result);
  4024. }
  4025. return asyncArray;
  4026. }
  4027. }
  4028. if (!isNullOrUndefined(onfulfilled)) {
  4029. onfulfilled(result);
  4030. }
  4031. return result;
  4032. }
  4033. catch (err) {
  4034. if (!isNullOrUndefined(onrejected)) {
  4035. onrejected(err);
  4036. }
  4037. }
  4038. });
  4039. }
  4040. /**
  4041. * @deprecated 已废弃,请直接使用 await 进行等待获取结果值即可
  4042. */
  4043. value() {
  4044. return this.then();
  4045. }
  4046. /**
  4047. * 允许使用 for-of 遍历内部的 _arr
  4048. */
  4049. *[Symbol.iterator]() {
  4050. for (const kv of this._arr) {
  4051. yield kv;
  4052. }
  4053. }
  4054. _addTask(task) {
  4055. const result = new AsyncArray(...this._arr);
  4056. result._tasks = [...this._tasks, task];
  4057. return result;
  4058. }
  4059. }
  4060.  
  4061. /**
  4062. * 包装一个函数为异步函数
  4063. * @param fn 任意一个函数
  4064. * @typeparam R 原函数函数返回值类型
  4065. * @returns 返回的异步结果 Promise 对象
  4066. */
  4067. function async(fn) {
  4068. return new Proxy(fn, {
  4069. apply(_, _this, args) {
  4070. return __awaiter(this, void 0, void 0, function* () {
  4071. return yield Reflect.apply(_, _this, args);
  4072. });
  4073. },
  4074. });
  4075. }
  4076.  
  4077. /**
  4078. * 将一个异步函数包装为具有时序的异步函数
  4079. * 注: 该函数会按照调用顺序依次返回结果,后面的调用的结果需要等待前面的,所以如果不关心过时的结果,请使用 {@link switchMap} 函数
  4080. * @param fn 一个普通的异步函数
  4081. * @returns 包装后的函数
  4082. */
  4083. function mergeMap(fn) {
  4084. // 当前执行的异步操作 id
  4085. let id = 0;
  4086. // 所执行的异步操作 id 列表
  4087. const ids = new Set();
  4088. return new Proxy(fn, {
  4089. apply(_, _this, args) {
  4090. return __awaiter(this, void 0, void 0, function* () {
  4091. const prom = Reflect.apply(_, _this, args);
  4092. const temp = id;
  4093. ids.add(temp);
  4094. id++;
  4095. yield wait(() => !ids.has(temp - 1));
  4096. ids.delete(temp);
  4097. return yield prom;
  4098. });
  4099. },
  4100. });
  4101. }
  4102.  
  4103. /**
  4104. * 将一个异步函数包装为具有时序的异步函数
  4105. * 注: 该函数会丢弃过期的异步操作结果,这样的话性能会稍稍提高(主要是响应比较快的结果会立刻生效而不必等待前面的响应结果)
  4106. * @param fn 一个普通的异步函数
  4107. * @returns 包装后的函数
  4108. */
  4109. function switchMap(fn) {
  4110. // 当前执行的异步操作 id
  4111. let id = 0;
  4112. // 最后一次异步操作的 id,小于这个的操作结果会被丢弃
  4113. let last = 0;
  4114. // 缓存最后一次异步操作的结果
  4115. let cache;
  4116. return new Proxy(fn, {
  4117. apply(_, _this, args) {
  4118. return __awaiter(this, void 0, void 0, function* () {
  4119. const temp = id;
  4120. id++;
  4121. const res = yield Reflect.apply(_, _this, args);
  4122. if (temp < last) {
  4123. return cache;
  4124. }
  4125. cache = res;
  4126. last = temp;
  4127. return res;
  4128. });
  4129. },
  4130. });
  4131. }
  4132.  
  4133. /**
  4134. * 将指定函数包装为只调用一次
  4135. * @param fn 需要包装的函数
  4136. * @returns 包装后的函数
  4137. */
  4138. function once(fn) {
  4139. let flag = true;
  4140. let cache;
  4141. return new Proxy(fn, {
  4142. apply(target, thisArg, args) {
  4143. if (flag === false) {
  4144. return cache;
  4145. }
  4146. flag = false;
  4147. // 如果是异步函数则返回异步的结果
  4148. return compatibleAsync(Reflect.apply(target, thisArg, args), res => {
  4149. cache = res;
  4150. return cache;
  4151. });
  4152. },
  4153. });
  4154. }
  4155.  
  4156. /**
  4157. * 将一个异步函数包装为具有时序的异步函数
  4158. * 注: 该函数会按照调用顺序依次返回结果,后面的执行的调用(不是调用结果)需要等待前面的,此函数适用于异步函数的内里执行也必须保证顺序时使用,否则请使用 {@link mergeMap} 函数
  4159. * 注: 该函数其实相当于调用 {@code asyncLimiting(fn, {limit: 1})} 函数
  4160. * 例如即时保存文档到服务器,当然要等待上一次的请求结束才能请求下一次,不然数据库保存的数据就存在谬误了
  4161. * @param fn 一个普通的异步函数
  4162. * @returns 包装后的函数
  4163. */
  4164. function concatMap(fn) {
  4165. // 当前执行的异步操作 id
  4166. let id = 0;
  4167. // 所执行的异步操作 id 列表
  4168. const ids = new Set();
  4169. return new Proxy(fn, {
  4170. apply(_, _this, args) {
  4171. return __awaiter(this, void 0, void 0, function* () {
  4172. const temp = id;
  4173. ids.add(temp);
  4174. id++;
  4175. yield wait(() => !ids.has(temp - 1));
  4176. const prom = Reflect.apply(_, _this, args);
  4177. ids.delete(temp);
  4178. return yield prom;
  4179. });
  4180. },
  4181. });
  4182. }
  4183.  
  4184. /**
  4185. * 重复执行指定的函数
  4186. * @param num 重复的次数
  4187. * @param fn 执行的函数,如果是异步函数,则返回 Array.<Promise>
  4188. * @param {...Object} args 参数
  4189. * @returns 执行返回结果
  4190. */
  4191. function repeatedCall(num, fn, ...args) {
  4192. return range(0, num).map(() => fn(...args));
  4193. }
  4194.  
  4195. /**
  4196. * 发布订阅模式
  4197. * @typeparam T 订阅主题的类型,虽然可以为 any,但这里是刻意进行限制以避免 “全局” 的发布订阅中心对象
  4198. */
  4199. class PubSubMachine {
  4200. constructor() {
  4201. /**
  4202. * 订阅者集合
  4203. */
  4204. this.subMap = new Map();
  4205. }
  4206. /**
  4207. * 发布一个主题
  4208. * @param topic 发布的主题
  4209. * @param [args] 主题订阅所需要的参数
  4210. */
  4211. pub(topic, ...args) {
  4212. const fns = this.subMap.get(topic);
  4213. if (fns === undefined) {
  4214. return;
  4215. }
  4216. fns.forEach(fn => fn(...args));
  4217. }
  4218. /**
  4219. * 订阅一个主题
  4220. * @param topic 订阅的主题
  4221. * @param fn 回调的函数
  4222. */
  4223. sub(topic, fn) {
  4224. if (!this.subMap.has(topic)) {
  4225. this.subMap.set(topic, []);
  4226. }
  4227. this.subMap.get(topic).push(fn);
  4228. }
  4229. /**
  4230. * 取消订阅
  4231. * @param topic 订阅的主题
  4232. * @param fn 订阅的函数,没有则删除这个主题下所有的函数
  4233. */
  4234. unsub(topic, fn) {
  4235. if (fn === undefined) {
  4236. this.subMap.delete(topic);
  4237. return;
  4238. }
  4239. if (!this.subMap.has(topic)) {
  4240. return;
  4241. }
  4242. const fns = this.subMap.get(topic);
  4243. fns.splice(fns.indexOf(fn), 1);
  4244. }
  4245. }
  4246.  
  4247. /**
  4248. * 提取对象数组为 Map
  4249. * @param arr 对象数组
  4250. * @param fields 提取的字段
  4251. * @returns 提取字段名对应其字段值数组的 Map
  4252. * @typeparam T 数组元素的类型,必须为可提取字段的对象
  4253. */
  4254. function extractFieldMap(arr, fields) {
  4255. return arr.reduce((res, v) => {
  4256. for (const [k, _arr] of res) {
  4257. _arr.push(Reflect.get(v, k));
  4258. }
  4259. return res;
  4260. }, arrayToMap(fields, k => k, () => new Array()));
  4261. }
  4262.  
  4263. /**
  4264. * 校验变量的类型
  4265. */
  4266. class TypeValidator {
  4267. /**
  4268. * 判断是否为字符串
  4269. * @param val 需要判断的值
  4270. * @returns 是否为字符串
  4271. */
  4272. static isString(val) {
  4273. return typeof val === 'string';
  4274. }
  4275. /**
  4276. * 判断是否为数字
  4277. * @param val 需要判断的值
  4278. * @returns 是否为数字
  4279. */
  4280. static isNumber(val) {
  4281. return typeof val === 'number';
  4282. }
  4283. /**
  4284. * 判断是否为布尔值
  4285. * @param val 需要判断的值
  4286. * @returns 是否为布尔值
  4287. */
  4288. static isBoolean(val) {
  4289. return typeof val === 'boolean';
  4290. }
  4291. /**
  4292. * 判断是否为 undefined
  4293. * @param val 需要判断的值
  4294. * @returns 是否为 undefined
  4295. */
  4296. static isUndefined(val) {
  4297. return val === undefined;
  4298. }
  4299. /**
  4300. * 判断是否为 null
  4301. * @param val 需要判断的值
  4302. * @returns 是否为 null
  4303. */
  4304. static isNull(val) {
  4305. return val === null;
  4306. }
  4307. /**
  4308. * 判断是否为 Symbol
  4309. * @param val 需要判断的值
  4310. * @returns 是否为 Symbol
  4311. */
  4312. static isSymbol(val) {
  4313. return typeof val === 'symbol';
  4314. }
  4315. /**
  4316. * 判断是否为对象
  4317. * 注: 函数(包括 ES6 箭头函数)将不被视为对象
  4318. * @param val 需要判断的值
  4319. * @returns 是否为对象
  4320. */
  4321. static isObject(val) {
  4322. return (!TypeValidator.isNull(val) &&
  4323. !TypeValidator.isUndefined(val) &&
  4324. typeof val === 'object');
  4325. }
  4326. /**
  4327. * 判断是否为数组
  4328. * @param val 需要判断的值
  4329. * @returns 是否为数组
  4330. */
  4331. static isArray(val) {
  4332. return Array.isArray(val);
  4333. }
  4334. /**
  4335. * 判断是否为数组
  4336. * @param val 需要判断的值
  4337. * @returns 是否为数组
  4338. */
  4339. static isFunction(val) {
  4340. return toString.call(val) === '[object Function]';
  4341. }
  4342. /**
  4343. * 判断是否为日期
  4344. * @param val 需要判断的值
  4345. * @returns 是否为日期
  4346. */
  4347. static isDate(val) {
  4348. return toString.call(val) === '[object Date]';
  4349. }
  4350. /**
  4351. * 判断是否为浏览器文件类型
  4352. * @param val 需要判断的值
  4353. * @returns 是否为浏览器文件类型
  4354. */
  4355. static isFile(val) {
  4356. return toString.call(val) === '[object File]';
  4357. }
  4358. /**
  4359. * 判断是否为浏览器二进制类型
  4360. * @param val 需要判断的值
  4361. * @returns 是否为浏览器二进制类型
  4362. */
  4363. static isBlob(val) {
  4364. return toString.call(val) === '[object Blob]';
  4365. }
  4366. /**
  4367. * 判断是否为浏览器流类型
  4368. * @param val 需要判断的值
  4369. * @returns 是否为浏览器流类型
  4370. */
  4371. static isStream(val) {
  4372. return TypeValidator.isObject(val) && TypeValidator.isFunction(val.pipe);
  4373. }
  4374. /**
  4375. * 判断是否为浏览器 ArrayBuffer 类型
  4376. * @param val 需要判断的值
  4377. * @returns 是否为浏览器 ArrayBuffer 类型
  4378. */
  4379. static isArrayBuffer(val) {
  4380. return toString.call(val) === '[object ArrayBuffer]';
  4381. }
  4382. /**
  4383. * 判断是否为浏览器 ArrayBufferView 类型
  4384. * @param val 需要判断的值
  4385. * @returns 是否为浏览器 ArrayBufferView 类型
  4386. */
  4387. static isArrayBufferView(val) {
  4388. return typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView
  4389. ? ArrayBuffer.isView(val)
  4390. : val && val.buffer && val.buffer instanceof ArrayBuffer;
  4391. }
  4392. /**
  4393. * 判断是否为浏览器 URLSearchParams 类型
  4394. * @param val 需要判断的值
  4395. * @returns 是否为浏览器 URLSearchParams 类型
  4396. */
  4397. static isURLSearchParams(val) {
  4398. return !TypeValidator.isUndefined(val) && val instanceof URLSearchParams;
  4399. }
  4400. /**
  4401. * 判断是否为浏览器 FormData 类型
  4402. * @param val 需要判断的值
  4403. * @returns 是否为浏览器 FormData 类型
  4404. */
  4405. static isFormData(val) {
  4406. return !TypeValidator.isUndefined(val) && val instanceof FormData;
  4407. }
  4408. }
  4409.  
  4410. /**
  4411. * 数组按照指定长度进行分段为二维数组
  4412. * 注: num 必须要大于 1
  4413. * @param arr 要进行分段的数组
  4414. * @param num 每段的长度
  4415. * @returns 分段后的二维数组
  4416. */
  4417. function segmentation(arr, num) {
  4418. return arr.reduce((res, v, i) => {
  4419. const index = (i + 1) % num;
  4420. if (index === 1) {
  4421. res.push([]);
  4422. }
  4423. res[res.length - 1].push(v);
  4424. return res;
  4425. }, new Array());
  4426. }
  4427.  
  4428. /**
  4429. * 切换 DOM 元素的 class
  4430. * @param {Element} el DOM 元素
  4431. * @param {Object} obj 切换的状态/class 键值对象
  4432. * @return 根据状态切换 class 的函数
  4433. */
  4434. function toggleClass(el, obj) {
  4435. const arr = Object.entries(obj);
  4436. /**
  4437. * 返回切换 class 的函数
  4438. * @param state 切换的状态
  4439. */
  4440. return function toggle(state) {
  4441. arr.forEach(([, v]) => el.classList.remove(v));
  4442. el.classList.add(obj[state]);
  4443. };
  4444. }
  4445.  
  4446. exports.AntiDebug = AntiDebug;
  4447. exports.ArrayValidator = ArrayValidator;
  4448. exports.AsyncArray = AsyncArray;
  4449. exports.CacheUtil = CacheUtil;
  4450. exports.CombinedPredicate = CombinedPredicate;
  4451. exports.DateConstants = DateConstants;
  4452. exports.DateFormatter = DateFormatter;
  4453. exports.FetchLimiting = FetchLimiting;
  4454. exports.LocalStorageCache = LocalStorageCache;
  4455. exports.Locker = Locker;
  4456. exports.Logger = Logger;
  4457. exports.NodeBridgeUtil = NodeBridgeUtil;
  4458. exports.PathUtil = PathUtil;
  4459. exports.PubSubMachine = PubSubMachine;
  4460. exports.StateMachine = StateMachine;
  4461. exports.StringStyleUtil = StringStyleUtil;
  4462. exports.StringValidator = StringValidator;
  4463. exports.TypeValidator = TypeValidator;
  4464. exports.aggregation = aggregation;
  4465. exports.and = and;
  4466. exports.antiDebug = antiDebug;
  4467. exports.appends = appends;
  4468. exports.arrayDiffBy = arrayDiffBy;
  4469. exports.arrayToMap = arrayToMap;
  4470. exports.arrayValidator = arrayValidator;
  4471. exports.asIterator = asIterator;
  4472. exports.assign = assign;
  4473. exports.async = async;
  4474. exports.asyncFlatMap = asyncFlatMap;
  4475. exports.asyncLimiting = asyncLimiting;
  4476. exports.autoIncrement = autoIncrement;
  4477. exports.blankToNull = blankToNull;
  4478. exports.blankToNullField = blankToNullField;
  4479. exports.bridge = bridge;
  4480. exports.cacheUtil = cacheUtil;
  4481. exports.compare = compare;
  4482. exports.compose = compose;
  4483. exports.concatMap = concatMap;
  4484. exports.copyText = copyText;
  4485. exports.createElByString = createElByString;
  4486. exports.curry = curry;
  4487. exports.dateBetween = dateBetween;
  4488. exports.dateConstants = dateConstants;
  4489. exports.dateEnhance = dateEnhance;
  4490. exports.dateFormat = dateFormat;
  4491. exports.dateParse = dateParse;
  4492. exports.debounce = debounce;
  4493. exports.deepFreeze = deepFreeze;
  4494. exports.deepProxy = deepProxy;
  4495. exports.deletes = deletes;
  4496. exports.deny = deny;
  4497. exports.diffBy = diffBy;
  4498. exports.download = download;
  4499. exports.downloadString = downloadString;
  4500. exports.downloadUrl = downloadUrl;
  4501. exports.emptyAllField = emptyAllField;
  4502. exports.emptyFunc = emptyFunc;
  4503. exports.excludeFields = excludeFields;
  4504. exports.excludeFieldsDeep = excludeFieldsDeep;
  4505. exports.extractFieldMap = extractFieldMap;
  4506. exports.fetchTimeout = fetchTimeout;
  4507. exports.fill = fill;
  4508. exports.filterItems = filterItems;
  4509. exports.findIndex = findIndex;
  4510. exports.flatMap = flatMap;
  4511. exports.floatEquals = floatEquals;
  4512. exports.formDataToArray = formDataToArray;
  4513. exports.format = format;
  4514. exports.getCookies = getCookies;
  4515. exports.getCursorPosition = getCursorPosition;
  4516. exports.getCusorPostion = getCusorPostion;
  4517. exports.getObjectEntries = getObjectEntries;
  4518. exports.getObjectKeys = getObjectKeys;
  4519. exports.getYearWeek = getYearWeek;
  4520. exports.groupBy = groupBy;
  4521. exports.insertText = insertText;
  4522. exports.isBlank = isBlank;
  4523. exports.isEditable = isEditable;
  4524. exports.isEmpty = isEmpty;
  4525. exports.isFloat = isFloat;
  4526. exports.isNullOrUndefined = isNullOrUndefined;
  4527. exports.isNumber = isNumber;
  4528. exports.isRange = isRange;
  4529. exports.lastFocus = lastFocus;
  4530. exports.listToTree = listToTree;
  4531. exports.loadResource = loadResource;
  4532. exports.loadScript = loadScript;
  4533. exports.logger = logger;
  4534. exports.mapToObject = mapToObject;
  4535. exports.mergeMap = mergeMap;
  4536. exports.nodeBridgeUtil = nodeBridgeUtil;
  4537. exports.not = not;
  4538. exports.objToFormData = objToFormData;
  4539. exports.objectToMap = objectToMap;
  4540. exports.once = once;
  4541. exports.onceOfSameParam = onceOfSameParam;
  4542. exports.or = or;
  4543. exports.parseUrl = parseUrl;
  4544. exports.pathUtil = pathUtil;
  4545. exports.randomInt = randomInt;
  4546. exports.range = range;
  4547. exports.readLocal = readLocal;
  4548. exports.removeEl = removeEl;
  4549. exports.removeText = removeText;
  4550. exports.repeatedCall = repeatedCall;
  4551. exports.returnItself = returnItself;
  4552. exports.returnReasonableItself = returnReasonableItself;
  4553. exports.safeExec = safeExec;
  4554. exports.segmentation = segmentation;
  4555. exports.setCursorPosition = setCursorPosition;
  4556. exports.setCusorPostion = setCusorPostion;
  4557. exports.sets = sets;
  4558. exports.singleModel = singleModel;
  4559. exports.sleep = sleep;
  4560. exports.sortBy = sortBy;
  4561. exports.spliceParams = spliceParams;
  4562. exports.strToArrayBuffer = strToArrayBuffer;
  4563. exports.strToDate = strToDate;
  4564. exports.stringValidator = stringValidator;
  4565. exports.switchMap = switchMap;
  4566. exports.throttle = throttle;
  4567. exports.timing = timing;
  4568. exports.toLowerCase = toLowerCase;
  4569. exports.toObject = toObject;
  4570. exports.toString = toString$1;
  4571. exports.toUpperCase = toUpperCase;
  4572. exports.toggleClass = toggleClass;
  4573. exports.treeMapping = treeMapping;
  4574. exports.treeToList = treeToList;
  4575. exports.trySometime = trySometime;
  4576. exports.trySometimeParallel = trySometimeParallel;
  4577. exports.uniqueBy = uniqueBy;
  4578. exports.wait = wait;
  4579. exports.waitResource = waitResource;
  4580. exports.watch = watch;
  4581. exports.watchEventListener = watchEventListener;
  4582. exports.watchObject = watchObject;
  4583.  
  4584. Object.defineProperty(exports, '__esModule', { value: true });
  4585.  
  4586. }));
  4587. //# sourceMappingURL=rx-util.js.map