夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript文章

    • 33个非常实用的JavaScript一行代码
    • 实用JavaScricpt封装
      • 比较两个值是否相等
      • 对象属性过滤
      • 将一维数组按指定长度转为二维数组
      • 按多条件过滤数组
      • 防抖
      • 节流
      • 获取 url 中的参数
      • 持久化对象
      • 倒计时
      • 格式化时间显示
    • ES5面向对象
    • ES6面向对象
    • 多种数组去重性能对比
    • 获取ip地址
    • ES6扩展符运用
    • file、base64和blob互相转化
    • 图片转base64格式
    • 弹窗的封装
    • websocket实现二维码扫描
    • 比typeof运算符更准确的类型判断
    • 前端下载excel文件
    • 数组与对象的互相转化
    • JS编码与转码
    • 树结构与扁平化相互转换
    • 实现微信登录
    • 常用表单验证
    • 在H5中实现OCR拍照识别身份证功能
    • 滚动条元素定位
    • 获取目标容器内的元素列表
    • 微信H5支付
    • js中的函数注释
    • 大模型流式数据前端实现
    • 定高虚拟列表
    • 不定高虚拟列表
    • 虚拟表格
    • 移动端文件预览
    • 浏览器缓存机制
    • 前端从剪切板获取word图片
    • 微信公众号调试
    • html导出pdf
    • node转化多语言
  • 前端架构

  • 学习笔记

  • 全栈项目

  • 前端
  • JavaScript文章
CUGGZ && yemao-zi
2021-11-02
目录

实用JavaScricpt封装

# 实用JavaScricpt封装

# 比较两个值是否相等

/**
 * 深度比较两个值是否相等
 * @param {*} a - 第一个值
 * @param {*} b - 第二个值
 * @returns {boolean} 是否相等
 */
function deepEqual(a, b) {
  // 处理基本类型和引用相同的情况
  if (a === b) return true;

  // 处理NaN情况
  if (Number.isNaN(a) && Number.isNaN(b)) return true;

  // 检查类型是否一致
  if (typeof a !== typeof b) return false;

  // 处理null值
  if (a === null || b === null) return a === b;

  // 处理数组
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) {
      if (!deepEqual(a[i], b[i])) return false;
    }
    return true;
  }

  // 处理对象
  if (typeof a === 'object' && typeof b === 'object') {
    const keysA = Object.keys(a);
    const keysB = Object.keys(b);

    if (keysA.length !== keysB.length) return false;

    for (const key of keysA) {
      if (!keysB.includes(key) || !deepEqual(a[key], b[key])) {
        return false;
      }
    }
    return true;
  }

  // 其他情况不相等
  return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 对象属性过滤

/**
 * 对象属性过滤
 * @params obj 对象。 Object
 * @params formitFn 自定义对数据格式化。(key:string,value:obj[key]):any=>newValue
 * @params emptyTypes 自定义需要移除的值,默认为[null, undefined, '',{},[]]。 any[]
 * @return 处理后的对象。Object
 */
export const omitEmptyValues = (
  obj,
  formitFn = (_key, value) => value,
  emptyTypes = [null, undefined, {}, [], ''],
) => {
  return Object.keys(obj)
    .filter(
      (key) =>
        !emptyTypes.some((item) => {
          return typeof item === 'object' && item !== null
            ? lodash.isEqual(item, obj[key])
            : item === obj[key];
        }),
    )
    .reduce((acc, key) => ({ ...acc, [key]: formitFn(key, obj[key]) }), {});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 将一维数组按指定长度转为二维数组

function pages(arr, len) {
    const pages = []
    arr.forEach((item, index) => {
        const page = Math.floor(index / len)
        if (!pages[page]) {
            pages[page] = []
        }
        pages[page].push(item)
    })
    return pages
}

// 使用
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(pages(arr, 3)) // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
console.log(pages(arr, 8)) // [[1, 2, 3, 4, 5, 6, 7, 8], [9]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 按多条件过滤数组

/**
 * @description: 手动多条件过滤数组
 * @param {Array} array 需要过滤的数组
 * @param {Object} filters 过滤条件
 * @param {Function} formmat 遍历时格式化对象(不改变原数组)
 * @return {Array}
 * @example
 * 
 const filters2 = {
  address: ['India','USA'],
  name: ['zs','ls'],
  sex:'男'
};
const users = [
  {name: 'zs', email: 'xxx1.com', age: 25, address: 'USA',sex:1},
  {name: 'ls', email: 'xxx2.com', age: 35, address: 'India',sex:1},
  {name: 'ww Smith', email: 'xxx3.com', age: 28, address: 'England',sex:0}
];
multiFilter(users,filters1,(item)=>({sex:item.sex===1?'男':'女',...item}))
 */
function multiFilter(array, filters, formmat = (item) => item) {
  const filterKeys = Object.keys(filters);
  const result = array.filter((item) => {
    return filterKeys.every((key) => {
      const newItem = formmat(item, key);
      let filterValue = filters[key];
      if (typeof filterValue === "string") {
        filterValue = filterValue ? [filterValue] : [];
      }
      if (!filterValue?.length) return true;
      return !!~filterValue.indexOf(newItem[key]);
    });
  });
  return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 防抖

  • scroll事件滚动触发事件
  • 搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。
  • 表单验证
  • 按钮提交事件。
  • 浏览器窗口缩放,resize事件(如窗口停止改变大小之后重新计算布局)等。
export function isObject(value) {
  return value !== null && typeof value === 'object' && !Array.isArray(value);
}

function debounce(
  func: (...args: any[]) => any,
  wait: number,
  options?: { leading?: boolean; trailing?: boolean; maxWait?: number },
) {
  let lastArgs: any[] | undefined,
    lastThis: undefined,
    maxWait: number,
    result: any,
    timerId: number | NodeJS.Timeout | undefined,
    lastCallTime: number | undefined;

  let lastInvokeTime = 0;
  let leading = false;
  let maxing = false;
  let trailing = true;

  // Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
  const useRAF = !wait && wait !== 0 && window && typeof window.requestAnimationFrame === 'function';

  if (typeof func !== 'function') {
    throw new TypeError('Expected a function');
  }
  wait = +wait || 0;
  if (options && isObject(options)) {
    leading = !!options.leading;
    maxing = 'maxWait' in options;
    maxWait = maxing ? Math.max(+options.maxWait! || 0, wait) : maxWait!;
    trailing = 'trailing' in options ? !!options.trailing : trailing;
  }

  function invokeFunc(time: number) {
    const args = lastArgs;
    const thisArg = lastThis;

    lastArgs = lastThis = undefined;
    lastInvokeTime = time;
    result = func.apply(thisArg, args as any);
    return result;
  }

  function startTimer(pendingFunc: () => void, wait: number) {
    if (useRAF) {
      window.cancelAnimationFrame(timerId as number);
      return window.requestAnimationFrame(pendingFunc);
    }
    return setTimeout(pendingFunc, wait);
  }

  function cancelTimer(id: number) {
    if (useRAF) {
      return window.cancelAnimationFrame(id);
    }
    clearTimeout(id);
  }

  function leadingEdge(time: number) {
    // Reset any `maxWait` timer.
    lastInvokeTime = time;
    // Start the timer for the trailing edge.
    timerId = startTimer(timerExpired, wait);
    // Invoke the leading edge.
    return leading ? invokeFunc(time) : result;
  }

  function remainingWait(time: number) {
    const timeSinceLastCall = time - lastCallTime!;
    const timeSinceLastInvoke = time - lastInvokeTime;
    const timeWaiting = wait - timeSinceLastCall;

    return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
  }

  function shouldInvoke(time: number) {
    const timeSinceLastCall = time - lastCallTime!;
    const timeSinceLastInvoke = time - lastInvokeTime;

    // Either this is the first call, activity has stopped and we're at the
    // trailing edge, the system time has gone backwards and we're treating
    // it as the trailing edge, or we've hit the `maxWait` limit.
    return (
      lastCallTime === undefined ||
      timeSinceLastCall >= wait ||
      timeSinceLastCall < 0 ||
      (maxing && timeSinceLastInvoke >= maxWait)
    );
  }

  function timerExpired() {
    const time = Date.now();
    if (shouldInvoke(time)) {
      return trailingEdge(time);
    }
    // Restart the timer.
    timerId = startTimer(timerExpired, remainingWait(time));
  }

  function trailingEdge(time: number) {
    timerId = undefined;

    // Only invoke if we have `lastArgs` which means `func` has been
    // debounced at least once.
    if (trailing && lastArgs) {
      return invokeFunc(time);
    }
    lastArgs = lastThis = undefined;
    return result;
  }

  function cancel() {
    if (timerId !== undefined) {
      cancelTimer(timerId as number);
    }
    lastInvokeTime = 0;
    lastArgs = lastCallTime = lastThis = timerId = undefined;
  }

  function flush() {
    return timerId === undefined ? result : trailingEdge(Date.now());
  }

  function pending() {
    return timerId !== undefined;
  }

  function debounced(this: any, ...args: any[]) {
    const time = Date.now();
    const isInvoking = shouldInvoke(time);

    lastArgs = args;
    lastThis = this;
    lastCallTime = time;

    if (isInvoking) {
      if (timerId === undefined) {
        return leadingEdge(lastCallTime);
      }
      if (maxing) {
        // Handle invocations in a tight loop.
        timerId = startTimer(timerExpired, wait);
        return invokeFunc(lastCallTime);
      }
    }
    if (timerId === undefined) {
      timerId = startTimer(timerExpired, wait);
    }
    return result;
  }
  debounced.cancel = cancel;
  debounced.flush = flush;
  debounced.pending = pending;
  return debounced;
}

export default debounce;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159

# 节流

  • DOM 元素的拖拽功能实现(mousemove)
  • 搜索联想(keyup)
  • 计算鼠标移动的距离(mousemove)
  • Canvas 模拟画板功能(mousemove)
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  • 监听滚动事件判断是否到页面底部自动加载更多
import debounce from './debounce';

export function isObject(value) {
  return value !== null && typeof value === 'object' && !Array.isArray(value);
}

function throttle(
  func: (...args: any[]) => any,
  wait: number,
  options?: { leading?: boolean; trailing?: boolean; maxWait?: number },
) {
  let leading = true;
  let trailing = true;

  if (typeof func !== 'function') {
    throw new TypeError('Expected a function');
  }
  if (options && isObject(options)) {
    leading = 'leading' in options ? !!options.leading : leading;
    trailing = 'trailing' in options ? !!options.trailing : trailing;
  }
  return debounce(func, wait, {
    leading,
    trailing,
    maxWait: wait,
  });
}

export default throttle;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 获取 url 中的参数

export const getUrlParamBySearch = ({
  name,
  path = '',
  decode = true,
} = {}) => {
  const _path = path || location?.href || '';

  // 分组捕获 两个子组
  const reg = /([^?&=]+)=([^&]+)/g;
  let params = {};
  _path.replace(reg, (_, k, v) => {
    // 为什么要解码两次?因为 uniapp 跳转时会对特殊字符进行一次编码(哪怕是你 encodeURIComponent 后产生的特殊字符)
    params[k] = decode ? decodeURIComponent(decodeURIComponent(v)) : v;
  });

  return name ? Reflect.get(params, name) : params;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 持久化对象

const getSessionStorage = (itemName) => {
  return JSON.parse(sessionStorage.getItem(itemName) || '""');
};

const setSessionStorage = (itemName, value) => {
  sessionStorage.setItem(itemName, JSON.stringify(value));
};

export const usePersist = (key, data, options) => {
  const { set, get } = options || {};
  if (data.constructor !== Object) {
    return data;
  }
  
  let defaultData = { ...data };
  Object.freeze(defaultData);

  const store = {
    ...defaultData,
    ...getSessionStorage(key),
  };

  const proxyData = new Proxy(store, {
    set(o, p, v, r) {
      set && set(o, p, v, r);
      // 如果值没有改变,则直接返回true,无需进行其他操作
      if (v === Reflect.get(store, p)) {
        return true;
      }
      
      // 值已更改,更新sessionStorage并设置新值
      setSessionStorage(key, {
        ...store,
        [p]: v,
      });
      return Reflect.set(o, p, v, r);
    },
    get(target, key) {
      get && get(target, key);
      if (key === "__proto__") {
        return undefined;
      }
      return target[key];
    },
  });

  return proxyData;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 倒计时

/**
 * 计算倒计时时间差
 * @param {string|number|Date} time - 目标时间
 * @returns {Object} 包含天、小时、分钟、秒的对象
 */
function countDownTime(time) {
  const nowTime = +new Date();
  const inputTime = +new Date(time);
  let times = (inputTime - nowTime) / 1000; // 转化为秒数
  
  // 如果时间差小于0,说明目标时间已过期
  if (times <= 0) {
    return {
      d: '00',
      h: '00',
      m: '00',
      s: '00'
    };
  }
  
  // 计算天、小时、分钟、秒
  const d = Math.floor(times / 60 / 60 / 24);
  const h = Math.floor(times / 60 / 60 % 24);
  const m = Math.floor(times / 60 % 60);
  const s = Math.floor(times % 60);
  
  // 使用 padStart 方法格式化为两位数字符串
  return {
    d: String(d).padStart(2, '0'),
    h: String(h).padStart(2, '0'),
    m: String(m).padStart(2, '0'),
    s: String(s).padStart(2, '0')
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 格式化时间显示

/**
 * 格式化时间显示(如:刚刚、1小时前、1天前等)
 * @param {string|number|Date} starttime - 起始时间
 * @returns {string} 格式化后的时间描述
 */
function showTime(starttime) {
  if (!starttime) return '未知时间';
  
  const nowtime = new Date(); // 获取当前时间
  const startTime = new Date(starttime); // 定义起始时间
  
  // 验证时间有效性
  if (isNaN(startTime.getTime())) {
    return '无效时间';
  }
  
  const lefttime = nowtime.getTime() - startTime.getTime();
  
  // 如果是未来时间,直接返回"刚刚"
  if (lefttime < 0) {
    return '刚刚';
  }
  
  // 计算各时间单位
  const seconds = Math.floor(lefttime / 1000);
  const minutes = Math.floor(seconds / 60);
  const hours = Math.floor(minutes / 60);
  const days = Math.floor(hours / 24);
  const weeks = Math.floor(days / 7);
  const months = Math.floor(days / 30);
  const years = Math.floor(days / 365);
  
  // 按优先级返回时间描述
  if (years > 0) {
    return years + "年前";
  } else if (months > 0) {
    return months + "月前";
  } else if (weeks > 0) {
    return weeks + "周前";
  } else if (days > 0) {
    return days + "天前";
  } else if (hours > 0) {
    return hours + "小时前";
  } else if (minutes > 0) {
    return minutes + "分钟前";
  } else {
    return "刚刚";
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
编辑 (opens new window)
#JavaScript
上次更新: 2025/10/28 14:16:43
33个非常实用的JavaScript一行代码
ES5面向对象

← 33个非常实用的JavaScript一行代码 ES5面向对象→

最近更新
01
H5调用微信jssdk
09-28
02
VueVirtualScroller
09-19
03
IoC 解决了什么痛点问题?
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式