2022面试常考手写基础题总结

1,430 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

前言

整理了26道2022年面试常遇到的,面试官要求手写的js基本题,如一些方法的实现等

手写深拷贝

const deepCopy = (obj) => {
  if (typeof obj !== 'object' && obj !== null) {
    return obj
  }

  // 创建新对象
  const cloneObj = Array.isArray(obj) ? [] : {};

  // symbol拷贝
  const symArr = Object.getOwnPropertySymbols(obj);
  symArr.forEach(key => {
    if (obj[key] && typeof obj[key] === 'object') {
      cloneObj[key] = deepCopy(obj[key]);
    } else cloneObj[key] = obj[key];
  })

  // 对象的key拷贝
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (obj[key] && typeof obj[key] === 'object') {
        cloneObj[key] = deepCopy(obj[key])
      } else cloneObj[key] = obj[key]
    }
  }
  
  return cloneObj
}

手写防抖函数

// 一定时间内,重复触发同一个事件,会先清除掉上一次的执行,然后开启一个新的执行事件,并等待一定时间执行 多次重复触发同一个事件,只执最后一次
function debounce (fn, delay) {
  let timer = null
  return function () {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(fn, delay)
  }
}

手写节流函数

// 持续触发事件时,一定时间内只能触发一次。而不是清除掉
// 定时器方式
function throttle (fn, delay) {
  let timer = null
  return function() {
    if (timer) return;
    timer = setTimeout(() => {
      fn.apply(this,arguments)
      timer = null
    }, delay)
  }
}

手写多维数组扁平化

function arrFlat (arr) {
  if (!arr.some(a => Array.isArray(a))) {
    return arr
  }
  return arrFlat(Array.prototype.concat.apply([], arr))
}

手写翻转二叉树

function invertTree (root) {
  if(root != null){
    let temp = root.left
    root.left = root.right
    root.right = temp
    invertTree(root.left)
    invertTree(root.right)
  }
  return root
}

归并排序

// 思路:
// 分:把数组劈成两半,再递归地对子数组进行“分”操作,直到分成一个个单独的数组
// 合:把两个数组合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组。
function mergeSort(arr){
  if (arr.length === 1) {
    return arr
  }
  const mid = Math.floor(arr.length / 2)
  const left = arr.slice(0, mid)
  const right = arr.slice(mid, arr.length)
  const orderLeft = mergeSort(left)
  const orderRight = mergeSort(right)
  const res = []
  while (orderLeft.length || orderRight.length) {
    if(orderLeft.length && orderRight.length){
      res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift())
    } else if (orderLeft.length) {
      res.push(orderLeft.shift())
    } else if (orderRight.length) {
      res.push(orderRight.shift())
    }
  }
  return res
}

闭包实现加法器

function add(){
  let count = 0;
  return function closure(){
    count++;
    console.log(count);
  }
}
var counter = add();
counter() // 1
counter() // 2
counter() // 3

数组去重

// 1. 双循环
function unique(arr){
  let res = [arr[0]]
  for (let i = 1; i < arr.length; i++) {
    let flag = true
    for (let j = 0; j < res.length; j++) {
      if (arr[i] === res[j]) {
        flag = false
        break
      }
    }
    if (flag) {
      res.push(arr[i])
    }
  }
  return res
}

// 2. indexOf
function unique(arr){
  let res = []
  for (let i = 0; i < arr.length; i++) {
    if (res.indexOf(arr[i]) === -1) {
      res.push(arr[i])
    }
  }
  return res
}

// 3. indexOf
function unique(arr){
  return arr.filter(function(item, index){
    return arr.indexOf(item) === index;
  })
}

// 4. 相邻元素去重
function unique(arr){
  arr = arr.sort()
  let res = []
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] !== arr[i-1]) {
      res.push(arr[i])
    }
  }
  return res
}

// 5. 利用对象属性去重
function unique(arr){
  let res = [], obj = {}
  for (let i = 0; i < arr.length; i++) {
    if (!obj[arr[i]]) {
      res.push(arr[i])
      obj[arr[i]] = 1
    } else {
      obj[arr[i]]++
    }
  }
  return res
}

// 6. Set与解构赋值去重
function unique(arr){
  return [...new Set(arr)]
}

// 7. Array.from 与 Set去重
function unique(arr){
  return Array.from(new Set(arr))
}

手写 apply

Function.prototype.myApply = function(context, args){
  context = context || window
  args = args || []
  const symbol = Symbol()
  context[symbol] = this
  const result = context[symbol](...args)
  delete context[symbol]
  return result
}

手写 call

Function.prototype.myCall = function(context, ...args){
  context = context || window
  args = args || []
  const symbol = Symbol()
  context[symbol] = this
  const result = context[symbol](...args)
  delete context[symbol]
  return result
}

手写 bind

Function.prototype.myBind = function(context, ...args){
  context = context || window
  args = args || []
  const fn = this
  return function newFn(...newArgs){
    // 考虑new的情况
    if (this instanceof newFn) {
      return new fn(...args, ...newArgs)
    }
    return fn.apply(context, [...args, ...newArgs])
  }
}

实现 add(1)(2)(3) 累加

function add(...args) {
  if (args.length < 7) { // 7为参数个数
    return function (...newArgs) {
      return add(...args, ...newArgs)
    }
  }
  return args.reduce((sum, cur) => sum + cur, 0)
}

// 如:
console.log(add(1)(2)(3)(4,5)(6)(7));

实现 new 方法

function _new(constructor, ...args){
  let obj = {}
  obj.__proto__ = constructor.prototype
  constructor.apply(obj, args)
  return obj 
}

// 示例:
function A(name) {
  this.name = name
}
A.prototype.say = () => {
  console.log('say')
}
let a1 = new A('123')
let a2 = _new(A, '456')
console.log(a1.name); // 123
console.log(a2.name); // 456
a1.say() // say
a2.say() // say

js 实现函数重载

function addMethod(object, name, fn) {
  var old = object[name]; // 把前一次添加的方法存在一个临时变量old里面
  object[name] = function () { // 重写了object[name]的方法
    if (fn.length === arguments.length) { // 调用object[name]方法时,如过传入的参数个数跟预期的一致,则直接调用
      return fn.apply(this, arguments);
    } else if (typeof old === "function") { // 否则,判断old是否是函数,如果是,就调用old
      return old.apply(this, arguments);
    }
  }
}

// 示例:
let obj = {}

addMethod(obj, 'fn', (name) => console.log(`我是${name}`))
addMethod(obj, 'fn', (name, age) => console.log(`我是${name}, 今年${age}岁`))
addMethod(obj, 'fn', (name, age, sport) => console.log(`我是${name}, 今年${age}岁, 喜欢${sport}`))

obj.fn(1)
obj.fn(1,2)
obj.fn(1,2,3)

Promise 方法实现6个

链接

只执行一次函数实现

// once
function once(fn){
  let done = true
  return function(){
    if (done) {
      done = false
      fn.apply(this, arguments)
    }
  }
}

// 示例
function pay (money) {
  console.log(`支付${money}元,调起api`)
}
let oncePay = once(pay)

oncePay(12) // 支付12元,调起api
oncePay(12)
oncePay(12)

模拟数组方法

// 1. 实现 forEach
function forEach(arr, fn){
  for(let i = 0; i < arr.length; i++){
    fn(arr[i])
  }
}

// 2. 实现 filter
function filter(arr, fn){
  let results = []
  for (let i = 0; i < arr.length; i++) {
    if (fn(arr[i])) {
      results.push(arr[i])
    }
  }
  return results
}

// 3. 实现 map
function map(arr, fn){
  let results = []
  for (let value of arr) {
    results.push(fn(value))
  }
  return results
}

// 4. 实现 every
function every(arr, fn){
  for (let value of arr) {
    if(!fn(value)){
      return false
    }
  }
  return true
}

// 5. 实现 some
function some(arr, fn){
  for (let value of arr) {
    if(fn(value)){
      return true
    }
  }
  return false
} 

发布订阅模式实现

class EventEmitter{
  // 构造函数创建信息中心
  constructor(){
    this.subs = Object.create(null)
  }

  // 注册事件 判断如果有该信息事件,就继续追加,反之加入信息事件
  $on(eventType, handler){
    this.subs[eventType] = this.subs[eventType] || []
    this.subs[eventType].push(handler)
  }

  // 触发事件 触发时执行每一个信息事件里存储的函数
  $emit(eventType){
    if(this.subs[eventType]){
      this.subs[eventType].forEach(f => f())
    }
  }
}

// 示例:
var em = new EventEmitter()

// 订阅者注册信息事件
em.$on('click',() => console.log('click1'))
em.$on('click',() => console.log('click2'))

// 发布者执行信息事件
em.$emit('click')

观察者模式实现

// 发布器
class Dep{
  constructor(){
    this.subs = []  // 记录所有的观察者
  }
  // 添加观察者
  addSub(sub){
    // 判断存在并且必须有update方法
    if(sub && sub.update){
      this.subs.push(sub)
    }
  }
  // 发布通知 挨个执行每个观察者的update方法
  notify(){
    this.subs.forEach(sub => sub.update())
  }
}
// 观察者
class Watcher{
  update(){
    console.log('update')
  }
}

// 示例:
var dep = new Dep()
var watcher = new Watcher()

dep.addSub(watcher)
dep.notify()

js实现 trim 函数

funciton trim(str){
  return str.replace(/^\s*|\s*$/g"");
}

reducer实现map

Array.prototype._map = function(fn, callbackThis) {
  let res = [] // 最终返回的新数组
  // 定义回调函数的执行环境
  // call第一个参数传入null,则this指向全局对象,同map的规则
  let CBThis = callbackThis || null
  this.reduce((brfore, after, idx, arr) => {
    // 传入map回调函数拥有的参数
    // 把每一项的执行结果push进res中
    res.push(fn.call(CBThis, after, idx, arr))
  }, null)
  return res
}

总结

大家如果有面试到新的手写方法之类的,欢迎补充,我更新文章
2022年就业行情萎靡的一年,祝大家都能找到自己满意的工作
整理不易,如果有帮到你,给个赞吧!👍

往期精彩文章

🌟 hash路由与history路由的区别
🌟 轻松理解前端模块化
🌟 ECMAScript新特性汇总,面试别在答错
🌟 Promise 面试常考手写方法汇总
🌟 Promise 常见误区