# 手写代码

面试官为啥总是让我们手撕 call、apply、bind? (opens new window)

# call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

// mdn 示例
function Product(name, price) {
  this.name = name
  this.price = price
}

function Food(name, price) {
  Product.call(this, name, price)
  this.category = 'food'
}

console.log(new Food('cheese', 5))

// 手写call
function myCall(obj, ...args) {
  const context = obj || window
  const fn = Symbol()
  context[fn] = this
  let res
  res = context[fn](...arg)
  delete context[fn]
  return res
}

Function.prototype.myCall = myCall

function Food(name, price) {
  Product.call(this, name, price)
  this.category = 'food'
}

console.log(new Food('cheese', 5))

# apply

apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。

const array = ['a', 'b']
const elements = [0, 1, 2]
array.push.apply(array, elements)
console.log(array) // ["a", "b", 0, 1, 2]

function myApply(obj, args) {
  const context = obj || window
  const fn = Symbol()
  context[fn] = this
  let res
  res = context[fn](...args)
  delete context[fn]
  return res
}

Function.prototype.myApply = myApply

array.push.myApply(array, [3, 4, 5])
console.log(array)

# bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

const module = {
  x: 42,
  getX: function () {
    return this.x
  },
}

const unboundGetX = module.getX
console.log(unboundGetX()) // The function gets invoked at the global scope
// expected output: undefined

const boundGetX = unboundGetX.bind(module)
console.log(boundGetX())
// expected output: 42

function myBind(obj, ...argsOut) {
  const context = obj || window
  const fn = Symbol()
  context[fn] = this
  return function (...argsIn) {
    return context[fn](...argsOut, ...argsIn)
  }
}
Function.prototype.myBind = myBind

const myBindGetX = unboundGetX.myBind(module)
console.log(boundGetX())

function twoPlus(a, b) {
  return 2 + a + b
}

// const sum = twoPlus.myBind(null, 1)
const sum = twoPlus.bind(null, 1)
sum(2)

# new

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

// 正常new的使用
function Car(make, model, year) {
  this.make = make
  this.model = model
  this.year = year
}

const car1 = new Car('Eagle', 'Talon TSi', 1993)

console.log(car1.make)

// 第二版的代码
function objectFactory() {
  // 创建一个新的对象
  var obj = new Object()
  // 取出参数的第一项 构造函数
  Constructor = [].shift.call(arguments)
  console.log(arguments)
  // 将构造函数的原型变为新对象的原型 (每个函数都有prototype 指向原型对象)让实例对象具有构造函数原型对象的属性
  obj.__proto__ = Constructor.prototype
  // 调用函数改变this的为新的对象 让实例对象具有构造函数的属性
  var ret = Constructor.apply(obj, arguments)
  // 当实际构造函数有返回值为对象时 返回该对象 否则返回新对象
  return typeof ret === 'object' ? ret : obj
}
const car2 = objectFactory(Car, 'Eagle', 'Talon TSi', 1994)

# interface

// son interface Father

function interfaceFn(obj, pro) {
  const left = obj.__proto__
  const right = pro.prototype
  while (true) {
    if (left === right) {
      return true
    } else if (left === null) {
      return false
    } else {
      left = left.__proto__
    }
  }
}

# 防抖 debounce

频繁触发只触发一次

lodash.debounce (opens new window)

  • 首次执行
  • 取消
function debounce(fn, time) {
  let timer
  return function () {
    clearTimeout(timer)
    const args = arguments
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, time)
  }
}

# 节流 throttle

频繁的触发控制触发频率 (可以触发多次) 返回的是一个函数

lodash.throttle (opens new window)

  • 首次执行
  • 结束执行
const input = () => {}

const fn = throttle(input, 1000)

document.addEventListener('scroll', fn())

function throttle(fn, time) {
  let start = 0
  return function () {
    let end = new Date()
    if (end - start > time) {
      fn.apply(this, arguments)
      start = end
    }
  }
}