15道高频js编程题 | 刷题打卡

2,778 阅读2分钟

本文正在参与掘金团队号上线活动,点击 查看大厂春招职位

一、背景

在面试的过程中,面试官为了考察编程功底,通常会出两道题目让应聘者手写代码。手写代码的题目通常会有一道算法题,有一道编程题。编程题常考的是手写js api,或者实现防抖,节流的功能。

为了对面试做充足的准备,我们平时应该经常练习,加强编程能力。这篇文章总结了面试中经常出现的15道js编程题。

二、15道编程题

1. 实现防抖和节流功能

防抖:

const debounce = function (cb, delay) {
  let timer = null
  return (...argv) => {
    clearTimeout(timer)
    timer = setTimeout(function () {
      cb(...argv)
    }, delay)
  }
}

节流:

const throttle = (cb, delay) => {
  let pre = 0,
    timer = null
  return (...argv) => {
    let now = new Date().getTime()
    if (now - pre > delay) {
      cb(...argv)
      pre = now
    } else {
      clearTimeout(timer)
      timer = setTimeout(function () {
        cb(...argv)
      }, delay)
    }
  }
}

2. 实现深拷贝

function deepClone(object, hash = new WeakMap()) {
  if (object == null) return object
  if (object instanceof Date) return new Date(object)
  if (object instanceof RegExp) return new RegExp(object)
  let newObj = object.constructor()
  console.log(Reflect.ownKeys(object), newObj)
  Reflect.ownKeys(object).forEach((key) => {
    newObj[key] = object[key]
  })
  return newObj
}

3. 手写call函数

Function.prototype.newCall = function (_this, ...argvs) {
  let context = _this
  context.fn = this
  let result = context.fn(...argvs)
  delete context.fn
  return result
}

4. 手写apply函数

Function.prototype.newApply = function (_this, argvs) {
  let context = _this
  context.fn = this
  let result = context.fn(...argvs)
  delete context.fn
  return result
}

5. 手写bind函数

Function.prototype.newBind = function (_this) {
  let fn = this,
    bindArgs = Array.prototype.slice.call(arguments, 1)
  return (...args) => {
    return fn.call(_this, ...bindArgs, ...args)
  }
}

6. 实现函数满足add(1)(2)(3)

这道题目的考察点在于你对于函数柯里化的理解程度

function curry(fn, args) {
    var length = fn.length;

    args = args || [];

    return function() {

        var _args = args.slice(0),

            arg, i;

        for (i = 0; i < arguments.length; i++) {

            arg = arguments[i];

            _args.push(arg);

        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}


var add = curry(function(a, b, c) {
    console.log([a, b, c]);
});

add("a", "b", "c") // ["a", "b", "c"]
add("a", "b")("c") // ["a", "b", "c"]
add("a")("b")("c") // ["a", "b", "c"]
add("a")("b", "c") // ["a", "b", "c"]

7. 实现观察者模式

class Subject {
  constructor(name) {
    this.name = name
    this.state = '灯不亮了'
    this.observers = []
  }
  attach(observer) {
    this.observers.push(observer)
  }
  setState(newState) {
    this.state = newState
    this.observers.forEach((observer) => observer.update(newState))
  }
}
//观察者
class Observer {
  constructor(name) {
    this.name = name
  }
  update(newState) {
    console.log(this.name + '说' + newState)
  }
}
let light = new Subject('灯')
let mm = new Observer('小明')
let jj = new Observer('小件')
light.attach(mm) // 订阅
light.attach(jj)
light.setState('灯不亮了') // 发布

8. 实现一个EventEmitter(发布订阅模式)

class EventEmitter {
  constructor() {
    this.hooks = {}
  }
  on(name, fn) {
    this.hooks[name] ? this.hooks[name].push(fn) : (this.hooks[name] = [fn])
  }
  emit(name) {
    if (this.hooks[name]) {
      this.hooks[name].forEach((fn) => fn())
    }
  }
}

9. 封装一个jsonp

function jsonp(url, data = {}, callback = 'callback') {
  // 处理json对象,拼接url
  data.callback = callback
  let params = []
  for (let key in data) {
    params.push(key + '=' + data[key])
  }
  console.log(params.join('&'))
  // 创建script元素
  let script = document.createElement('script')
  script.src = url + '?' + params.join('&')
  document.body.appendChild(script)
  // 返回promise
  return new Promise((resolve, reject) => {
    window[callback] = (data) => {
      try {
        resolve(data)
      } catch (e) {
        reject(e)
      } finally {
        // 移除script元素
        script.parentNode.removeChild(script)
        console.log(script)
      }
    }
  })
}
jsonp('http://photo.sina.cn/aj/index', {
  page: 1,
  cate: 'recommend'
}, 'jsoncallback').then(data => {
  console.log(data)
})

10. 实现new 关键字

  1. 定义一个对象obj

  2. 此对象的__proto__指向构造函数的prototype

  3. 执行构造函数,确保构造函数的this指向obj

function myNew() {
  let constructor = [].shift.call(arguments)
  let obj = {}
  obj.__proto__ = constructor.prototype
  let result = constructor.apply(obj, arguments)
  return typeof result === 'object' ? result || obj : obj
}

11. 实现instanceof关键字

核心:原型链查找

function _instanceof(A, B) {
  while (B.prototype) {
    if (A.__proto__ == B.prototype) {
      return true
    } else {
      B = B.prototype
    }
  }
  return false
}

12. 实现Object.assign

Object.newAssign = function () {
  let target = [].shift.call(arguments)
  for (let i = 0; i < arguments.length; i++) {
    let nextObj = arguments[i]
    if (nextObj != null) {
      for (let key in nextObj) {
        if (nextObj.hasOwnProperty(key)) {
          target[key] = nextObj[key]
        }
      }
    }
  }
  return target
}

13. 实现一个解析url参数为对象的函数

function parse(url) {
  var regExp = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/
  let list = url.match(regExp)
  let fields = [
    'url',
    'scheme', // Scheme(协议)
    'slash', // 斜线
    'host', // Host(主机名)
    'port', // 端口
    'path', // 路径
    'query', // 参数
    'hash', // 锚点
  ]
  let obj = {}
  for (let i = 0; i < list.length; i++) {
    obj[fields[i]] = list[i]
  }
  console.log(obj)
  return obj
}
parse('https://harttle.land:80/tags.html?simple=true#HTML')

14. js格式化数字(添加千位分隔符)

function formNum(num) {
  if (typeof num !== 'number') return num
  num += ''
  return num.replace(/\d{1,3}(?=(\d{3})+$)/g, function (s) {
    return s + ','
  })
}

15. 函数去重

函数去重其实难度不大,但是面试官往往会让你多说几种实现方案来考察你知识面的广度。此处我写了6种函数去重的方式。

1. new Set去重,缺点:没法去重{}

function unique(arr) {
  return [...new Set(arr)]
}

2. 双重for循环+splice,缺点:NaN和{} 无法去重

function unique1(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] === arr[j]) {
        arr.splice(j, 1)
        j--
      }
    }
  }
  return arr
}

3.for循环+新建数组+indexof,缺点:NaN和{} 无法去重

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

4.for循环+新建数组 + includes ,缺点:{}没有去重

function unique3(arr) {
  let array = []
  for (let i = 0; i < arr.length; i++) {
    if (!array.includes(arr[i])) {
      array.push(arr[i])
    }
  }
  return array
}

5. hasOwnProperty + object + filter, 全部去重了

function unique4(arr) {
  let obj = {}
  return arr.filter((item, index) => {
    let res = obj.hasOwnProperty(typeof item + item)
      ? false
      : (obj[typeof item + item] = true)
    return res
  })
}

6.new Map + 新建数组,缺点:{}没有去重

function unique5(arr) {
  let map = new Map(),
    array = []
  for (let i = 0; i < arr.length; i++) {
    if (!map.has(arr[i])) {
      map.set(arr[i], true)
      array.push(arr[i])
    }
  }
  return array
}