前端常见手写代码
防抖
用于用户频繁触发某个事件(如输入搜索),避免函数被频繁执行,最近一次触发需要等待一定的时间间隔才会被执行,如果期间再次触发执行,则重置定时器,直到停止触发等待足够时间
js
function debounce(fn, delay) {
var timer;
return function () {
clearTimeout(timer);
timer = setTimeout(fn.bind(this, ...arguments), delay);
};
}
// 测试
function task() {
console.log('run task', arguments);
}
const debounceTask = debounce(task, 1000);
window.addEventListener('scroll', debounceTask);
首次立即执行
js
function debounce(fn, delay) {
var timer = null;
var first = true;
return function () {
clearTimeout(timer);
const f = fn.bind(this, ...arguments);
if (first) {
f();
first = false;
} else {
timer = setTimeout(f, delay);
}
};
}
节流
用于限制函数执行频率
javascript
function throttle(fn, delay) {
var last = 0;
var timer;
return function () {
const now = Date.now();
clearTimeout(timer);
if (now - last >= delay) {
last = now;
fn.apply(this, arguments);
} else {
timer = setTimeout(() => {
fn.apply(this, arguments);
last = Date.now();
}, delay - (now - last));
}
};
}
深拷贝
JSON 方法
javascript
// 不支持值为 undefined、函数和循环引用的情况
const cloneObj = JSON.parse(JSON.stringify(obj));
递归拷贝
js
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
const copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key]);
}
}
return copy;
}
stringify 方法实现
js
function stringify(obj) {
if (obj === null || typeof obj !== 'object') {
// 数组 undefined 会被转换成 null
return obj === undefined || obj === null ? 'null' : obj;
}
if (Array.isArray(obj)) {
return '[' + json.map((m) => stringify(m)).join(',') + ']';
}
return (
'{' +
Object.keys(obj)
// 过滤值为 undefined 的 key
.filter((key) => obj[key] !== undefined)
.map((key) => `"${key}":${stringify(obj[key])}`)
.join(',') +
'}'
);
}
继承
ES5 继承
组合继承
javascript
function Parent(value) {
this.val = value;
}
Parent.prototype.getValue = function () {
console.log(this.val);
};
function Child(value) {
Parent.call(this, value);
}
Child.prototype = new Parent();
- 缺点:原型上创建了父类属性(调用父类构造函数导致)
寄生组合继承
javascript
function Parent(value) {
this.val = value;
}
Parent.prototype.getValue = function () {
console.log(this.val);
};
function Child(value) {
Parent.call(this, value);
}
Child.prototype = Object.create(Parent.prototype, {
// 原型构造函数指向Child
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true,
},
});
ES6 继承
javascript
class Parent {
constructor(value) {
this.val = value;
}
getValue() {
console.log(this.val);
}
}
class Child extends Parent {
constructor(value) {
super(value);
}
}
数组去重
Set
javascript
[...new Set([1, 3, 4, 5, 3, 1])];
Map
javascript
function uniq(arr) {
var visited = {};
var res = [];
for (e of arr) {
if (visited[e]) continue;
res.push(e);
visited[e] = true;
}
return res;
}
虚拟滚动
tsx
import React, { useState, useCallback, useRef, useEffect } from 'react';
export interface VirtualScrollProps {
items: string[];
height: number;
rowHeight: number;
}
export default function VirtualScroll({ items, height, rowHeight }: VirtualScrollProps) {
// 保存滚动条位置的状态
const [scrollTop, setScrollTop] = useState(0);
// 获取列表容器元素的引用
const containerRef = useRef<HTMLDivElement>(null);
// 计算可见区域的行数
const visibleRowCount = Math.ceil(height / rowHeight);
// 监听滚动条的滚动事件,更新滚动条位置的状态
const handleScroll = useCallback(() => {
const scrollTop = containerRef.current!.scrollTop;
setScrollTop(scrollTop);
}, []);
// 使用 useEffect 添加滚动条滚动事件的监听器,并在组件卸载时移除监听器
useEffect(() => {
const container = containerRef.current;
container!.addEventListener('scroll', handleScroll);
return () => {
container!.removeEventListener('scroll', handleScroll);
};
}, [handleScroll]);
// 计算可见区域的起始行和结束行
const start = Math.floor(scrollTop / rowHeight);
const end = Math.min(start + visibleRowCount + 1, items.length);
// 渲染可见区域内的元素
const visibleItems = items.slice(start, end).map((item, index) => (
<div key={start + index} style={{ height: rowHeight }}>
{item}
</div>
));
// 计算列表容器的 paddingTop 和 paddingBottom,确保可见区域内的元素能够正确地垂直对齐
const paddingTop = start * rowHeight;
const paddingBottom = (items.length - end) * rowHeight;
return (
<div ref={containerRef} style={{ height, overflowY: 'scroll' }}>
{/* 使用 paddingTop 和 paddingBottom 让可见区域内的元素能够正确地垂直对齐 */}
<div style={{ paddingTop, paddingBottom }}>{visibleItems}</div>
</div>
);
}
限制最大并发数
js
function limit(maxCount) {
return {
count: 0,
queue: [],
pauseState: false,
push(task) {
this.queue.push(task);
this.next();
},
next() {
if (this.queue.length && this.count < maxCount) {
this.count++;
this.queue
.shift()()
.finally(() => {
this.count--;
if (!this.pauseState) {
this.next();
}
});
}
},
pause() {
this.pauseState = true;
},
start() {
this.pauseState = false;
let max = Math.min(this.queue.length, maxCount - this.count);
for (let i = 0; i < max; i++) {
this.next();
}
},
};
}
// 测试代码
const scheduler = limit(2);
function addTask(time, order) {
scheduler.push(() =>
new Promise((resolve) => setTimeout(resolve, time)).then(() => console.log(order)),
);
}
addTask(1000, 1);
addTask(500, 2);
addTask(300, 3);
addTask(400, 4);
// 2 3 1 4 耗时 1200ms
延迟调用
js
function lazyMan(name) {
let runing = false;
const sleep = (time) => () =>
new Promise((resolve) => {
console.log(`Wake up after ${time}`);
setTimeout(resolve, time * 1000);
});
const obj = {
tasks: [],
async next() {
const task = this.tasks.shift();
if (task) {
await task();
this.next();
}
},
run() {
Promise.resolve().then(() => {
if (runing) {
return;
}
runing = true;
this.next();
});
},
eat(food) {
this.tasks.push(() => console.log(`Eat ${food}`));
this.run();
return this;
},
sleep(time) {
this.tasks.push(sleep(time));
this.run();
return this;
},
sleepFirst(time) {
this.tasks.unshift(sleep(time));
this.run();
return this;
},
};
obj.tasks.push(() => console.log(`Hi! This is ${name}!`));
obj.run();
return obj;
}
lazyMan('Hank').sleep(10).eat('dinner');
// Hi! This is Hank!
// Wake up after 10
// Eat dinner
lazyMan('Hank').eat('dinner').sleepFirst(10);
// Wake up after 10
// Hi! This is Hank!
// Eat dinner