rx-util

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址