有时候总会为了一个小小的功能在网上搜寻了大半天的时间,预览了很多框架和插件,一直没找到合适的,最后只能花时间自己写一个。如果完成的功能没有做好记录,再次遇见这个问题,处理起来就比较费劲。随着开发量的增多,这些功能点根本记不住,所以我把这些开发中遇到比较常见且难实现的方法汇总到一块,再次遇见时简单CV一下,就可以略过这些难题啦。
CSS
文字溢出
p { /*单行显示省略号*/
width:300px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
p { /*多行显示省略号*/
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
禁止选择
div {
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
touch-action:设置触摸屏用户操纵元素的区域 (例如,浏览器内置的缩放功能)
滚动条样式
::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: transparent;
}
::-webkit-scrollbar-track { /*空白区域*/
background-color: transparent;
}
::-webkit-scrollbar-thumb { /*滑动区域*/
border-radius: 3px;
background-image: linear-gradient(135deg, #09f, #3c9);
}
JavaScript
在平时的开发中,偶尔也会用到以下插件,在开发中提供了不少的处理函数。
lodashjs:一致性、模块化、高性能的 JavaScript 实用工具库
dayjs:解析、验证、操作和显示日期和时间
数组操作
生成数组
生成一个0-99的数组
const createArr = (n) => Array.from(new Array(n), (v, i) => i)
const arr = createArr(100) // 0 - 99 数组
数组去重
const removeDuplicates = list => [...new Set(list)]
removeDuplicates([0, 0, 2, 4, 5]) // [0,2,4,5]
数组交集
取多个数组中的交集
const intersection = (a, ...arr) => [...new Set(a)].filter((v) => arr.every((b) => b.includes(v)))
intersection([1, 2, 3, 4], [2, 3, 4, 7, 8], [1, 3, 4, 9]) // [3, 4]
数组子集
检查一个数组是否为其他数组的子集
const isSubset = (a, b) => new Set(b).size === new Set(b.concat(a)).size;
isSubset([1, 2], [1, 2, 3, 4]); // true
isSubset([1, 2, 5], [1, 2, 3, 4]); // false
数组重复数
获取数组中指定值的重复次数
const countOccurrences = (arr, val) => arr.filter((item) => item === val).length;
countOccurrences([2, 1, 3, 3, 2, 3], 2); // 2
countOccurrences(['a', 'b', 'a', 'c', 'a', 'b'], 'a'); // 3
最大值索引
获取数组中的最大值的索引
const indexOfMax = (arr) => arr.reduce((prev, curr, i, a) => (curr > a[prev] ? i : prev), 0);
indexOfMax([1, 3, 9, 7, 5]); // 2
最小值索引
获取数组中的最小值的索引
const indexOfMin = (arr) => arr.reduce((prev, curr, i, a) => (curr < a[prev] ? i : prev), 0)
indexOfMin([2, 5, 3, 4, 1, 0, 9]) // 5
数组判空
const isNotEmpty = arr => Array.isArray(arr) && arr.length > 0;
isNotEmpty([1, 2, 3]); // true
数组合并
const merge = (a, b) => a.concat(b);
const merge = (a, b) => [...a, ...b];
数组项交换
const swapItems = (a, i, j) => (a[i] && a[j] && [...a.slice(0, i), a[j], ...a.slice(i + 1, j), a[i], ...a.slice(j + 1)]) || a;
swapItems([1, 2, 3, 4, 5], 1, 4); // [1, 5, 3, 4, 2]
数组随机项
获取数组随机项
const randomItems = (arr, count) => arr.concat().reduce((p, _, __, arr) => (p[0] < count ? [p[0] + 1, p[1].concat(arr.splice((Math.random() * arr.length) | 0, 1))] : p), [0, []])[1];
randomItems([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3); // [4, 8, 5]
树结构转换
将对象数组转换成树形结构
let arr = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
function toTree (data) {
let result = [];
let map = {};
// 1. 先构建map结构,以各个子项id为key
data.forEach((item) => {
map[item.id] = item
})
// 2. 再循环目标数组,判断上面构建的map中,是否存在当前遍历的pid
data.forEach((item) => {
let parent = map[item.pid];
if(parent) {
// 3. 存在就可以进行children的插入
(parent.children || (parent.children = [])).push(item)
} else {
// 4. 不存在就是顶级节点,直接push即可
result.push(item)
}
})
return result;
}
console.log(toTree(arr));
数字操作
四舍五入
保留小数点后几位
const round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`)
round(10.255, 2) // 10.26
数字补零
当需要在一个数字不足位数时前面补零操作
const replenishZero = (num, len, zero = 0) => num.toString().padStart(len, zero)
replenishZero(8, 2) // 08
生成随机数
给定一个范围生成一个随机数
const randomNum = (m, n) => Math.floor(Math.random() * (n - m + 1) + m);
randomNum(5, 10)
生成随机数组
给定一个范围生成一个随机数组
const randomArrayInRange = (min, max, n) => Array.from({ length: n }, () => Math.floor(Math.random() * (max - min + 1)) + min);
randomArrayInRange(1, 100, 10); // [11, 82, 41, 35, 76, 83, 43, 15, 60, 54]
求平均数
const average = (...args) => args.reduce((a, b) => a + b) / args.length;
average(1, 2, 3, 4, 5); // 3
求和
const sum = (...args) => args.reduce((a, b) => a + b);
sum(1, 2, 3, 4); // 10
最大公约数
const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
gcd(10, 15); // 5
直线角度
计算由两点定义的直线的角度
// In radians
const radiansAngle = (p1, p2) => Math.atan2(p2.y - p1.y, p2.x - p1.x);
// In degrees
const degreesAngle = (p1, p2) => (Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180) / Math.PI;
radiansAngle({x:1,y:5}, {x:6,y:10})
degreesAngle({x:1,y:5}, {x:6,y:10})
两点距离
const distance = (p1, p2) => Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
distance({x:1,y:5}, {x:6,y:10})
数的阶乘
const factorial = (n) => (n <= 1 ? 1 : n * factorial(n - 1));
factorial(2); // 2
factorial(3); // 6
factorial(4); // 24
对象操作
验证对象
判断一个值是否是对象
const isObject = (v) => v !== null && typeof v === 'object';
对象判空
const isEmpty = (obj) => JSON.stringify(obj) === '{}';
删除无效属性
删除一个对象中的属性值为null或undefined的所有属性
const removeNullUndefined = (obj) => Object.entries(obj).reduce((a, [k, v]) => (v == null ? a : ((a[k] = v), a)), {});
removeNullUndefined({name: '', age: undefined, sex: null}) // { name: '' }
反转对象键值
当你需要将对象的键值对交换
const invert = (obj) => Object.keys(obj).reduce((res, k) => Object.assign(res, { [obj[k]]: k }), {})
invert({name: 'jack'}) // {jack: 'name'}
对象数组去重
function unique(arr, key) {
let obj = {};
if (key) { // 指定对象内部的键名为判断条件
return arr.filter((item, index) => {
const i = arr.findIndex(t => t[key] === item[key]);
return i === index;
})
} else {
return arr.filter(function (item) {
return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
})
}
}
unique([{a: 1,b: 2}, {a: 1,b: 2}, {a: 1,b: 5}]) // [{a: 1,b: 2}, {a: 1,b: 5}]
unique([{a: 1,b: 2}, {a: 1,b: 2}, {a: 1,b: 5}],'a') // [{a: 1,b: 2}]
键值分组
数据结构转换以键值分组
function groupArrayByKey(arr = [], key) {
return arr.reduce((t, v) => (!t[v[key]] && (t[v[key]] = []), t[v[key]].push(v), t), {})
}
groupArrayByKey([
{classId: "1",name: "张三",age: 16},
{classId: "1",name: "李四",age: 15},
{classId: "2",name: "王五",age: 16},
{classId: "3",name: "赵六",age: 15},
{classId: "2",name: "孔七",age: 16}
], 'classId')
{
"1": [
{
"classId": "1",
"name": "张三",
"age": 16
},
{
"classId": "1",
"name": "李四",
"age": 15
}
],
"2": [
{
"classId": "2",
"name": "王五",
"age": 16
},
{
"classId": "2",
"name": "孔七",
"age": 16
}
],
"3": [
{
"classId": "3",
"name": "赵六",
"age": 15
}
]
}
对象排序
根据键名首字母进行排序
const sort = (obj) =>
Object.keys(obj)
.sort()
.reduce((p, c) => ((p[c] = obj[c]), p), {});
sort({white: '#ffffff',black: '#000000',red: '#ff0000',green: '#008000',blue: '#0000ff'});
// { black: '#000000',blue: '#0000ff',green: '#008000',red: '#ff0000',white: '#ffffff'}
对象深拷贝
function clone(obj) {
return JSON.parse(JSON.stringify(obj))
}
字符串操作
链接处理
function params(url, params) {
if (params) { // 有参数时拼接
return params ? url + '?' + Object.keys(params)
.filter(key => params[key] || params[key] === 0)
.map(key => `${key}=${params[key]}`)
.toString().replace(/,/g, '&') :
url
} else { // 没参数时获取
let obj = {};
url.match(/(\w+)=(\w+)/g).forEach(item => {
Object.assign(obj, {
[item.split('=')[0]]: item.split('=')[1]
})
})
return obj
}
}
params('http://127.0.0.1?id=15&name=kin') // { id: 15, name: 'kin' }
params('http://127.0.0.1', { id: 20, name: 'kinron' }) // http://127.0.0.1?id=20&name=kinron
基本链接
获取不带参数的基本链接
const baseUrl = (url) => (url.indexOf('?') === -1 ? url : url.slice(0, url.indexOf('?')));
baseUrl('https://127.0.0.1?foo=bar&hello=world'); // 'https://127.0.0.1'
字符串哈希
const hash = (str) => str.split('').reduce((prev, curr) => (Math.imul(31, prev) + curr.charCodeAt(0)) | 0, 0);
hash('hello'); // 99162322
首字母大写
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
capitalize("hello world") // Hello world
翻转字符串
const reverse = str => str.split('').reverse().join('');
reverse('hello world'); // 'dlrow olleh'
变量类型
获取字符串中变量的类型
const getTypeOf = (obj) => Object.prototype.toString.call(obj).match(/\[object (.*)\]/)[1];
随机字符串
给定字符生成随机字符串
const generateString = (length, chars) =>
Array(length)
.fill('')
.map((v) => chars[Math.floor(Math.random() * chars.length)])
.join('');
generateString(10, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
验证字符串
判断一个值是否是字符串
const isString = (value) => Object.prototype.toString.call(value) === '[object String]';
驼峰式
字符串格式化
const toCamelCase = (str) => str.trim().replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''));
toCamelCase('background-color'); // backgroundColor
toCamelCase('-webkit-scrollbar-thumb'); // WebkitScrollbarThumb
toCamelCase('_hello_world'); // HelloWorld
toCamelCase('hello_world'); // helloWorld
单词数
统计单词数
const countWords = (str) => str.trim().split(/\s+/).length;
countWords('Hello World'); // 2
countWords('Hello World'); // 2
countWords(' Hello World '); // 2
字符重复数
const characterCount = (str, char) => str.split(char).length - 1;
characterCount('192.168.1.1', '.'); // 3
文件拓展名
const ext = (fileName) => fileName.split('.').pop();
ext('data.json') // json
版本判断
function version(newv, oldv) {
const v1 = newv.split('.');
const v2 = oldv.split('.');
const len = Math.max(v1.length, v2.length);
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i] || 0);
const num2 = parseInt(v2[i] || 0);
if (num1 > num2) {
return true;
} else if (num1 < num2) {
return false;
}
}
return 0;
}
version('1.0.6', '1.0.5') // true
系统判断
function system() {
try {
let u = navigator.userAgent;
let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //g
let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
if (isAndroid) return 'Android'
else if (isIOS) return 'IOS'
else return 'pc'
} catch {
console.log('navigator is not define')
}
}
uuid
生成一个id
const uuid = (a) => (a ? (a ^ ((Math.random() * 16) >> (a / 4))).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid))
uuid()
时间操作
是否为今天
const isToday = (date) => date.toISOString().slice(0, 10) === new Date().toISOString().slice(0, 10)
秒数转换
将秒数转换为 hh:mm:ss 格式
const formatSeconds = (s) => new Date(s * 1000).toISOString().substr(11, 8)
formatSeconds(200) // 00:03:20
间隔天数
const diffDays = (date, otherDate) => Math.ceil(Math.abs(date - otherDate) / (1000 * 60 * 60 * 24))
diffDays(new Date('2014-12-19'), new Date('2020-01-01')); // 1839
间隔月数
const monthDiff = (startDate, endDate) => Math.max(0, (endDate.getFullYear() - startDate.getFullYear()) * 12 - startDate.getMonth() + endDate.getMonth())
monthDiff(new Date('2020-01-01'), new Date('2021-01-01')); // 12
日期排序
const sortDescending = (arr) => arr.sort((a, b) => a.getTime() > b.getTime());
sortDescending([new Date('2023/05/09'),new Date('2022/01/05')])
日期格式转换
当你需要将日期转换为为 YYYY-MM-DD 格式
const formatYmd = (date) => date.toISOString().slice(0, 10);
formatYmd(new Date())
DOM操作
重载页面
const reload = () => location.reload();
reload()
回到顶部
const goToTop = () => window.scrollTo(0, 0);
goToTop()
元素滚动
元素顺滑的滚动到可视区域的起点
const scrollToTop = (element) =>
element.scrollIntoView({ behavior: "smooth", block: "start" })
scrollToTop(document.body)
元素顺滑的滚动到可视区域的终点
const scrollToBottom = (element) =>
element.scrollIntoView({ behavior: "smooth", block: "end" })
scrollToBottom(document.body)
文本粘贴
复制文本到粘贴板上
const copy = (text) => navigator.clipboard?.writeText && navigator.clipboard.writeText(text)
copy('需要粘贴的文本')
是否页面底部
判断是否在页面底部
const isAtBottom = () => document.documentElement.clientHeight + window.scrollY >= document.documentElement.scrollHeight;
元素位置
获取元素相对于文档的位置
const getPosition = (ele) => ((r = ele.getBoundingClientRect()), { left: r.left + window.scrollX, top: r.top + window.scrollY });
getPosition(document.body); // { left: 0, top: 0 }
其他操作
防抖函数
function debounce(fn, delay) {
let timer = null //借助闭包
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(fn, delay)
}
}
节流函数
function throttle(fn, delay) {
let valid = true
return function () {
if (!valid) {
return false
}
valid = false
setTimeout(() => {
fn()
valid = true;
}, delay)
}
}
单次函数
函数仅执行一次
const once = (fn) =>
(
(ran = false) =>
() =>
ran ? fn : ((ran = !ran), (fn = fn()))
)()
let n = 0;
const incOnce = once(() => ++n);
incOnce(); // n = 1
incOnce(); // n = 1
Node操作
随机字符串
const randomStr = () => require('crypto').randomBytes(32).toString('hex');
调用接口
const axios = require("axios")
function getValue() {
return new Promise((resolve, reject) => {
axios.get("https://test.com").then((res) => {
console.log(res.data)
resolve(res.data)
}).catch(() => {
reject("fail")
})
})
}
下载文件
const fs = require('fs');
const request = require("request")
function download(url, outPath) {
let stream = fs.createWriteStream(outPath)
return new Promise(function (resolve, reject) {
request(url).pipe(stream).on('close', function () {
console.log(targetUrl + '下载完毕');
resolve(targetUrl)
});
})
}
获取文件列表
const testFolder = './test';
const fs = require('fs');
fs.readdirSync(testFolder).forEach(file => {
console.log(file);
});