1.数组转树
/**
* 把平铺的数组结构转成树形结构
*/
const arr = [
//pid是说他的父节点的id
{ id: "01", name: "张大大", pid: "", job: "项目经理" },
{ id: "02", name: "小亮", pid: "01", job: "产品leader" },
{ id: "03", name: "小美", pid: "01", job: "UIleader" },
{ id: "04", name: "老马", pid: "01", job: "技术leader" },
{ id: "05", name: "老王", pid: "01", job: "测试leader" },
{ id: "06", name: "老李", pid: "01", job: "运维leader" },
{ id: "07", name: "小丽", pid: "02", job: "产品经理" },
{ id: "08", name: "大光", pid: "02", job: "产品经理" },
{ id: "09", name: "小高", pid: "03", job: "UI设计师" },
{ id: "10", name: "小刘", pid: "04", job: "前端工程师" },
{ id: "11", name: "小华", pid: "04", job: "后端工程师" },
{ id: "12", name: "小李", pid: "04", job: "后端工程师" },
{ id: "13", name: "小赵", pid: "05", job: "测试工程师" },
{ id: "14", name: "小强", pid: "05", job: "测试工程师" },
{ id: "15", name: "小涛", pid: "06", job: "运维工程师" },
];
//递归法
function toTree(list, parId) {
let len = list.length;
function loop(parId) {
//返回输入的节点下面所有子节点
let res = [];
for (let i = 0; i < len; i++) {
let item = list[i];
if (item.pid === parId) {
//.children表示父子关系,让树形成
item.children = loop(item.id); //找子节点的子节点
res.push(item);
}
}
return res;
}
return loop(parId);
}
let result = toTree(arr, "");
console.log(result);
// hash法
function toTreeHash(list) {
const res = [];
const map = {};
// of遍历值(也就是下方的value对象),in遍历key
// 1. 先遍历一遍数组,把每个元素的 id 映射为节点对象
for (const item of list) {
map[item.id] = { ...item, children: [] };
}
// 此时大概是这样:map={
// "01":{
// id:"01",
// name:"张大大",
// children:[]
// },
// "02":{
// id:"02",
// name:"小亮",
// children:[]
// }
}
// 2. 再遍历数组,将节点放入其父节点的 children 中
for (const item of list) {
const node = map[item.id];
if (item.pid) {
if (!map[item.pid]) continue; // 防御性处理,避免写了pid,但是找不到这个pid对应的节点而报错
map[item.pid].children.push(node);
} else {
res.push(node); // pid为空字符串(false),就是根节点
}
}
return res;
const tree = toTreeHash(arr);
console.log(tree);
// 优势说明
// 时间复杂度:O(n),因为我们只遍历两次 arr;
// 利用了哈希表 map 进行父子节点的快速定位与组装;
// 比递归版本更适用于大数据量场景;
// 不依赖元素顺序,结构更健壮。
2.防抖节流:
function debounce(fn, delay) {
let timer = null;
const debounceFunc = function (...arg) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arg);
//apply语法:fn.apply(thisArg, argsArray)
//...arg是数组,而这里正需要传入数组。
//fn.call与fn.apply一样,只不过第二个参数不用数组了
}, delay);
};
return debounceFunc;
}
// v2
function debounce(fn, delay, immediate = false) {
//immediate=false意思是如果不传入true,那么默认他就是false
let timer = null;
let hasInvoked = false; //has invoked means 这一轮已经调用过了
const debounceFunc = function (...args) {
//args意思是arguments,参数,但是不要写arguments,因为他是一个内置的东西
if (timer) clearTimeout(timer);
if (immediate && !hasInvoked) {
fn.call(this, ...args);
hasInvoked = true;
} else {
timer = setTimeout(() => {
fn.call(this, ...args);
hasInvoked = false;
}, delay);
}
};
return debounceFunc;
}
// 使用时间戳的节流函数会在第一次触发事件时立即执行(因为此时的now是一个特别大的数,绝对大于delay),
// 以后每过 wait 秒之后才执行一次,并且最后一次触发事件不会被执行
function throttle(fn, delay) {
let last = 0;
const throttleFunc = function (...args) {
const now = new Date().getTime();
if (now - last >= delay) {
fn.apply(this, args);
last = now;
}
};
return throttleFunc;
}
export default MyComponent;
示例:使用React调用防抖节流要用useCall和useRef 因为: React每次render都会创造一套新的变量及其闭包 // React 中直接调用 debounce 会导致每次 render 都重新创建防抖函数 // useCallback:避免每次 render 都重新执行 debounce(),从而避免重新创建新的 timer 闭包 // useRef:解决debounce只能拿到最开始render的旧的变量闭包的问题,让旧函数也能读取最新值。
function Component() {
const [value, setValue] = useState("");
const valueRef = useRef(value);
valueRef.current = value; //useRef不会触发重新渲染,且能跨渲染记住值
const handleInput = useCallback(
debounce(() => {
console.log(valueRef.current);
}, 500),
[],
);
return (
<input
value={value}
onChange={(e) => {
setValue(e.target.value); //每次输入组件就会重新渲染render
handleInput();
}}
/>
);
}
自定义hook:要求必须是useXXX写法,如下
function useDebounceCallback(fn, delay) {
const fnRef = useRef(fn);
fnRef.current = fn;
const debounceFunc = useCallback(
debounce((...args) => {
fnRef.current(...args);
}, delay),
[delay],
);
return debounceFunc;
}
export default useDebounceCallback;