[纯干货]web前端面试必考-JS手写题

195 阅读4分钟

最近在准备面试,花了一段时间,广采精华,整理出了以下在前端面试中非常大概率会考到的JS手写面试题。希望对诸君面试准备和巩固知识起到帮助。

如有代码优化的建议,请评论指出,我们共同进步!

话不多说,直接上代码!

1、使用js代码实现一个事件类Event,包含以下功能:绑定事件、解绑事件、派发事件

class Event {
  constructor() {
    this._cache = []
  }
  on (type, callback) {
    let fns = this._cache[type] || []
    if (fns.indexOf(callback) === -1) {
      fns.push(callback)
    }
    return this
  }
  off (type, callback) {
    let fns = this._cache[type]
    if (Array.isArray(fns)) {
      if (callback) {
        let index = fns.indexOf(callback)
        if (index !== -1) {
          fns.splice(index, 1)
        }
      } else {
        fns.length = 0
      }
    }
    return this
  }
  trigger (type, data) {
    let fns = this._cache[type]
    if (Array.isArray(fns)) {
      fns.forEach((fn) => {
        fn(data)
      })
    }
    return this
  }
}

2、实现深拷贝deepClone

const deepClone = (obj) => {
  if (typeof obj !== 'object') return
  let newObj = obj instanceof Array ? [] : {}
  //使用Reflect.ownKeys可以获取Symbol属性
  //for(let key of Reflect.ownKeys(obj)) {
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
    }
  }
  return newObj
}

3、快速排序

const quickSort = (array) => {
  let pivot = array[array.length - 1]
  let left = arrary.filter((v, i) => v < pivot && i !== array.length - 1)
  let right = array.filter(v => v > pivot)
  return [...quickSort(left), pivot, ...quickSort(right)]
}

4、冒泡排序

const bubbleSort = (array) => {
  let temp
  for(let i = 0; i < array.length; i++) {
    for(let j = 0;j< array.length - 1 -i; j++) {
      if(array[j] > array[j+1]) {
        temp = array[j]
        result[j] = result[j+1]
        result[j+1] = result[j]
      }
    }
  }
  return array
}

5、二分查找

(1)一维二分查找

const binarySearch = (target, array) => {
  let i = 0
  let j = array.length-1
  while(i <= j) {
   if (target > array[i]) {
      i++
    }else if (target < array[j]) {
      j--
    } else {
      return true
    }
  }
  return false
}

(2)二维二分查找(长宽一致正方形矩阵)

const binarySearch = (target, array) => {
  let i = 0
  let j = array[i].length - 1
  while (i < array[i].length && j >= 0) {
    if ( target > array[i][j]) {
      i++
    } else if (target < array[i][j]) {
      j--
    } else {
      return true
    }
  }
  return false
}

6、实现call

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

7、实现apply

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

8、实现bind

Function.prototype.myBind = (context, ...args) => {
  const fn = this
  return function newFn (...newArgs) {
    return fn.apply(context, [...args, ...newArgs])
  }
}

9、实现new

const myNew = (obj, ...args) => {
  let newObj = {}
  newObj.__proto__ = obj.prototype
  //以上两步可以合并为:
  //let newObj = Object.create(obj.prototype)
  let result = obj.apply(newObj, args)
  return typeof result === 'object' ? result : newObj
}

10、实现instanceof

const myInstanceOf = (left, right) => {
  let leftVal = left.prototype
  let rightVal = right.prototype
  while (true) {
    if (leftVal === null) return false
    if (leftVal === rightVal) return true
    leftVal = leftVal.__proto__
  }
}

11、函数防抖deBounce

const deBounce = (fn, wait = 50) => {
  let timer = 0
  return function (...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, wait)
  }
}

12、函数节流throttle

const throttle = (fn, wait = 50) => {
  let prev = 0
  return function (...args) {
    let now = +new Date()
    if (now - prev > wait || !prev) {
      prev = now
      fn.apply(this, args)
    }
  }
}

13、数组去重

//方法一
const uniqueArr = [...new Set(array)] //Array.from(new Set(array))
//方法二
const uniqueArr = (array) => {
  let newArr = []
  array.forEach(item => {
    if (newArr.indexOf(item) === -1) {
      newArr.push(item)
    }
  })
  return newArr
}
//方法三
const uniqueArr = (array) => {
  return array.filter((item, index) => {
    return array.indexOf(item) === index
  })
}
//方法四
const uniqueArr = (array) => {
  return array.reduce((prev, curr) => {
    return prev.includes(curr) ? prev : [...prev, ...curr] //prev.concat(curr)
  }, [])
}

14、数组扁平化

//方法一
const arrayFlat = array.flat(Infinity)
//方法二
const arrayFlat = (array) => {
  return array.reduce((prev, curr) => {
    return prev.concat(Array.isArray(curr) ? arrayFlat(curr) : curr)
  }, [])
}

15、数组柯里化

//方法一
const curry = (fn, args) => {
  const length = fn.length //期望函数的参数个数
  args = args || []
  return function () {
    const newArgs = args.concat(Array.prototype.slice.call(arguments))
    // 如果参数个数小于最初的fn所需参数个数,则递归调用继续收集参数,否则直接调用期望函数
    if (newArgs.length >= length) {
      return fn.apply(this, newArgs)
    } else {
      return curry.call(this, fn, newArgs)
    }
  }
}
//ES6版本
let curry = (fn, arr = []) => (...args) => (
  arg => arg.length === fn.length
    ? fn(...arg)
    : curry(fn, arg)
)([...arr, ...args])

//用例
function multiFn (a, b, c) {
  return a * b * c;
}
let multi = curry(multiFn);
multi(2)(3)(4);

16、继承的实现

//***原型链关系begin***
//构造器原型链
Child.__proto__ === Parent
Parent.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
//实例原型链
child.__proto__ === Child.prototype
Child.prototype.__proto__ === Parent.prototype
Parent.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
//***原型链关系end***

//原型链继承
Child.prototype = new Parent()
Child.prototype.constructor = Child
//类式继承
function Child (name, parentName) {
  Parent.call(this, parentName)
  this.name = name
}
//组合式继承
function Child (name, parentName) {
  Parent.call(this, parentName)
  this.name = name
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
//寄生组合继承
function Child (name, parentName) {
  Parent.call(this, parentName)
  this.name = name
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
//ES6继承
class Parent {
  constructor(name) {
    this.name = name
  }
}
class Child extends Parent {
  constructor(name, parentName) {
    super(parentName)
    this.name = name
  }
}
const child = new Child('son', 'father')

17、实现promise

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise (fn) {
  const taht = this
  that.state = PENDING
  that.value = null
  that.resolvedCallbacks = []
  that.rejectedCallbacks = []
  function resolve (value) {
    if (that.state === PENDING) {
      that.state = RESOLVED
      that.value = value
      that.resolvedCallbacks.map(cb => cb(that.value))
    }
  }
  function reject (value) {
    if (that.state === PENDING) {
      that.state = REJECTED
      that.value = value
      that.rejectedCallbacks.map(cb => cb(that.value))
    }
  }
  try {
    fn(resolve, reject)
  } catch (e) {
    reject(e)
  }
}
MyPromise.prototype.then = (onFulfilled, onRejected) => {
  const that = this
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected = typeof onRejected === 'function' ? onRejected :
    r => {
      throw r
    }
  if (that.state === PENDING) {
    that.resolvedCallbacks.push(onFulfilled)
    that.rejectedCallbacks.push(onRejected)
  }
  if (that.state === RESOLVED) {
    onFulfilled(that.value)
  }
  if (that.state === REJECTED) {
    onRejected(that.value)
  }
}

//用例
new MyPromise((resolve, reject) => {
  resolve(1)
}).then(value => {
  console.log(value)
})

18、原生实现Ajax

let request = new XMLHttpRequest()
request.open('GET', url, true)
request.onreadystatechange = function () {
  if (request.readyState === 4 && request.status === 200) {
    console.log('请求正常')
  } else {
    console.log('请求异常')
  }
}
request.send()

19、实现Object.create

const create = (proto) => {
  function F () { }
  F.prototype = proto //将原型挂在构造函数的prototype上
  F.prototype.constructor = F
  return new F()
}

20、实现千位分隔符

//方法一
const format = (num) => {
  const reg = /\d{1,3}(?=(\d{3})+$)/g //表示前面有1~3个数字,后面的至少由一组3个数字结尾
  return (num + '').replace(reg, '$&,') //$& 表示与正则表达式相匹配的内容
}
//方法二
const format = (num) => {
  num = num + ''
  let str = ''
  for (let i = num.length - 1, j = 1; i >= 0; i--, j++) {
    if (j % 3 === 0 && i != 0) { //每隔三位加逗号,过滤正好在第一个数字的情况
      str += num[i] + ','
      continue
    }
    str += num[i] //倒着累加数字
  }
  return str.split('').reverse().join('') //字符串=>数组=>反转=>字符串
}

21、解析URL params为对象

let str = "http://www.baidu.com/we/index.html?id=898602B8261890349226&aaa=123&ccc=456";
let urlObject = (str) => {
  let finalObj = new Object()
  let data = str.slice(str.indexOf('?') + 1, str.length - 1)
  let subParams = data.substr(1).split('&')
  for (let i = 0; i < subParams.length; i++) {
    let singleParam = subParams[i].split('=')
    finalObj[singleParam[0]] = singleParam[1]
  }
  return finalObj
}

22、判断是否为数组

//方法一 通过instanceof判断
arr instanceof Array
//方法二 通过constructor判断
arr.constructor === Array
//方法三 通过Object.prototype.toString.call()判断
Object.prototype.toString.call(arr) === '[object Array]'
//方法四 通过Array.isArray()判断
Array.isArray(arr)

23、实现jsonp

//简述
//1、定义一个函数,用于处理接收到的跨域数据
//2、生成一个script节点(dom节点),src属性记入传入的目的URL以及参数
//3、将新创建的节点添加到DOM树上
//详述
//1、动态创建地创建script标签以发起请求,在src中填写请求的目标路径,并传入查询参数callback也就是回调函数的函数名。
//2、服务器接收到请求时,会根据查询参数callback返回执行回调函数的语句,并在参数传入请求方想要的数据。
//3、请求方接收到响应后就会执行这个语句也就是执行回调函数,这样请求方就能在回调函数中取得想要的数据。
const callbackFunc = (result) => {
  console.log(result)
}
let jsonpScript = document.createElement('script')
jsonpScript.src = 'http://demo.com/test?callback=callbackFunc'
document.body.appendChild(jsonpScript)

24、实现一个简单的cache工具

//使用闭包来隐藏数据,只提供API
const myCache = () => {
    let data = {}
    return {
        set: function(key, val) {
            data[key] = val
        },
        get: function (key) {
            return data[key]
        }
    }
}

25、实现一个简单的图片懒加载

//通过Promise实现
const loadImage = (path) => {
    return new Promise ((resolve, reject) => {
        let img = new Image()
        img.onload = function() {
            resolve(img)
        }
        img.onerror = function() {
            reject('图片加载失败')
        }
        img.src = path
    })
}
//用例
let result = loadImage('www.baidu.com').then((res)=>{console.log(res,1)}).catch((e) => {console.log(e,2)})