vite-uni-dev-tool
Version:
vite-uni-dev-tool, debug, uni-app, 一处编写,到处调试
307 lines (256 loc) • 8.02 kB
text/typescript
import { isBoolean, isNil, isObject, isString, isFunction } from './language';
/**
* 自定义set函数 - 安全地设置嵌套对象属性
* @param obj 目标对象
* @param path 属性路径数组
* @param value 要设置的值
*/
export function setValueByPath(obj: any, path: string, value: any) {
if (obj == null || typeof path !== 'string') {
return;
}
// 支持两种路径格式: "a.b.c" 或 "a[0].b"
const segments = path
.replace(/\[(\d+)\]/g, '.$1') // 将 a[0] 转换为 a.0
.split('.')
.filter((segment) => segment !== ''); // 过滤空段
if (segments.length === 0) {
return;
}
let current = obj;
const lastIndex = segments.length - 1;
for (let i = 0; i < lastIndex; i++) {
const key = segments[i];
// 如果路径不存在且不是最后一个属性,创建对象或数组
if (!current[key]) {
// 判断下一级是否为数字索引(数组)
const nextKey = segments[i + 1];
current[key] = /^\d+$/.test(nextKey) ? [] : {};
}
current = current[key];
}
// 设置最终值
current[segments[lastIndex]] = value;
}
export function getValueByPath(obj: any, path: string, defaultValue?: any) {
if (obj == null || typeof path !== 'string') {
return defaultValue;
}
// 支持两种路径格式: "a.b.c" 或 "a[0].b"
const segments = path
.replace(/\[(\d+)\]/g, '.$1') // 将 a[0] 转换为 a.0
.split('.')
.filter((segment) => segment !== ''); // 过滤空段
let current = obj;
for (const segment of segments) {
if (current == null) {
return defaultValue;
}
current = current[segment];
}
return current !== undefined ? current : defaultValue;
}
/**
* 计算对象在内存中的近似大小
* @param obj 要计算大小的对象
* @returns 对象的近似大小(以字节为单位)
*/
export function calculateObjectSize(obj: any): number {
obj = parseValue(obj);
// 处理基本类型
if (obj === null || obj === undefined) {
return 0;
}
// 处理原始类型
const type = typeof obj;
if (type === 'boolean') {
return 4; // 布尔值通常占4字节
}
if (type === 'number') {
return 8; // JavaScript 数字是双精度浮点数(8字节)
}
if (type === 'string') {
// 假设每个 UTF-16 字符占2字节
return obj.length * 2;
}
if (type === 'symbol') {
// 符号在内存中的大小通常与引用大小相同
return 8;
}
// 处理日期对象
if (obj instanceof Date) {
return 8; // 日期内部表示为时间戳(8字节)
}
// 处理正则表达式
if (obj instanceof RegExp) {
return calculateObjectSize(obj.source) + calculateObjectSize(obj.flags);
}
// 处理数组和普通对象
let totalSize = 0;
// 对象头部信息(简化估算)
totalSize += 24; // 假设对象头部占24字节
if (Array.isArray(obj)) {
// 数组的长度属性
totalSize += 4;
// 计算每个元素的大小
for (const item of obj) {
// 数组元素引用(假设每个引用占8字节)
totalSize += 8;
// 元素本身的大小
totalSize += calculateObjectSize(item);
}
} else {
// 计算对象每个属性的大小
for (const key in obj) {
if (obj?.hasOwnProperty?.(key)) {
// 属性名的大小(假设每个字符占2字节)
totalSize += key.length * 2;
// 属性引用(假设每个引用占8字节)
totalSize += 8;
// 属性值的大小
totalSize += calculateObjectSize(obj[key]);
}
}
}
return totalSize;
}
/**
* 存储单位转换
* @param bytes 字节数
* @param options 转换选项
* @returns 转换后的存储大小字符串
*/
interface FormatStorageOptions {
precision?: number; // 精度,默认为2
useBinary?: boolean; // 是否使用二进制单位,默认为true
includeUnit?: boolean; // 是否包含单位,默认为true
}
export function formatStorageSize(
bytes: number,
options: FormatStorageOptions = {},
): string {
const { precision = 2, useBinary = true, includeUnit = true } = options;
if (bytes === 0) {
return includeUnit ? `0 Bytes` : `0`;
}
const units = useBinary
? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']
: ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
const base = useBinary ? 1024 : 1000;
let unitIndex = 0;
while (bytes >= base && unitIndex < units.length - 1) {
bytes /= base;
unitIndex++;
}
const formattedSize = bytes.toFixed(precision);
return includeUnit ? `${formattedSize} ${units[unitIndex]}` : formattedSize;
}
/**
* 序列化包含循环引用的对象
* @param {Object} obj - 要序列化的对象
* @param {WeakMap} [visited] - 用于记录已处理的对象(内部递归使用)
* @returns {string} - 序列化后的字符串
*/
export function serializeCircular(
obj: any,
options: { maxDepth: number; visited: WeakMap<any, true> } = {
maxDepth: 10,
visited: new WeakMap(),
},
): string {
const { maxDepth, visited } = options;
// 处理 null 和基本类型
if (obj === null || typeof obj !== 'object') {
return JSON.stringify(obj);
}
// 检测循环引用
if (visited.has(obj)) {
return '[Circular]';
}
// 记录当前对象已处理
visited.set(obj, true);
// 处理日期对象
if (obj instanceof Date) {
return `"${obj.toISOString()}"`;
}
// 处理正则表达式
if (obj instanceof RegExp) {
return `/^${obj.source}$/${obj.flags}`;
}
// 检查是否达到最大深度
if (maxDepth <= 0) {
return typeof obj === 'object' ? '{}' : '[]';
}
// 递归处理数组
if (Array.isArray(obj)) {
const serializedItems = obj.map((item) =>
serializeCircular(item, { maxDepth: maxDepth - 1, visited }),
);
return `[${serializedItems.join(', ')}]`;
}
// 递归处理普通对象
const keys = Object.keys(obj);
const serializedProps = keys.map((key) => {
const value = serializeCircular(obj[key], {
maxDepth: maxDepth - 1,
visited,
});
return `"${key}": ${value}`;
});
return `{${serializedProps.join(', ')}}`;
}
/**
* 处理value
* 基础类型数据直接返回value
* 引用类型数据判断 value 是否存在循环引用的情况,存在则将循环引用的第二次赋值为 [Circular]
* 最大处理 deep 层数
*
* @export
* @param {*} value
* @param {number} [deep=6]
*/
export function parseValue(value: any, deep: number = 6) {
const seen = new WeakMap<object, string[]>(); // 存储对象及其路径
function process(value: any, currentDeep: number, path: string[] = []): any {
if (isString(value)) {
return `${value}`;
}
if (isBoolean(value)) {
return `${value}`;
}
if (isFunction(value)) {
return `f(...) { ... }`;
}
if (isNil(value)) {
return `${value}`;
}
// 处理基本类型
if (!isObject(value)) {
return value;
}
// 检查是否达到最大深度
if (currentDeep <= 0) {
return '[MaxDepth]';
}
// 检查循环引用
if (seen.has(value)) {
const circularPath = seen.get(value)!.join('.');
return `[Circular: ${circularPath}]`;
}
// 标记当前对象为已访问,并记录路径
seen.set(value, [...path]);
// 处理数组
if (Array.isArray(value)) {
return value.map((item, index) =>
process(item, currentDeep - 1, [...path, String(index)]),
);
}
// 处理对象
const result: Record<string, any> = {};
for (const [key, val] of Object.entries(value)) {
result[key] = process(val, currentDeep - 1, [...path, key]);
}
return result;
}
return process(value, deep);
}