「前端· 2023」白板手写代码问题总结

前言

常见的白板手写题,常见的排序算法以及 js 方法的实现。

🫧 手写冒泡排序 (默认从小到大)

冒泡排序属于稳定的排序,空间复杂度O(n), 时间复杂度O(n^2)。稳定的排序指的是不会改变元素的相对位置

js 复制代码
/**
 * 冒泡排序
 * @param {number[]} arr
 */
function bubbleSort (arr) {
    for (let i = 0; i < arr.length; i += 1) {
        for (let j = i + 1; j < arr.length; j += 1) {
            if (arr[i] > arr[j]) {
                [arr[i], arr[j]] = [arr[j], arr[i]]
            }
        }
    }
    return arr
}

// [0, 1, 2, 3, 4, 5, 6, 7]
console.log(bubbleSort([1, 4, 3, 0, 2, 5, 7, 6]))

手写归并排序 (默认从小到大)

归并排序属于稳定的排序,时间复杂度是 O(nlogn)。

js 复制代码
// 合并数组
function merge (left, right) {
    const arr = []
    while (left.length && right.length) {
        if (left[0] > right[0]) {
            arr.push(right.shift())
        } else {
            arr.push(left.shift())
        }
    }
    const max = left.length ? left : right
    return [...arr, ...max]
}

/**
 * 归并排序
 * @param {number[]} arr
 */
function mergeSort (arr) {
    if (arr.length <= 1) {
        return arr
    }

    const midd = Math.floor(arr.length / 2)
    const left = arr.splice(0, midd)
    const right = arr

    return merge(mergeSort(left), mergeSort(right))
}

// [-3, 0, 0, 1, 1, 2, 2, 3, 4, 4, 100, 101]
console.log(mergeSort([4, 1, 2, 3, 0, 100, -3, 2, 1, 101, 4, 0]))

手写插入排序 (默认从小到大)

插入排序是稳定的排序,时间复杂度是O(n^2)

js 复制代码
/**
 * 插入排序
 * @param {number[]} arr
 */
function insertionSort(arr) {
    for (let i = 0; i < arr.length; i += 1) {
        for (let j = i + 1; j < arr.length; j += 1) {
            if (arr[i] > arr[j]) {
                const insertionItem = arr.splice(j, 1)[0]
                arr.splice(i, 0, insertionItem)
            }
        }
    }

    return arr
}

// [-3, -2, 0, 0, 1, 2, 4, 4, 4, 100, 101, 300]
console.log(insertionSort([4, 300, -2, 4, 0, 100, -3, 2, 1, 101, 4, 0]))

手写快速排序 (默认从小到大)

快速排序是不稳定的排序,最坏的情况时间复杂度是O(n^2)。平均的时间复杂度是O(nlogn)

js 复制代码
/**
 * 快速排序
 * @param {number[]} arr
 */
function quickSort(arr) {
    if (arr.length <= 1) {
        return arr
    }

    const accurateValue = arr[0]
    const max = []
    const min = []

    for (let i = 1; i < arr.length; i += 1) {
        if (arr[i] >= accurateValue) {
            max.push(arr[i])
        } else {
            min.push(arr[i])
        }
    }

    return [...quickSort(min), accurateValue, ...quickSort(max)]
}

// [-7, -6, -1, 1, 1, 7, 12, 30, 30, 30, 41, 101]
console.log(quickSort([-1, 30, -7, 1, 30, 101, 30, 12, 41, 1, -6, 7]))

🍓 手写选择排序 (默认从小到大)

选择排序是不稳定排序,时间复杂度是O(n^2)

js 复制代码
/**
 * 选址排序
 * @param {number[]} arr
 */
function selectionSort(arr) {
    for (let i = 0; i < arr.length; i += 1) {
        let minIndex = i
        for (let j = i + 1; j < arr.length; j += 1) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j
            }
        }
        const min = arr.splice(minIndex, 1)[0]
        arr.splice(i, 0, min)
    }

    return arr
}

// [-101, -30, -26, -3, 0, 2, 2, 50, 60, 60, 97, 100]
console.log(selectionSort([100, 60, -3, 2, -30, -101, 50, 60, 2, 0, -26, 97]))

手写 throttle 节流

js 复制代码
/**
 * @param {Function} func
 * @param {number} wait
 */
function throttle (func, wait) {
    let timer = null
    let lastArgs = null
    return function (...param) {
        if (!timer) {
            func.call(this, ...param)
            timer = setTimeout(() => {
                if (lastArgs) {
                    func.call(this, ...lastArgs)
                }
                lastArgs = null
                timer = null
            }, wait)
        } else {
            lastArgs = [...param]
        }
    }
}

手写 debounce 防抖

js 复制代码
/**
 * @param {Function} func
 * @param {number} wait
 */
function debounce(func, wait) {
    let timer = null
    return function (...params) {
        function clear () {
            clearTimeout(timer)
            timer = null
        }
        function run () {
            timer = window.setTimeout(() => {
                func.call(this, ...param)
                clear()
            }, wait)
        }
        if (!timer) {
            run()
        } else {
            clear()
            run()
        }
    }
}

手现 Array.prototype.flat() 数组扁平化

js 复制代码
/**
 * @param { Array } arr
 * @param { number } depth
 * @returns { Array }
 */
function flat(arr, depth = 1) {
    let result = []
    if (depth > 0) {
        let isHaveArray = false
        for (let i = 0; i < arr.length; i += 1) {
            const item = arr[i]
            if (Array.isArray(item)) {
                isHaveArray = true
                result.push(...item)
            } else {
                result.push(item)
            }
        }

        if (isHaveArray) {
            return flat(result, depth - 1)
        } else {
            return result
        }
    } else {
        result = arr
        return result
    }
}

// [1, 2, 3, 4, 5, 6, 7, 8]
console.log(flat([1,2,[3,4],[5,6,[7,8]]], 2))

手写数组去重

js 复制代码
/**
 * @param {any[]} arr
 */
function deduplication(arr) {
    return [...new Set(arr)]
}

手写洗牌算法

js 复制代码
/**
 * @param {any[]} arr
 */
function shuffle(arr) {
    for (let i = 0; i < arr.length; i += 1) {
        const array = new Uint32Array(1);
        const j = Math.floor((crypto.getRandomValues(array)[0] / 0xffffffff) * arr.length);
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
}

// [3, 2, 5, 1, 0, 6, 9, 7, 4, 8]
console.log(shuffle([0,1,2,3,4,5,6,7,8,9]))

🎁 手写二分查找

js 复制代码
/**
 * @param {number[]} arr - ascending unique array
 * @param {number} target
 * @return {number}
 */
function binarySearch(arr, target){
  let start = 0
  let end = arr.length - 1
  while (start <= end) {
    let midd = Math.floor((start + end) / 2)
    if (arr[midd] === target) {
        return midd
    }
    if (arr[midd] < target) {
        start = midd + 1
    } else {
        end = midd - 1
    }
  }

  return -1
}

// 0
console.log(binarySearch([1,2,3], 1))

手写 memo

类似 React 中的 memo

js 复制代码
/**
 * @param {Function} func
 * @param {(args:[]) => string }  [resolver] - cache key generator
 */
function memo(func, resolver) {
    const map = new Map()
    return function (...params) {
        let key
        if (typeof resolver === function) {
            key = resolver(...params)
        } else {
            key = [...params].join('-')
        }
        if (map.has(key)) {
            return map.get(key)
        } else {
            const val = func.apply(this, [...params])
            map.set(key, val)
            return val
        }
    }
}

手写 Promise.all

Promise.all, 当所有的 Promise 都成功时,返回的 Promise.all 也会返回成功,并返回一个包含所有值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。

js 复制代码
function all (promises) {
    return new Promise((resolve, reject) => {
        if (!promises.length) {
            return resolve([])
        }
        const result = []
        let total = 0
        for (let i = 0; i < promises.length; i += 1) {
            const promise = promises[i] instanceof Promise ? promises[i] : Promise.resolve(promises[i])
            promise.then((res) => {
                result[i] = res
                total += 1
                if (total === promises.length) {
                    resolve(result)
                }
            }).catch((err) => {
                reject(err)
            })
        }
    })
}

手现 Promise.allSettled

Promise.allSettled, 无论所有的 Promise 都成功还是失败,都会将结果返回

js 复制代码
/**
 * @param {Array<any>} promises - notice that input might contains non-promises
 * @return {Promise<Array<{status: 'fulfilled', value: any} | {status: 'rejected', reason: any}>>}
 */
function allSettled(promises) {
    return new Promise((resolve, reject) => {
        if (!promises.length) {
            return resolve([])
        }
        const result = []
        let total = 0
        for (let i = 0; i < promises.length; i += 1) {
            const promise = promises[i] instanceof Promise ? promises[i] : Promise.resolve(promises[i])
            promise.then((res) => {
                result[i] = {
                    status:"fulfilled",
                    value: res
                }
                total += 1
                if (total === promises.length) {
                    resolve(result)
                }
            }).catch((err) => {
                reject(err)
                result[i] = {
                    status:"rejected",
                    reason: err
                }
                total += 1
                if (total === promises.length) {
                    resolve(result)
                }
            })
        }
    })
}

手现 Promise.any

Promise.any, 如果有一个成功会立即返回,如果全部失败会一起返回所有失败的结果

js 复制代码
/**
 * @param {Array<Promise>} promises
 * @return {Promise}
 */
function any(promises) {
    return new Promise((resolve, reject) => {
        if (!promises.length) {
            return reject(new AggregateError('No Promise in Promise.any was resolved', []))
        }
        let total = 0
        const errors = []
        for (let i = 0; i < promises.length; i += 1) {
            const promise = promises[i] instanceof Promise ? promises[i] : Promise.resolve(promises[i])
            promise.then((res) => {
                resolve(res)
            }).catch((err) => {
                errors[i] = err
                total += 1
                if (count === promises.length) {
                    reject(new AggregateError('No Promise in Promise.any was resolved', errors))
                }
            })
        }
    })
}

手现 Promise.race

Promise.race, 会返回一组 promise 之中,如果有一个成功或者失败就会立即返回

js 复制代码
/**
 * @param {Array<Promise>} promises
 * @return {Promise}
 */
function race(promises) {
    return new Promise((resolve, reject) => {
        if (promises.length) {
            for (let i = 0; i < promises.length; i += 1) {
                const promise = promises[i] instanceof Promise ? promises[i] : Promise.resolve(promises[i])
                promise.then((res) => {
                    resolve(res)
                }).catch((err) => {
                    reject(err)
                })
            }
        }
    })
}

手写 ES5 继承

js 复制代码
// ----------------- 👨 父类的构造函数,以及方法 -----------------
function Father (name) {
    this.name = name
}
// 父类方法
Father.prototype.sayname = function () {
    console.log(this.name)
}

// ----------------- 👦 子类的构造函数,以及方法 -----------------
function Son (name, age) {
    // 调用父类的构造函数
    Father.call(this, name)
    this.age = age
}
Son.prototype = Object.create(Father.prototype)
// 修正原型链
Son.prototype.constructor = Son
// 子类方法
Son.prototype.sayAge = function() {
  console.log(this.age)
}

🎊 手写 new

js 复制代码
/**
 * @param {Function} constructor
 * @param {any[]} args - argument passed to the constructor
 * `myNew(constructor, ...args)` should return the same as `new constructor(...args)`
 */
const myNew = (constructor, ...args) => {
    const obj = Object.create({})
    // 调用构造函数
    const newObj = constructor.call(obj, ...args)
    // 修改obj的___proto___, 指向构造函数的prototype
    Object.setPrototypeOf(obj, constructor.prototype)
    return newObj || obj
}

手写 call

js 复制代码
Function.prototype.myCall = function (thisArg, ...args) {
    if (thisArg === undefined || thisArg === null) {
        thisArg = window
    }
    if (typeof thisArg === 'string') {
        thisArg = new String(thisArg)
    }
    if (typeof thisArg === 'number') {
        thisArg = new Number(thisArg)
    }
    if (typeof thisArg === 'boolean') {
        thisArg = new Boolean(thisArg)
    }
    const key = Symbol()
    // this 是调用 call 的函数
    thisArg[key] = this
    const result = thisArg[key](...args)
    delete thisArg[key]
    return result
}

手写 apply

js 复制代码
Function.prototype.myApply = function (thisArg, argsArray = []) {
    // 如果第二参数不是对象抛出错误
    if (typeof argsArray !== 'object') {
        throw new Error('Uncaught TypeError: CreateListFromArrayLike called on non-object')
    }
    if (thisArg === undefined || thisArg === null) {
        thisArg = window
    }
    if (typeof thisArg === 'string') {
        thisArg = new String(thisArg)
    }
    if (typeof thisArg === 'number') {
        thisArg = new Number(thisArg)
    }
    if (typeof thisArg === 'boolean') {
        thisArg = new Boolean(thisArg)
    }
    const key = Symbol()
    thisArg[key] = this
    const result = thisArg[key](...args)
    delete thisArg[key]
    return result
}

手写 bind

js 复制代码
Function.prototype.myBind = function (thisArg, ...initArgs) {
    if (thisArg === undefined || thisArg === null) {
        thisArg = window
    }
    if (typeof thisArg === 'string') {
        thisArg = new String(thisArg)
    }
    if (typeof thisArg === 'number') {
        thisArg = new Number(thisArg)
    }
    if (typeof thisArg === 'boolean') {
        thisArg = new Boolean(thisArg)
    }
    // 调用 bind 的函数
    const that = this
    return function (...args) {
        const key = Symbol()
        thisArg[key] = that
        const result = thisArg[key](...initArgs, ...args)
        delete thisArg[key]
        return result
    }
}

手写 instanceof

A instanceof B, 自下往上查找A的原型(A.proto)是否等于B.prototype, 直到向上查找到null

js 复制代码
/**
 * @param {any} obj
 * @param {target} constructor
 * @return {boolean}
 */
function myInstanceOf(obj, constructor) {
    if (typeof obj !== 'object' || obj === null) {
        return false
    }
    // 或者使用 Object.getPrototypeOf(obj) 获取 __proto__
    const proto = obj.__proto__
    if (proto === null) {
        return false
    }
    if (proto === constructor?.prototype) {
        return true
    } else {
        return myInstanceOf(proto, constructor)
    }
}

手写 Object.create

js 复制代码
/**
 * @param {any} proto
 * @return {object}
 */
function myObjectCreate(proto) {
    if (!(proto instanceof Object)) {
        throw new Error('Uncaught TypeError: Object prototype may only be an Object or null')
    }
    const obj = {}
    // Object.setPrototypeOf(obj, proto)
    obj.__proto__ = proto
    return obj
}

🚀 手写 Object.is

Object.is 不会转换两个值的类型,和 === 相似,但是他们之间也存在一些区别,区别如下:
NaN 使用 === 是不相等的,使用 Object.is 返回 true
+0 和 -0 使用 === 是相等的,使用 Object.is 返回 false

js 复制代码
/**
 * @param {any} a
 * @param {any} b
 * @return {boolean}
 */
function is(a, b) {
    if (typeof a === 'number' && typeof b === 'number') {
        if (isNaN(a) && isNaN(b)) {
            return true
        }
        if (a === 0 && b === 0) {
            // Infinity === -Infinity false
            return 1 / a === 1 / b
        }
    }
    return a === b
}

手写 Array.prototype.reduce

  • Array.prototype.reduce(callbackFn, initialValue)
    • callbackFn 参数:
      *
      1. accumulator 上一次调用 callbackFn 的结果。在第一次调用时,如果指定了 initialValue 则为指定的值,否则为 array[0] 的值
        1. currentValue 当前元素的值。在第一次调用时,如果指定了 initialValue,则为 array[0] 的值,否则为 array[1]
        1. currentIndex currentValue 在数组中的索引位置。在第一次调用时,如果指定了 initialValue 则为 0,否则为 1
        1. array 调用了 reduce() 的数组本身
    • initialValue 参数:
      • 第一次调用回调时初始化 accumulator 的值。
      • 如果指定了 initialValue,则 callbackFn 从数组中的第一个值作为 currentValue 开始执行。
      • 如果没有指定 initialValue,则 accumulator 初始化为数组中的第一个值,并且 callbackFn 从数组中的第二个值作为 currentValue 开始执行。在这种情况下,如果数组为空(没有第一个值可以作为 accumulator 返回),则会抛出错误
js 复制代码
Array.prototype.myReduce = function (callback, initialValue) {
    // myReduce 参数的长度,判断是否传入了 initialValue 
    const argsLength = arguments.length

    // this.length 是调用 myReduce 方法的数组的长度
    // 如果 myReduce 参数为 1 个,并且数组长度为 0,抛出错误
    if (argsLength === 1 && this.length === 0) {
        throw new Error('Uncaught TypeError: Reduce of empty array with no initial value')
    }

    // 判断是否指定了 initialValue
    let index = argsLength === 1 ? 1 : 0
    let resultValue = argsLength === 1 ? this[0] : initialValue

    for (let i = index; i < this.length; i += 1) {
        resultValue = callback(resultValue, this[i], i, this)
    }

    return resultValue
}

手写 Array.prototype.map

  • map(callbackFn, thisArg)
    • callbackFn 参数:
      • element 数组中当前正在处理的元素
      • index 正在处理的元素在数组中的索引
      • array 调用了 map() 的数组本身
    • thisArg 参数:
      • 执行 callbackFn 时用作 this 的值
js 复制代码
Array.prototype.myMap = function(callback, thisArg) {
    const result = []
    const that = thisArg || this
    this.forEach(function (item, i) {
        result[i] = callback.call(that, item, i, that)
    })
    return result
}

手写 ajax

js 复制代码
const xhr = new XMLHttpRequest()
xhr.open("GET", url, false)
xhr.onreadystatechange = function () {
    // 接收到返回的处理
    if (xhr.status === 200) {
        // ...
    }
}
xhr.send()

🍉 手写 once

js 复制代码
/**
 * @param {Function} func
 * @return {Function}
 */
function once(func) {
    let result = undefined
    let isActive = false
    return function (...params) {
        if (!isActive) {
            result = func.call(this, ...params)
            isActive = true
        }
        return result 
    }
}

手写简单的双向绑定

js 复制代码
// element 的 value 与 state 的 value 相互绑定
/**
 * @param {{value: string}} state
 * @param {HTMLInputElement} element
 */
function model(state, element) {
    element.value = state.value
    Object.defineProperty(state, 'value', {
        get: () => element.value,
        set: (value) => element.value = value,
    })
}

手写 promisify

js 复制代码
// const callback = (error, data) => {
//   if (error) {
//     // 出错的时候
//   } else {
//     // 成功的时候
//   }
// }

// const func = (arg1, arg2, callback) => {
//   // 一些异步逻辑
//   if (hasError) {
//     callback(someError)
//   } else {
//     callback(null, someData)
//   }
// }

/**
 * @param {(...args) => void} func
 * @returns {(...args) => Promise<any}
 */
function promisify(func) {
    return function (...params) {
        const that = this
        return new Promise(function(resolve, reject) {
            func.apply(that, [...params], function(error, data) {
                if (error) {
                    reject(error)
                } else {
                    resolve(data)
                }
            })
        })
    }
}

手写 trim

js 复制代码
/**
 * @param {string} str
 * @return {string}
 */
function trim(str) {
    return str.replace(/^\s+|\s+$/g, '')
}

🏆 手写 Promise 节流

假设你需要调用100个API获取数据,如果使用Promise.all(),100个请求会同时到达你的服务器,如果你的服务器性能很低的话,这就会是个负担。节流API请求,使得任何时刻最多只有5个请求正在进行中。

js 复制代码
/**
 * @param {() => Promise<any>} func
 * @param {number} max
 * @return {Promise}
 */
function throttlePromises(funcs, max){
  return new Promise((resolve, reject) => {
    const result = []
    const len = funcs.length
    // 当前正在执行的任务数
    let jobs = 0
    // 当前已完成的任务数
    let count = 0
    // 处理请求列表
    const handleJobs = () => {
        while (jobs < max && funcs.length) {
            let promise = funcs.shift()()
            // 当前任务的索引
            let index = len - funcs.length - 1
            promise = promise instanceof Promise ? promise : Promise.resolve(promise)
            promise.then((res) => {
                result[index] = res
                count += 1
                jobs -= 1
                if (count === len) {
                    resolve(result)
                }
                // 当任务执行完成后,主动判断最大队列是否还有空闲
                handleJobs()
            }).catch(() => {
                reject(err)
            })
            jobs += 1
        }
    }
    handleJobs()
  })
}

手写 Event Emitter

  • 需求:
    • 支持订阅功能
      • const sub1 = emitter.subscribe('event1', callback1), 并且同一个 callback 可以重复订阅同一个事件
    • 支持取消订阅
      • sub1.release()
    • 支持触发订阅
      • emitter.emit('event1', 1, 2)
js 复制代码
class EventEmitter {
    constructor() {
        this.map = {}
    }

    subscribe(eventName, callback) {
        const sub = {
            eventName,
            callback,
        };
        const that = this

        if ((this.map[eventName]) {
            this.map[eventName].push(sub)
        } else {
            this.map[[eventName]] = [sub]
        }

        return {
            release () {
                this.map = {
                    ...this.map,
                    [eventName]: that.map[eventName].filter((s) => s !== sub)
                }
            }
        }
    }

    emit(eventName, ...args) {
        if (this.map[eventName]) {
            this.map[eventName].forEach((sub) => {
                sub.callback(...args)
            })
        }
    }
}

手写 Math.sqrt

Math.sqrt 可以用来取得平方根
使用二分查找法

js 复制代码
/**
 * @param {any} x
 * @return {number}
 */
function mySqrt(x) {
    if (isNaN(x)) {
        return NaN
    }
    if (typeof x !== 'number') {
        return NaN
    }
    if (x < 0) {
        return NaN
    }
    if (x === 0) {
        return 0
    }
    if (x === 1) {
        return 1
    }

    let result

    const _Sqrt = (start, end) => {
        if (start <= end) {
            const mid = Math.floor((end + start) / 2)
            if (mid ** 2 < x) {
                result = mid
                sqrt(mid + 1, end)
            } else if (mid ** 2 > x) {
                sqrt(start, mid - 1)
            } else {
                result = mid
            }
        }
    }

    _Sqrt(0, x)

    return result
}

手写 Promise.prototype.finally

Promise.prototype.finally() 无论 promise 是成功还是失败都会执行一个callback

js 复制代码
/**
 * @param {Promise<any>} promise
 * @param {() => void} onFinally
 * @returns {Promise<any>}
 */
function myFinally(promise, onFinally) {
    return promise.then(res => {
        return Promise.resolve(onFinally()).then(() => {
            return res
        })
    }).catch(error => {
        return Promise.resolve(onFinally()).then(() => {
            throw err
        })
    })
}

手写 chunk

js 复制代码
// chunk([1,2,3,4,5], 1)
// [[1], [2], [3], [4], [5]]

// chunk([1,2,3,4,5], 3)
// [[1, 2, 3], [4, 5]]

/** 
 * @param {any[]} items
 * @param {number} size
 * @returns {any[][]}
 */
function chunk(items, size) {
    if (size === 0 || items?.length === 0) {
        return []
    }

    const result = []

    while (items?.length) {
        result.push(items.splice(0, size))
    }

    return result
}

🍰 手现 curry

js 复制代码
// const join = (a, b, c) => {
//    return `${a}_${b}_${c}`
// }
// const curriedJoin = curry(join)
// '1_2_3'
// curriedJoin(1, 2, 3) 
// '1_2_3'
// curriedJoin(1)(2, 3) 
// '1_2_3'
// curriedJoin(1, 2)(3) 

function curry(func, ...initArgs) {
    return function (...restArgs) {
        return ((params) => {
            return params.length >= func.length ? func.call(this, ...params) : curry(func, ...restArgs)
        })([...initArgs, ...restArgs])
    }
}

手写 LRU 缓存

利用 js Map 特性实现

js 复制代码
class LRU {
    constructor(max) {
        this.map = new Map()
        this.max = max
    }

    get(key) {
        if (this.map.has(key)) {
            let value = this.map.get(key)
            // 先删除,再设置, 相当于更新到map的最后一位(最新的一位)
            this.map.delete(key)
            this.map.set(key, value)
            return value
        } else {
            return -1
        }
    }

    put(key, value) {
        // 如果已经有了这个key,先删除,再设置, 相当于更新到map的最后一位(最新的一位)
        if (this.map.has(key)){
            this.map.delete(key)
        }
        this.map.set(key, value)
        // 判断是否超过 LRU 的最大限制
        if (this.map.size > this.max) {
            // 删除map的第一位(最老的一位)
            const lastKey = this.map.keys().next().value
            this.map.delete(lastKey)
        }
    }
}

手写 cloneDeep 深拷贝

如果需要写出一个完善的深拷贝,需要考虑到的点还是很多的

js 复制代码
// hash 用来规避循环
const hash = new WeakMap()

function isObject(value) {
  return value !== null && (typeof value === "object" || typeof value === "function")
}

function getSymbolKeys(value) {
  let keys = Object.getOwnPropertySymbols(value)
  keys = keys.filter((key) => value.propertyIsEnumerable(key))
  return keys
}

function getAllKeys(value) {
  let keys = Object.keys(value)
  keys = [...keys, ...getSymbolKeys(value)]
  return keys
}

function cloneDeep(data) {
    let result = null

    if (!isObject(data)) {
        return data
    }


    const isArray = Array.isArray(data)

    if (isArray) {
        result = []
    } else {
        result = Object.create(Object.getPrototypeOf(data))
    }

    // 规避循环引用
    if (hash.has(data)) {
        return hash.get(data)
    } else {
        hash.set(data, result)
    }

    const keys = getAllKeys(data)

    for (let i = 0; i < keys.length; i += 1) {
        const key = keys[i]
        const val = data[key]
        result[key] = cloneDeep(val)
    }

    return result
}
相关推荐
科技探秘人8 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人9 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR14 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香16 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969319 分钟前
前端预览word、excel、ppt
前端·word·excel
小华同学ai24 分钟前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
problc29 分钟前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
Gavin_91533 分钟前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
待磨的钝刨3 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json