# 实用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
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
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
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
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
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
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
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
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
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
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