很多很多个前端手写题

0 阅读4分钟

慢慢更新

深拷贝

面试手写

思路上大体分为两部分:

  1. 基本数据类型
  2. 引用数据类型
    1. 日期
    2. 正则
    3. html元素
    4. 考虑了循环引用
function deepClone(target, hash = new WeakMap()) {
  if (target === null) return target
  if (target instanceof Date) return new Date(target)
  if (target instanceof RegExp) return new RegExp(target)
  if (target instanceof HTMLElement) return target
  if (typeof target !== 'object') return target

  let cloneTarget = new target.constructor()
  if (hash.has(target)) {
    return hash.get(target)
  }
  hash.set(target,cloneTarget)
  Reflect.ownKeys(target).forEach(key => {
    cloneTarget[key] = deepClone(target[key], hash)
  })
  return cloneTarget
}
let a = {
  aa: [1, 2, 3]
}
let b = deepClone(a)
b.aa[0] = 1111
console.log(b)
console.log(a)

日常使用

懂得都懂

JSON.parse(JSON.stringify(obj))

数组转树形结构

这里使用浅拷贝就可以了,可以避免修改原有数组

const arr = [
  { 'id': '29', 'pid': '', 'name': '总裁办' },
  { 'id': '2c', 'pid': '', 'name': '财务部' },
  { 'id': '2d', 'pid': '2c', 'name': '财务核算部' },
  { 'id': '2f', 'pid': '2c', 'name': '薪资管理部' },
  { 'id': 'd2', 'pid': '', 'name': '技术部' },
  { 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部' }
]
function listToTree(arr) {
  let res = []
  let map = {}
  arr.forEach((item) => {
    map[item.id] = { // 浅拷贝,避免修改原有数组
      ...item,
      children: []
    }
  })
  arr.forEach((item) => {
    let parent = map[item.pid]
    if (parent) {
      parent.children.push(map[item.id])
    } else {
      res.push(map[item.id])
    }
  })
  return res
}
const treeList = listToTree(arr)
console.log(treeList);

树形结构转数组

function flatten(tree, res = []) {
  tree.forEach((item) => {
    let { children, ...obj } = item
    res.push(obj)
    if (children) {
      flatten(children, res)
    }
  })
  return res
}

数组去重

只有数字的简单去重

function uniqueArr(arr) {
  return Array.from(new Set(arr))
}

有引用类型的复杂去重

使用 JSON.stringify 解决

let arr = [
  {
    name: '1',
    age: 1
  },
  {
    name: '2',
    age: 2
  },
  {
    name: '1',
    age: 1
  },
  123,
  1,
  123,
  [1, 2, 3],
  [1],
  [1, 2, 3]
]

function uniqueArr(arr) {
  let res = []
  let set = new Set()
  arr.forEach((item) => {
    if (typeof item === 'object') {
      if (!set.has(JSON.stringify(item))) {
        res.push(item)
        set.add(JSON.stringify(item))
      }
    } else {
      if (!set.has(item)) {
        res.push(item)
        set.add(item)
      }
    }
  })
  return res
}
console.log(uniqueArr(arr))

结果:

image.png

数组扁平化

forEach + 递归

function copyFlat(arr, depth) {
  let res = []
  function flat(arr, depth) {
    arr.forEach((item) => {
      if (Array.isArray(item) && depth > 0) { // 注意递归的终止条件应该写在这里
        flat(item, depth - 1)
      } else {
        res.push(item)
      }
    })
  }
  flat(arr, depth)
  return res
}
console.log(copyFlat([1,[2, [3]]], 1))
function copyFlat(arr, depth) {
  if (depth === 0) return arr
  return arr.reduce((res, cur) => {
    return res.concat(Array.isArray(cur) ? copyFlat(cur, depth - 1) : cur)
  }, [])
}
console.log(copyFlat([1,[2, [3]]], 1))

防抖和节流

防抖

function debounce(fn, delay = 1000) {
  let timer = null
  return function () {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arguments)
    }, delay)
  }
}

节流

function throttle(fn, delay = 1000) {
  let timer = 0
  return function () {
    let now = Date.now()
    if (now - timer >= delay) {
      fn.apply(this, arguments)
      timer = now
    }
  }
}

继承

组合继承

组合继承 = 原型链继承 + 构造函数继承

function Father() {

}
function Son() {
  Father.call(this) // 原型链继承
}
Son.prototype = new Father() // 构造函数继承

优点:

  1. 可以拿到父类的属性和方法
  2. 并且属性和方法不会与其他子类实例共享

缺点:

  1. 调用了两次父类构造,所以属性上可以理解为父类上有的属性,子类上有一份相同的,总共有两份,子类上的属性会覆盖父类,这样不是特别好,最好的情况应该是父类身上没有属性,只有子类身上有继承过来的父类属性,那么父类就不应该被构造调用
  2. 子类的原型对象是父类了,所以构造函数也指向父类,不是我们想要的结果

寄生组合式继承

function inheritPrototype(subType, superType) {
  let prototype = Object.create(superType.prototype)
  prototype.constructor = subType
  subType.prototype = prototype
}
function Father() {

}
function Son() {
  Father.call(this)
}
inheritPrototype(Son, Father)

目前最好的继承方式,优点就是解决了组合式继承的问题

判断一个数组是否包含另一个数组

function arrInclude(arr1, arr2) {
  return arr2.every(item => arr1.includes(item))
}
console.log(arrInclude([2, 3, 4, 5, 6, 7, 8, 9, 10], [2, 3]))

获取 url 参数

牛客手写题

function getUrlParam(sUrl, sKey) {
  let paramStr = sUrl.indexOf('#') === -1 ? sUrl.slice(sUrl.indexOf('?') + 1) : sUrl.slice(sUrl.indexOf('?') + 1, sUrl.indexOf('#'))
  let params = paramStr.split('&')
  let paramObj = {}
  params.forEach((item) => {
    let [key, value] = item.split('=')
    if (paramObj[key]) {
      paramObj[key] = [].concat(paramObj[key], value)
    } else {
      paramObj[key] = value
    }
  })
  if (sKey) {
    return paramObj[sKey] ? paramObj[sKey] : ''
  }
  return paramObj
}

函数柯里化

参数定长

function curry(fn) {
  let len = fn.length
  let outArg = [].slice.call(arguments, 1)
  return function () {
    let inArg = [].slice.call(arguments)
    let allArg = [...outArg, ...inArg]
    if (allArg.length >= len) {
      return fn.apply(null, allArg)
    } else {
      return curry.call(null, fn, ...allArg)
    }
  }
}
function sum(a,b,c) {
  return a + b + c
}
let cur = curry(sum)
console.log(cur(1)(2)(3))

手写 call、apply、bind

call

Function.prototype.mycall = function (context, ...args) { // call 以对象形式进行传参,非严格模式下会对包装类型对象进行包装
  let isStrict = (function () { return this === undefined })()
  if (!isStrict) {
    let type = typeof context
    if (type === 'string') {
      context = new String(context)
    } else if (type === 'number') {
      context = new Number(context)
    } else if (type === 'boolean') {
      context = new Boolean(context)
    }
  }
  let fn = this
  if (!context) {
    return fn(...args)
  }
  let symbol = Symbol()
  context[symbol] = fn
  let res = context[symbol](...args)
  delete context[symbol]
  return res
}

apply

相比于 call,只是把形参变了一下

Function.prototype.myapply = function (context, args = []) {
  let isStrict = (function () { return this === undefined })()
  if (!isStrict) {
    let type = typeof context
    if (type === 'number') {
      context = new Number(context)
    } else if (type === 'boolean') {
      context = new Boolean(context)
    } else if (type === 'string') {
      context = new String(context)
    }
  }
  let fn = this
  if (!context) {
    return fn(...args)
  }
  let symbol = Symbol()
  context[symbol] = fn
  let res = context[symbol](...args)
  delete context[symbol]
  return res
}

bind

Function.prototype.mybind = function (context, ...args) {
  let fn = this
  let newFn = function (...newArgs) {
    if (this instanceof newFn) {
      return fn.call(this, ...args, ...newArgs)
    }
    return fn.call(context, ...args, ...newArgs)
  }
  newFn.prototype = Object.create(fn.prototype)
  return newFn 
}

function foo(something) {
  this.a = something;
}
var obj1 = {};
var bar = foo.mybind(obj1);
bar(2); // 让foo此时指向obj1
console.log(obj1.a); // 2
var baz = new bar(3); // new修改了this指向,指向新创建出来的foo对象,并将该新创建的foo对象的a赋值为3
console.log(obj1.a); // 2,因为上面this指向新的对象,所以obj1的a没有再被修改
console.log(baz.a); // 3

文章参考

利用非递归方式实现数组转化成树形结构(1)

一个最简单的扁平数组与树形结构相互转换的算法