常见的手写函数

412 阅读10分钟

防抖函数

每次触发事件时都取消之前的延时调用方法

  /**
   * @description 在一定时间内,持续触发,会重新计时
   * @param (fn, wait) fn-函数, wait-延迟时间
   * @return 新函数
   */
  function debounce(fn, wait = 500) {
    let timer = null
    return function(...args) {
      if(timer) {
        clearTimeout(timer)
      }
      timer = setTimeout(() => {
        fn.apply(this, args)
        timer = null
      }, wait)
    }
  }
  • 使用
  // HTML部分
  <input type="input" id="username" />

   // JS部分
  const inputEL = document.querySelector('#username')
  const listenFn = debounce(function(event) {
    console.log(event.target.value)
  }, 200)
  inputEL.addEventListener('input', listenFn)

节流函数

无论如何触发,在一定时间内必定执行一次

  /**
   * @description 无论如何触发,在一定时间内必定执行一次
   * @param (fn, wait) fn-函数, wait-延迟时间
   * @return 新函数
   */
  function  throttle(fn, wait = 500) {
    let  timer = null
    return  function(...args) {
      // 上一次定时器任务还没执行,什么都不做
      if(timer) {
        return
      }
      timer = setTimeout(() => {
        fn.apply(this, args)
        timer = null
      }, wait)
    }
  }
  • 使用
  function sayHi(e) {
    console.log(e.target.innerWidth, e.target.innerHeight)
  }
  window.addEventListener('resize', throttle(sayHi, 2000))

斐波那契数列

题目来自

力扣-斐波那契数列

描述:

  • 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 结果大于1000000007时,需要做取模处理,如计算初始结果为:1000000008,请返回 1。

方式一:递归+闭包

/**
 * @description 使用递归+闭包
 * @param  {number}  n
 * @return  {number}
 */
const  fib = function (n) {
  // 缓存, 提前有n=0,1,2的值
  const  cache = new  Map([
    [0, 0],
    [1, 1],
    [2, 1],
  ])

  // 内部函数
  function  inner(n) {
    // 缓存中有,直接取出
    if (cache.has(n)) {
      return  cache.get(n)
    }
    // 公式 f(n) = f(n-2) + f(n-1)
    let  result = inner(n - 2) + inner(n - 1)
    // 结果超过1000000007,做取模处理
    if (result > 1000000007) {
      result = result % 1000000007
    }
    // 把当前n的结果存储
    cache.set(n, result)
    return  result
  }
  return  inner(n)
}

方式二:纯遍历(动态规划)

/**
 * @param {number} n
 * @return {number}
 */
const fib = function(n) {
  // 有n=0,1,2的值
  const  map = new  Map([
    [0, 0],
    [1, 1],
    [2, 1],
  ])
  if(n <= 2) {
    return map.get(n)
  }
  let i = 3
  let first = map.get(1) 
  let second = map.get(2) 
  while(i <= n) {
    let result = second + first
    if(result > 1000000007) {
        result = result % 1000000007
    }
    // 移动结果,为下次计算做准备
    first = second
    second = result
    i++
  }
  return second
}

手写call函数

  • 原理:函数作为对象的属性调用时,this指向对象

  • 思想思路:

  • 获取thisArg参数和剩余参数
  • 获取调用call的函数,把该函数设置到thisArg.func属性上
  • 执行thisArg.func函数,并接收函数执行后的结果result
  • 移除thisArg上的func函数
  • 返回执行结果
  Function.prototype.myCall = function(thisArg, ...args) {
    // 值为 null, undefined时, 自动指向全局对象window
    thisArg = thisArg == null ? window : thisArg
    // 获取调用myCall时的方法,并把函数设置到thisArg.func属性上
    thisArg.func = this
    // 执行thisArg.func函数,接收结果
    const result = thisArg.func(...args)
    // 移除thisArg上的func
    delete thisArg.func
    // 返回结果
    return result
  }
  • 测试myCall
  function sayHello(time) {
    console.log(`${this.name} say ${this.word} at ${time}`);
  }
  const bottle = {
    name: 'violet-mio', 
    word: 'hello'
  }
  // myCall把sayHello中this指向变为bottle对象,可以获取到bottle上的属性
  sayHello.myCall(bottle, new Date().toLocaleTimeString()) // violet-mio say hello at 下午4:46:28

手写bind函数

  • 获取thisArg剩余参数args
  • 获取调用myBind的函数,保存到func
  • 新建一个返回函数resultFn,在resultFn中做如下逻辑
    • 获取执行resultFn时的secondArgs, 把之前的参数args和secondArgs做合并
    • 判断是否使用new关键字执行resultFn函数
      • 如果是,resultFn函数内部this不变
      • 否则,resultFn函数内部this修改为thisArg
    • 通过apply执行func函数,并返回执行结果
  • 修改resultFn函数的原型,绑定到Object.create(func.prototype)
  Function.prototype.myBind = function(thisArg, ...args) {
    thisArg = thisArg == null ? window : thisArg
    // 保持调用myBind的函数
    const func = this

    const resultFn = function(...secondArgs) {
      // 合并参数
      const fullArgs = args.concat(secondArgs)
      // 如果是通过 new 关键字调用的,绑定 this 为实例对象
      const realThis = this instanceof resultFn ? this : thisArg
      return func.apply(realThis, fullArgs)
    }

    // 绑定原型
    resultFn.prototype = Object.create(func.prototype)
    return resultFn
  }
  • 测试myBind
  function Person(name, age) {
    console.log('函数调用')
    // 如果是new 执行的,实例对象的__proto__属性会指向构造函数的prototype
    console.log('是否使用new', this.__proto__ === Person.prototype)
    this.name = name
    this.age = age
  }
  Person.prototype.sayHi = function() {
    console.log('sayHi函数执行');
    console.log(this.name, this.age);
  }
  const obj = {
    name: '我是obj传进来的name',
    age: '我是obj传进来的age'
  }

  const Student = Person.bind(obj, '实际参数name')
  // 通过new创建Point执行bind函数后返回值的实例,并为构造函数传入参数
  const stu = new Student('实际参数age')
  stu.sayHi()
  普通函数调用
  const teaher = Person.bind(obj, '实际参数name')
  teaher()

手写curry函数柯理化

  • 获取原始函数fn的可接受参数的长度
  • 定义一个数组,缓存递归过程中的参数
  • 返回柯理化函数,进行参数的收集
  • 在柯里化函数中
    • 合并本次参数和以前的参数
    • 判断参数是否大于等于可接受参数的长度
      • 若成立,执行原函数
      • 否则,返回柯理化函数,继续参数的收集
const curry = function (fn) {
  // 原函数可接受参数的数量
  const limitLen = fn.length
  // 存储递归过程的所有参数,可以在递归出口计算值
  let params = []

  return function _innerCurry(...args) {
    // 合并参数
    params = params.concat(args)
    // 如果合并的参数个数大于等于fn接受参数的数量
    if(params.length >= limitLen) {
      // 就执行fn函数
      return fn.apply(null, params)
    } else {
      // 返回一个柯里化函数, 继续收集参数
      return _innerCurry
    }
  }
}
  • 不使用柯理化求sum(2)(3)(5)的计算结果
function add(num1, num2, num3) {
  return num1 + num2 + num3
}

const sum = function(a) {
  return function(b) {
    return function(c) {
      return add(a, b, c)
    }
  }
}
console.log(sum(2)(3)(5))
  • 使用函数柯理化
const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)(5))

实现new操作符

  • 创建空对象,链接原型到构造函数的显式原型上
  • 执行构造函数,改变this为空对象,保存执行结果
  • 构造函数执行结果是对象就返回该结果;否则返回obj对象
function myNew(fn, ...args) {
  // 创建空对象,链接原型到构造函数的显式原型上,obj对象就可以访问原型上的方法和属性
  const obj = Object.create(fn.prototype)
  // 使用apply执行构造函数,this指向为obj对象
  const result = fn.apply(obj, args)
  // 如果构造函数执行结果是对象就返回该结果;否则,返回obj对象
  return result instanceof Object ? result : obj
}

es5实现es6的继承(寄生式组合继承)

  • 创建一个空对象空对象的原型链接到父类显式原型
  • 空对象constructor指向子类
  • 子类的显式原型指向空对象
/**
 * @description 寄生组合继承
 * @params childClass - 子类,parentClass - 父类
 */
function inherit(Child, Parent) {
  // 创建一个`空对象`,`空对象`的原型链接到`父类`的`显式原型`上
  const obj = Object.create(Parent.prototype)
  // `空对象`的`constructor`指向`子类`
  obj.construct = Child
  // 子类的显式原型指向`副本对象`
  Child.prototype = obj
}

function Person(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}
Person.prototype.sayName = function () {
  console.log(this.name)
}
function Student(name, age) {
  // 调用父类函数构造函数
  Person.call(this, name)
  this.age = age
}

// 寄生式组合继承
inherit(Student, Person)
// 子类方法必须在继承之后设置
Student.prototype.sayAge = function () {
  console.log(this.age)
}

const stu = new Student('violet', 20)
console.log(stu)
stu.sayName()
stu.sayAge()

手写promise

实现构造函数

  • promise有三个状态pending,fulfilled,rejected
  • promise可以由pending改变为fulfilled或者pending改变为rejected,状态不可逆
  • 创建promise实例,会传递一个函数executorFn,在实例过程中,会立即调用该函数,把resolvereject传递给使用者
  • 如果业务正常,使用者会调用resolve()函数,把成功结果传递进去
    • resolve()函数中,修改状态为fulfilled并存储成功的值
  • 如果业务失败,使用者会调用reject()函数,把失败原因传递进去
    • reject()函数中,修改状态为rejected并存储失败原因
class MyPromise {
  // promise有三个状态 pending, fulfilled, rejected
  static PENDING = 'pending'
  static FULFILLED = 'fulfilled'
  static REJECTED = 'rejected'

  constructor(executorFn) {
    this.state = MyPromise.PENDING  // 初始化状态为pending
    this.value = undefined  // 成功后的值
    this.reason = undefined  // 失败后的原因

    // 调用此方法就是成功
    const resolve = (value) => {
      if(this.state === MyPromise.PENDING) {
        // 将状态修改为FULFILLED
        this.state = MyPromise.FULFILLED
        // 存储成功的值
        this.value = value
      }
    }

    // 调用此方法就是失败
    const reject = (reason) => {
      if(this.state === MyPromise.PENDING) {
        // 将状态修改为REJECTED
        this.state = MyPromise.REJECTED
        // 存储失败原因
        this.reason = reason
      }
    }
    try {
      // 立即执行executorFn函数,将resove和reject传递给使用者
      executorFn(resolve, reject)
    } catch (error) {
      // executorFn执行过程出现异常,调用reject函数
      reject(error)
    }
  }
}

// 使用
const p = new  MyPromise((resolve, reject) => {
  resolve('已解决')
  // reject('已拒绝')
})
console.log(p)

实现then方法

调用then时,promise实例对象出处于PENDING, FULFILLED, REJECTED中的一个状态

  • PENDING(等待状态)时,需要先收集回调函数,再状态变更为FULFILLED或者REJECTED时,执行回调函数
  • FULFILLED(已解决)时,可以直接执行成功的回调函数
  • FULFILLED(已解决)时,可以直接执行失败的回调函数
  • 为了支持链式调用,then方法需要返回一个新的promise实例对象
class MyPromise {
  constructor(executorFn) {
    // 省略内容。。。
    this.state = MyPromise.PENDING  // 初始化状态为pending
    this.onResolvedCallbacks = [] // 存储成功的回调函数
    this.onRejectedCallbacks = [] // 存储失败的回调函数

    // 调用此方法就是成功
    const resolve = (value) => {
      if(this.state === MyPromise.PENDING) {
        // 取出onResolvedCallbacks中函数依次执行
        this.onResolvedCallbacks.forEach(fn  =>  fn())
      }
    }
    // 调用此方法就是失败
    const reject = (reason) => {
      if(this.state === MyPromise.PENDING) {
        this.onRejectedCallbacks.forEach(fn  =>  fn())
      }
    }
    // 省略内容。。。
  }
  /**
   * @description then函数
   * @param 接收两个函数作为参数,分别是onFulfilled, onRejected
   */
  then(onFulfilled, onRejected) {
    // 避免传递进来的不是函数
    // 将非函数值放到函数中
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v
    onRejected = typeof onRejected === 'function' ? onRejected : (err) => { throw err }

    const p1 = new MyPromise((resolve, reject) => {
      // 如果是异步任务,状态还没有变化,需要存储then中的回调函数,等待状态变化后,在执行
      if(this.state === MyPromise.PENDING) {
        // 缓存成功回调
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const newValue = onFulfilled(this.value)
              resolve(newValue) // p1.value = newValue
            } catch (err) {
              reject(err)
            }
          })
        })
        // 缓存失败回调
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const newReason = onRejected(this.reason)
              resolve(newReason) // p1.reason = newReason
            } catch (err) {
              reject(err)
            }
          })
        })
      }
      
      // 已解决状态
      if(this.state === MyPromise.FULFILLED) {
        // 使用setTimeout模拟异步,保证在调用栈为空后才执行
        setTimeout(() => {
          try {
            // 取出存储的成功值
            const newValue = onFulfilled(this.value)
            resolve(newValue)
          } catch (err) {
            reject(err)
          }
        })
      }
      
      // 已拒绝状态
      if(this.state === MyPromise.REJECTED) {
        // 使用setTimeout模拟异步,保证在调用栈为空后才执行
        setTimeout(() => {
          try {
            // 取出存储的成功值
            const newReason = onRejected(this.reason)
            resolve(newReason)
          } catch (err) {
            reject(err)
          }
        })
      }
    })

    return p1
  }
}

优化then:提取公共部分

上面then内部代码,有部分代码时类似的,可以坐下提取

  // then方法,接收两个参数 onFulfilled成功回调函数和onRejected失败时的回调函数
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (val) => val
      onRejected = typeof onRejected === 'function' ? onRejected : (err) => { throw err }

      const asyncHander = (cb, currentResult) => {
        setTimeout(() => {
          try {
            const newResult = cb(currentResult)
            // 传递给下一个
            resolve(newResult)
          } catch (error) {
            reject(error)
          } 
        })
      }
  
      // 当前promise是FULFILLED已解决状态
      if(this.state === MyPromise.FULFILLED) {
        asyncHander(onFulfilled, this.value)
      }

      // 当前promise是REJECTED已解决状态
      if(this.state === MyPromise.REJECTED) {
        asyncHander(onRejected, this.reason)
      }

      // 状态还是PENDING等待状态,缓存回调函数
      if(this.state === MyPromise.PENDING) {
        this.onFulfilledCallbacks.push(() => {
          asyncHander(onFulfilled, this.value)
        })
        this.onRejectedCallbacks.push(() => {
          asyncHander(onRejected, this.reason)
        })
      }
    })
  }

实现catch方法

catch就是then的精简版,接收参数onRejected只处理onRejected

  catch(onRejected) {
    return this.then(null, onRejected)
  }

实现resolve静态方法

实例化一个promise实例对象,数据传递给promise实例对象.resolve方法

class MyPromise {
  static resolve(result) {
    return new MyPromise((resolve, reject) =>  {
      resolve(result)
    })
  }
}

实现reject静态方法

实例化一个promise实例对象,原因传递给promise实例对象.reject方法

class MyPromise {
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }
}

实现race静态方法

  • 接收一个数组,数组中存储有promise实例
  • 实例化一个新的promise实例对象
  • 在新的promise实例对象中,遍历promise实例数组
    • 取出一个promise实例,执行该promise实例的then方法,只要有一个完成状态变更(成功或者失败)就返回
  • 返回新的promise实例对象
class MyPromise {
  // 只要有一个完成(成功或者失败)就返回
  static race(promiseList) {
    return new MyPromise((resolve, reject) => {
      promiseList.forEach(p => {
        p.then(resolve, reject)
      })
    })
  }
}

实现all静态方法

  • 接收一个数组,数组中存储有promise实例

  • 实例化一个新的promise实例对象

  • 在新的promise实例对象中,遍历promise实例数组

    • 取出一个promise实例,执行该promise实例的then方法,全部成功就返回结果,有一个失败,就返回失败的
  • 返回新的promise实例对象

class MyPromise {
  // 执行数组所有promise,全部成功就返回结果,有一个失败,就返回失败的
  static all(arr) {
    return new MyPromise((resolve, reject) => {
      const result = []
      let index = 0
      let length = arr.length
      arr.forEach(p => {
        p.then(res => {
          result.push(res)
          // 最后一个了,说明全部执行成功,可以返回了
          if(index++ === length-1) {
            resolve(result)
          }
        }).catch(err => {
          reject(err)
        })
      })
    })
  }
}