@whitesev/utils
Version:
一个常用的工具库
1,656 lines (1,635 loc) • 129 kB
text/typescript
import {
clearInterval as WorkerClearInterval,
clearTimeout as WorkerClearTimeout,
setInterval as WorkerSetInterval,
setTimeout as WorkerSetTimeout,
} from "worker-timers";
import { version } from "./../package.json";
import { ajaxHooker } from "./ajaxHooker/ajaxHooker.js";
import { AjaxHooker1_2_4 } from "./ajaxHooker/ajaxHooker1.2.4";
import { ColorConversion } from "./ColorConversion";
import { CommonUtil } from "./CommonUtil";
import { UtilsDictionary } from "./Dictionary";
import { domUtils } from "./DOMUtils";
import { GBKEncoder } from "./GBKEncoder";
import { Hooks } from "./Hooks";
import { Httpx } from "./Httpx";
import { indexedDB } from "./indexedDB";
import { LockFunction } from "./LockFunction";
import { Log } from "./Log";
import { ModuleRaid } from "./ModuleRaid";
import { Progress } from "./Progress";
import { TryCatch } from "./TryCatch";
import type { UtilsAjaxHookResult } from "./types/ajaxHooker";
import { type ArgsType, type JSTypeNames, type UtilsOwnObject } from "./types/global";
import type { ReactInstance } from "./types/React";
import type { WindowApiOption } from "./types/WindowApi";
import { GenerateUUID } from "./UtilsCommon";
import { UtilsGMCookie } from "./UtilsGMCookie";
import { GMMenu } from "./UtilsGMMenu";
import { Vue } from "./Vue";
import { WindowApi } from "./WindowApi";
class Utils {
private windowApi: typeof WindowApi.prototype;
constructor(option?: WindowApiOption) {
this.windowApi = new WindowApi(option);
}
/** 版本号 */
version = version;
/**
* JSON数据从源端替换到目标端中,如果目标端存在该数据则替换,不添加,返回结果为目标端替换完毕的结果
* @param target 目标数据
* @param source 源数据
* @param isAdd 是否可以追加键,默认false
* @example
* Utils.assign({"1":1,"2":{"3":3}}, {"2":{"3":4}});
* >
* {
"1": 1,
"2": {
"3": 4
}
}
*/
assign = CommonUtil.assign.bind(CommonUtil);
/**
* 异步替换字符串
* @param string 需要被替换的目标字符串
* @param pattern 正则匹配模型
* @param asyncFn 异步获取的函数
*/
asyncReplaceAll(
string: string,
pattern: RegExp | string,
asyncFn: (item: string) => Promise<string>
): Promise<string>;
async asyncReplaceAll(string: string, pattern: RegExp | string, asyncFn: (item: string) => Promise<string>) {
const that = this;
if (typeof string !== "string") {
throw new TypeError("string必须是字符串");
}
if (typeof asyncFn !== "function") {
throw new TypeError("asyncFn必须是函数");
}
let reg;
if (typeof pattern === "string") {
reg = new RegExp(that.toRegExpStr(pattern), "g");
} else if (pattern instanceof RegExp) {
if (!pattern.global) {
throw new TypeError("pattern必须是全局匹配");
}
reg = new RegExp(pattern);
} else {
throw new TypeError("pattern必须是正则对象");
}
let result = [];
let match;
let lastIndex = 0;
while ((match = reg.exec(string)) !== null) {
/* 异步获取匹配对应的字符串 */
const item = asyncFn(match[0]);
/* 获取该匹配项和上一个匹配项的中间的字符串 */
const prefix = string.slice(lastIndex, match.index);
lastIndex = match.index + match[0].length;
result.push(item);
result.push(prefix);
}
result.push(string.slice(lastIndex));
/* 等待所有异步完成 */
result = await Promise.all(result);
return result.join("");
}
/**
* ajax劫持库,支持xhr和fetch劫持。
* + 来源:https://bbs.tampermonkey.net.cn/thread-3284-1-1.html
* + 作者:cxxjackie
* + 版本:1.4.8
* + 旧版本:1.2.4
* + 文档:https://scriptcat.org/zh-CN/script-show-page/637/
* @param useOldVersion 是否使用旧版本,默认false
*/
ajaxHooker = (useOldVersion: boolean = false): UtilsAjaxHookResult => {
if (useOldVersion) {
return AjaxHooker1_2_4();
} else {
return ajaxHooker();
}
};
/**
* 根据坐标点击canvas元素的内部位置
* @param canvasElement 画布元素
* @param clientX X坐标,默认值0
* @param clientY Y坐标,默认值0
* @param view 触发的事件目标
*/
canvasClickByPosition(
canvasElement: HTMLCanvasElement,
clientX?: number | string,
clientY?: number | string,
view?: Window & typeof globalThis
): void;
canvasClickByPosition(canvasElement: HTMLCanvasElement, clientX = 0, clientY = 0, view = this.windowApi.window) {
if (!(canvasElement instanceof HTMLCanvasElement)) {
throw new Error("Utils.canvasClickByPosition 参数canvasElement必须是canvas元素");
}
clientX = parseInt(clientX.toString());
clientY = parseInt(clientY.toString());
const eventInit: MouseEventInit & {
cancelBubble: boolean;
} = {
cancelBubble: true,
cancelable: true,
clientX: clientX,
clientY: clientY,
view: view,
detail: 1,
};
canvasElement.dispatchEvent(new MouseEvent("mousedown", eventInit));
canvasElement.dispatchEvent(new MouseEvent("mouseup", eventInit));
}
/**
* 复制formData数据
* @param formData 需要clone的数据
*/
cloneFormData<T extends FormData>(formData: T, filterFn?: (key: string, value: string | Blob) => boolean): T {
const clonedFormData = new FormData() as T;
formData.forEach((value, key) => {
const isFilter = typeof filterFn === "function" ? filterFn(key, value) : false;
if (typeof isFilter === "boolean" && isFilter) {
return;
}
clonedFormData.append(key, value);
});
return clonedFormData;
}
/**
* 函数重载实现
* @example
* let getUsers = Utils.createOverload();
* getUsers.addImpl("",()=>{
* console.log("无参数");
* });
*
* getUsers.addImpl("boolean",()=>{
* console.log("boolean");
* });
*
* getUsers.addImpl("string",()=>{
* console.log("string");
* });
*
* getUsers.addImpl("number","string",()=>{
* console.log("number string");
* });
*/
createOverload(): {
/**
* 前面的参数都是字符串,最后一个参数是函数
*/
addImpl<T extends JSTypeNames[]>(...args: [...T, (...args: ArgsType<T>) => any]): void;
};
createOverload(): {
/**
* 前面的参数都是字符串,最后一个参数是函数
*/
addImpl<T extends JSTypeNames[]>(...args: [...T, (...args: ArgsType<T>) => any]): void;
} {
const fnMap = new Map();
function overload(this: any, ...args: any[]) {
const key = args.map((it) => typeof it).join(",");
const fn = fnMap.get(key);
if (!fn) {
throw new TypeError("没有找到对应的实现");
}
return fn.apply(this, args);
}
overload.addImpl = function (...args: any[]) {
const fn = args.pop();
if (typeof fn !== "function") {
throw new TypeError("最后一个参数必须是函数");
}
const key = args.join(",");
fnMap.set(key, fn);
};
return overload;
}
/**
* 颜色转换
* @returns
*/
ColorConversion = ColorConversion;
/**
* 深拷贝
* @param obj 对象
*/
deepClone = CommonUtil.deepClone.bind(CommonUtil);
/**
* 防抖函数
* @param fn 需要触发的回调
* @param delay 防抖判定时间(毫秒),默认是0ms
*/
debounce<A extends any[], R>(fn: (...args: A) => R, delay?: number): (...args: A) => void;
debounce<A extends any[], R>(fn: (...args: A) => R, delay = 0) {
let timer: any = null as any;
const that = this;
return function (...args: A) {
that.workerClearTimeout(timer);
timer = that.workerSetTimeout(function () {
fn.apply(that, args);
}, delay);
};
}
/**
* 字典
* @example
* let dictionary = new Utils.Dictionary();
* let dictionary2 = new Utils.Dictionary();
* dictionary.set("test","111");
* dictionary.get("test");
* > '111'
* dictionary.has("test");
* > true
* dictionary.concat(dictionary2);
**/
Dictionary = UtilsDictionary;
/**
* 下载base64格式的数据
* @param base64Data 需要转换的base64数据
* @param fileName 需要保存的文件名
* @param isIFrame (可选)是否使用iframe进行下载
* @example
* Utils.downloadBase64("data:image/jpeg:base64/,xxxxxx");
**/
downloadBase64(base64Data: string, fileName: string, isIFrame?: boolean): void;
downloadBase64(base64Data: string, fileName: string, isIFrame = false) {
const that = this;
if (typeof base64Data !== "string") {
throw new Error("Utils.downloadBase64 参数 base64Data 必须为 string 类型");
}
if (typeof fileName !== "string") {
throw new Error("Utils.downloadBase64 参数 fileName 必须为 string 类型");
}
if (isIFrame) {
/* 使用iframe */
const $iframe = this.windowApi.document.createElement("iframe");
$iframe.style.display = "none";
$iframe.src = base64Data;
(this.windowApi.document.body || this.windowApi.document.documentElement).appendChild($iframe);
that.workerSetTimeout(() => {
$iframe!.contentWindow!.document.execCommand("SaveAs", true, fileName);
(this.windowApi.document.body || this.windowApi.document.documentElement).removeChild($iframe);
}, 100);
} else {
/* 使用A标签 */
const linkElement = this.windowApi.document.createElement("a");
linkElement.setAttribute("target", "_blank");
linkElement.download = fileName;
linkElement.href = base64Data;
linkElement.click();
}
}
/**
* 选中页面中的文字,类似Ctrl+F的选中
* @param str (可选)需要寻找的字符串,默认为空
* @param caseSensitive(可选)默认false
* + true 区分大小写
* + false (默认) 不区分大小写
* @returns
* + true 找到
* + false 未找到
* + undefined 不可使用该Api
* @example
* Utils.findWebPageVisibleText("xxxxx");
* > true
**/
findWebPageVisibleText(str?: string, caseSensitive?: boolean): boolean | void;
findWebPageVisibleText(str = "", caseSensitive = false) {
let TRange = null;
let strFound;
if ((this.windowApi.globalThis as any).find) {
/* CODE FOR BROWSERS THAT SUPPORT window.find */
const windowFind = (this.windowApi.self as any).find;
strFound = windowFind(str, caseSensitive, true, true, false);
if (strFound && this.windowApi.self.getSelection && !this.windowApi.self.getSelection()!.anchorNode) {
strFound = windowFind(str, caseSensitive, true, true, false);
}
if (!strFound) {
strFound = windowFind(str, 0, 1);
while (windowFind(str, 0, 1)) continue;
}
} else if (navigator.appName.indexOf("Microsoft") != -1) {
/* EXPLORER-SPECIFIC CODE */
if (TRange != null) {
TRange = TRange as any;
TRange.collapse(false);
strFound = TRange.findText(str);
if (strFound) TRange.select();
}
if (TRange == null || strFound == 0) {
TRange = (this.windowApi.self.document.body as any).createTextRange();
strFound = TRange.findText(str);
if (strFound) TRange.select();
}
} else if (navigator.appName == "Opera") {
alert("Opera browsers not supported, sorry...");
return;
}
return strFound ? true : false;
}
/**
* 格式化byte为KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB
* @param byteSize 字节
* @param addType (可选)是否添加单位
* + true (默认) 添加单位
* + false 不添加单位
* @returns
* + {string} 当addType为true时,且保留小数点末尾2位
* + {number} 当addType为false时,且保留小数点末尾2位
* @example
* Utils.formatByteToSize("812304");
* > '793.27KB'
* @example
* Utils.formatByteToSize("812304",false);
* > 793.27
**/
formatByteToSize(byteSize: number | string): number;
formatByteToSize<T extends boolean>(byteSize: number | string, addType?: T): T extends true ? string : number;
formatByteToSize(byteSize: number | string, addType = true) {
byteSize = parseInt(byteSize.toString());
if (isNaN(byteSize)) {
throw new Error("Utils.formatByteToSize 参数 byteSize 格式不正确");
}
let result = 0;
let resultType = "KB";
const sizeData: UtilsOwnObject<number> = {};
sizeData.B = 1;
sizeData.KB = 1024;
sizeData.MB = sizeData.KB * sizeData.KB;
sizeData.GB = sizeData.MB * sizeData.KB;
sizeData.TB = sizeData.GB * sizeData.KB;
sizeData.PB = sizeData.TB * sizeData.KB;
sizeData.EB = sizeData.PB * sizeData.KB;
sizeData.ZB = sizeData.EB * sizeData.KB;
sizeData.YB = sizeData.ZB * sizeData.KB;
sizeData.BB = sizeData.YB * sizeData.KB;
sizeData.NB = sizeData.BB * sizeData.KB;
sizeData.DB = sizeData.NB * sizeData.KB;
for (const key in sizeData) {
result = byteSize / (sizeData as any)[key];
resultType = key;
if (sizeData.KB >= result) {
break;
}
}
result = result.toFixed(2) as any;
result = addType ? result + resultType.toString() : (parseFloat(result.toString()) as any);
return result;
}
/**
* 应用场景: 当你想要获取数组形式的元素时,它可能是其它的选择器,那么需要按照先后顺序填入参数
* 第一个是优先级最高的,依次下降,如果都没有,返回空列表
* 支持document.querySelectorAll、$("")、()=>{return document.querySelectorAll("")}
* @param NodeList
* @example
* Utils.getNodeListValue(
* document.querySelectorAll("div.xxx"),
* document.querySelectorAll("a.xxx")
* );
* > [...div,div,div]
* @example
* Utils.getNodeListValue(divGetFunction,aGetFunction);
* > [...div,div,div]
*/
getNodeListValue(...args: (NodeList | (() => HTMLElement))[]): HTMLElement[];
getNodeListValue(...args: (NodeList | (() => HTMLElement))[]) {
let resultArray: HTMLElement[] = [];
for (const arg of args) {
let value = arg as any;
if (typeof arg === "function") {
/* 方法 */
value = arg();
}
if (value.length !== 0) {
resultArray = [...value];
break;
}
}
return resultArray;
}
/**
* 自动判断N个参数,获取非空的值,如果都是空,返回最后一个值
*/
getNonNullValue(...args: any[]): any;
getNonNullValue(...args: any[]) {
let resultValue = args[args.length - 1];
const that = this;
for (const argValue of args) {
if (that.isNotNull(argValue)) {
resultValue = argValue;
break;
}
}
return resultValue;
}
/**
* 获取格式化后的时间
* @param text (可选)需要格式化的字符串或者时间戳,默认:new Date()
* @param formatType (可选)格式化成的显示类型,默认:yyyy-MM-dd HH:mm:ss
* + yyyy 年
* + MM 月
* + dd 天
* + HH 时 (24小时制)
* + hh 时 (12小时制)
* + mm 分
* + ss 秒
* @returns {string} 返回格式化后的时间
* @example
* Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss");
* > '23:59:00'
* @example
* Utils.formatTime(1899187424988,"HH:mm:ss");
* > '15:10:13'
* @example
* Utils.formatTime()
* > '2023-1-1 00:00:00'
**/
formatTime(text?: string | number | Date, formatType?: string): string;
/**
* 获取格式化后的时间
* @param text (可选)需要格式化的字符串或者时间戳,默认:new Date()
* @param formatType (可选)格式化成的显示类型,默认:yyyy-MM-dd HH:mm:ss
* + yyyy 年
* + MM 月
* + dd 天
* + HH 时 (24小时制)
* + hh 时 (12小时制)
* + mm 分
* + ss 秒
* @returns {string} 返回格式化后的时间
* @example
* Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss");
* > '23:59:00'
* @example
* Utils.formatTime(1899187424988,"HH:mm:ss");
* > '15:10:13'
* @example
* Utils.formatTime()
* > '2023-1-1 00:00:00'
**/
formatTime(
text?: string | number | Date,
formatType?:
| "yyyy-MM-dd HH:mm:ss"
| "yyyy/MM/dd HH:mm:ss"
| "yyyy_MM_dd_HH_mm_ss"
| "yyyy年MM月dd日 HH时mm分ss秒"
| "yyyy年MM月dd日 hh:mm:ss"
| "yyyy年MM月dd日 HH:mm:ss"
| "yyyy-MM-dd"
| "yyyyMMdd"
| "HH:mm:ss"
| "yyyy"
| "MM"
| "dd"
| "HH"
| "mm"
| "ss"
): string;
formatTime(text = new Date(), formatType = "yyyy-MM-dd HH:mm:ss") {
const time = text == null ? new Date() : new Date(text);
/**
* 校验时间补0
* @param timeNum
* @returns
*/
function checkTime(timeNum: number) {
if (timeNum < 10) return `0${timeNum}`;
return timeNum;
}
/**
* 时间制修改 24小时制转12小时制
* @param hourNum 小时
* @returns
*/
function timeSystemChange(hourNum: number) {
return hourNum > 12 ? hourNum - 12 : hourNum;
}
const timeRegexp = {
yyyy: time.getFullYear(),
/* 年 */
MM: checkTime(time.getMonth() + 1),
/* 月 */
dd: checkTime(time.getDate()),
/* 日 */
HH: checkTime(time.getHours()),
/* 时 (24小时制) */
hh: checkTime(timeSystemChange(time.getHours())),
/* 时 (12小时制) */
mm: checkTime(time.getMinutes()),
/* 分 */
ss: checkTime(time.getSeconds()),
/* 秒 */
};
Object.keys(timeRegexp).forEach(function (key) {
const replaecRegexp = new RegExp(key, "g");
formatType = formatType.replace(replaecRegexp, (timeRegexp as any)[key]);
});
return formatType;
}
/**
* 字符串格式的时间转时间戳
* @param text 字符串格式的时间,例如:
* + 2022-11-21 00:00:00
* + 00:00:00
* @returns 返回时间戳
* @example
* Utils.formatToTimeStamp("2022-11-21 00:00:00");
* > 1668960000000
**/
formatToTimeStamp(text: string): number;
formatToTimeStamp(text: string) {
/* 把字符串格式的时间(完整,包括日期和时间)格式化成时间 */
if (typeof text !== "string") {
throw new Error("Utils.formatToTimeStamp 参数 text 必须为 string 类型");
}
if (text.length === 8) {
/* 该字符串只有时分秒 */
const today = new Date();
text = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()} ${text}`;
}
text = text.substring(0, 19);
text = text.replace(/-/g, "/");
const timestamp = new Date(text).getTime();
return timestamp;
}
/**
* gbk格式的url编码,来自https://greasyfork.org/zh-CN/scripts/427726-gbk-url-js
* @example
* let gbkEncoder = new Utils.GBKEncoder();
* gbkEncoder.encode("测试");
* > '%B2%E2%CA%D4'
* gbkEncoder.decode("%B2%E2%CA%D4");
* > 测试
*/
GBKEncoder = GBKEncoder;
/**
* 获取NodeList或Array对象中的最后一个的值
* @param targetObj
* @returns
* @example
* Utils.getArrayLastValue(document.querySelectorAll("div"));
* > div
* @example
* Utils.getArrayLastValue([1,2,3,4,5]);
* > 5
*/
getArrayLastValue<R = unknown>(targetObj: NodeList | any[]): R;
getArrayLastValue(target: NodeList | any[]) {
return target[target.length - 1];
}
/**
* 应用场景: 当想获取的元素可能是不同的选择器的时候,按顺序优先级获取
* 参数类型可以是Element或者是Function
* @returns 如果都没有的话,返回null
* @example
* // 如果a.aaa不存在的话,取a.bbb,这里假设a.aaa不存在
* Utils.getArrayRealValue(document.querySelector("a.aaa"),document.querySelector("a.bbb"));
* > a.bbb
* @example
* Utils.getArrayRealValue(()=>{return document.querySelector("a.aaa").href},()=>{document.querySelector("a.bbb").getAttribute("data-href")});
* > javascript:;
*/
getArrayRealValue(...args: (NodeList | (() => HTMLElement))[]): any;
getArrayRealValue(...args: (NodeList | (() => HTMLElement))[]) {
let result = null;
for (let arg of args) {
if (typeof arg === "function") {
/* 方法 */
(<any>arg) = arg();
}
if (arg != null) {
result = arg;
break;
}
}
return result;
}
/**
* 获取天数差异,如何获取某个时间与另一个时间相差的天数
* @param timestamp1 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now()
* @param timestamp2 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now()
* @param type (可选)返回的数字的表达的类型,比如:年、月、天、时、分、秒、auto,默认天
* @example
* Utils.getDaysDifference(new Date().getTime());
* > 0
* @example
* Utils.getDaysDifference(new Date().getTime(),undefined,"秒");
* > 0
*/
getDaysDifference(timestamp1?: number, timestamp2?: number, type?: "auto"): string;
/**
* 获取天数差异,如何获取某个时间与另一个时间相差的天数
* @param timestamp1 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now()
* @param timestamp2 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now()
* @param type (可选)返回的数字的表达的类型,比如:年、月、天、时、分、秒、auto,默认天
* @example
* Utils.getDaysDifference(new Date().getTime());
* > 0
* @example
* Utils.getDaysDifference(new Date().getTime(),undefined,"秒");
* > 0
*/
getDaysDifference(timestamp1?: number, timestamp2?: number, type?: "年" | "月" | "天" | "时" | "分" | "秒"): number;
getDaysDifference(timestamp1 = Date.now(), timestamp2 = Date.now(), type = "天"): number | string {
type = type.trim();
if (timestamp1.toString().length === 10) {
timestamp1 = timestamp1 * 1000;
}
if (timestamp2.toString().length === 10) {
timestamp2 = timestamp2 * 1000;
}
const smallTimeStamp = timestamp1 > timestamp2 ? timestamp2 : timestamp1;
const bigTimeStamp = timestamp1 > timestamp2 ? timestamp1 : timestamp2;
const oneSecond = 1000; /* 一秒的毫秒数 */
const oneMinute = 60 * oneSecond; /* 一分钟的毫秒数 */
const oneHour = 60 * oneMinute; /* 一小时的毫秒数 */
const oneDay = 24 * oneHour; /* 一天的毫秒数 */
const oneMonth = 30 * oneDay; /* 一个月的毫秒数(30天) */
const oneYear = 12 * oneMonth; /* 一年的毫秒数 */
const bigDate = new Date(bigTimeStamp);
const smallDate = new Date(smallTimeStamp);
let remainderValue = 1;
if (type === "年") {
remainderValue = oneYear;
} else if (type === "月") {
remainderValue = oneMonth;
} else if (type === "天") {
remainderValue = oneDay;
} else if (type === "时") {
remainderValue = oneHour;
} else if (type === "分") {
remainderValue = oneMinute;
} else if (type === "秒") {
remainderValue = oneSecond;
}
let diffValue = Math.round(Math.abs(((bigDate as any) - (smallDate as any)) / remainderValue));
if (type === "auto") {
const timeDifference = bigTimeStamp - smallTimeStamp;
diffValue = Math.floor(timeDifference / (24 * 3600 * 1000));
if (diffValue > 0) {
(diffValue as any) = `${diffValue}天`;
} else {
/* 计算出小时数 */
const leave1 = timeDifference % (24 * 3600 * 1000); /* 计算天数后剩余的毫秒数 */
const hours = Math.floor(leave1 / (3600 * 1000));
if (hours > 0) {
(diffValue as any) = `${hours}小时`;
} else {
/* 计算相差分钟数 */
const leave2 = leave1 % (3600 * 1000); /* 计算小时数后剩余的毫秒数 */
const minutes = Math.floor(leave2 / (60 * 1000));
if (minutes > 0) {
(diffValue as any) = `${minutes}分钟`;
} else {
/* 计算相差秒数 */
const leave3 = leave2 % (60 * 1000); /* 计算分钟数后剩余的毫秒数 */
const seconds = Math.round(leave3 / 1000);
(diffValue as any) = `${seconds}秒`;
}
}
}
}
return diffValue;
}
/**
* 获取最大值
* @example
* Utils.getMaxValue(1,3,5,7,9)
* > 9
*/
getMaxValue(...args: number[]): number;
/**
* 获取最大值
* @example
* Utils.getMaxValue([1,3,5])
* > 5
*/
getMaxValue(val: number[]): number;
/**
* 获取最大值
* @example
* Utils.getMaxValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)})
* > 456
*/
getMaxValue(val: UtilsOwnObject<number>, handler: (key: any, value: any) => number): number;
/**
* 获取最大值
* @example
* Utils.getMaxValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)})
* > 2
*/
getMaxValue(...args: any[]): number {
const result = [...args];
let newResult: number[] = [];
if (result.length === 0) {
return void 0 as any as number;
}
if (result.length > 1) {
if (result.length === 2 && typeof result[0] === "object" && typeof result[1] === "function") {
const data = result[0];
const handleDataFunc = result[1];
Object.keys(data).forEach((keyName) => {
newResult = [...newResult, handleDataFunc(keyName, data[keyName])];
});
} else {
result.forEach((item) => {
if (!isNaN(parseFloat(item))) {
newResult = [...newResult, parseFloat(item)];
}
});
}
return Math.max(...newResult);
} else {
result[0].forEach((item: any) => {
if (!isNaN(parseFloat(item))) {
newResult = [...newResult, parseFloat(item)];
}
});
return Math.max(...newResult);
}
}
/**
* 获取页面中最大的z-index的元素信息
* @param deviation 获取最大的z-index值的偏移,默认是1
* @param node 进行判断的元素,默认是document
* @param ignoreCallBack 执行元素处理时调用的函数,返回false可忽略不想要处理的元素
* @example
* Utils.getMaxZIndexNodeInfo();
* > {
* node: ...,
* zIndex: 1001
* }
**/
getMaxZIndexNodeInfo(
deviation?: number,
target?: Element | ShadowRoot | Document,
ignoreCallBack?: ($ele: Element | HTMLElement | ShadowRoot) => boolean | void
): {
node: Element;
zIndex: number;
};
getMaxZIndexNodeInfo(
deviation = 1,
target: Element | ShadowRoot | Document = this.windowApi.document,
ignoreCallBack?: ($ele: Element | HTMLElement | ShadowRoot) => boolean | void
): {
node: Element;
zIndex: number;
} {
deviation = Number.isNaN(deviation) ? 1 : deviation;
const that = this;
// 最大值 2147483647
// const maxZIndex = Math.pow(2, 31) - 1;
// 比较值 2000000000
const maxZIndexCompare = 2 * Math.pow(10, 9);
// 当前页面最大的z-index
let zIndex = 0;
// 当前的最大z-index的元素,调试使用
let maxZIndexNode: Element | null = null;
/**
* 元素是否可见
* @param $css
*/
function isVisibleNode($css: CSSStyleDeclaration): boolean {
return $css.position !== "static" && $css.display !== "none";
}
/**
* 查询元素的z-index
* 并比较值是否是已获取的最大值
* @param $ele
*/
function queryMaxZIndex($ele: Element) {
if (typeof ignoreCallBack === "function") {
const ignoreResult = ignoreCallBack($ele);
if (typeof ignoreResult === "boolean" && !ignoreResult) {
return;
}
}
/** 元素的样式 */
const nodeStyle = that.windowApi.window.getComputedStyle($ele);
/* 不对position为static和display为none的元素进行获取它们的z-index */
if (isVisibleNode(nodeStyle)) {
const nodeZIndex = parseInt(nodeStyle.zIndex);
if (!isNaN(nodeZIndex)) {
if (nodeZIndex > zIndex) {
// 赋值到全局
zIndex = nodeZIndex;
maxZIndexNode = $ele;
}
}
// 判断shadowRoot
if ($ele.shadowRoot != null && $ele instanceof ShadowRoot) {
$ele.shadowRoot.querySelectorAll("*").forEach(($shadowEle) => {
queryMaxZIndex($shadowEle);
});
}
}
}
target.querySelectorAll("*").forEach(($ele) => {
queryMaxZIndex($ele);
});
zIndex += deviation;
if (zIndex >= maxZIndexCompare) {
// 最好不要超过最大值
zIndex = maxZIndexCompare;
}
return {
node: maxZIndexNode!,
zIndex: zIndex,
};
}
/**
* 获取页面中最大的z-index
* @param deviation 获取最大的z-index值的偏移,默认是1
* @param node 进行判断的元素,默认是document
* @param ignoreCallBack 执行元素处理时调用的函数,返回false可忽略不想要处理的元素
* @example
* Utils.getMaxZIndex();
* > 1001
**/
getMaxZIndex(
deviation?: number,
target?: Element | DocumentOrShadowRoot | Document,
ignoreCallBack?: ($ele: Element | HTMLElement | ShadowRoot) => boolean | void
): number;
getMaxZIndex(
deviation = 1,
target: Element | ShadowRoot | Document = this.windowApi.document,
ignoreCallBack?: ($ele: Element | HTMLElement | ShadowRoot) => boolean | void
): number {
return this.getMaxZIndexNodeInfo(deviation, target, ignoreCallBack).zIndex;
}
/**
* 获取最小值
* @example
* Utils.getMinValue(1,3,5,7,9)
* > 1
*/
getMinValue(...args: number[]): number;
/**
* 获取最小值
* @example
* Utils.getMinValue([1,3,5])
* > 1
*/
getMinValue(val: number[]): number;
/**
* 获取最小值
* @example
* Utils.getMinValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)})
* > 123
*/
getMinValue(val: UtilsOwnObject<number>, handler: (key: any, value: any) => number): number;
/**
* 获取最小值
* @example
* Utils.getMinValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)})
* > 0
*/
getMinValue(val: UtilsOwnObject<number>[], handler: (index: number, value: any) => number): number;
getMinValue(...args: any[]): number {
const result = [...args];
let newResult: number[] = [];
if (result.length === 0) {
return void 0 as any as number;
}
if (result.length > 1) {
if (result.length === 2 && typeof result[0] === "object" && typeof result[1] === "function") {
const data = result[0];
const handleDataFunc = result[1];
Object.keys(data).forEach((keyName) => {
newResult = [...newResult, handleDataFunc(keyName, data[keyName])];
});
} else {
result.forEach((item) => {
if (!isNaN(parseFloat(item))) {
newResult = [...newResult, parseFloat(item)];
}
});
}
return Math.min(...newResult);
} else {
result[0].forEach((item: any) => {
if (!isNaN(parseFloat(item))) {
newResult = [...newResult, parseFloat(item)];
}
});
return Math.min(...newResult);
}
}
/**
* 获取随机的安卓手机User-Agent
* @example
* Utils.getRandomAndroidUA();
* > 'Mozilla/5.0 (Linux; Android 10; MI 13 Build/OPR1.170623.027; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.3490.40 Mobile Safari/537.36'
**/
getRandomAndroidUA(): string {
const that = this;
const mobileNameList = [
"LDN-LX3",
"RNE-L03",
"ASUS_X00ID Build/NMF26F",
"WAS-LX3",
"PRA-LX3",
"MYA-L03",
"Moto G Play",
"Moto C Build/NRD90M.063",
"Redmi Note 4 Build/NRD90M",
"HUAWEI VNS-L21 Build/HUAWEIVNS-L21",
"VTR-L09",
"TRT-LX3",
"M2003J15SC Build/RP1A.200720.011; wv",
"MI 13 Build/OPR1.170623.027; wv",
];
/* 安卓版本 */
const androidVersion = that.getRandomValue(12, 14);
/* 手机型号 */
const randomMobile = that.getRandomValue(mobileNameList);
/* chrome大版本号 */
const chromeVersion1 = that.getRandomValue(130, 140);
const chromeVersion2 = that.getRandomValue(0, 0);
const chromeVersion3 = that.getRandomValue(2272, 6099);
const chromeVersion4 = that.getRandomValue(1, 218);
return `Mozilla/5.0 (Linux; Android ${androidVersion}; ${randomMobile}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.${chromeVersion2}.${chromeVersion3}.${chromeVersion4} Mobile Safari/537.36`;
}
/**
* 获取随机的电脑端User-Agent
* + Mozilla/5.0:以前用于Netscape浏览器,目前大多数浏览器UA都会带有
* + Windows NT 13:代表Window11系统
* + Windows NT 10.0:代表Window10系统
* + Windows NT 6.1:代表windows7系统
* + WOW64:Windows-on-Windows 64-bit,32位的应用程序运行于此64位处理器上
* + Win64:64位
* + AppleWebKit/537.36:浏览器内核
* + KHTML:HTML排版引擎
* + like Gecko:这不是Geckeo 浏览器,但是运行起来像Geckeo浏览器
* + Chrome/106.0.5068.19:Chrome版本号
* + Safari/537.36:宣称自己是Safari?
* @returns 返回随机字符串
* @example
* Utils.getRandomPCUA();
* > 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5068.19 Safari/537.36'
**/
getRandomPCUA(): string {
const that = this;
/* chrome大版本号 */
const chromeVersion1 = that.getRandomValue(130, 140);
const chromeVersion2 = that.getRandomValue(0, 0);
const chromeVersion3 = that.getRandomValue(2272, 6099);
const chromeVersion4 = that.getRandomValue(1, 218);
return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.${chromeVersion2}.${chromeVersion3}.${chromeVersion4} Safari/537.36`;
}
/**
* 获取随机值
* @example
* Utils.getRandomValue(1,9,6,99)
* > 6
*/
getRandomValue<T>(...args: T[]): T;
/**
* 获取随机值
* @example
* Utils.getRandomValue([1,2,3])
* > 3
* @example
* Utils.getRandomValue({1:"结果1",2:"结果2",3:"结果3"}})
* > 结果2
*/
getRandomValue<T>(val: T[] | UtilsOwnObject<T>): T;
/**
* 获取两个数之间随机值
* @example
* Utils.getRandomValue(1,9)
* > 6
*/
getRandomValue(val_1: number, val_2: number): number;
/**
* 获取随机值
* @example
* Utils.getRandomValue({1:1},{2:2})
* > {1: 1}
*/
getRandomValue<T>(val_1: UtilsOwnObject<T>, val_2: UtilsOwnObject<T>): T;
getRandomValue(...args: any[]): any {
const result = [...args];
if (result.length > 1) {
if (result.length === 2 && typeof result[0] === "number" && typeof result[1] === "number") {
const leftNumber = result[0] > result[1] ? result[1] : result[0];
const rightNumber = result[0] > result[1] ? result[0] : result[1];
return Math.round(Math.random() * (rightNumber - leftNumber)) + leftNumber;
} else {
return result[Math.floor(Math.random() * result.length)];
}
} else if (result.length === 1) {
const paramData = result[0];
if (Array.isArray(paramData)) {
return paramData[Math.floor(Math.random() * paramData.length)];
} else if (typeof paramData === "object" && Object.keys(paramData).length > 0) {
const paramObjDataKey = Object.keys(paramData)[Math.floor(Math.random() * Object.keys(paramData).length)];
return paramData[paramObjDataKey];
} else {
return paramData;
}
}
}
/**
* 获取元素上的使用React框架的实例属性,目前包括reactFiber、reactProps、reactEvents、reactEventHandlers、reactInternalInstance
* @param element 需要获取的目标元素
* @returns
* @example
* Utils.getReactInstance(document.querySelector("input"))?.reactProps?.onChange({target:{value:"123"}});
*/
getReactInstance(element: HTMLElement | Element): ReactInstance {
const result = {};
if (element == null) {
return result;
}
const keys = Object.keys(element);
keys.forEach((domPropsName) => {
if (domPropsName.startsWith("__react")) {
const propsName = domPropsName.replace(/__(.+)\$.+/i, "$1");
const propsValue = Reflect.get(element, domPropsName);
if (propsName in result) {
console.error(`重复属性 ${domPropsName}`);
} else {
Reflect.set(result, propsName, propsValue);
}
}
});
return result;
}
/**
* 获取对象上的Symbol属性,如果没设置keyName,那么返回一个对象,对象是所有遍历到的Symbol对象
* @param target 目标对象
* @param keyName (可选)Symbol名或者Symbol对象
*/
getSymbol(target: any, keyName?: string | symbol) {
if (typeof target !== "object") {
throw new TypeError("target不是一个对象");
}
const objectsSymbols = Object.getOwnPropertySymbols(target);
if (typeof keyName === "string") {
const findSymbol = objectsSymbols.find((key) => {
return key.toString() === keyName;
});
if (findSymbol) {
return target[findSymbol];
}
} else if (typeof keyName === "symbol") {
const findSymbol = objectsSymbols.find((key) => {
return key === keyName;
});
if (findSymbol) {
return (target as any)[findSymbol];
}
} else {
const result = {};
objectsSymbols.forEach((item) => {
(result as any)[item] = target[item];
});
return result;
}
}
/**
* 获取文本的字符长度
* @param text
* @example
* Utils.getTextLength("测试文本")
* > 12
*/
getTextLength(text: string): number {
const encoder = new TextEncoder();
const bytes = encoder.encode(text);
return bytes.length;
}
/**
* 获取文本占据的空间大小,返回自动的单位,如12 Kb,14 K,20 MB,1 GB
* @param text 目标字符串
* @param addType (可选)是否添加单位
* + true (默认) 自动添加单位
* + false 不添加单位
* @example
* Utils.getTextStorageSize("测试文本");
* > '12.00B'
*/
getTextStorageSize<T extends boolean>(text: string, addType?: T): T extends true ? string : number;
getTextStorageSize(text: string, addType = true) {
const that = this;
return that.formatByteToSize(that.getTextLength(text), addType);
}
/**
* 获取迅雷协议的Url
* @param url Url链接或者其它信息
*/
getThunderUrl(url: string): string;
getThunderUrl(url: string): string {
if (url == null) {
throw new TypeError("url不能为空");
}
if (typeof url !== "string") {
throw new TypeError("url必须是string类型");
}
if (url.trim() === "") {
throw new TypeError("url不能为空字符串或纯空格");
}
return `thunder://${this.windowApi.globalThis.btoa(`AA${url}ZZ`)}`;
}
/**
* 对于GM_cookie的兼容写法,当无法使用GM_cookie时可以使用这个,但是并不完全兼容,有些写不出来且限制了httponly是无法访问的
* @example
let GM_cookie = new Utils.GM_Cookie();
GM_cookie.list({name:"xxx_cookie_xxx"},function(cookies,error){
if (!error) {
console.log(cookies);
console.log(cookies.value);
} else {
console.error(error);
}
});
GM_cookie.set({name:"xxx_cookie_test_xxx",value:"这是Cookie测试值"},function(error){
if (error) {
console.error(error);
} else {
console.log('Cookie set successfully.');
}
})
GM_cookie.delete({name:"xxx_cookie_test_xxx"},function(error){
if (error) {
console.error(error);
} else {
console.log('Cookie set successfully.');
}
})
**/
GM_Cookie = UtilsGMCookie;
/**
* 注册油猴菜单,要求本地存储的键名不能存在其它键名`GM_Menu_Local_Map`会冲突/覆盖
* @example
let GM_Menu = new Utils.GM_Menu({
data: [
{
menu_key: "menu_key",
text: "测试按钮",
enable: true,
accessKey: "a",
autoClose: false,
showText(text, enable) {
return "[" + (enable ? "√" : "×") + "]" + text;
},
callback(data) {
console.log("点击菜单,值修改为", data.enable);
},
},
],
autoReload: false,
GM_getValue,
GM_setValue,
GM_registerMenuCommand,
GM_unregisterMenuCommand,
});
// 获取某个菜单项的值
GM_Menu.get("menu_key");
> true
// 获取某个菜单项的开启/关闭后显示的文本
GM_Menu.getShowTextValue("menu_key");
> √测试按钮
// 添加键为menu_key2的菜单项
GM_Menu.add({
key:"menu_key2",
text: "测试按钮2",
enable: false,
showText(text,enable){
return "[" + (enable ? "√" : "×") + "]" + text;
},
callback(data){
console.log("点击菜单,值修改为",data.enable);
}
});
// 使用数组的方式添加多个菜单,如menu_key3、menu_key4
GM_Menu.add([
{
key:"menu_key3",
text: "测试按钮3",
enable: false,
showText(text,enable){
return "[" + (enable ? "√" : "×") + "]" + text;
},
callback(data){
console.log("点击菜单,值修改为",data.enable);
}
},
{
key:"menu_key4",
text: "测试按钮4",
enable: false,
showText(text,enable){
return "[" + (enable ? "√" : "×") + "]" + text;
},
callback(data){
console.log("点击菜单,值修改为",data.enable);
}
}
]);
// 更新键为menu_key的显示文字和点击回调
GM_Menu.update({
menu_key:{
text: "更新后的测试按钮",
enable: true,
showText(text,enable){
return "[" + (enable ? "√" : "×") + "]" + text;
},
callback(data){
console.log("点击菜单更新后的测试按钮,新值修改为",data.enable);
}
}
});
// 删除键为menu_key的菜单
GM_Menu.delete("menu_key");
**/
GM_Menu = GMMenu;
/**
* 基于Function prototype,能够勾住和释放任何函数
*
* .hook
* + realFunc {string} 用于保存原始函数的函数名称,用于unHook
* + hookFunc {string} 替换的hook函数
* + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1
* + methodName {string} 匿名函数需显式传入目标函数名eg:this.Begin = function(){....};}
*
* .unhook
* + realFunc {string} 用于保存原始函数的函数名称,用于unHook
* + funcName {string} 被Hook的函数名称
* + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1
* @example
let hook = new Utils.Hooks();
hook.initEnv();
function myFunction(){
console.log("我自己需要执行的函数");
}
function testFunction(){
console.log("正常执行的函数");
}
testFunction.hook(testFunction,myFunction,window);
**/
Hooks = Hooks;
/**
* 为减少代码量和回调,把GM_xmlhttpRequest封装
* 文档地址: https://www.tampermonkey.net/documentation.php?ext=iikm
* 其中onloadstart、onprogress、onreadystatechange是回调形式,onabort、ontimeout、onerror可以设置全局回调函数
* @param _GM_xmlHttpRequest_ 油猴中的GM_xmlhttpRequest
* @example
let httpx = new Utils.Httpx(GM_xmlhttpRequest);
let postResp = await httpx.post({
url:url,
data:JSON.stringify({
test:1
}),
timeout: 5000
});
console.log(postResp);
> {
status: true,
data: {responseText: "...", response: xxx,...},
msg: "请求完毕",
type: "onload",
}
if(postResp === "onload" && postResp.status){
// onload
}else if(postResp === "ontimeout"){
// ontimeout
}
* @example
// 也可以先配置全局参数
let httpx = new Utils.Httpx(GM_xmlhttpRequest);
httpx.config({
timeout: 5000,
async: false,
responseType: "html",
redirect: "follow",
})
// 优先级为 默认details < 全局details < 单独的details
*/
Httpx = Httpx;
/**
* 浏览器端的indexedDB操作封装
* @example
let db = new Utils.indexedDB('web_DB', 'nav_text')
let data = {name:'管理员', roleId: 1, type: 1};
db.save('list',data).then((resolve)=>{
console.log(resolve,'存储成功')
})
db.get('list').then((resolve)=>{
console.log(resolve,'查询成功')
})
db.getPaging('list',20,10).then((resolve)=>{
console.log(resolve,'查询分页偏移第20,一共10行成功');
})
db.delete('list').then(resolve=>{
console.log(resolve,'删除成功---->>>>>>name')
})
db.deleteAll().then(resolve=>{
console.log(resolve,'清除数据库---->>>>>>name')
})
**/
indexedDB = indexedDB;
/**
* 判断目标函数是否是Native Code
* @param target
* @returns
* + true 是Native
* + false 不是Native
* @example
* Utils.isNativeFunc(window.location.assign)
* > true
*/
isNativeFunc(target: (...args: any[]) => any): boolean;
isNativeFunc(target: (...args: any[]) => any): boolean {
return Boolean(target.toString().match(/^function .*\(\) { \[native code\] }$/));
}
/**
* 判断当前的位置是否位于页面底部附近
* @param nearBottomHeight (可选)判断在底部的误差值,默认:50
* @returns
* + true 在底部附近
* + false 不在底部附近
*/
isNearBottom(nearBottomHeight?: number): boolean;
/**
* 判断元素内当前的位置是否位于元素内底部附近
* @param target 需要判断的元素
* @param nearBottomHeight (可选)判断在底部的误差值,默认:50
*/
isNearBottom(target: HTMLElement, nearBottomHeight?: number): boolean;
isNearBottom(...args: any[]): boolean {
let nearBottomHeight = 50;
const checkWindow = () => {
// 已滚动的距离
const scrollTop: number = this.windowApi.window.pageYOffset || this.windowApi.document.documentElement.scrollTop;
// 视窗高度
const viewportHeight: number =
this.windowApi.window.innerHeight || this.windowApi.document.documentElement.clientHeight;
// 最大滚动距离
const maxScrollHeight: number = this.windowApi.document.documentElement.scrollHeight - nearBottomHeight;
return scrollTop + viewportHeight >= maxScrollHeight;
};
const checkNode = ($ele: HTMLElement) => {
// 已滚动的距离
const scrollTop: number = $ele.scrollTop;
// 视窗高度
const viewportHeight: number = $ele.clientHeight;
// 最大滚动距离
const maxScrollHeight: number = $ele.scrollHeight - viewportHeight - nearBottomHeight;
return scrollTop >= maxScrollHeight;
};
const firstArg = args[0];
if (args.length === 0 || typeof args[0] === "number") {
// nearBottomHeight
//
return checkWindow();
} else if (typeof args[0] === "object" && args[0] instanceof HTMLElement) {
// target
// target,nearBottomHeight
if (typeof args[1] === "number" && !Number.isNaN(args[1])) {
nearBottomHeight = args[1];
}
return checkNode(args[0]);
} else {
throw new TypeError(`参数1类型错误${typeof firstArg}`);
}
}
/**
* 判断对象是否是元素
* @param target
* @returns
* + true 是元素
* + false 不是元素
* @example
* Utils.isDOM(document.querySelector("a"))
* > true
*/
isDOM = CommonUtil.isDOM.bind(CommonUtil);
/**
* 判断浏览器是否支持全屏
*/
isFullscreenEnabled(): boolean;
isFullscreenEnabled(): boolean {
return !!(
(this.windowApi.document as any).fullscreenEnabled ||
(this.windowApi.document as any).webkitFullScreenEnabled ||
(this.windowApi.document as any).mozFullScreenEnabled ||
(this.windowApi.document as any).msFullScreenEnabled
);
}
/**
* 判断对象是否是jQuery对象
* @param target
* @returns
* + true 是jQuery对象
* + false 不是jQuery对象
* @example
* Utils.isJQuery($("a"))
* > true
*/
isJQuery(target: any): boolean;
isJQuery(target: any): boolean {
let result = false;
if (typeof jQuery === "object" && target instanceof jQuery) {
result = true;
}
if (target == null) {
return false;
}
if (typeof target === "object") {
/* 也有种可能,这个jQuery对象是1.8.3版本的,页面中的jQuery是3.4.1版本的 */
const jQueryProps = [
"add",
"addBack",
"addClass",
"after",
"ajaxComplete",
"ajaxError",
"ajaxSend",
"ajaxStart",
"ajaxStop",
"ajaxSuccess",
"animate",
"append",
"appendTo",
"attr",
"before",
"bind",
"blur",
"change",
"children",
"clearQueue",
"click",
"clone",
"closest",
"constructor",
"contents",
"contextmenu",
"css",
"data",
"dblclick",
"delay",
"delegate",
"dequeue",
"each",
"empty",
"end",
"eq",
"extend",
"fadeIn",
"fadeOut",
"fadeTo",
"fadeToggle",
"filter",
"find",
"first",
"focus",
"focusin",
"focusout",
"get",
"has",
"hasClass",
"height",
"hide",
"hover",
"html",
"index",
"init",
"innerHeight",
"innerWidth",
"insertAfter",
"insertBefore",
"is",
"jquery",
"keydown",
"keypress",
"keyup",
"last",
"load",
"map",
"mousedown",
"mouseenter",
"mouseleave",
"mousemove",
"mouseout",
"mouseover",
"mouseup",
"next",
"nextAll",
"not",
"off",
"offset",
"offsetParent",
"on",
"one",
"outerHeight",
"outerWidth",
"parent",
"parents",
"position",
"prepend",
"prependTo",
"prev",
"prevAll",
"prevUntil",
"promise",
"prop",
"pushStack",
"queue",
"ready",
"remove",
"removeAttr",
"removeClass",
"removeData",
"removeProp",
"replaceAll",
"replaceWith",
"resize",
"scroll",
"scrollLeft",
"scrollTop",
"select",
"show",
"siblings",
"slice",
"slideDown",
"slideToggle",
"slideUp",
"sort",
"splice",
"text",
"toArray",
"toggle",
"toggleClass",
"trigger",
"triggerHandler",
"unbind",
"width",
"wrap",
];
for (const jQueryPropsName of jQueryProps) {
if (!(jQueryPropsName in target)) {
result = false;
/* console.log(jQueryPropsName); */
break;
} else {
result = true;
}
}
}
return result;
}
/**
* 判断当前设备是否是移动端
* @param userAgent (可选)UA字符串,默认使用当前的navigator.userAgent
* @returns
* + true 是移动端
* + false 不是移动端
* @example
* Utils.isPhone();
* > true
**/
isPhone(userAgent?: string): boolean;
isPhone(userAgent: string = navigator.userAgent): boolean {
return Boolean(/(iPhone|iPad|iPod|iOS|Android|Mobile)/i.test(userAgent));
}
/**
* 判断传递的字符串是否是由相同的字符组成
* @param targetStr 需要判断的字符串,长度(.length)需要≥2
* @param coefficient 系数(默认:1),某个字符重复的系数大于它那么就是返回true,默认全部
*/
isSameChars(targetStr: string, coefficient?: number): boolean;
isSameChars(targetStr: string, coefficient: number = 1): boolean {
if (typeof targetStr !== "string") {
throw new TypeError("参数 str 必须是 string 类型");
}
if (targetStr.length < 2) {
return false;
}
targetStr = targetStr.toLowerCase();
const targetCharMap: UtilsOwnObject<string> = {};
let targetStrLength = 0;
for (const char of targetStr) {
if (Reflect.has(targetCharMap, char)) {
(targetCharMap as any)[char]++;
} else {
(targetCharMap as any)[char] = 1;
}
targetStrLength++;
}
let result = false;
for (const char in targetCharMap) {
if ((targetCharMap as any)[char] / targetStrLength >= coefficient) {