1.浅拷贝
数组浅拷贝 Array.prototype.concat()
// concat() 方法用于连接两个或多个数组。
// 该方法不会改变现有的数组,而是返回一个新的数组
const newArr = arr.concat()
数组浅拷贝 Array.prototype.slice()
// slice() 方法可从已有的数组中返回选定的元素
// 注意: slice() 方法不会改变原始数组,而是返回一个新的数组
const newArr = arr.slice()
对象浅拷贝 Object.assign()
Object.assign() 方法可以将任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象
const newObj = Object.assign({}, obj)
扩展运算符 ...
// 数组
const newArr = [...arr];
// 对象
const newObj = {...obj};
2.深拷贝
const obj = structuredClone(obj)
JSON.parse(JSON.stringify(obj))
不能拷贝函数和undefined, 日期,正则
const newData = JSON.parse(JSON.stringify(data))
递归深拷贝
// 深拷贝对象
export function deepClone(obj) {
const _toString = Object.prototype.toString
// null, undefined, non-object, function
if (!obj || typeof obj !== 'object') {
return obj
}
// DOM Node
if (obj.nodeType && 'cloneNode' in obj) {
return obj.cloneNode(true)
}
// Date
if (_toString.call(obj) === '[object Date]') {
return new Date(obj.getTime())
}
// RegExp
if (_toString.call(obj) === '[object RegExp]') {
const flags = []
if (obj.global) {
flags.push('g')
}
if (obj.multiline) {
flags.push('m')
}
if (obj.ignoreCase) {
flags.push('i')
}
return new RegExp(obj.source, flags.join(''))
}
const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {}
for (const key in obj) {
result[key] = deepClone(obj[key])
}
return result
}
/**
* 深拷贝简化版
* @param {any} target
* @param {WeakMap} map
* @returns {any} cloneTarget
*/
const _completeDeepClone = (target, map = new WeakMap()) => {
// 基本数据类型,直接返回
if (typeof target !== 'object' || target === null) return target
// 函数 正则 日期 ES6新对象,执行构造题,返回新的对象
const constructor = target.constructor
if (/^(Date|Map|Set)$/i.test(constructor.name)) return new constructor(target)
if (constructor === Function) {
var arr = target.toString().replace(/\n|\r/g, "").trim().match(/\((.*?)\)\s*\{(.*)\}/).slice(1); //进行source处理
return new Function(arr[0].trim(), arr[1]);
}
if(constructor === RegExp) {
return new RegExp(target.source, target.flags);
}
// map标记每一个出现过的属性,避免循环引用
if (map.get(target)) return map.get(target)
map.set(target, true)
const cloneTarget = Array.isArray(target) ? [] : {}
for (prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = _completeDeepClone(target[prop], map)
}
}
return cloneTarget
}
/**
* 深拷贝加强版
* @param {any} parent
* @returns {any} child
*/
export const clone = parent => {
// 判断类型
const isType = (obj, type) => {
if (typeof obj !== "object") return false;
const typeString = Object.prototype.toString.call(obj);
let flag;
switch (type) {
case "Array":
flag = typeString === "[object Array]";
break;
case "Date":
flag = typeString === "[object Date]";
break;
case "RegExp":
flag = typeString === "[object RegExp]";
break;
case "Set":
flag = typeString === "[object Set]";
break;
case "Map":
flag = typeString === "[object Map]";
break;
default:
flag = false;
}
return flag;
};
// 处理正则
const getRegExp = re => {
var flags = "";
if (re.global) flags += "g";
if (re.ignoreCase) flags += "i";
if (re.multiline) flags += "m";
return flags;
};
// 维护储存循环引用的对象
const cached = new WeakMap;
const _clone = parent => {
if (parent === null) return null;
if (typeof parent !== "object") return parent;
// 循环引用则直接返回
if (cached.get(parent)) return cached.get(parent)
let child, proto;
if (isType(parent, "Array")) {
// 对数组做特殊处理
child = [];
} else if (isType(parent, "RegExp")) {
// 对正则对象做特殊处理
child = new RegExp(parent.source, getRegExp(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (isType(parent, "Date")) {
// 对Date对象做特殊处理
child = new Date(parent.getTime());
} else if (isType(parent, "Set")) {
// 对Set对象做特殊处理
child = new Set();
parent.forEach(val => {
child.add(_clone(val))
})
} else if (isType(parent, "Map")) {
// 对Map对象做特殊处理
child = new Map();
parent.forEach((val, key) => {
child.set(key, _clone(val))
})
} else {
// 处理对象原型
proto = Object.getPrototypeOf(parent);
// 利用Object.create切断原型链
child = Object.create(proto);
}
cached.set(parent, child)
for (let i in parent) {
// 递归
child[i] = _clone(parent[i]);
}
return child;
};
return _clone(parent);
};
3. 防抖
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这n 秒内事件又被触发,则重新计时。
使用场景: 搜索框、按钮提交
普通防抖
function debounce(fn, delay = 200) {
// 记录上⼀次的延时器
var timer = null;
return function (...args) {
// 清除上⼀次延时器
timer && clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
}
}
自执行函数版
const debounce = (() => {
let timer = null;
return (fn, delay = 200) => {
timer && clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
}
})()
vue、vue3版
function debounce(fn, delay = 300) {
// 记录上⼀次的延时器
var timer = null;
return function (this: unknown, ...args) {
timer && clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
使用防抖
// vue2
methods: {
loadList:debounce(() => {
console.log('加载数据')
}, 500)
}
// vue3
const loadList = debounce(() => {
console.log('加载数据')
}, 500)
// pinia中使用
selecFilter: debounce(async function (this, value, name) {
// this获取state的值
}, 1000),
4. 节流
函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
拖拽场景、缩放场景、动画场景、scroll滚动
定时器版 (最后一次也会触发)
function throttle(fn, delay = 300){
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
clearTimeout(timer);
timer = null;
}, delay)
}
}
}
function throttle(fn, delay = 300) {
let isThrottling = false
// 核心思路,函数多次执行只有当 isThrottling 为 false 时才会进入函数体
return function (...args) {
if (!isThrottling) {
isThrottling = true;
setTimeout(() => {
isThrottling = false;
fn.apply(this, args);
}, delay)
}
}
}
时间戳版 (第一次就触发)
function throttle(fn, delay = 200){
var lastTime = 0;
return function (...args) {
var nowTime = Date.now();
if (nowTime - lastTime > delay) {
lastTime = nowTime;
fn.apply(this, args);
}
}
}
控制第一次和最后一次
function throttle3(fn, delay, op = {}) {
let timer = null;
let pre = 0;
return function (...args) {
let now = Date.now();
if (now - pre > delay) {
if (pre == 0 && !op.bengin) {
pre = now
return
}
if (timer) {
clearTimeout(timer)
timer = null
}
fn.apply(this, args);
pre = now
} else if (!timer && op.end) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null
}, delay)
}
}
}
vue2, vue3版
function throttle(fn, delay = 300) {
var lastTime = Date.now();
return function (this: unknown, ...args) {
var nowTime = Date.now();
if (nowTime - lastTime > delay) {
lastTime = nowTime;
fn.apply(this, args);
}
};
}
使用节流
// vue2
methods:{
appSearch:debounce(function(){
this.getList()
},300)
}
// vue3
const appSearch = debounce(function(){
this.getList()
},300)
5. 将连字符转化为转驼峰
'on-click' => 'onClick'
/**
* 将连字符转化为转驼峰
* @param {string} str 连字符
* @returns {string}
*/
const camelize = str => {
return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
}
6. 将小驼峰转化为连字符字符串
'onClick' => 'on-click'
/**
* 将小驼峰转化为连字符字符串
* @param {string} str 驼峰字符
* @returns {string}
*/
const hyphenate = (str: string) => str.replace(/\B([A-Z])/g,'-$1').toLowerCase()
7. 日期转换并自动补零
将不同的日期格式转换成统一的格式 2023-07-04 09:54:00
// const num1 = 0.13; num1.toLocaleString('zh',{style:'percent'})); 得到 13%
/**
* 日期转换并自动补零
*/
formatTime(time) {
if (time) {
const date = new Date(time).toLocaleString('zh', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' });
//时间字符串两位显示 2203/02/15 16:41:02
return date;
}
return '';
}
/**
* 日期转换并自动补零
* @param {type} 参数
* @returns {type} 返回值
*/
formatTime(time) {
if (time) {
const date = new Date(time).toLocaleString().split(' ');
const date1 = date[0]
.split('/')
.map((item) => {
if (item.length === 1) {
return '0' + item;
}
return item;
})
.join('-');
return date1 + ' ' + date[1];
}
return '';
//return time? new Date(time).toLocaleString().replace(/(\d{4})\/(\d{1,2})\/(\d{1,2})/, (match, year, month, day) => {return `${year}/${month.padStart(2, '0')}/${day.padStart(2, '0')}`}): ''
}
/**
* 表格时间格式化
*/
export function formatDate(cellValue) {
if (cellValue == null || cellValue == "") return "";
var date = new Date(cellValue)
var year = date.getFullYear()
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
}
/**
* 格式化时间
*/
export const formateTime = (data?: string) => {
const da = data || new Date().getTime();
const dt = new Date(da);
const y = dt.getFullYear();
const m = (`${dt.getMonth() + 1}`).padStart(2, '0');
const d = (`${dt.getDate()}`).padStart(2, '0');
const hh = (`${dt.getHours()}`).padStart(2, '0');
const mm = (`${dt.getMinutes()}`).padStart(2, '0');
const ss = (`${dt.getSeconds()}`).padStart(2, '0');
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
};
8. 获取本日/本周/本月的开始时间和当前时间
/**
* 获取近24小时、近一周、近一月、近一年的时间范围
*/
export const useGetDate = (period: string) => {
const now = new Date();
const endTime: any = new Date();
const startTime: any = now;
switch (period) {
case 'day':
startTime.setHours(now.getHours() - 24);
break;
case 'week':
startTime.setDate(now.getDate() - 7);
break;
case 'month':
startTime.setMonth(now.getMonth() - 1);
break;
case 'year':
startTime.setFullYear(now.getFullYear() - 1);
break;
default:
break;
}
return [formatTime(startTime), formatTime(endTime)];
};
/**
* 获取日期
* @param {string} type
* @returns {object}
*/
const getDate = (type: string) => {
let startTime = '';
let endTime = '';
if (type === '本月') {
startTime = formatTime(new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-1') + ' 00:00:00';
endTime = formatTime(new Date()) + ' 23:59:59';
}
if (type === '本周') {
// 当天的时间戳
const today = Date.parse(new Date().toLocaleDateString());
// 当天星期几换算时间戳
const week = (new Date().getDay() - 1) * 24 * 60 * 60 * 1000;
startTime = formatTime(new Date(today - week)) + ' 00:00:00';
endTime = formatTime(new Date()) + ' 23:59:59';
}
if (type === '今日') {
startTime = formatTime(new Date()) + ' 00:00:00';
endTime = formatTime(new Date()) + ' 23:59:59';
}
return { startTime, endTime };
};
9. 月份范围转月份列表
获取如 2023-05 到2023-07之间的所有月份['2023-05','2023-06','2023-07']
/**
* 获取日期
* @param {string} start 开始日期
* @param {string} end 结束日期
* @returns {array}
*/
getMonthBetween(start, end) {
//传入的格式YYYY-MM
var result = [];
var s = start.split("-");
var e = end.split("-");
var min = new Date();
var max = new Date();
var yearMonthCode;
min.setFullYear(s[0], s[1] * 1 - 1, 1);//开始日期
max.setFullYear(e[0], e[1] * 1, 1);//结束日期
var curr = max;
while (curr > min) {
const year = curr.getFullYear()
const month = curr.getMonth()
yearMonthCode = month > 9 ? (year + '-' + month) : (year + '-0' + month)
result.push(yearMonthCode);
curr.setMonth(month - 1);
}
return result;
},
11 日期范围转日期列表
function getDateList(startDate, endDate) {
const dateList = [];
// 将开始日期和结束日期转换为 Date 对象
const start = new Date(startDate);
const end = new Date(endDate);
// 循环遍历日期范围
while (start <= end) {
// 获取当前日期的月份和日期,并格式化为 'MM-DD' 的形式
const month = String(start.getMonth() + 1).padStart(2, '0');
const day = String(start.getDate()).padStart(2, '0');
const formattedDate = `${month}-${day}`;
// 将格式化后的日期添加到日期列表中
dateList.push(formattedDate);
// 递增日期
start.setDate(start.getDate() + 1);
}
return dateList;
}
// 示例用法
const startDate = '2023-08-02';
const endDate = '2023-08-05';
const result = getDateList(startDate, endDate);
console.log(result); // 输出:['08-02', '08-03', '08-04', '08-05']
12 年份范围转年份列表
function getYearList(startYear, endYear) {
const yearList = [];
// 将开始年份和结束年份转换为数字
const start = parseInt(startYear);
const end = parseInt(endYear);
// 循环遍历年份范围
for (let year = start; year <= end; year++) {
// 将当前年份添加到年份列表中
yearList.push(String(year));
}
return yearList;
}
// 示例用法
const startYear = '2020';
const endYear = '2023';
const result = getYearList(startYear, endYear);
console.log(result); // 输出:['2020', '2021', '2022', '2023']
13. 二维数组展开
[[1,2,3],4,[5,6]] 展开成[1,2,3,4,5,6]
const arr1 = [[1,2,3],4,[5,6]]
const arr2 = arr1.reduce((pre, cur) => pre.concat(cur), [])
14. 根据数组子项的属性去重
[{id: 1},{id: 2},{id: 1}] 根据id去重得到 [{id: 1},{id: 2}]
const arr1 = [{id: 1},{id: 2},{id: 1}]
const arr2 = arr1.reduce((pre, cur) => {
if (!pre.find(v=>v.id === cur.id)){
pre.push(cur)
}
return pre
},[])
15. 数字千分位加逗号
console.log(toThousands(1234567.65432)); // 1,234,567.65
/**
* 数字千分位加逗号
*/
const toThousands = (value: number | string) => {
return parseFloat(value || 0).toLocaleString();
};
/**
* 数字千分位加逗号
* @param {type} 参数
* @returns {type} 返回值
*/
function toThousands(num) {
let newNum = num.toString()
if (newNum === '0' || newNum === '0%') return '-'
if (newNum.includes('%') || !newNum) return newNum
let pointNum = '' // 小数点尾数
if (newNum.includes('.')) {
const arr = newNum.split('.')
newNum = arr[0]
pointNum = arr[1].slice(0, 2)
}
let result = ''
while (newNum.length > 3) {
result = ',' + newNum.slice(-3) + result
newNum = newNum.slice(0, -3)
}
return pointNum ? (newNum + result + '.' + pointNum) : (newNum + result)
}
15.数字转中文
/*
* 数字转中文
*/
function changeNumToHan (num) {
var arr1 = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
var arr2 = ['', '十', '百', '千', '万', '十', '百', '千', '亿', '十', '百', '千', '万', '十', '百', '千', '亿']
if (!num || isNaN(num)) return '零'
var english = num.toString().split('')
var result = ''
for (var i = 0; i < english.length; i++) {
var des_i = english.length - 1 - i// 倒序排列设值
result = arr2[i] + result
var arr1_index = english[des_i]
result = arr1[arr1_index] + result
}
result = result.replace(/零(千|百|十)/g, '零').replace(/十零/g, '十') // 将【零千、零百】换成【零】 【十零】换成【十】
result = result.replace(/零+/g, '零') // 合并中间多个零为一个零
result = result.replace(/零亿/g, '亿').replace(/零万/g, '万') // 将【零亿】换成【亿】【零万】换成【万】
result = result.replace(/亿万/g, '亿') // 将【亿万】换成【亿】
result = result.replace(/零+$/, '') // 移除末尾的零
// 将【一十】换成【十】
result = result.replace(/^一十/g, '十')
return result
}
16.数组数据转换
- 数组转树状 - 递归
数组转树状 - 递归
function listToTree(list, pid = 0) {
// 如果有一个 pid ,找出对应层级的部门
const res = []
list.forEach(item => {
if (item.pid === pid) {
// 每找到一个当前层级的部门,就以当前部门的 id
// 作为下一层的 pid 进行搜索
const children = listToTree(list, item.id)
// 如果找到,就放入当前的部门中
if (children.length > 0) {
item.children = children
}
res.push(item)
}
})
return res
}
- 数组转树状 - 给父节点添加children
function listToTree(list, pid = 0) {
// 删除 所有 children,以防止多次调用
list.forEach(function(item) {
delete item.children;
});
list.forEach(item => {
// item.pid===pid说明没有父部门
if (item.pid !== pid) {
const parent = list.find(el => el.id === item.pid)
if (parent) {
parent.children = parent.children || []
parent.children.push(item)
}
}
})
return list.filter(item => item.pid === pid)
}
- 树状结构转一维数组
树状结构转一维数组
function treeToArray(list) {
const arr = []
list.forEach(item => {
arr.push(item)
if (item.children && item.children.length) {
arr.push(...treeToArray(item.children))
}
})
// 删除 所有 children
arr.forEach(function (item) {
delete item.children;
});
return arr
}
- 数组扁平化 - flat
数组扁平化 - flat
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
]
//flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
arr = arr.flat(Infinity)
console.log(arr); //[1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
- 数组扁平化 - toString + parseFloat
数组扁平化 - toString + parseFloat
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
]
arr = arr.toString().split(',').map(item => parseFloat(item))
console.log(arr); //[1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
- 数组扁平化 - JSON.stringify
数组扁平化 - JSON.stringify
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
]
arr = JSON.stringify(arr).replace(/(\[|\])/g,'').split(',').map(item => parseFloat(item))
console.log(arr); //[1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
- 数组扁平化 - while + Array.isArray + concat
数组扁平化 - while + Array.isArray + concat
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
]
// 循环验证是否为数组
// some() 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值
while(arr.some(item => Array.isArray(item))) {
// concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组
arr = [].concat(...arr)
}
console.log(arr);
17. replace 正则手机号去敏感
function tranPhone(num) {
let str = String(num)
str = str.replace(/^(\d{3})\d{4}(\d{4})/,"$1****$2")
return str
}
console.log(tranPhone(15655656485));
18. 字符串保留#号后面的内容
"赤盛#104" 只保留 "#104"
const str = "赤盛#104";
const newStr = str.replace(/.*(#.*)/, "$1");
console.log(newStr); // 输出:#104
19.匹配字符串中第一个数字
是多少#32' 保留32
// '是多少#32'.match(/#(\d+)/) ['#32', '32', index: 3, input: '是多少#32', groups: undefined]
'是多少#32'.match(/#(\d+)/)[1] // 32
20.判断数据类型
JavaScript 数据类型
原始类型(值类型、基本类型) :字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。
引用数据类型(对象类型) :对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)
typeof
let obj={
name:'dawn',
age:21
}
let fn=function() {
console.log('我是 function 类型');
}
console.log(typeof 1); //number
console.log(typeof 'abc'); //string
console.log(typeof true); //boolean
console.log(typeof undefined); //undefined
console.log(typeof fn); //function
console.log(typeof (new Date) ); //object
console.log(typeof null); //object
console.log(typeof [1,2,3]); //object
console.log(typeof obj); //object
instanceof
let arr=[1,2,3,4,5,6,7]
let obj={
name:'dawn',
age:21
}
let fn=function() {
console.log('我是 function 类型');
}
console.log(arr instanceof Array); //true
console.log(obj instanceof Object); //true
console.log(fn instanceof Function); //true
console.log((new Date) instanceof Date); //true
Object.prototype.toString.call
let obj = {
name: 'dawn',
age: 21
}
let fn = function () {
console.log('我是 function 类型');
}
console.log(Object.prototype.toString.call(1)); // [object Number]
console.log(Object.prototype.toString.call('Hello tomorrow')); // [object String ]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(fn)); // [object Function]
console.log(Object.prototype.toString.call(new Date)); // [object Date]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call([1, 2, 3])); // [object Array]
console.log(Object.prototype.toString.call(obj)); // [object Object]
constructor
let arr = [1, 2, 3, 4, 5, 6, 7]
let obj = {
name: 'dawn',
age: 21
}
let fn = function () {
console.log('我是 function 类型');
}
console.log((9).constructor === Number); //true
console.log('hello'.constructor === String); //true
console.log(true.constructor === Boolean); //true
console.log(fn.constructor === Function); //true
console.log((new Date).constructor === Date); //true
console.log(obj.constructor === Object); //true
console.log([1, 2, 3].constructor === Array); //true
判断数组额外方法
- Array.isArray()
let arr = []
console.log(Array.isArray(arr)) //true
- Object.getPrototypeOf()
let arr = []
console.log(Object.getPrototypeOf(arr) == Array.prototype) //true
- Array.prototype.isPrototypeof()
let arr = []
console.log(Array.prototype.isPrototypeof(arr)) //true
判断对象额外方法
- Object.getPrototypeOf()
Object.getPrototypeOf(val) === Object.prototype // true 代表为对象
21.发布订阅模式 + 单例模式实现一处绑定多处调用
发布订阅模式, 使用场景: 类似事件总线, 可以跨组件绑定事件和触发事件
// class版 发布订阅者模式
class EventEmitter {
static instance: EventEmitter;
constructor() {
// key: 事件名, value: callback[] 回调数组
this.events = {}
}
on(name, callback) {
if (typeof callback !== 'function') return console.error('请传入正确的回调函数');
const events = this.events[name]
if (events) {
events.push(callback)
} else {
this.events[name] = [callback]
}
}
once(name, callback) {
if (typeof callback !== 'function') return console.error('请传入正确的回调函数');
const onceCallback = (...args) => {
callback(...args)
this.off(name)
}
this.on(name, onceCallback)
}
emit(name, ...args) {
const events = this.events[name]
if (!events) return console.warn(`${name}事件不存在`);
for (const event of events) {
event(...args);
}
}
off(name, callback) {
if (!this.events[name]) return console.warn(`${name}事件不存在`);
if (!callback) {
// 没有callback 就删除整个事件
delete this.events[name]
}
this.events[name] = this.events[name].filter(item => item !== callback)
}
// 获取实例
static getInstance():EventEmitter {
//如果有实例则返回,没有则创建并返回
return this.instance || (this.instance = new EventEmitter())
}
}
// vue3 hooks版发布订阅模式
const events = new Map()
const EventEmitter = function () {
function on(name, callback) {
if (typeof callback !== 'function') return console.error('请传入正确的回调函数');
let event = events.get(name)
if (!event) {
event.set(name, (event = new Set()))
}
event.add(callback)
}
function once(name, callback) {
if (typeof callback !== 'function') return console.error('请传入正确的回调函数');
const onceCallback = (...args) => {
callback(...args)
off(name)
}
on(name, onceCallback)
}
function emit(name, ...args) {
const event = events.get(name)
if (!event) return console.warn(`${name}事件不存在`);
event.forEach(fn => fn(...args))
}
function off(name, callback) {
const event = events.get(name)
if (!event) return console.warn(`${name}事件不存在`);
if (!callback) {
// 没有callback 就删除整个事件
events.delete(name)
}
event.delete(callback)
}
return { on, once, emit, off }
}
22.观察者模式 + 单例模式实现多处绑定,集体消息通知
观察者模式, 使用场景: 绑定子类的更新方法, 再父类统一遍历触发
// class模式版
// 观察者模式
class Observerd {
static instance: Observerd;
constructor() {
this.observerList = {}
}
addObserver(name, observer) {
const list = this.observerList[name]
if (list) {
list.push(observer)
} else {
this.observerList[name] = [observer]
}
}
notify(name) {
const list = this.observerList[name]
if (!list) return
// list.forEach(observer => observer.update())
list.forEach(observer => observer())
}
// 获取实例
static getInstance():Observerd {
//如果有实例则返回,没有则创建并返回
return this.instance || (this.instance = new Observerd())
}
}
// vue3 + hooks版
const observerList = new Map()
const Observerd = function () {
function addObserver(name, observer) {
let list = observerList.get(name)
if (!list) {
observerList.set(name, (list = new Set()))
}
event.add(observer)
}
function notify(name) {
const list = observerList.get(name)
if (!list) return
// list.forEach(observer => observer.update())
list.forEach(observer => observer())
}
return { addObserver, notify }
}
base64 编码和解码
直接用btoa编码str会报错字符串包含超出 Latin1 范围的字符,所有要用封装后的方法
/**
* 编码base64
*/
export function Encode64(data) {
const str = typeof data === 'string'? data : JSON.stringify(data)
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toSolidBytes(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
/**
* 解码base64
*/
function Decode64(str) {
return decodeURIComponent(atob(str).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}