前端面试手写

141 阅读7分钟

防抖:连续操作时,停止操作后,触发一次函数执行

function debounce(callback, wait) {
  let time
  return function () {
    clearTimeout(time)
    let _this = this
    time = setTimeout(() => {
      callback.call(_this, ...arguments)
    }, wait)
  }
}

// 执行函数体
const callback = (...args) => {
  console.log(...args)
  console.log("防抖,停止操作后执行一次")
}

const fn = debounce(callback, 500)

// 多次触发函数
let interVal = setInterval(() => {
  fn(1, 2, 3, 4)
}, 100)

// 停止触发
setTimeout(() => {
  clearInterval(interVal)
}, 1000)

节流:连续触发时,每周期内触发一次函数执行

// 每次执行时记录时间,下个周期时再执行一次
function throttle(callback, time) {
  let preTime = Date.now()
  return function () {
    let _this = this
    const curTime = Date.now()
    if (curTime - preTime >= time) {
      preTime = curTime
      callback.call(_this, ...arguments)
    }
  }
}

// 执行函数体
const callback = (...args) => {
  console.log(...args)
}

const fn = throttle(callback, 1000)

// 多次触发函数
let interVal = setInterval(() => {
  fn(1, 2, 3, 4)
}, 100)

// 停止触发
setTimeout(() => {
  clearInterval(interVal)
}, 3000)

深拷贝&&浅拷贝

浅拷贝的特点:

‌复制基本数据类型‌:浅拷贝会复制基本数据类型的值。 ‌复制引用类型‌:对于引用类型,浅拷贝只复制引用,而不是引用指向的对象。 ‌共享内存‌:浅拷贝的新对象和原始对象共享相同的内存地址,因此修改其中一个对象会影响另一个对象。

浅拷贝的实现方法:

‌扩展运算符(Spread Operator)‌:使用扩展运算符可以创建一个对象或数组的浅拷贝副本。 例如:const shallowCopyObj = { ...originalObj }; ‌Object.assign()方法‌:Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 例如:const shallowCopyObj = Object.assign({}, originalObj);

浅拷贝与深拷贝的区别:

‌深拷贝‌:

深拷贝会递归复制对象及其所有嵌套的对象,确保新对象与原始对象完全独立,修改新对象不会影响原始对象。

‌浅拷贝‌:

浅拷贝只复制对象的顶层结构,对于嵌套的对象或数据,浅拷贝不会进行递归复制,因此新对象和原始对象在引用类型上共享内存。

选择使用浅拷贝还是深拷贝取决于具体的需求。如果需要对对象进行修改而不影响原始对象,或者处理嵌套的对象结构,那么深拷贝是更合适的选择。

而对于简单的数据结构或者只需要引用原始对象的情况,浅拷贝可能更加高效和节省内存


const shallowCopy = obj => {
  if (Array.isArray(obj)) {
    return [...obj]
  } else if (typeof obj === "object" && obj !== null) {
    return Object.assign({}, obj)
  }
  return obj
}

const deepCopy = obj => {
  if (Array.isArray(obj)) {
    const res = []
    obj.forEach(item => {
      res.push(deepCopy(item))
    })
  } else if (typeof obj === "object" && obj !== null) {
    const res = {}
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        res[key] = deepCopy(obj[key])
      }
    }
    return res
  }
  return obj
}

new操作符

1.创建一个空的简单 JavaScript 对象。为方便起见,我们称之为 newInstance。

2.如果构造函数的 prototype 属性是一个对象,则将 newInstance 的 [[Prototype]] 指向构造函数的 prototype 属性,否则 newInstance 将保持为一个普通对象,其 [[Prototype]] 为 Object.prototype。

备注:因此,通过构造函数创建的所有实例都可以访问添加到构造函数 prototype 属性中的属性/对象。

3.使用给定参数执行构造函数,并将 newInstance 绑定为 this 的上下文(换句话说,在构造函数中的所有 this 引用都指向 newInstance)。

4.如果构造函数返回非原始值,则该返回值成为整个 new 表达式的结果。否则,如果构造函数未返回任何值或返回了一个原始值,则返回 newInstance。(通常构造函数不返回值,但可以选择返回值,以覆盖正常的对象创建过程。)

  const newInstance = {}
  newInstance.__proto__ = fn.prototype
  const params = Array.prototype.slice.call(arguments, 1)
  const fnRes = fn.apply(newInstance, params)
  const isObj = typeof fnRes === "object" && fnRes !== null
  const isFunction = typeof fnRes === "function"
  if (isObj || isFunction) {
    return fnRes
  }
  return newInstance
}

函数柯里化

函数柯里化 柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数, 并且返回接受余下的参数且返回结果的新函数的技术

function sum(a, b, c, d, e) {
  return a + b + c + d + e
}
function curry(fn, ...args1) {
  return function (...args2) {
    const params = args1 || []
    const newArgs = args2.concat(params)
    if (fn.length > newArgs.length) {
      return curry.call(this, fn, ...newArgs)
    } else {
      return fn.call(this, ...newArgs)
    }
  }
}

const myCurry = curry(sum)
console.log(myCurry(1)(2, 3)(4, 5))
console.log(myCurry(1, 2, 3)(4, 5))

promise

class myPromise {
  constructor(executer) {
    this.state = "pending"
    this.value = undefined
    this.reason = undefined
    this.onFulFilledCallbacks = []
    this.onRejectFilledCallbacks = []
    const resolve = val => {
      if (this.state === "pending") {
        this.state = "fulfilled"
        this.value = val
        this.onFulFilledCallbacks.forEach(callback => {
          callback(val)
        })
      }
    }
    const reject = val => {
      if (this.state === "pending") {
        this.state = "rejected"
        this.reason = val
        this.onRejectFilledCallbacks.forEach(callback => {
          callback(val)
        })
      }
    }
    try {
      executer(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
  then(onFulFilled, onRejectFilled) {
    onFulFilled =
      typeof onFulFilled === "function" ? onFulFilled : value => value
    onRejectFilled =
      typeof onRejectFilled === "function"
        ? onRejectFilled
        : reason => {
            throw reason
          }

    let promise = new myPromise((resolve, reject) => {
      if (this.state === "fulfilled") {
        setTimeout(() => {
          try {
            let x = onFulFilled(this.value)
            resolve(x)
          } catch (error) {
            reject(error)
          }
        })
      } else if (this.state === "rejected") {
        setTimeout(() => {
          try {
            let y = onRejectFilled(this.reason)
            resolve(y)
          } catch (error) {
            reject(error)
          }
        })
      } else {
        this.onFulFilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulFilled(this.value)
              resolve(x)
            } catch (error) {
              reject(error)
            }
          })
        })
        this.onRejectFilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let y = onRejectFilled(this.reason)
              reject(y)
            } catch (error) {
              reject(error)
            }
          })
        })
      }
    })
    return promise
  }
  catch(onRejected) {
    return this.then(null, onRejected)
  }
  finally(onFinally) {
    return this.then(
      value => resolve(onFinally()).then(() => value),
      reason =>
        reject(onFinally()).then(() => {
          throw reason
        })
    )
  }

  static resolve(value) {
    return new myPromise(resolve => {
      resolve(value)
    })
  }
  static reject(reason) {
    return new myPromise((resolve, reject) => {
      reject(reason)
    })
  }
  // Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,
  // 并返回一个 Promise。当所有输入的 Promise 都被兑现时,
  // 返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),
  // 并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,
  // 则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
  static all(promises) {
    return new myPromise((resolve, reject) => {
      const result = []
      promises.forEach(promise => {
        promise
          .then(value => {
            result.push(value)
            if (result.length === promises.length) {
              resolve(result)
            }
          })
          .catch(reject)
      })
    })
  }
  //  Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。
  //  这个返回的 promise 会随着第一个 promise 的敲定而敲定。
  static race(promises) {
    return new myPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(resolve).catch(reject)
      })
    })
  }
  //  Promise.any() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。
  //  当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。
  //  当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。
  static any(promises) {
    return new myPromise((resolve, reject) => {
      const result = []
      promises.forEach(promise => {
        promise
          .then(value => {
            resolve(value)
          })
          .catch(reason => {
            result.push(reason)
            if (result.length === promise.length) {
              reject(new AggregateError("All promises were rejected"))
            }
          })
      })
    })
  }
  //  Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,
  //  并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),
  //  返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。
  static allSettled(promises) {
    return new myPromise((resolve, reject) => {
      const result = []
      promises.forEach(promise => {
        promise
          .then(value => {
            result.push({
              status: "fulfilled",
              value,
            })
            if (result.length === promises.length) {
              resolve(result)
            }
          })
          .catch(reason => {
            result.push({
              status: "rejected",
              reason,
            })
            if (result.length === promises.length) {
              reject(result)
            }
          })
      })
    })
  }
}

// test
const p1 = new myPromise((resolve, reject) => {
  setTimeout(() => resolve("p1 resolved"), 1000)
})

const p2 = new myPromise((resolve, reject) => {
  setTimeout(() => reject("p2 rejected"), 1500)
})

const p3 = new myPromise((resolve, reject) => {
  setTimeout(() => resolve("p3 resolved"), 2000)
})

myPromise
  .all([p1, p2, p3])
  .then(results => {
    console.log("All resolved:", results)
  })
  .catch(error => {
    console.error("All error:", error)
  })

myPromise.race([p1, p2, p3]).then(result => {
  console.log("Race winner:", result)
})

myPromise.allSettled([p1, p2, p3]).then(results => {
  console.log("All settled:", results)
})

myPromise
  .any([p1, p2, p3])
  .then(result => {
    console.log("Any resolved:", result)
  })
  .catch(error => {
    console.error("Any error:", error)
  })

p1.then(result => {
  console.log(result)
  return "then result"
})
  .catch(error => {
    console.error(error)
  })
  .finally(() => {
    console.log("Finally for p1")
  })

实现lodash中的_get方法

// input
const obj = { 选择器: { to: { toutiao: "FE Coder"} }, target: [1, 2, { name: 'byted'}]};
get(obj, '选择器.to.toutiao', 'target[0]', 'target[2].name');

// output
['FE coder', 1, 'byted']
function _get(obj, ...args) {
  const result = []
  args.forEach(path => {
    const array = path.replace(/\[/g, ".").replace(/\]/g, "").split(".")
    const res = array.reduce((pre, cur) => {
      return pre[cur]
    }, obj)
    result.push(res)
  })
  console.log(result)
  return result
}

reduce

function myReduce(callback, initval) {
  let index = 1
  let accumulator = this[0]
  if (initval !== null && initval !== undefined) {
    index = 0
    accumulator = initval
  }
  while (index < this.length) {
    /** 
        @params accumulator 上一次调用 callbackFn 的结果。在第一次调用时,如果指定了 initialValue 则为指定的值,否则为 array[0] 的值。
        @params currentValue当前元素的值。在第一次调用时,如果指定了 initialValue,则为 array[0] 的值,否则为 array[1]。
        @params currentIndex 在数组中的索引位置。在第一次调用时,如果指定了 initialValue 则为 0,否则为 1。
        @params array调用了 reduce() 的数组本身。
    */
    accumulator = callback(accumulator, this[index], index, this)
    index++
  }
  return accumulator
}

Array.prototype.myReduce = myReduce
let arr = [1, 2, 3, 4, 5]
const res = arr.myReduce((pre, cur, index, arr) => {
  console.log(pre, cur, index, arr)
  return pre + cur
}, 1)
console.log(res)

find

image.png

function find(callback, thisArg) {
  if (!Array.isArray(this)) {
    return;
  }
  const len = this.length;
  for (let i = 0; i < len; i++) {
    if (callback.call(thisArg, this[i], i, this)) {
      return this[i];
    }
  }
  return;
}

Array.prototype.myFind = find;
console.log([1, 2, 3].myFind(n => n > 1));