记录开发中常用的js
技巧,封装常用函数等等
功能类
rgb 和 hex 颜色相互转换
hex color
由 6 位16进制
字符组成#ffffff
。rgb color
由 3 组0 - 255
的数值组成rgb(255,255,255)
。两者的对应关系为两个 hex 字符对应一组 rgb 数值,即ff, ff, ff
对应255, 255, 255
。取出一组为例ff => 255
hexToRgb
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
| function hexToRgb(hexColor: string = ""): number[] | string { const rgb: number[] = []; const hexReg = /^#[0-9a-fA-F]{6}?$/; const shortHexReg = /^#[0-9a-fA-F]{3}$/; const isShort: boolean = shortHexReg.test(hexColor);
if (!hexReg.test(hexColor) && !isShort) { return "请输入合法字符,例如#000000或#000"; }
hexColor = hexColor.slice(1);
if (isShort) { let shortColor = "";
for (let i = 0; i < hexColor.length; i++) { shortColor += hexColor[i].repeat(2); }
hexColor = shortColor; }
for (let i = 0; i < hexColor.length; i += 2) { const value1 = parseInt(hexColor[i], 16) * 16; const value2 = parseInt(hexColor[i + 1], 16); rgb.push(value1 + value2); }
return rgb; }
|
rgbToHex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function rgbToHex(rgbColor: number[]): string { let hexColor = "#";
if (rgbColor.length !== 3) { return "参数错误,rgb(255, 255, 255)"; }
rgbColor.forEach((color) => { if (color > 255 || color < 0) return "参数错误,rgb值应大于等于0,小于等于255";
const str1 = Math.floor(color / 16); const str2 = color % 16; hexColor += str1 + str2; });
return hexColor; }
|
获取 url query 参数
自己实现
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
| interface QueryProps { [props: string]: string; }
function formatQueryStrToObj(queryStr?: string): QueryProps { const query: QueryProps = {};
const params: string[] = (queryStr || window.location.search) .replace(/^\?/, "") .split("&");
params.forEach((item) => { let key = item; let value = "";
if (/=/.test(item)) { const pairs = item.split("="); key = pairs[0]; value = pairs[1]; }
key !== "" && (query[decodeURIComponent(key)] = decodeURIComponent(value)); });
return query; }
|
使用原生方法
1 2 3 4 5 6 7 8 9 10
| function formatQueryStrToObj(): QueryProps { const query: QueryProps = {}; const search = new URLSearchParams(window.location.search);
for (let [key, value] of search) { query[key] = value; }
return query; }
|
整理 get 请求参数
自己实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| interface ParamsProps { [props: string]: any; }
function formatQueryParams( params: ParamsProps, withPrefix: boolean = true ): string { let queryUrl = withPrefix ? "?" : "";
for (let key in params) { queryUrl += `${key}=${params[key]}&`; }
return queryUrl.slice(0, -1); }
|
使用原生方法
1 2 3 4 5 6 7
| interface ParamsProps { [props: string]: any; }
function formatQueryParams(params: ParamsProps): string { return new URLSearchParams(params).toString(); }
|
是否为移动端设备
通过读取userAgent
中的信息来判断当前是否为移动端设备
1 2 3 4 5 6
| function isMobile(): boolean { const reg = /Android/; const ua = window.navigator.userAgent;
return reg.test(ua); }
|
相对地址转绝对地址
利用a
标签实现
1 2 3 4 5
| function transformUrl(url: string): string { const a = document.createElement("a"); a.setAttribute("href", url); return a.href; }
|
复制文本
原理
- 核心函数
document.execCommand('copy', false)
会将选区(window.getSelection
)中的内容复制到剪切板
- 现在要做的就是把想要复制的内容添加到选区中即可
- 如果内容在表单元素中,那么调用表单元素的
$el.select()
方法即可将内容添加至选区
- 如果内容在普通元素中,需要创建一个
range
来获取该元素的内容,然后将range
添加到选区中即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function copy($el: Element): void { const selection = window.getSelection();
if ($el instanceof HTMLInputElement || $el instanceof HTMLTextAreaElement) { $el.select(); } else { const range = document.createRange(); const end = $el.childNodes.length; range.setStart($el, 0); range.setEnd($el, end); if (selection) { selection.removeAllRanges(); selection.addRange(range); } }
document.execCommand("copy", false); selection?.removeAllRanges(); }
|
数字千位分隔符
西方人习惯将 10000 说成10 个 1000,如10,000
使用系统方法
1 2 3
| function formatNum(num: number): string { return num.toLocaleString("en-US"); }
|
自己实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function formatNum(num: number): string { const str = num + ""; const result: string[] = []; const length = str.length;
for (let i = length - 1; i >= 0; i--) { result.push(str[i]); if ((length - i) % 3 === 0 && i !== 0) { result.push(","); } }
return result.reverse().join(""); }
|
工具类
防抖
在timeout
时间内连续触发防抖函数
并不会每次都执行,只会执行最后一次
1 2 3 4 5 6 7 8 9 10 11
| function debounce(fn: () => {}, timeout: number): () => void { let timer: number | undefined = undefined;
return function (...args: []) { clearTimeout(timer);
timer = setTimeout(() => { fn.call(null, ...args); }, timeout); }; }
|
节流
连续触发节流函数
并不会每次都执行,而是每隔timeout
时间执行一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function throttle(fn: () => {}, timeout: number): () => void { let flag = true;
return function (...args: []) { if (flag) { flag = false;
setTimeout(() => { flag = true; fn.call(null, ...args); }, timeout); } }; }
|
compose
compose
函数接受多个函数做为参数,这些函数会从右至左一次执行,并且上一个函数的返回值会作为下一个函数的输入值
compose
函数的优势在于可以像搭建积木一样任意搭配函数的组合,最终得到不同的结果
1 2 3 4 5 6 7 8 9
| function compose(...fns: Array<() => {}>): () => {} { return function (...params: any[]) { return fns.reduceRight(function (state: any, fn, index) { return index === fns.length - 1 ? fn.apply(null, state) : fn.call(null, state); }, params); }; }
|
pipe
pipe
函数与compose
函数类型,只是函数的执行方向是从左至右
1 2 3 4 5 6 7
| function pipe(...fns: Array<() => {}>): () => {} { return function (...params: any[]) { return fns.reduce(function (state: any, fn, index) { return index === 0 ? fn.apply(null, state) : fn.call(null, state); }, params); }; }
|
类型判断
使用Object.prototype.toString.call()
可以获取到任意值的类型,返回值的格式为[Object Type]
1 2 3 4 5 6 7
| function getType(value: any): string { return Object.prototype.toString.call(value).slice(8, -1).toLowerCase(); }
function isType(value: any, expectType: string): boolean { return getType(value) === expectType.toLowerCase(); }
|
手写类
参考
柯里化
作用
- 将
sum(1,2,3)
的执行格式改为sum(1)(2)(3)
- 可复用参数,
const fn = sum(1)(2)
=> fn(3)或者fn(4)
,共用1和2
这两个参数
1 2 3 4 5 6 7 8 9 10 11 12 13
| function curry(fn: (...args: any[]) => any): (...args: any[]) => any { const args: any[] = [];
function helper(...left: any[]): any { args.push(...left); if (args.length === fn.length) { return fn(args); } return helper; } return helper; }
|
1 2 3 4 5 6 7 8 9 10
| function curry(fn: (...args: any[]) => any): (...args: any[]) => any { function helper(...args: any[]): any { if (args.length === fn.length) { return fn(...args); } return helper.bind(null, ...args); } return helper; }
|
深拷贝
在javascript
中有两种数据类型,一种是基础数据类型,另一种是引用数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function deepClone(target) { const isObject = (val) => typeof val === "object" && val !== null;
if (!isObject(target)) return target;
const copyTarget = Array.isArray(target) ? [] : {}; const targetKeys = Object.keys(target);
targetKeys.forEach((key) => { copyTarget[key] = deepClone(target[key]); });
return copyTarget; }
|
字符串模板
思路
- 利用字符串的
replace
方法找出所有的${}
- 在利用取出
${}
括号内的值作为属性名在参数对象中找对应的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| interface TemplateParams { [prop: string]: number | string | boolean; }
function template(temp: string, params: TemplateParams): string { return temp.replace(/\$\{([^\{\}\$]*)\}/g, function (subStr, match) { return typeof params[match] !== "undefined" ? String(params[match]) : subStr; }); }
|
JSON.stringify
JSON.parse
EventEmitter
setInterval
使用setTimeout
实现setInterval
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| interface SetIntervalReturn { clear: () => void; }
function _setInterval(fn: () => any, timeout: number = 0): SetIntervalReturn { let timer: number;
function interval() { timer = setTimeout(() => { fn(); interval(); }, timeout); }
interval();
return { clear() { clearTimeout(timer); }, }; }
|
reduce
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function _reduce( arr: any[], cb: (preVal: any, currVal: any, index?: number, self?: any[]) => any, initVal?: any ): any { let start = 0; let result: any = initVal;
if (!initVal) { result = arr[0]; if (!result) return; start = 1; }
for (let i = start; i < arr.length; i++) { result = cb(result, arr[i], i, arr); }
return result; }
|
flat
flat
是扁平化数组的函数
原理
concat
函数可以实现数组的一层扁平化,[].concat([1,2,3]) => [1,2,3]
- 结构符可以实现深度为 1的数组扁平化,
[].concat(...[1,2,3,[4,5]]) => [1,2,3,4,5]
- 遍历数组,判断当前项的类型,如果是数组类型,则递归执行当前函数,如果不是则直接返回
1 2 3 4 5 6 7 8 9 10
| function flat(arr: any[], depth: number = 1): any[] { return depth >= 1 ? arr.reduce((result, curr) => { return result.concat( Array.isArray(curr) ? flat(curr, depth - 1) : curr ); }, []) : arr; }
|
1 2 3 4 5 6 7 8 9
| function flat(arr: any[], depth: number = 1): any[] { while (arr.some((item) => Array.isArray(item)) && depth > 0) { arr = [].concat(...arr); depth--; }
return arr; }
|
apply
原理
- 函数内部的
this
指向调用该函数的上下文(对象)
- 因此如果想将函数的
this
指向某个上下文,只需要让这个上下文执行此函数即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function _apply( ctx: null | any, fn: (...[]) => void | any, params: any[] = [] ): any { if (ctx === null) return fn(...params);
ctx = Object(ctx); const fnName = Symbol(); ctx[fnName] = fn; const result: any = ctx[fnName](...params); delete ctx[fnName];
return result; }
|
发现一个问题使用let
声明的变量不会添加到window
上,使用var
声明的变量可以
call
call
函数和apply
函数的作用一样,只是给函数传参的方式不同。apply
函数只能传递一个数组作为参数,call
函数可以传递任意多个参数
1 2 3 4 5 6 7 8 9 10 11
| function _call(ctx: any, fn: (...[]) => any, ...args: any[]): any { if (!ctx) return fn(...args);
ctx = Object(ctx); const fnName = Symbol(); ctx[fnName] = fn; const result = ctx[fnName](...args); delete ctx[fnName];
return result; }
|
bind
bind
方法会返回一个绑定好作用域的函数,除此之外和call
函数一致
1 2 3 4 5 6 7
| function _bind( ctx: object | null, fn: (...args: any[]) => any, ...args: any[] ): (...args: any[]) => any { return (...left: any[]) => fn.call(ctx, ...args, ...left); }
|
instanceof
原理
- 创建一个构造函数
function Person() {}
new
一个构造函数const person = new Person()
会创建一个实例
- 实例的
__proto__
属性会指向构造函数的Person.prototype
属性
- 因此可以判断实例原型链上的
__proto__
属性和构造函数的prototype
属性是否相等即可知道某个对象是否为某个构造函数的实例
自己实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function _instanceof(instance: any, constructor: any): boolean { const prototype = constructor?.prototype; let __proto__ = instance?.__proto__;
while (__proto__ && prototype) { if (__proto__ === prototype) { return true; }
__proto__ = __proto__.__proto__; }
return false; }
|
使用原生方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function _instanceof(instance: any, constructor: any): boolean { const prototype = constructor?.prototype; let __proto__ = Object.getPrototypeOf(instance);
while (__proto__ && prototype) { if (__proto__ === prototype) { return true; }
__proto__ = Object.getPrototypeOf(__proto__); }
return false; }
|
new
Promise
Array
去重
indexOf
遍历数组,判断当前元素在数组中第一次出现的下标是否与当前下标一致
1 2 3 4 5 6 7 8 9 10 11
| function unique(arr: any[]): any[] { const result: any[] = [];
for (let i = 0; i < arr.length; i++) { if (arr.indexOf(arr[i]) === i) { result.push(arr[i]); } }
return result; }
|
排序
对数组排序后,当前元素和下一个元素不同,则将当前元素添加到新数组中
1 2 3 4 5 6 7 8 9 10 11 12
| function unique(arr: Array<number | string>): Array<number | string> { const result: Array<string | number> = []; arr.sort();
for (let i = 0; i < arr.length; i++) { if (arr[i] !== arr[i + 1]) { result.push(arr[i]); } }
return result; }
|
Set
利用Set
的hash特性以及不能存在重复元素的特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function unique(arr: any[]): any[] { const result: any[] = []; const set = new Set();
arr.forEach((item) => { if (!set.has(item)) { set.add(item); result.push(item); } });
return result; }
function unique(arr: any[]): any[] { return Array.from(new Set(arr)); }
|
排序
冒泡排序
1 2 3 4 5 6 7 8 9 10 11 12 13
| function bubbleSort(arr: number[]): number[] { const length = arr.length;
for (let i = 0; i < length - 1; i++) { for (let j = 0; j < length - 1; j++) { if (arr[j] > arr[j + 1]) { [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; } } }
return arr; }
|
选择排序
1 2 3 4 5 6 7 8 9 10 11 12 13
| function selectSort(arr: number[]): Array<number> { const length = arr.length;
for (let i = 0; i < length - 1; i++) { for (let j = i + 1; j < length; j++) { if (arr[i] > arr[j]) { [arr[i], arr[j]] = [arr[j], arr[i]]; } } }
return arr; }
|
快排
在数组中找一个基准值,遍历整个数组(除基准值外),比基准值小的放到基准值的左侧,否则放在右侧。再对左右两侧的值进行相同操作即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function quickSort(arr: number[]): number[] { if (arr.length <= 1) return arr;
const left: number[] = []; const right: number[] = []; const index: number = Math.floor(arr.length / 2); const middle: number = arr[index];
for (let i = 0; i < arr.length; i++) { if (i !== index) { const item = arr[i];
if (item > middle) { right.push(item); } else { left.push(item); } } }
return quickSort(left).concat([middle]).concat(quickSort(right)); }
|
Object
is 空对象
1 2 3
| function isEmptyObj(obj: object): boolean { return JSON.stringfy(obj) === "{}"; }
|
String
repeat
padStart
padEnd
Number
是不是整数
一个数取整后还等于它本身就是整数
1 2 3
| function isInteger(num: number): boolean { return Math.floor(num) === num; }
|
Date
今天是周几
日期对象的toString()
方法返回的内容中包含周的信息
1 2 3 4 5
| function getDate(): string { const date = new Date();
return date.toString().slice(0, 3); }
|
使用Date
自带方法getDay
1 2 3 4 5
| function getDate(): number { const date = new Date();
return date.getDay(); }
|
日期字符串格式是否正确
正确的日期格式YYYY-MM-DD
、YYYY-M-D
等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function isExactDateString(dateStr: string): boolean { const dateReg = /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/; const monthMap: number[] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (!dateReg.test(dateStr)) return false;
const datePair = dateStr.split("-"); const month: number = +datePair[1]; const date: number = +datePair[2];
if (!(month >= 1 && month <= 12)) return false;
if (!(date >= 1 && date <= monthMap[month - 1])) return false;
return true; }
|
两天是否在同一周
注意
getDay
方法的返回值范围为0 - 6
,0 表示周天
思路
- 将日期字符串转为日期毫秒值,经过计算可以得知两个日期相差多少天
dayGap
- 在以其中一个日期为基准,获取该日期是周几
dateDay
dayGap
和dateDay
之间的关系只要满足指定条件,即在同一周,具体过程看代码最后if else
部分
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
|
function twoDayInSameWeek(date1: string, date2: string): boolean | string { const dateReg = /[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}/;
if (!dateReg.test(date1) || !dateReg.test(date2)) { return "日期格式错误:应为YYYY-MM-DD"; }
const oneDayTime = 24 * 60 * 60 * 1000; const date1Obj = new Date(date1); const date2Obj = new Date(date2); const date1Day: number = date1Obj.getDay() || 7; const date1Time = date1Obj.getTime(); const date2Time = date2Obj.getTime(); const dayGap: number = Math.abs(date1Time - date2Time) / oneDayTime;
if (date1Time <= date2Time) { return date1Day + dayGap <= 7; } else { return date1Day - dayGap >= 0; } }
|
某一年的第几天
参数
- 传入
YYYY-MM-DD
格式的日期,判断该日期是YYYY
年的第几天
- 不传参数则表示今天是今年的第几天
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
|
function dayOfYear(date?: string): number | string { const reg = /[0-9]{4}-[0-9]{2}-[0-9]{2}/; const oneDayTime = 24 * 60 * 60 * 1000;
if (date && !reg.test(date)) return "日期格式错误:应为YYYY-MM-DD";
const calcDayOfYear = (date: string): number => { const year = date.slice(0, 4); const beginningTime = new Date(`${year}-1-1`).getTime(); const currentTime = new Date(date).getTime();
return Math.floor((currentTime - beginningTime) / oneDayTime) + 1; };
if (!date) { const currDate = new Date(); const year = currDate.getFullYear(); const month = String(currDate.getMonth() + 1).padStart(2, "0"); const day = String(currDate.getDate()).padStart(2, "0");
return calcDayOfYear(`${year}-${month}-${day}`); }
return calcDayOfYear(date); }
|
编码/解码
encodeURIComponent
escape