数组转换树形结构
function composeTree(list = [], mainMatches = 'id', viceMatches = 'pid') {
const data = JSON.parse(JSON.stringify(list)) // 浅拷贝不改变源数据
const result = []
if (!Array.isArray(data)) {
return result
}
data.forEach((item) => {
delete item.children
})
const map = {}
data.forEach((item) => {
map[item[mainMatches]] = item
})
data.forEach((item) => {
const parent = map[item[viceMatches]]
if (parent) {
(parent.children || (parent.children = [])).push(item)
} else {
result.push(item)
}})
return result
}
树形结构转换数组
// 将树数据转化为平铺数据
flatTreeData(treeData, childKey = 'children') {
const arr = [];
const expanded = (data) => {
if (data && data.length > 0) {
data
.filter((d) => d)
.forEach((e) => {
arr.push(e);
expanded(e[childKey] || []);
});
}
};
expanded(treeData);
return arr;
}
柯里化
// 实现函数柯里化
function curry(fn) {
// 返回一个新函数
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args); // 如果参数够了,就执行原函数,返回结果
} else {
//返回一个新函数,继续递归去进行柯里化,利用闭包,将当前已经传入的参数保存下来
return function (...args2) {
//递归调用 curried 函数
return curried.apply(this, [...args, ...args2]); //新函数调用时会继续传参,拼接参数
};
}
};
}
深拷贝
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let cloneObj = Array.isArray(obj) ? [] : {};
for(let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
浅拷贝
function shallowCopy (obj){
// 只拷贝对象,基本类型或null直接返回
if(typeof obj !== 'object' || obj === null) {
return obj;
}
// 判断是新建一个数组还是对象
let newObj = Array.isArray(obj) ? []: {};
//for…in会遍历对象的整个原型链,如果只考虑对象本身的属性,需要搭配hasOwnProperty
for(let key in obj ){
//hasOwnProperty判断是否是对象自身属性,会忽略从原型链上继承的属性
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key];//只拷贝对象本身的属性
}
}
return newObj;
}
数组的转化
let primitiveArr = [
{ year: '2010', time: '0909' },
{ year: '2011', time: '0909' },
{ year: '2012', time: '0909' },
{ year: '2010', time: '1010' }
]
function changeArr(arr) {
let getObj = {}
for (let j = 0; j < primitiveArr.length; j++) {
if (getObj[primitiveArr[j].year]) {
getObj[primitiveArr[j].year].push(primitiveArr[j])
} else {
getObj[primitiveArr[j].year] = [primitiveArr[j]]
}
}
// console.log(getObj);
let arr2 = []
for (const key in getObj) {
arr2.push(getObj[key])
}
return arr2
}
let text = changeArr(primitiveArr)
console.log(text, '-11--');
求和
function add(firstNum, secondNum) {
let computedNum = firstNum + secondNum
if (secondNum + 1 > 100) {
return computedNum
} else {
return add(computedNum, secondNum + 1)
}
}
let allNum = add(1, 2)
console.log('和1-100值是', allNum);
字符次数出现最多的方法
/**
* 用途:
for...in: 通常用于遍历对象的属性。
for...of: 通常用于遍历数组或类似数组的对象,以及其他可迭代对象。
迭代内容:
for...in: 迭代的是属性名。
for...of: 迭代的是值。
*/
let str = 'aaaabbbccd'
function maxNum(str) {
let chatObj = {}
//第一种方法
// for (let index = 0; index < str.length; index++) {
// !chatObj[str.charAt(index)] ?
// chatObj[str.charAt(index)] = 1
// : chatObj[str.charAt(index)] += 1
// }
//第二种方法
for (let char of str) {
chatObj[char] = (chatObj[char] || 0) + 1;
}
console.log(chatObj)
let setName = ''
let setValue = 1
for (const key in chatObj) {
console.log(key, '---in');
if (chatObj[key] > setValue) {
setName = key;
setValue = chatObj[key]
}
return {
name: setName,
value: setValue
}
}
}
let getMaxNum = maxNum(str)
console.log(getMaxNum, '----get');
数组降序
const numbers = [1, 2, 34, 5, 6, 7, 8, 0];
// 使用 sort() 方法进行降序排序
numbers.sort((a, b) => b - a);
console.log(numbers); // 输出: [34, 9, 8, 7, 6, 5, 2, 1]
function insertionSortDescending(arr) {
for (let i = 1; i < arr.length; i++) {
let key = arr[i];
let j = i - 1;
// 移动元素
while (j >= 0 && arr[j] < key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
}
const sortedNumbers = insertionSortDescending(numbers);
console.log(sortedNumbers); // 输出: [34, 9, 8, 7, 6, 5, 2, 1]
数组升序
const numbers = [1, 2, 34, 5, 6, 7, 8, 0];
numbers.sort((a, b) => a - b);
console.log(numbers); // 输出: [1, 2, 5, 6, 7, 8, 9, 34]
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
let key = arr[i];
let j = i - 1;
// 移动元素
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
}
// const sortedNumbers = insertionSort(numbers);
// console.log(sortedNumbers); // 输出: [1, 2, 5, 6, 7, 8, 9, 34]
数组扁平化
//flat方式
const arr = [1,[2,[3,[4,5]]],6]
// arr.flat([depth]) flat的参数代表的是需要展开几层,如果是Infinity的话,就是不管嵌套几层,全部都展开
console.log(arr.flat(Infinity)) //[1,2,3,4,5,6]
//递归方式
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
//如果当前元素还是一个数组
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));//递归拼接
} else {
result.push(arr[i]);
}
}
return result;
}
console.log(flatten(arr)); // [1, 2, 3, 4]
数组去重
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log([...new Set(arr)]); //[ 1, 2, 3, 5, 9, 8 ]
console.log(Array.from(new Set(arr))); //[ 1, 2, 3, 5, 9, 8 ]
<---------------------或者------------------------>
function unique(arr) {
return arr.filter((item, index, array) => {
return array.indexOf(item) === index;
});
}
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log(unique(arr)); // [1, 2, 3, 5, 9, 8]
手写类型判断函数
function getType(value) {
// 判断数据是 null 的情况
if (value === null) {
return String(value);
}
// 判断数据是基本数据类型的情况和函数的情况,使用typeof
if (typeof value !== "object") {
return typeof value;
} else {
let valueClass = Object.prototype.toString.call(value);
type = valueClass.split(" ")[1].split("");
type.pop();
return type.join("").toLowerCase();
}
}
防抖
防抖(Debounce) 确保在指定的时间间隔内,无论连续触发了多少次事件,只有最后一次事件会在该间隔结束后执行。(触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。) 工作原理是,当事件持续触发时,只有在事件停止触发n秒后,才会执行事件函数。如果在n秒内事件被重新触发,那么之前的计时会被重置。这种技术通常用于搜索框输入、按钮点击等场景,避免短时间内的频繁请求。
//fn是需要防抖的函数,delay是等待时间
function debounce(fn, delay = 500) {
let timer = null;
// 这里返回的函数是每次用户实际调用的防抖函数
return function(...args) { //...args是es6的剩余参数语法,将多余的参数放入数组,用来代替arguments对象
// 如果已经设定过定时器了就清空上一次的定时器
if(timer) {
clearTimeout(timer);
}
// 开始一个新的定时器,延迟执行用户传入的方法;注:定时器的返回值是一个数值,作为定时器的编号,可以传入clearTimeout来取消定时器
timer = setTimeout(() => { //这里必须是箭头函数,不然this指向window,要让this就指向fn的调用者
fn.apply(this, args);
}, delay)
}
}
// 测试
function task() {
console.log('run task')
}
const debounceTask = debounce(task, 1000)
window.addEventListener('scroll', debounceTask)
节流(定时器版本)
确保在指定的时间间隔内,无论触发了多少次事件,只有一次事件会被执行,后续事件在这个间隔内都不会执行。(连续触发事件但是在 n 秒中只执行第一次触发函数) 是指在一定时间内,无论函数被触发多少次,函数只会在固定的时间间隔内执行一次。如果在时间间隔内有多次触发事件,只会执行最后一次。节流技术常用于滚动事件、鼠标移动等场景,限制函数的执行频率。
function throttle(fn, delay = 500) {
let timer = null;
return function(...args) {
// 当前有任务了,直接返回
if(timer) {
return;
}
timer = setTimeout(() => {
fn.apply(this, args);
//执行完后,需重置定时器,不然timer一直有值,无法开启下一个定时器
timer = null;
}, delay)
}
}
节流(时间戳版本)
确保在指定的时间间隔内,无论触发了多少次事件,只有一次事件会被执行,后续事件在这个间隔内都不会执行。(连续触发事件但是在 n 秒中只执行第一次触发函数) 是指在一定时间内,无论函数被触发多少次,函数只会在固定的时间间隔内执行一次。如果在时间间隔内有多次触发事件,只会执行最后一次。节流技术常用于滚动事件、鼠标移动等场景,限制函数的执行频率。
function throttle(fn, delay = 500) {
let prev = Date.now();// 上一次执行该函数的时间
return function(...args) {
let now = Date.now();//返回从UTC到当前时间的毫秒数
// 如果差值大于等于设置的等待时间就执行函数
if (now - prev >= delay) {
fn.apply(this, args);
prev = Date.now();
}
};
}
手写异步控制并发数
function limitRequest(urls = [], limit = 3) {
return new Promise((resolve, reject) => {
const len = urls.length
let count = 0
// 同步启动limit个任务
while (limit > 0) {
start()
limit -= 1
}
function start() {
const url = urls.shift() // 从数组中拿取第一个任务
if (url) {
axios.post(url).finally(() => {
if (count == len - 1) {
// 最后一个任务完成
resolve()
} else {
// 完成之后,启动下一个任务
count++
start()
}
})
}
}
})
}
// 测试
limitRequest(['http://aaa', 'http://bbb', 'http://ccc', 'http://ddd', 'http://eee'])
懒加载图片实现
const imgs = document.getElementsByTagName('img');
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
let num = 0;
function lazyLoad() {
for (let i = 0; i < imgs.length; i++) {
let distance = viewHeight - imgs[i].getBoundingClientRect().top;
if(distance >= 0) {
imgs[i].src = imgs[i].getAttribute('data-src');
num = i+1;
}
}
}
window.addEventListener('scroll', lazyLoad, false);