uView中一些实用的Js函数

uView中一些实用的Js函数

用uView UI开发了一些小应用,它除了给我们提供全面的UI组件外,还提供了一些常用的JS方法,如:深度克隆、数字格式化、日期格式化等,这些方法我们不仅仅可以用到uniapp中,还可以用到其它的前端项目中

深度克隆

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
/**
* @description 深度克隆
* @param {object} obj 需要深度克隆的对象
* @param cache 缓存
* @returns {*} 克隆后的对象或者原值(不是对象)
*/
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (cache.has(obj)) return cache.get(obj);
let clone;
if (obj instanceof Date) {
clone = new Date(obj.getTime());
} else if (obj instanceof RegExp) {
clone = new RegExp(obj);
} else if (obj instanceof Map) {
clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)]));
} else if (obj instanceof Set) {
clone = new Set(Array.from(obj, value => deepClone(value, cache)));
} else if (Array.isArray(obj)) {
clone = obj.map(value => deepClone(value, cache));
} else if (Object.prototype.toString.call(obj) === '[object Object]') {
clone = Object.create(Object.getPrototypeOf(obj));
cache.set(obj, clone);
for (const [key, value] of Object.entries(obj)) {
clone[key] = deepClone(value, cache);
}
} else {
clone = Object.assign({}, obj);
}
cache.set(obj, clone);
return clone;
}

JS对象深度合并

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
/**
* @description JS对象深度合并
* @param {object} target 需要拷贝的对象
* @param {object} source 拷贝的来源对象
* @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
*/
function deepMerge(target = {}, source = {}) {
target = deepClone(target)
if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target;
const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target);
for (const prop in source) {
if (!source.hasOwnProperty(prop)) continue;
const sourceValue = source[prop];
const targetValue = merged[prop];
if (sourceValue instanceof Date) {
merged[prop] = new Date(sourceValue);
} else if (sourceValue instanceof RegExp) {
merged[prop] = new RegExp(sourceValue);
} else if (sourceValue instanceof Map) {
merged[prop] = new Map(sourceValue);
} else if (sourceValue instanceof Set) {
merged[prop] = new Set(sourceValue);
} else if (typeof sourceValue === 'object' && sourceValue !== null) {
merged[prop] = deepMerge(targetValue, sourceValue);
} else {
merged[prop] = sourceValue;
}
}
return merged;
}

去除空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @description 去除空格
* @param String str 需要去除空格的字符串
* @param String pos both(左右)|left|right|all 默认both
*/
function trim(str, pos = 'both') {
str = String(str)
if (pos == 'both') {
return str.replace(/^\s+|\s+$/g, '')
}
if (pos == 'left') {
return str.replace(/^\s*/, '')
}
if (pos == 'right') {
return str.replace(/(\s*$)/g, '')
}
if (pos == 'all') {
return str.replace(/\s+/g, '')
}
return str
}

数字格式化

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
/**
* @description 数字格式化
* @param {number|string} number 要格式化的数字
* @param {number} decimals 保留几位小数
* @param {string} decimalPoint 小数点符号
* @param {string} thousandsSeparator 千分位符号
* @returns {string} 格式化后的数字
*/
function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
number = (`${number}`).replace(/[^0-9+-Ee.]/g, '')
const n = !isFinite(+number) ? 0 : +number
const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator
const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint
let s = ''

s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')
const re = /(-?\d+)(\d{3})/
while (re.test(s[0])) {
s[0] = s[0].replace(re, `$1${sep}$2`)
}

if ((s[1] || '').length < prec) {
s[1] = s[1] || ''
s[1] += new Array(prec - s[1].length + 1).join('0')
}
return s.join(dec)
}

对象转url参数

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
/**
* @description 对象转url参数
* @param {object} data,对象
* @param {Boolean} isPrefix,是否自动加上"?"
* @param {string} arrayFormat 规则 indices|brackets|repeat|comma
*/
function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
const prefix = isPrefix ? '?' : ''
const _result = []
if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
for (const key in data) {
const value = data[key]
// 去掉为空的参数
if (['', undefined, null].indexOf(value) >= 0) {
continue
}
// 如果值为数组,另行处理
if (value.constructor === Array) {
// e.g. {ids: [1, 2, 3]}
switch (arrayFormat) {
case 'indices':
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
for (let i = 0; i < value.length; i++) {
_result.push(`${key}[${i}]=${value[i]}`)
}
break
case 'brackets':
// 结果: ids[]=1&ids[]=2&ids[]=3
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`)
})
break
case 'repeat':
// 结果: ids=1&ids=2&ids=3
value.forEach((_value) => {
_result.push(`${key}=${_value}`)
})
break
case 'comma':
// 结果: ids=1,2,3
let commaStr = ''
value.forEach((_value) => {
commaStr += (commaStr ? ',' : '') + _value
})
_result.push(`${key}=${commaStr}`)
break
default:
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`)
})
}
} else {
_result.push(`${key}=${value}`)
}
}
return _result.length ? prefix + _result.join('&') : ''
}

格式化时间

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
/**
* @description 格式化时间
* @param {String|Number} dateTime 需要格式化的时间戳
* @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd
* @returns {string} 返回格式化后的字符串
*/
function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {
let date
// 若传入时间为假值,则取当前时间
if (!dateTime) {
date = new Date()
}
// 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容)
else if (/^\d{10}$/.test(dateTime?.toString().trim())) {
date = new Date(dateTime * 1000)
}
// 若用户传入字符串格式时间戳,new Date无法解析,需做兼容
else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
date = new Date(Number(dateTime))
}
// 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间
// 处理 '2022-07-10 01:02:03',跳过 '2022-07-10T01:02:03'
else if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) {
date = new Date(dateTime.replace(/-/g, '/'))
}
// 其他都认为符合 RFC 2822 规范
else {
date = new Date(dateTime)
}

const timeSource = {
'y': date.getFullYear().toString(), // 年
'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月
'd': date.getDate().toString().padStart(2, '0'), // 日
'h': date.getHours().toString().padStart(2, '0'), // 时
'M': date.getMinutes().toString().padStart(2, '0'), // 分
's': date.getSeconds().toString().padStart(2, '0') // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
}

for (const key in timeSource) {
const [ret] = new RegExp(`${key}+`).exec(formatStr) || []
if (ret) {
// 年可能只需展示两位
const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0
formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))
}
}

return formatStr
}

时间戳转为多久之前

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
/**
* @description 时间戳转为多久之前
* @param {String|Number} timestamp 时间戳
* @param {String|Boolean} format
* 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
* 如果为布尔值false,无论什么时间,都返回多久以前的格式
* @returns {string} 转化后的内容
*/
function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
if (timestamp == null) timestamp = Number(new Date())
timestamp = parseInt(timestamp)
// 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
if (timestamp.toString().length == 10) timestamp *= 1000
let timer = (new Date()).getTime() - timestamp
timer = parseInt(timer / 1000)
// 如果小于5分钟,则返回"刚刚",其他以此类推
let tips = ''
switch (true) {
case timer < 300:
tips = '刚刚'
break
case timer >= 300 && timer < 3600:
tips = `${parseInt(timer / 60)}分钟前`
break
case timer >= 3600 && timer < 86400:
tips = `${parseInt(timer / 3600)}小时前`
break
case timer >= 86400 && timer < 2592000:
tips = `${parseInt(timer / 86400)}天前`
break
default:
// 如果format为false,则无论什么时间戳,都显示xx之前
if (format === false) {
if (timer >= 2592000 && timer < 365 * 86400) {
tips = `${parseInt(timer / (86400 * 30))}个月前`
} else {
tips = `${parseInt(timer / (86400 * 365))}年前`
}
} else {
tips = timeFormat(timestamp, format)
}
}
return tips
}

防抖

防抖的意思是,在连续的操作中,无论进行了多长时间,只有某一次的操作后在指定的时间内没有再操作,这一次才被判定有效。具体场景可以搜索框输入关键字过程中实时 请求服务器匹配搜索结果,如果不进行处理,那么就是输入框内容一直变化,导致一直发送请求。如果进行防抖处理,结果就是当我们输入内容完成后,一定时间(比如500ms)没有再 输入内容,这时再触发请求。

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
let timeout = null

/**
* 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function debounce(func, wait = 500, immediate = false) {
// 清除定时器
if (timeout !== null) clearTimeout(timeout)
// 立即执行,此类情况一般用不到
if (immediate) {
const callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) typeof func === 'function' && func()
} else {
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(() => {
typeof func === 'function' && func()
}, wait)
}
}

节流

节流的意思是,规定时间内,只触发一次。比如我们设定500ms,在这个时间内,无论点击按钮多少次,它都只会触发一次。具体场景可以是抢购时候,由于有无数人 快速点击按钮,如果每次点击都发送请求,就会给服务器造成巨大的压力,但是我们进行节流后,就会大大减少请求的次数。

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
let timer; let
flag
/**
* 节流原理:在一定时间内,只能触发一次
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!flag) {
flag = true
// 如果是立即执行,则在wait毫秒内开始时执行
typeof func === 'function' && func()
timer = setTimeout(() => {
flag = false
}, wait)
}
} else if (!flag) {
flag = true
// 如果是非立即执行,则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false
typeof func === 'function' && func()
}, wait)
}
}

test 规则校验-是否车牌号、验证手机格式、验证电子邮箱格式、验证身份证号码

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/**
* 验证电子邮箱格式
*/
function email(value) {
return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)
}

/**
* 验证手机格式
*/
function mobile(value) {
return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value)
}

/**
* 验证URL格式
*/
function url(value) {
return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/
.test(value)
}

/**
* 验证日期格式
*/
function date(value) {
if (!value) return false
// 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳
if (number(value)) value = +value
return !/Invalid|NaN/.test(new Date(value).toString())
}

/**
* 验证ISO类型的日期格式
*/
function dateISO(value) {
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
}

/**
* 验证十进制数字
*/
function number(value) {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}

/**
* 验证字符串
*/
function string(value) {
return typeof value === 'string'
}

/**
* 验证整数
*/
function digits(value) {
return /^\d+$/.test(value)
}

/**
* 验证身份证号码
*/
function idCard(value) {
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
value
)
}

/**
* 是否车牌号
*/
function carNo(value) {
// 新能源车牌
const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
// 旧车牌
const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
if (value.length === 7) {
return creg.test(value)
} if (value.length === 8) {
return xreg.test(value)
}
return false
}

/**
* 金额,只允许2位小数
*/
function amount(value) {
// 金额,只允许保留两位小数
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
}

/**
* 中文
*/
function chinese(value) {
const reg = /^[\u4e00-\u9fa5]+$/gi
return reg.test(value)
}

/**
* 只能输入字母
*/
function letter(value) {
return /^[a-zA-Z]*$/.test(value)
}

/**
* 只能是字母或者数字
*/
function enOrNum(value) {
// 英文或者数字
const reg = /^[0-9a-zA-Z]*$/g
return reg.test(value)
}

/**
* 验证是否包含某个值
*/
function contains(value, param) {
return value.indexOf(param) >= 0
}

/**
* 验证一个值范围[min, max]
*/
function range(value, param) {
return value >= param[0] && value <= param[1]
}

/**
* 验证一个长度范围[min, max]
*/
function rangeLength(value, param) {
return value.length >= param[0] && value.length <= param[1]
}

/**
* 是否固定电话
*/
function landline(value) {
const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
return reg.test(value)
}

/**
* 判断是否为空
*/
function empty(value) {
switch (typeof value) {
case 'undefined':
return true
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
break
case 'boolean':
if (!value) return true
break
case 'number':
if (value === 0 || isNaN(value)) return true
break
case 'object':
if (value === null || value.length === 0) return true
for (const i in value) {
return false
}
return true
}
return false
}

/**
* 是否json字符串
*/
function jsonString(value) {
if (typeof value === 'string') {
try {
const obj = JSON.parse(value)
if (typeof obj === 'object' && obj) {
return true
}
return false
} catch (e) {
return false
}
}
return false
}

/**
* 是否数组
*/
function array(value) {
if (typeof Array.isArray === 'function') {
return Array.isArray(value)
}
return Object.prototype.toString.call(value) === '[object Array]'
}

/**
* 是否对象
*/
function object(value) {
return Object.prototype.toString.call(value) === '[object Object]'
}

/**
* 是否短信验证码
*/
function code(value, len = 6) {
return new RegExp(`^\\d{${len}}$`).test(value)
}

/**
* 是否函数方法
* @param {Object} value
*/
function func(value) {
return typeof value === 'function'
}

/**
* 是否promise对象
* @param {Object} value
*/
function promise(value) {
return object(value) && func(value.then) && func(value.catch)
}

/** 是否图片格式
* @param {Object} value
*/
function image(value) {
const newValue = value.split('?')[0]
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i
return IMAGE_REGEXP.test(newValue)
}

/**
* 是否视频格式
* @param {Object} value
*/
function video(value) {
const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i
return VIDEO_REGEXP.test(value)
}

/**
* 是否为正则对象
* @param {Object}
* @return {Boolean}
*/
function regExp(o) {
return o && Object.prototype.toString.call(o) === '[object RegExp]'
}

除了上面这个方法外,还有其他高精度加减计算的方法,也可以借鉴一番

uView中一些实用的Js函数

https://blogs.52fx.biz/posts/1354188727.html

作者

eyiadmin

发布于

2023-08-15

更新于

2024-05-31

许可协议

评论