工程笔记
手写题
本篇目录
import { CodeSandbox } from '@/components/CodeSandbox'
手写题
题目 1:防抖和节流实现
import { useState, useRef, useCallback } from 'react'
export default function DebounceThrottleDemo() {
const [debounceCount, setDebounceCount] = useState(0)
const [throttleCount, setThrottleCount] = useState(0)
const debounceRef = useRef(0)
const throttleRef = useRef(0)
function debounce(fn, delay) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
function throttle(fn, delay) {
let lastTime = 0
return function (...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}
// function throttle(fn, delay) {
// let timer = null
// return function (...args) {
// if (!timer) {
// timer = setTimeout(() => {
// fn.apply(this, args)
// timer = null
// }, delay)
// }
// }
// }
const handleDebounce = debounce(() => {
debounceRef.current += 1
setDebounceCount(debounceRef.current)
}, 1000)
// 使用useCallback,避免每次lastTime为0
const handleThrottle = useCallback(
throttle(() => {
throttleRef.current += 1
setThrottleCount(throttleRef.current)
}, 2000),
[]
)
return (
<div>
<button onClick={handleDebounce}>测试防抖</button>
<span>防抖触发次数:{debounceCount}</span>
<br />
<button onClick={handleThrottle}>测试节流</button>
<span>节流触发次数:{throttleCount}</span>
</div>
)
}
总结:
题目 2:new
import { useState } from 'react'
export default function NewDemo() {
const [result, setResult] = useState('')
function myNew(Fn, ...args) {
// const obj = {}
// obj.__proto__ = Fn.prototype
const obj = Object.create(Fn.prototype)
const res = Fn.apply(obj, args)
// 用 instanceof 可以排除 null
return res instanceof Object ? res : obj
}
function handleClick() {
function Person(name) {
this.name = name
}
const p = myNew(Person, '张三')
setResult(JSON.stringify(p))
}
return (
<div>
<button onClick={handleClick}>测试 myNew</button>
<div>结果:{result}</div>
</div>
)
}
总结:
- 先创建一个空对象,对象的隐式原型指向构造函数的显示原型,用 Object.create 实现更好
- 然后执行构造函数,将构造函数的 this 指向这个空对象,拿到执行结果
- 最后返回这个空对象,如果构造函数返回的是一个对象,则返回这个对象,否则返回这个空对象
Object.create(Fn.prototype) 和 const obj = {} {}.proto = Fn.prototype 其实没有什么大区别,也就是 Object.create 更遵循 ES6 写法规范,两者输出是是一样的
题目 3:instanceof
import { useState } from 'react'
export default function InstanceOfDemo() {
const [result, setResult] = useState('')
function instanceOf(obj, fn) {
let proto = Object.getPrototypeOf(obj)
while (proto !== null) {
if (proto === fn.prototype) return true
proto = Object.getPrototypeOf(proto)
}
return false
}
function handleClick() {
function Person() {}
const p = new Person()
setResult(instanceOf(p, Person) ? '是' : '否')
}
return (
<div>
<button onClick={handleClick}>测试 instanceOf</button>
<div>结果:{result}</div>
</div>
)
}
总结:
- 核心逻辑是左边的 proto 上一直查找有没有等于右边的 prototype,有就返回 true,没有就返回 false
- while 循环,直到 proto 为 null 为止,因为原型链的尽头是 null,退出条件是找到了则返回 true,没找到则在循环外返回 false
- 在循环中 proto = Object.getPrototypeOf(proto) 一直往上找
题目 4:拍平数组
import { useState } from 'react'
export default function FlatDemo() {
const [result, setResult] = useState('')
function flat(arr) {
return arr.reduce((pre, next) => pre.concat(Array.isArray(next) ? flat(next) : next), [])
}
function handleClick() {
const a = [
[1, 2],
[3, 4],
[2, 3, 4, [7, 4, 9]]
]
setResult(JSON.stringify(flat(a)))
}
return (
<div>
<button onClick={handleClick}>测试 flat</button>
<div>结果:{result}</div>
</div>
)
}
总结:
题目 5:深拷贝
import { useState } from 'react'
export default function DeepCloneDemo() {
const [result, setResult] = useState('')
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (cache.has(obj)) return cache.get(obj)
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
const target = Array.isArray(obj) ? [] : {}
cache.set(obj, target)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
target[key] = deepClone(obj[key], cache)
}
}
return target
}
function handleClick() {
const obj = { a: 1, b: { c: 2 } }
const clone = deepClone(obj)
setResult(JSON.stringify(clone))
}
return (
<div>
<button onClick={handleClick}>测试 deepClone</button>
<div>结果:{result}</div>
</div>
)
}
总结:
题目 6:控制请求并发
import { useState, useEffect } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
function controlConcurrent(arr, max) {
let running = 0 // 当前正在执行的请求数
let index = 0 // 当前要执行的请求索引
function runNext() {
// 如果所有请求都已执行完,或者当前正在执行的请求数达到最大值,则返回
if (index >= arr.length || running >= max) {
return
}
running++ // 增加正在执行的请求数
const currentIndex = index
index++
// 执行当前请求
arr[currentIndex]()
.then(() => {
console.log(`请求 ${currentIndex + 1} 完成`)
})
.catch((err) => {
console.error(`请求 ${currentIndex + 1} 失败:`, err)
})
.finally(() => {
running-- // 减少正在执行的请求数
runNext() // 执行下一个请求
})
}
for (let i = 0; i < max; i++) {
runNext()
}
}
const arr = [
() => fetch('https://imber.netlify.app/api?time=2'),
() => fetch('https://imber.netlify.app/api?time=3'),
() => fetch('https://imber.netlify.app/api?time=2'),
() => fetch('https://imber.netlify.app/api?time=1'),
() => fetch('https://imber.netlify.app/api?time=3'),
() => fetch('https://imber.netlify.app/api?time=2')
]
return (
<div>
<button onClick={() => controlConcurrent(arr, 3)}>控制请求并发</button>
<div>结果在控制台查看接口请求</div>
</div>
)
}
总结:
- 递归实现
- 先根据 max 创建 max 个请求,
- 然后定义 runNext 函数
- runNext 核心是两个计数变量,一个记录当前队列的索引 index,一个记录正在执行的请求数 running
- 退出条件是 index >= arr.length 或者 running >= max
- index++ 和 新建一个 const currentIndex = index++ 拿到当前任务的索引来执行
- 最后在 finally 中递归调用 runNext 函数,这时候 running--,实现并发控制
对于 index++ 和 ++index 的区别:
let index = 0
const result = index++
console.log(result) // 输出: 0 (返回自增前的值)
console.log(index) // 输出: 1 (变量已自增)
let index = 0
const result = ++index
console.log(result) // 输出: 1 (返回自增后的值)
console.log(index) // 输出: 1 (变量已自增)
题目 7:数组转树
import { useState } from 'react'
export default function ArrayToTreeDemo() {
const [result, setResult] = useState('')
function arrayToTree(items, id = null, link = 'parentId') {
return items
.filter((item) => item[link] === id) // 1. 筛选当前层级的节点
.map((item) => ({
// 2. 转换每个节点
...item, // 3. 保留原有属性
children: arrayToTree(items, item.id) // 4. 递归查找子节点
}))
}
function handleClick() {
const arr = [
{ id: 1, parentId: null, name: 'A' },
{ id: 2, parentId: 1, name: 'B' },
{ id: 3, parentId: 1, name: 'C' },
{ id: 4, parentId: 2, name: 'D' }
]
setResult(JSON.stringify(arrayToTree(arr)))
}
return (
<div>
<button onClick={handleClick}>测试数组转树</button>
<div>结果:{result}</div>
</div>
)
}
总结:
题目 8:大文件上传
题目 9:Call
Function.prototype.myCall = function (context, ...args) {
context = context || window
const fnKey = Symbol()
context[fnKey] = this
const res = context[fnKey](...args) // 相当于谁调用,this就指向谁
delete context[fnKey]
return res
}
// 测试示例
function greet(greeting, other) {
return `${greeting}, ${this.name}${other}`
}
const person = { name: 'Alice' }
greet.myCall(person, 'Hello', '!') // 输出: Hello, Alice!
题目 10:Apply
Function.prototype.myApply = function (context, argsArray) {
context = context || window
const fnKey = Symbol()
context[fnKey] = this
const args = Array.isArray(argsArray) ? argsArray : []
const res = context[fnKey](...args) // 相当于谁调用,this就指向谁
delete context[fnKey]
return res
}
// 测试示例
function greet(greeting, other) {
return `${greeting}, ${this.name}${other}`
}
const person = { name: 'Alice' }
greet.myApply(person, ['Hello', '!']) // 输出: Hello, Alice!