rx-util

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

目前为 2019-10-02 提交的版本。查看 最新版本

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