JS 手写基础
面试精简版 · 记住:输入输出 → 最小模板 → 常见坑
1 实现 bind
最简面试手写版(支持 new)
- bind 返回一个新函数,绑定 this + 预设参数
- 三个要点:① 合并两次参数 ② new 调用时 this 指向实例 ③ 继承原函数 prototype
Function.prototype.myBind = function (context, ...presetArgs) {
const fn = this
function bound(...callArgs) {
// new 调用时 this 是 bound 的实例,普通调用时 this 固定为 context
const thisArg = this instanceof bound ? this : context
return fn.apply(thisArg, presetArgs.concat(callArgs))
}
// 让 new bound() 的实例能继承原函数 fn 的原型方法
bound.prototype = Object.create(fn.prototype)
return bound
}
2 深拷贝
基础版 + WeakMap 处理循环引用
- 只考虑三种:简单类型 + Array + Object
- WeakMap 解决循环引用(
a.self = a)
// 仅考虑三种:简单类型 + Array + Object
function deepClone(origin, mp = new WeakMap()) {
if (typeof origin !== 'object' || origin === null) {
return origin
}
// WeakMap 处理循环引用,已拷贝过的直接返回
if (mp.has(origin)) return mp.get(origin)
const target = Array.isArray(origin) ? [] : {}
mp.set(origin, target) // 先存再递归,防止死循环
for (let key in origin) {
if (origin.hasOwnProperty(key)) {
target[key] = deepClone(origin[key], mp)
}
}
return target
}
完善版:支持 Symbol 属性
function deepClone(origin, map = new WeakMap()) {
if (origin === null || typeof origin !== 'object') return origin
if (map.get(origin)) return map.get(origin)
let target = Array.isArray(origin) ? [] : {}
map.set(origin, target)
const keys = [
...Object.keys(origin),
...Object.getOwnPropertySymbols(origin)
]
for (let key of keys) {
if (origin.hasOwnProperty(key)) {
target[key] = deepClone(origin[key], map)
}
}
return target
}
3 发布订阅 EventBus
数据结构:subscribers = { event: [cb1, cb2] }
class PubSub {
constructor() {
this.subscribers = {}
}
subscribe(event, callback) {
if (!this.subscribers[event]) {
this.subscribers[event] = []
}
this.subscribers[event].push(callback)
}
unsubscribe(event, callback) {
if (!this.subscribers[event]) return
this.subscribers[event] = this.subscribers[event]
.filter(sub => sub !== callback)
if (this.subscribers[event].length === 0) {
delete this.subscribers[event]
}
}
publish(event, data) {
if (!this.subscribers[event]) return
this.subscribers[event].forEach(cb => cb(data))
}
}
4 数组扁平化 flat
全部拍平 + 按层数拍平
// 全部拍平
// 子问题:元素就推进去,数组就 concat 递归自己
function myFlat(arr) {
let ans = []
for (let i = 0; i < arr.length; i++) {
let item = arr[i]
if (!Array.isArray(item)) {
ans.push(item)
} else {
ans = ans.concat(myFlat(item)) // 直接递归自己就好了
}
}
return ans
}
// 按层数拍平(注意默认值 layer=1)
function myFlat(arr, layer = 1) {
if (layer === 0) return arr // layer 为 0 不再拍平
let ans = []
for (let i = 0; i < arr.length; i++) {
let item = arr[i]
if (!Array.isArray(item)) {
ans.push(item)
} else {
ans = ans.concat(myFlat(item, layer - 1))
}
}
return ans
}
5 函数柯里化
定长参数版:参数够了就执行,不够就继续收集
- 核心:把 args 累积,
args.length >= fn.length 时执行
- 支持
add(1,2)(3) / add(1)(2)(3) 等任意组合
// 总体逻辑:把传入的参数全部保存进 args,一旦数量够了就执行
// curry(fn, ...args, ...args2) 会把两次参数合并作为新的 args 传给下一个 curry
function curry(fn, ...args) {
if (args.length >= fn.length) {
return fn(...args) // 参数够了,直接执行
}
// 参数不够,返回新函数继续收集
return (...args2) => curry(fn, ...args, ...args2)
}
function add1(x, y, z) { return x + y + z }
const add = curry(add1)
add(1, 2, 3) // 6
add(1)(2)(3) // 6
add(1, 2)(3) // 6
不定长参数版(用 () 收口)
function add(...args) {
let sum = args.reduce((a, b) => a + b, 0)
function fn(...more) {
if (more.length === 0) return sum
sum += more.reduce((a, b) => a + b, 0)
return fn
}
return fn
}
add(1)(2)(3)() // 6
add(1, 2, 3)(4)() // 10
add(1)(2)(3)(4)(5)() // 15
6 防抖 debounce
触发后 n 秒执行,重复触发重新计时
- 场景:搜索输入、resize、邮箱校验
- 坑:外层用
function 保留 this,内层用 fn.call(this, ...args)
const debounce = (fn, delay) => {
let timer
return function (...args) { // 这里要用 function,箭头函数没有自己的 this
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.call(this, ...args) // 用 call 保证 this 不丢失
}, delay)
}
}
7 节流 throttle
固定间隔内只执行一次(技能 CD)
const throttle = (fn, delay) => {
let flag = true
return function (...args) {
if (!flag) return
fn.call(this, ...args)
flag = false
setTimeout(() => {
flag = true
}, delay)
}
}
8 实现 call / apply
原理:把函数挂到目标对象上调用,用 Symbol 防覆盖
// 原理:把函数挂到目标对象上调用,this 就指向它了
// greet.myCall(person, 'Hello') → 在 person 作用域下调用 greet
// call
Function.prototype.myCall = function (thisArg, ...args) {
const fn = this // this 指向调用 myCall 的函数,即要改变 this 的那个函数
if (typeof fn !== 'function') throw new TypeError(fn + ' is not a function')
thisArg = thisArg || globalThis // 没传 this 就绑定全局
const sym = Symbol('temp') // 用 Symbol 防止覆盖 thisArg 上已有的属性
thisArg[sym] = fn // 把函数挂到目标对象上
let ans = thisArg[sym](...args) // 通过目标对象调用,this 就指向它了
delete thisArg[sym] // 用完删掉临时属性
return ans
}
// apply(和 call 唯一区别:参数是数组)
Function.prototype.myApply = function (context, args) {
if (typeof this !== 'function') throw new TypeError(this + ' is not a function')
context = context || globalThis
if (!Array.isArray(args) && args !== null && args !== undefined) {
throw new TypeError('CreateListFromArrayLike called on non-object')
}
const sym = Symbol()
context[sym] = this
const res = args ? context[sym](...args) : context[sym]()
delete context[sym]
return res
}