手写常用API

149 阅读4分钟

前言:中级前端必需要会的手写常用API,平时开发的时候我们要思考一下用到的api 是怎么实现的,原理是什么,这样再遇到兼容性问题的情况,就可以自己去实现一个polyfill 去做兼容。而这也是一些面试大厂经常会问到的一些问题。就算用不到,但也得知道😂 , 欢迎留言,持续更新中....

1. new 的内部机制,自己实现一个new。

/*
 思路:new 做了什么?
  1\. 创建一个对象,使得创建的对象的__proto__ === Fn.prototype
  2\. 调用Fn 的构造函数。传入的this 是创建的对象。
  3\. 如果Fn 的构造函数返回了一个对象,则返回这个对象,如果是普通类型的值,返回新建的对象。
*/

function person(options) {
  this.name = options.name
  // return this.name 或者 {} 可以自己试试啥效果😂
}

function myNew(fn, options) {
  var obj = {}
  obj.__proto__ = fn.prototype

  var res = fn.call(obj, options)

  if (typeof res === 'object') {
    return res
  }

  return obj
}

var p = myNew(person, { name: '小强' }) // person {name: "小强"}

var p2 = new person({ name: '小强' }) // person {name: "小强"}

2. instanceof 是用来干什么的,实现一个instanceof。

/*
 思路:x instanceof y ,查询x 的原型链上是否有 y.prototype, 也就是是否有 
   x.__proto__.__proto__.... === y.prototype 这样的等式成立, 如果有就返回true,否则返回false
   也就是 x 是否 y 的子类。
   1\. 循环判断 x.__proto__.__proto__  === y.prototype
   2\. 如果 x.__proto__.__proto__ === null 都没发现 y.prototype 就返回false
*/

function myInstanceOf(x, y) {
   let _proto = x.__proto__
   while(_proto !== null) {
      if (x.__proto__ === y.prototype) {
         return true
      }

      _proto = _proto.__proto__
   }

   return false
}

myInstanceOf([], Array) // true
myInstanceOf({}, Array) // false

3. 实现一个promise

/*
  思路:
    1\. promise 是一个微任务对象,接收一个函数参数fn, fn 需要一个resolve,一个reject 函数。
    2\. then 和 catch 的作用其实是注册fulfilled 和 rejected 状态时候要执行的事件。
    3\. then 接收0个或者一个或者两个参数,第一个参数是 resolve 成功之后执行的函数,如果有返回值,
       可以接着.then ,所以需要在.then 函数里返回this, 第二个参数是 reject 失败之后执行的函数
       也需要返回this
    4\. promise 类有三个属性,一个是status 状态,pendding ,fulfilled , rejected ,状态的改变
       是不可逆的,只能是pendding => fulfilled , pendding => rejected  一个是 reason,
       保存失败的原因。 一个是then 的返回值 value。
    5\. then 会执行所有注册的函数,某一个有返回值,后面的函数的参数都会改变,reject 注册的函数
       只会执行第一个,后面再注册的不会执行(实测😄)。
*/

class Promise {
   constructor(exector) {
     this.status = 'pendding'
     this.reason = ''
     this.value = ''

     this.fulfilledCallBackList = []
     this.rejectedCallBack = () => {}

     let resolve = (value) => {
       if (this.status === 'pendding') {
          this.status = 'fulfilled'
          this.value = value
          console.log(this.fulfilledCallBackList[0])

          this.fulfilledCallBackList.forEach((fn) => {
             let res = fn(this.value)
             console.log(this.value)
             if (res) {
               this.value = res 
             } else {
               return
             }
          })
       }
     }

     let reject = (reason) => {
        if (this.status === 'pendding') {
          this.status = 'rejected'
          this.reason = reason

          this.rejectedCallBack(this.reason)
        }
     }

     exector(resolve, reject)
   }

   then(resolveFn, rejectFn) {
     if (resolveFn) {
        this.fulfilledCallBackList.push(resolveFn)
     }

     if (rejectFn) {
       !this.callBackCallBack &&
          (this.callBackCallBack = rejectFn)
     }

     return this
   }

   catch(rejectFn) {

     if (rejectFn) {
       !this.callBackCallBack &&
          (this.callBackCallBack = rejectFn)
     }

     return this
   }
}

4. 函数柯里化,实现一个函数使得 add(1, 2) = 3 && add(1)(2) = 3 && add(1)(2)(3) = 6

/*
  思路:
   1\. 要做的能在函数执行后还能继续执行,需要返回一个函数,在最后一次的时候通过toString来
     展示函数的toString 形式。
   2\. 因为参数不确定,所以每次执行都要把新的参数记录下来,所以需要用到闭包。
*/

function add() {
  let args = []

  args.push(...arguments)

  let fn = () => {
    args.push(...arguments)

    return fn
  }

  fn.toString = () => {
    return args.reduce((t, i) => t + i, 0)
  }

  return fn
}

5.eventBus 的实现

/*
  思路:
   1\. eventBus 有三个核心函数,分别是 on,emit,off
*/

function eventBus () {
  this.eventDict = {}

  this.on = function (eventName, fn) {
    this.eventDict[eventName] = this.eventDict[eventName] || []
    this.evnetDict[eventName].push[fn]
  }

  this.emit = function (eventName) {
    this.eventDict[eventName].forEach((fn) => {
      fn()
    })
  }

  this.off = function (eventName, fn) {
    let i = this.eventDict.indexOf(fn)
    if (i > 0) {
      this.eventDict.splice(i, 1)
    }
  } 
}

6. 防抖

/*
 思路:防抖是一段时间内多次调用某个函数,最后一次调用生效,其他的不生效。
  1\. 因为要记录当前是否有还没执行的fn,需要用到闭包
*/

function debounce(fn, interval) {
  let timer = null
  return function () {
    if (timer) clearTimeout(timer)

    timer = setTimeout(() => {
      fn()
    }, interval)
  } 
}

let de = debounce(() => { console.log('test') }, 1000)

de()
de()
de()
de()
de()
de() // test

7. 节流

/*
  思路:节流是一段时间内函数只执行一次或者多次。
  1\. 因为也要记录当前是否有还没执行的fn,也需要用到闭包
*/

function throttle(fn, timer) {
  let flag = false

  return function () {
    if (!flag) fn()
    flag = true

    setTimeout(() => {
      flag = false
    }, timer)    
  }
}

let th = throttle(() => { console.log('test') }, 1000)

th() // test
th()
th()
th()

setTimeout(() => {
 th() // test
}, 1000)

8.数组去重

/*
  思路:有好多种方法。
  1\. 利用reduce 方法,遍历一遍数组,找出不重复的组合起来。
  2\. 利用 ES6 Set 类型 和 数组的 Array.from 方法去重
  3\. 其他的都差不多思路😂
*/

function uniqueArray(arr) {
  return arr.reduce((t, i) =>
     [].concat(t.indexOf(i) !== -1 ? t : [...t, i])
  , [])
}

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

9. 数组扁平化

/*
  思路:
   1\. 数组可能有好几层,想到的是递归,不知道还有没有其他的好方法。
*/

function flatten(arr) {
  return arr.reduce((t, i) => 
     [].concat(t, Array.isArray(i) && flatten(i) || i)
  , [])
}

flatten([1,2,3,4,[5,6,7,[8]]])
// [1, 2, 3, 4, 5, 6, 7, 8]

10. 实现数组方法 splice

/*
 思路:splice 是数组原型链上的方法,所以我们要放到Array.prototype 上。
  1\. splice 接受参数。分别是 [start,[deleteCount, [item1, [item2, [...itemn]]]]] 
  2\. start 是开始删除的位置,deleteCount 代表删除几个元素,如果不传,就代表删除start 开始
    的所有元素,item1... itemn代表增加的元素
*/

Array.prototype.splice = function (
  start = 0,
  deleteCount = this.length - start,
  ...args
) {

  console.log('[this start]', this)
  let _arr = this
  let result = [].concat(
    _arr.slice(0, start),
    args,
    _arr.slice(start + deleteCount)
  )

  let res = _arr.slice(start, start + deleteCount)

  result.map((item, i) => {
    this[i] = item
  })

  this.length = result.length

  console.log('[this end]', this)
  return res
}

未完待续...