JavaScript 公用脚本工具库

1. 数据类型

判断 JavaScript 的数据类型,包含基本类型、引用类型以及自定义的类。

1.1 核心代码

function getDataType(data) {
  // 处理 null 的特殊情况
  if (data=== null) return 'null'
  
  // 处理基本类型(除 null 外)和函数
  const baseType = typeof data
  if (baseType !== 'object') {
    // 处理 NaN 的特殊情况
    if (Number.isNaN(data)) return 'nan'
    return baseType
  }

  // 通过 Object.prototype.toString 获取详细类型
  const detailType = Object.prototype.toString.call(data)
  const typeName = detailType.slice(8, -1).toLowerCase()

  // 处理包装对象的情况(如 new Number(123))
  if (typeName === 'object') {
    const constructorName = data.constructor?.name?.toLowerCase()
    if (constructorName && constructorName !== 'object') {
      return constructorName
    }
  }

  return typeName
}

// 测试用例
console.log(getDataType(null))          // 'null'
console.log(getDataType(undefined))     // 'undefined'
console.log(getDataType(true))          // 'boolean'
console.log(getDataType(42))            // 'number'
console.log(getDataType(NaN))           // 'nan'
console.log(getDataType('hello'))       // 'string'
console.log(getDataType(Symbol()))      // 'symbol'
console.log(getDataType(10n))           // 'bigint'
console.log(getDataType({}))            // 'object'
console.log(getDataType([]))            // 'array'
console.log(getDataType(/regex/))       // 'regexp'
console.log(getDataType(new Date()))    // 'date'
console.log(getDataType(() => {}))      // 'function'
console.log(getDataType(new Number(1))) // 'number'
console.log(getDataType(new String('')))// 'string'
console.log(getDataType(Math))          // 'math'
console.log(getDataType(JSON))          // 'json'

1.2 代码解析

功能特性:

  1. 完整支持所有 JavaScript 内置类型
  2. 特殊处理情况:
    • 精确识别 null
    • 单独处理 NaN
    • 区分包装对象和原始值
    • 识别内置对象类型(如 DateRegExp 等)
  3. 返回统一的小写类型字符串

实现原理:

  1. 优先处理 null 的特殊情况
  2. 使用 typeof 处理基本类型(除 null
  3. 通过 Object.prototype.toString 识别对象子类型
  4. 处理包装对象的构造函数名称
  5. 单独处理 NaN 的特殊情况
  6. 方法返回的可能类型包括:
    • ‘null’
    • ‘undefined’
    • ‘boolean’
    • ‘number’
    • ‘nan’
    • ‘string’
    • ‘symbol’
    • ‘bigint’
    • ‘array’
    • ‘object’
    • ‘function’
    • ‘regexp’
    • ‘date’
    • ‘math’
    • ‘json’
    • 其他内置对象的类型名称(如 ‘map’、‘set’ 等)

2. 解析路径

支持绝对路径和相对路径的解析,并兼容浏览器和 Node.js 环境。

2.1 核心代码

/**
 * 解析 URL 地址
 * @param {string} url - 要解析的 URL 字符串
 * @param {string} [base] - 可选的基础 URL(用于解析相对路径)
 * @returns {object} 包含解析后信息的对象
 */
function parseUrl(url, base) {
  // 自动处理浏览器环境的基础 URL
  if (typeof base === 'undefined' && typeof window !== 'undefined') {
    base = window.location.href;
  }

  try {
    // 创建 URL 对象
    const parsed = new URL(url, base || 'http://127.0.0.1');
    
    // 解析查询参数
    const queryParams = {};
    parsed.searchParams.forEach((value, key) => {
      queryParams[key] = decodeURIComponent(value);
    });

    return {
      href: decodeURIComponent(parsed.href),
      protocol: parsed.protocol.replace(':', ''),
      hostname: parsed.hostname,
      port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
      path: parsed.pathname,
      query: decodeURIComponent(parsed.search),
      queryParams: queryParams,
      hash: parsed.hash.replace('#', ''),
      isAbsolute: /^[a-z][a-z0-9+.-]*:/.test(url),
      origin: parsed.origin,
      auth: parsed.username || parsed.password 
        ? `${parsed.username}:${parsed.password}`
        : null
    };
  } catch (error) {
    console.error('Invalid URL:', error.message);
    return null;
  }
}

// 使用示例
console.log(parseUrl('https://developer.mozilla.org/zh-CN/search?q=URL'))
console.log(parseUrl('../path/../to/file?query=1', 'https://developer.mozilla.org/dir/sub'))
console.log(parseUrl('//developer.mozilla.org/zh-CN/docs/Learn'))
console.log(parseUrl('/images/logo.png'))
console.log(parseUrl('?query=test'))
console.log(parseUrl('#anchor'))

2.2 代码解析

功能特性:

  1. 完整解析:支持解析协议、认证信息、域名、端口、路径、查询参数和哈希
  2. 智能处理,支持:
    • 自动识别绝对/相对 URL
    • 自动解码 URL 编码字符(如中文)
    • 自动处理默认端口(HTTP=80, HTTPS=443
  3. 兼容性,支持:
    • 浏览器和 Node 环境
    • 正确处理相对路径(需提供 base 参数)
  4. 错误处理:对非法 URL 返回 null 并打印错误信息
  5. 兼容中文等特殊字符编解码

返回值解析:

属性类型说明
hrefstring完整 URL
protocolstring协议(不含冒号)
hostnamestring域名
portnumber端口号
pathstring路径部分
querystring完整查询字符串(含问号)
queryParamsobject解析后的查询参数对象
hashstring哈希值(不含井号)
isAbsoluteboolean是否为绝对 URL
originstring协议+域名+端口
authstring认证信息(username:password)

3. 颜色转换

十六进制的颜色值转 rgba 颜色字符串。

3.1 核心代码

/**
 * 将十六进制颜色值转换为RGBA格式
 * @param {string} hex - 十六进制颜色值(支持 #RGB、#RRGGBB 格式)
 * @param {number} alpha - 透明度(0-1之间的数值)
 * @returns {string} rgba颜色字符串
 */
function hexToRGBA(hex, alpha = 1) {
  // 移除 # 号并验证格式
  let hexValue = hex.replace(/^#/, '');
  
  // 有效性验证
  if (!/^([0-9A-F]{3}){1,2}$/i.test(hexValue)) {
    throw new Error('Invalid hexadecimal color value');
  }

  // 处理简写格式(如 #RGB)
  if (hexValue.length === 3) {
    hexValue = hexValue.split('').map(c => c + c).join('');
  }

  // 解析颜色分量
  const r = parseInt(hexValue.substring(0, 2), 16);
  const g = parseInt(hexValue.substring(2, 4), 16);
  const b = parseInt(hexValue.substring(4, 6), 16);

  // 处理透明度范围
  const a = Math.min(1, Math.max(0, Number(alpha)));

  return `rgba(${r}, ${g}, ${b}, ${a.toFixed(4).replace(/\.?0+$/, '')})`;
}

// 使用示例
console.log(hexToRGBA('#f00', 0.5));       // rgba(255, 0, 0, 0.5)
console.log(hexToRGBA('#00ff00', 1));     // rgba(0, 255, 0, 1)
console.log(hexToRGBA('abc', 0.75));      // rgba(170, 187, 204, 0.75)
console.log(hexToRGBA('#336699', 1.5));   // rgba(51, 102, 153, 1)
console.log(hexToRGBA('#a1b2c3', -0.1));  // rgba(161, 178, 195, 0)

3.2 代码解析

功能特性:

  1. 格式支持:
    • 支持 #RGB 简写格式
    • 支持 #RRGGBB 完整格式
    • 支持不带 # 号的输入
  2. 有效性验证:
    • 自动检测非法十六进制颜色值
    • 抛出可识别的错误信息
  3. 透明度处理:
    • 自动限制透明度在 0-1 之间
    • 支持数字类型输入
    • 智能处理小数精度
  4. 输出优化:
    • 自动去除多余的小数位(如 1.0000 → 1
    • 保留必要精度(如 0.1234 → 0.1234

3.3 异常处理

try {
  hexToRGBA('#zzz', 0.5)
} catch (e) {
  console.error(e.message) // 输出:Invalid hexadecimal color value
}

4. 数据深拷贝

深度拷贝 JavaScript 的数据类型。

4.1 核心代码

function deepClone(target, map = new WeakMap()) {
  // 处理基本类型和函数
  if (typeof target !== 'object' || target === null) return target

  // 处理循环引用
  if (map.has(target)) return map.get(target)

  // 处理特殊对象类型
  const constructor = target.constructor
  if (/^(Date|RegExp)$/i.test(constructor.name)) {
    return new constructor(target)
  }

  // 初始化拷贝对象
  const clone = Array.isArray(target) ? [] : {}

  // 记录已拷贝对象
  map.set(target, clone)

  // 处理Symbol作为key的情况
  const symbolKeys = Object.getOwnPropertySymbols(target)
  if (symbolKeys.length) {
    symbolKeys.forEach(symKey => {
      clone[symKey] = deepClone(target[symKey], map)
    })
  }

  // 递归拷贝属性
  for (const key in target) {
    if (target.hasOwnProperty(key)) {
      clone[key] = deepClone(target[key], map)
    }
  }
  return clone
}

4.2 代码解析

功能特性:

  1. 支持数据类型:
    • 基本类型(Number/String/Boolean等)
    • 普通对象/数组
    • Date/RegExp对象
    • 循环引用
    • Symbol 作为对象键
  2. 解决循环引用:
    • 使用 WeakMap 记录已拷贝对象,避免无限递归
  3. 保持原型链:
    • 自动限制透明度在 0-1 之间
    • 支持数字类型输入
    • 智能处理小数精度
  4. 输出优化:
    • 通过 target.constructor 创建正确类型的对象

方案对比:

方法优点缺点
JSON方法简单快速丢失函数/Symbol/特殊对象
递归实现支持更多类型需自行处理特殊对象和循环引用
structuredClone浏览器原生支持不支持函数/无法克隆DOM节点
Lodash.cloneDeep功能最完整需引入第三方库
本实现平衡功能与代码体积不处理Map/Set等ES6+数据结构

5. 数据深度合并

将两个复杂的对象,合并成单个对象。

5.1 核心代码

function deepMerge(target, source, map = new WeakMap()) {
  // 处理非对象类型或循环引用
  if (typeof source !== 'object' || source === null) return source;
  if (map.has(source)) return map.get(source);

  // 处理特殊对象类型(如Date/RegExp)
  const constructor = source.constructor;
  if (constructor === Date || constructor === RegExp) {
    return new constructor(source);
  }

  // 初始化合并容器
  const output = Array.isArray(source) ? [...source] : { ...target };
  map.set(source, output);

  // 递归合并逻辑
  Object.keys(source).forEach(key => {
    const sourceVal = source[key];
    const targetVal = target?.[key];
    
    if (typeof sourceVal === 'object' && sourceVal !== null) {
      output[key] = deepMerge(targetVal || {}, sourceVal, map);
    } else if (Array.isArray(sourceVal)) {
      output[key] = Array.isArray(targetVal) 
        ? [...targetVal, ...sourceVal] 
        : [...sourceVal];
    } else {
      output[key] = sourceVal;
    }
  });

  // 处理Symbol键
  Object.getOwnPropertySymbols(source).forEach(sym => {
    output[sym] = source[sym];
  });

  return output;
}

5.2 代码解析

功能特性:

  1. 合并策略:
    • 基础类型:源对象属性直接覆盖目标对象
    • 嵌套对象:递归合并保留双方属性,如 {a:1} 合并 {b:2} {a:1,b:2}
    • 数组处理:默认采用追加模式([1].concat([2]),可通过修改逻辑实现覆盖
  2. 特殊类型支持:
    • 自动处理 Date/RegExp 等对象的独立拷贝
    • 支持 Symbol 作为对象键的合并
  3. 防御性设计:
    • 使用 WeakMap 防止循环引用导致的无限递归
    • 通过对象展开符 ... 确保原始对象不被修改

方案对比:

方法优点缺点
JSON序列化简单快速丢失函数/Symbol/原型链
Lodash.mergeWith功能最完整需引入第三方库
扩展运算符语法简洁仅支持一级浅合并
本实现支持递归合并和特殊类型需自行处理Map/Set等类型

扩展建议:

  1. 数组覆盖模式:
    • 将数组处理逻辑改为 output[key] = [...sourceVal] 实现完全覆盖
  2. 混合合并策略:
    • 可添加回调函数参数,允许自定义不同类型属性的合并规则
  3. 防御性设计:
    • 使用 Lodash_.mergeWith() 实现更复杂的合并逻辑

6. 数据精度

用于处理 JavaScript 的数据精度问题。

6.1 核心代码

function getDecimalLength(num) {
    const str = String(num);
    const index = str.indexOf('.');
    const expIndex = str.indexOf('e-');
    if (expIndex > 0) { // 处理科学计数法
        const exp = parseInt(str.slice(expIndex + 2));
        return (index > 0 ? str.slice(index + 1, expIndex).length : 0) + exp;
    }
    return index > 0 ? str.slice(index + 1).length : 0;
}

function toInt(num, len) {
    return +num.toString().replace('.', '').padEnd(len + (num.toString().split('.')[0] || '').length, '0');
}

const precision = {
    add: (a, b) => {
        const maxLen = Math.max(getDecimalLength(a), getDecimalLength(b));
        const base = 10 ** maxLen;
        return (precision.mul(a, base) + precision.mul(b, base)) / base;
    },
    sub: (a, b) => precision.add(a, -b),
    mul: (a, b) => {
        const len1 = getDecimalLength(a);
        const len2 = getDecimalLength(b);
        const base = 10 ** (len1 + len2);
        return (toInt(a, len2) * toInt(b, len1)) / base;
    },
    div: (a, b) => {
        const len1 = getDecimalLength(a);
        const len2 = getDecimalLength(b);
        const base = 10 ** (len2 - len1);
        return (toInt(a, len2) / toInt(b, len1)) * base;
    }
};

6.2 代码解析

功能特性:

  1. 处理科学计数法表达式
  2. 处理字符串数值表达式

扩展建议:

  1. 精度限制:Number.MAX_SAFE_INTEGER,超出范围抛出异常
  2. 非数字以及非字符串数字:需要处理 NaN 等情况

7. 时间格式化

给定合法时间和格式,将其格式化成对应的数据格式。

7.1 核心代码

function formatDate(date = new Date(), format = 'YYYY-MM-DD HH:mm:ss') {
    const d = date instanceof Date ? date : new Date(date);
    
    const pad = (n) => String(n).padStart(2, '0');
    const time = {
        YYYY: d.getFullYear(),
        MM: pad(d.getMonth() + 1),
        DD: pad(d.getDate()),
        HH: pad(d.getHours()),
        hh: pad(d.getHours() % 12 || 12),
        mm: pad(d.getMinutes()),
        ss: pad(d.getSeconds()),
        A: d.getHours() < 12 ? 'AM' : 'PM',
        a: d.getHours() < 12 ? 'am' : 'pm'
    };
    
    return format.replace(
        /YYYY|MM|DD|HH|hh|mm|ss|A|a/g, 
        match => time[match]
    );
}

7.2 代码解析

功能特性:

  1. 支持动态格式配置
  2. 自动处理12/24小时制转换
  3. 严格区分大小写(如MM与mm)
  4. 保持原Date对象引用关系
  5. 时间复杂度O(n),n为格式字符串长度

扩展建议:

  1. 支持毫秒级格式化
  2. 处理无效日期返回值
  3. 处理时区转换

8. 扁平结构树数据格式化

将扁平化的树数据格式化成树结构数据。

8.1 核心代码

function formatFlattenTreeData(nodes, id = 'id', pId = 'pId') {
    const map = new Map();
    const roots = [];
    const orphanNodes = []; // 新增孤儿节点收集,即 pId 在 nodes 中找不到对应的 id

    // 浅拷贝避免污染原数据
    const copy = nodes.map(n => ({ ...n, children: [] }));
    copy.forEach(node => map.set(node[id], node));

    copy.forEach(node => {
        const parentId = node[pId];
        if (parentId != null) {
            map.has(parentId)
                ? map.get(parentId).children.push(node)
                : orphanNodes.push(node); // 明确处理异常数据
        } else {
            roots.push(node);
        }
    });

    if (orphanNodes.length > 0) {
        console.warn(`Orphan nodes detected: ${orphanNodes.length}`);
    }
    // 可选项:将孤儿节点作为根节点返回
    return roots.concat(orphanNodes);
}

8.2 代码解析

功能特性:

  1. 高效构建:利用Map实现O(1)查找,两次O(n)遍历完成树构建,时间复杂度O(n)最优
  2. 逻辑清晰:分初始化、建立索引、构建父子关系三阶段,代码易读
  3. 多根支持:正确收集 parentId == null 的节点作为根节点集合
  4. 浅拷贝避免污染原数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值