@nsnanocat/util
Version:
Pure JS's util module for well-known iOS network tools
268 lines (253 loc) • 9.43 kB
JavaScript
/* https://www.lodashjs.com */
/**
* 轻量 Lodash 工具集。
* Lightweight Lodash-like utilities.
*
* 说明:
* Notes:
* - 这是 Lodash 的“部分方法”简化实现,不等价于完整 Lodash
* - This is a simplified subset, not a full Lodash implementation
* - 各方法语义可参考 Lodash 官方文档
* - Method semantics can be referenced from official Lodash docs
* - 导入时建议使用 `Lodash as _`,遵循 lodash 官方示例惯例
* - Use `Lodash as _` when importing, following official lodash example convention
*
* 参考:
* Reference:
* - https://www.lodashjs.com
* - https://lodash.com
*/
export class Lodash {
/**
* HTML 特殊字符转义。
* Escape HTML special characters.
*
* @param {string} string 输入文本 / Input text.
* @returns {string}
* @see {@link https://lodash.com/docs/#escape lodash.escape}
* @see {@link https://www.lodashjs.com/docs/lodash.escape lodash.escape (中文)}
*/
static escape(string) {
const map = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
};
return string.replace(/[&<>"']/g, m => map[m]);
}
/**
* 按路径读取对象值。
* Get object value by path.
*
* @param {object} [object={}] 目标对象 / Target object.
* @param {string|string[]} [path=""] 路径 / Path.
* @param {*} [defaultValue=undefined] 默认值 / Default value.
* @returns {*}
* @see {@link https://lodash.com/docs/#get lodash.get}
* @see {@link https://www.lodashjs.com/docs/lodash.get lodash.get (中文)}
*/
static get(object = {}, path = "", defaultValue = undefined) {
// translate array case to dot case, then split with .
// a[0].b -> a.0.b -> ['a', '0', 'b']
if (!Array.isArray(path)) path = Lodash.toPath(path);
const result = path.reduce((previousValue, currentValue) => {
return Object(previousValue)[currentValue]; // null undefined get attribute will throwError, Object() can return a object
}, object);
return result === undefined ? defaultValue : result;
}
/**
* 递归合并源对象的自身可枚举属性到目标对象
* Recursively merge source enumerable properties into target object.
* @description 简化版 lodash.merge,用于合并配置对象
* @description A simplified lodash.merge for config merging.
*
* 适用情况:
* - 合并嵌套的配置/设置对象
* - 需要深度合并而非浅层覆盖的场景
* - 多个源对象依次合并到目标对象
*
* 限制:
* - 仅处理普通对象 (Plain Object),不处理 Date/RegExp 等特殊对象
* - Map/Set 仅支持同类型合并,不递归内部值
* - 数组会被直接覆盖,不会合并数组元素
* - 不处理循环引用,可能导致栈溢出
* - 不复制 Symbol 属性和不可枚举属性
* - 不保留原型链,仅处理自身属性
* - 会修改原始目标对象 (mutates target)
*
* @param {object} object - 目标对象
* @param {object} object - Target object.
* @param {...object} sources - 源对象(可多个)
* @param {...object} sources - Source objects.
* @returns {object} 返回合并后的目标对象
* @returns {object} Merged target object.
* @see {@link https://lodash.com/docs/#merge lodash.merge}
* @see {@link https://www.lodashjs.com/docs/lodash.merge lodash.merge (中文)}
* @example
* const target = { a: { b: 1 }, c: 2 };
* const source = { a: { d: 3 }, e: 4 };
* Lodash.merge(target, source);
* // => { a: { b: 1, d: 3 }, c: 2, e: 4 }
*/
static merge(object, ...sources) {
if (object === null || object === undefined) return object;
for (const source of sources) {
if (source === null || source === undefined) continue;
for (const key of Object.keys(source)) {
const sourceValue = source[key];
const targetValue = object[key];
switch (true) {
case Lodash.#isPlainObject(sourceValue) && Lodash.#isPlainObject(targetValue):
// 递归合并对象
object[key] = Lodash.merge(targetValue, sourceValue);
break;
case sourceValue instanceof Map && targetValue instanceof Map:
// 合并 Map(空 Map 跳过)
if (sourceValue.size > 0) {
for (const [k, v] of sourceValue) {
targetValue.set(k, v);
}
}
break;
case sourceValue instanceof Set && targetValue instanceof Set:
// 合并 Set(空 Set 跳过)
if (sourceValue.size > 0) {
for (const v of sourceValue) {
targetValue.add(v);
}
}
break;
case Array.isArray(sourceValue) && sourceValue.length === 0 && targetValue !== undefined:
// 空数组不覆盖已有值
break;
case (sourceValue instanceof Map && sourceValue.size === 0 && targetValue !== undefined):
case (sourceValue instanceof Set && sourceValue.size === 0 && targetValue !== undefined):
// 空 Map/Set 不覆盖已有值
break;
case sourceValue !== undefined:
object[key] = sourceValue;
break;
}
}
}
return object;
}
/**
* 判断值是否为普通对象 (Plain Object)
* Check whether a value is a plain object.
* @param {*} value - 要检查的值
* @param {*} value - Value to check.
* @returns {boolean} 如果是普通对象返回 true
* @returns {boolean} Returns true when value is a plain object.
* @see {@link https://lodash.com/docs/#isPlainObject lodash.isPlainObject}
* @see {@link https://www.lodashjs.com/docs/lodash.isPlainObject lodash.isPlainObject (中文)}
*/
static #isPlainObject(value) {
if (value === null || typeof value !== "object") return false;
const proto = Object.getPrototypeOf(value);
return proto === null || proto === Object.prototype;
}
/**
* 删除对象指定路径并返回对象。
* Omit paths from object and return the same object.
*
* @param {object} [object={}] 目标对象 / Target object.
* @param {string|string[]} [paths=[]] 要删除的路径 / Paths to remove.
* @returns {object}
* @see {@link https://lodash.com/docs/#omit lodash.omit}
* @see {@link https://www.lodashjs.com/docs/lodash.omit lodash.omit (中文)}
*/
static omit(object = {}, paths = []) {
if (!Array.isArray(paths)) paths = [paths.toString()];
paths.forEach(path => Lodash.unset(object, path));
return object;
}
/**
* 仅保留对象指定键(第一层)。
* Pick selected keys from object (top level only).
*
* @param {object} [object={}] 目标对象 / Target object.
* @param {string|string[]} [paths=[]] 需要保留的键 / Keys to keep.
* @returns {object}
* @see {@link https://lodash.com/docs/#pick lodash.pick}
* @see {@link https://www.lodashjs.com/docs/lodash.pick lodash.pick (中文)}
*/
static pick(object = {}, paths = []) {
if (!Array.isArray(paths)) paths = [paths.toString()];
const filteredEntries = Object.entries(object).filter(([key, value]) => paths.includes(key));
return Object.fromEntries(filteredEntries);
}
/**
* 按路径写入对象值。
* Set object value by path.
*
* @param {object} object 目标对象 / Target object.
* @param {string|string[]} path 路径 / Path.
* @param {*} value 写入值 / Value.
* @returns {object}
* @see {@link https://lodash.com/docs/#set lodash.set}
* @see {@link https://www.lodashjs.com/docs/lodash.set lodash.set (中文)}
*/
static set(object, path, value) {
if (!Array.isArray(path)) path = Lodash.toPath(path);
path.slice(0, -1).reduce((previousValue, currentValue, currentIndex) => (Object(previousValue[currentValue]) === previousValue[currentValue] ? previousValue[currentValue] : (previousValue[currentValue] = /^\d+$/.test(path[currentIndex + 1]) ? [] : {})), object)[path[path.length - 1]] = value;
return object;
}
/**
* 将点路径或数组下标路径转换为数组。
* Convert dot/array-index path string into path segments.
*
* @param {string} value 路径字符串 / Path string.
* @returns {string[]}
* @see {@link https://lodash.com/docs/#toPath lodash.toPath}
* @see {@link https://www.lodashjs.com/docs/lodash.toPath lodash.toPath (中文)}
*/
static toPath(value) {
return value
.replace(/\[(\d+)\]/g, ".$1")
.split(".")
.filter(Boolean);
}
/**
* HTML 实体反转义。
* Unescape HTML entities.
*
* @param {string} string 输入文本 / Input text.
* @returns {string}
* @see {@link https://lodash.com/docs/#unescape lodash.unescape}
* @see {@link https://www.lodashjs.com/docs/lodash.unescape lodash.unescape (中文)}
*/
static unescape(string) {
const map = {
"&": "&",
"<": "<",
">": ">",
""": '"',
"'": "'",
};
return string.replace(/&|<|>|"|'/g, m => map[m]);
}
/**
* 删除对象路径对应的值。
* Remove value by object path.
*
* @param {object} [object={}] 目标对象 / Target object.
* @param {string|string[]} [path=""] 路径 / Path.
* @returns {boolean}
* @see {@link https://lodash.com/docs/#unset lodash.unset}
* @see {@link https://www.lodashjs.com/docs/lodash.unset lodash.unset (中文)}
*/
static unset(object = {}, path = "") {
if (!Array.isArray(path)) path = Lodash.toPath(path);
const result = path.reduce((previousValue, currentValue, currentIndex) => {
if (currentIndex === path.length - 1) {
delete previousValue[currentValue];
return true;
}
return Object(previousValue)[currentValue];
}, object);
return result;
}
}