Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

手写实现那回事 #23

Open
Leo-lin214 opened this issue Feb 10, 2020 · 0 comments
Open

手写实现那回事 #23

Leo-lin214 opened this issue Feb 10, 2020 · 0 comments

Comments

@Leo-lin214
Copy link
Owner

好的文章可参考如下。
各种源码实现,你想要的这里都有

当下不管是在面试过程中还是在日常开发过程中,一些底层的实现变得越来越有效率性,那么当理解如何实现时,能否直接写出一份实现呢?

为此,我特意写了一些自己的理解总结,便于后期的回顾或分享。😄

Bind 函数实现

实现 Bind 函数的关键点在于两点。

  1. 箭头函数中的 this 指向函数定义时所在的作用域
  2. 使用 Bind 函数绑定返回的函数,在使用 new 时,this 的指向会指向新创建的对象,而不是原来已绑定的对象。
答案

  Function.prototype.myBind = function(obj) {
    // 先判断是否为函数
    if(typeof this !== 'function') {
      throw new Error('This is not function...')
    }
    const remainArgs = Array.prototype.slice.call(arguments, 1) // 获取剩余参数
    const TempFun = function() {} // 构造一个空函数
    const self = this // 缓存当前函数
    if(self.prototype) { // 保存当前函数的原型
      TempFun.prototype = self.prototype
    }
    const returnFun = function() {
      return self.apply(this instanceof TempFun ? this : obj, remainArgs)
    }
    returnFun.prototype = new TempFun()
    return returnFun
  }
  

Call 和 Apply 函数实现

实现 Call 函数和 Apply 函数,主要体现在两点。

  1. 将当前函数保存下来,并使用 Symbol 作为属性名保存在相应对象下面。
  2. 执行刚刚在相应对象下使用 Symbol 保存的函数,并把该结果进行返回。
答案

  Function.prototype.myCall = function(obj) {
    if(typeof this !== 'function') {
      throw new Error('This is not a function...')
    }
    const selfFun = this // 保存当前函数
    const remainArgs = [...arguments].slice(1) // 获取剩余参数
    const fn = Symbol('fn') // 避免对象函数名冲突
    obj[fn] = selfFun
    const result = obj[fn](...remainArgs) // 保存最终的对象调用函数结果
    delete obj[fn] // 删除相应的对象函数
    return result
  }
  

Reduce 函数实现

实现 Reduce 函数,主要体现在两点。

  1. 根据参数获取初始值。
  2. 在遍历数组的过程中,必须要注意[ empty ]情况。
答案

  // reduce方法实现
  Array.prototype.myReduce = function(callbackFun) {
    let returnSum = undefined // 叠加器最终变量
    let index = 0 // 数组遍历的索引
    const self = this // 获取当前数组
    if(arguments.length > 1) {
      returnSum = arguments[1]
    } else if(arguments.length === 1) {
      returnSum = self[index]
      index = 1
    }
    while(index < self.length) {
      let hasVal = self.hasOwnProperty(index)
      if(hasVal) {
        let currentVal = self[index]
        returnSum = callbackFun(returnSum, currentVal, index)
      }
      index++ 
    }
    return returnSum
  }
  

New 实现

new 内部机制主要经历以下四个步骤:

  1. 创建一个新的对象。
  2. 将构造函数的作用域赋给该对象。
  3. 执行构造函数中的代码。
  4. 返回该新的对象。

因此要实现一个 new 方法,那么必须抓住两点。

  1. 必须将新创建的对象的原型指针指向构造函数的原型对象。
  2. 执行完构造函数中的代码后,必须判断其返回值是否为对象,若是则 return,若不是则直接返回刚刚新创建的对象。
答案

  // new实现
  function myNew() {
    const obj = new Object() // 创建一个新的对象
    const argsFun = Array.prototype.shift.call(arguments) // 获取构造函数
    obj.__proto__ = argsFun.prototype // 将新对象的原型指针指向构造函数的原型对象
    const remainArgs = Array.prototype.slice.call(arguments, 0) // 获取剩余参数
    const result = argsFun.apply(obj, remainArgs) // 执行构造函数中的代码
    return typeof result === 'object' && result !== null ? result : obj // 返回新的对象
  }
  

需要注意的是,new 是属于关键字,不能被重写,因此只能使用函数来模拟实现 new 的功能。

async/await 实现

async/await 是 generator 语法糖,基于 promise 进行编写的。由于 async/await 都是属于关键字,不能被重写,只能通过使用函数来模拟实现。

要实现 async/await 函数,必须遵循下列几点要求。

  1. async 函数只能面向 generator 生成器函数,并且最终会返回一个函数,函数执行后会返回一个 Promise 实例。
  2. 执行 generator 生成器函数时,需使用递归形式进行自执行,直到最后的 done 为 true 为止才返回最终的结果。
  3. 需要进行异常处理。
答案

  // async/await实现
  function asyncFun(callbackFun) {
    return  function() {
      const self = this
      const args = arguments
      return new Promise((resolve, reject) => {
        const gen = callbackFun.apply(self, args)
        function _next(value) { // 用于遍历迭代器
          awaitFun(resolve, reject, gen, _next, _throw, 'next', value)
        }
        function _throw(err) { // 用于迭代器遍历时抛出异常
          awaitFun(resolve, reject, gen, _next, _throw, 'throw', err)
        }
        _next() // 迭代器自执行
      })
    }
  }
  function await(res, rej, gen, nextFun, throwFun, funKey, args) {
    const nextRes = gen[funKey](args)
    if (nextRes.done) {
      res(nextRes.value)
    } else {
      Promise.resolve(nextRes.value).then(nextFun, throwFun)
    }
  }
  

双向数据绑定的实现

在 JavaScript 为双向数据绑定的实现提供了两个 API,分别对应于 ES5 版本的Object.defineProperty和 ES6 版本Proxy

  1. Object.defineProperty版本

    答案
    
      // Object.defineProperty实现双向数据绑定
      const data = {
        text: ''
      }
      const inputEle = document.querySelector('input')
      const spanEle = document.querySelector('span')
      Object.defineProperty(data, 'text', {
        set(val) {
          spanEle.text = val
        }
      })
      inputEle.addEventListener('input', function(e) {
        data.text = this.value
      })
      
  2. Proxy版本

    答案
    
      // Proxy实现双向数据绑定
      const data = {
        text: ''
      }
      const inputEle = document.querySelector('input')
      const spanEle = document.querySelector('span')
      const handler = {
        set(target, key, value) {
          target[key] = value
          spanEle.text = value
          return value
        }
      }
      const proxyObj = new Proxy(data, handler)
      inputEle.addEventListener('input', function(e) {
        proxyObj.text = this.value
      })
      

Object.create方法实现

实现 Object.create 方法,主要是利用寄生式继承。

简单理解,就是先构建一个空构造函数,接着将参数对象赋给构造函数的原型对象,并将原型对象中 constructor 指回空构造函数,最后返回空构造函数实例

答案

  // Obejct.create方法实现
  Object.prototype.myCreate = function(obj) {
    function Noop() {}
    Noop.prototype = obj
    Noop.constructor = Noop
    return new Noop()
  }
  

instanceof 实现

instanceof 的机制就是判断右边的构造函数的原型对象是否在左边对象的__proto__的原型链上。

要实现 instanceof,那么需要注意的是,利用遍历一直寻找左边对象的__proto__,若等于右边构造函数的原型对象,则返回 true,否则直到遍历为 null 为止

答案

  // instanceof实现
  function myInstanceof(left, right) {
    let leftPro = left.__proto__
    let rightPro = right.prototype
    while(leftPro) {
      if(leftPro === rightPro) return true
      leftPro = leftPro.__proto__
    }
    return false
  }
  

Object.getOwnPropertyNames方法实现

Object.getOwnPropertyNames 用于获取一个对象的所有属性名,不包括原型对象上的。

那么,要实现 Object.getOwnPropertyNames 方法,可结合使用 in 操作符和 Object.hasOwnProperty 实现

答案

  // Object.getOwnPropertyNams实现
  Object.myGetOwnPropertyNames = function(obj) {
    if(typeof obj !== 'object') throw new Error('This is not a object...')
    const resultArr = []
    for(let allKey in obj) {
      if(Object.hasOwnProperty.call(obj, allKey)) {
        resultArr.push(allKey)
      }
    }
    return resultArr
  }
  

Promise 实现

Promise 拥有三种状态,分别是pending、fullfilled、rejected。

而 Promise 的实现原理就是,通过队列形式控制每个成功回调以及失败回调的处理,内部使用私有变量保存当前处理状态以及处理所得到的值。接下来一一分析每个方法的实现要点。

  1. 静态resolve方法。

    返回一个Promise实例,先判断当前状态是否为pending,若不是直接返回。

    然后判断参数值是否为Promise实例,若是则必须调用then方法等待其调用完毕才能进行下一步。

    最后需要对成功回调队列和失败回调队列中进行清空处理(即使用shift方法,一一执行其回调函数)。

    上述操作必须在一个setTimeout中进行。

  2. 静态reject方法。

    返回一个Promise实例,先判断当前状态是否为pending,若不是直接返回。

    无需判断为Promise实例,直接对失败回调队列进行清空处理(即使用shift方法,一一执行其回调函数)。

    上述操作必须在一个setTimeout中进行。

  3. then方法。

    返回一个Promise实例,先创建两个处理函数(分别为成功处理函数和失败处理函数)。

    成功处理函数中操作便是判断then方法的第一个参数是否为函数,若是则先执行其函数(其中内部值作为其函数的参数),接着再执行静态resolve方法清空成功回调队列操作。

    失败处理函数中操作便是判断then方法的第二个参数是否为函数,若是则先执行其函数(其中内部值作为其函数的参数),接着再执行静态reject方法清空失败回调队列操作。

    根据状态判断,当内部状态为pending时,则成功回调队列push进成功处理函数,失败回调队列push进失败处理函数,当内部状态为fullfilled时,则直接执行成功处理函数(其中内部值作为其函数的参数),当内部状态为rejected时,则直接执行失败回调处理函数(其中内部值作为其函数的参数)。

  4. catch方法。

    相当于直接执行this.then(undefined, rejectedFun)。

  5. finally方法。

    相当于直接执行this.then(val => MyPromise.resolve(callback()).then(() => val), val => MyPromise.reoslve(callback).then(() => { throw val }))

  6. 静态all方法。

    返回一个Promise实例,创建一个空数组,遍历参数Promise数组,执行每一个Promise的then方法,然后将处理好的值push进空数组中,当判断数组长度与参数Promise数组长度一致时即可使用reoslve将处理好的数组返回。

  7. 静态race方法。

    返回一个Promise实例,遍历参数Promise数组,执行每一个Promise的then方法,一旦有值返回即可直接resolve返回。

答案

  // Promise类实现
  class MyPromise {
    constructor(handleFun) {
      handleFun(this._resolve, this._reject)
      this._status = 'pending' // 当前的promise状态
      this._value = undefined  // 当前promise处理的值
      this._fullFilledQuene = [] // 当前promise接受处理队列
      this._rejectedQuene = [] // 当前promise拒绝处理队列
    }
    _resolve(val) {
      setTimeout(function() {
        if(this._status !== 'pending') return
        this._status = 'fullfilled'
        const runFullfilledQuene = (res) => {
          let callback
          while(callback = this._fullFilledQuene.shift()) {
            callback(res)
          }
        }
        const runRejectedQuene = (err) => {
          let callback
          while(callback = this._rejectedQuene.shift()) {
            callback(err)
          }
        }
        if(val instanceof MyPromise) {
          val.then(res => {
            this._value = res
            runFullfilledQuene(res)
          }, err => {
            runRejectedQuene(err)
          })
        } else {
          this._value = val
          runFullfilledQuene(val)
        }
      })
    }
    _reject(err) {
      setTimeout(function() {
        if(this._status !== 'pending') return
        this._status = 'rejected'
        this._value = err
        let callback
        while(callback = this._rejectedQuene.shift()) {
          callback(err)
        }
      })
    }
    then(fullFilledFun, rejectedFun) {
      return new MyPromise((resolve, reject) => {
        // 封装一个promise回调成功的处理
        const handleFullFilledFun = val => {
          if(typeof fullFilledFun !== 'function') {
            reject(val)
          } else {
            const value = fullFilledFun(val)
            if(value instanceof MyPromise) {
              value.then(res => {
                resolve(res)
              }, err => {
                reject(err)
              })
            }
          }
        }
        // 封装一个promise回调失败的处理
        const handleRejectedFun = err => {
          if(typeof rejectedFun !== 'function') {
            reject(err)
          } else {
            const value = rejectedFun(err)
            if(value instanceof MyPromise) {
              value.then(res => {
                resolve(res)
              }, err => {
                reject(err)
              })
            }
          }
        }
        switch(this._status) {
          case 'pending':
            this._fullFilledQuene.push(handleFullFilledFun)
            this._rejectedQuene.push(handleRejectedFun)
            break
          case 'fullfilled':
            handleFullFilledFun(this._value)
            break
          case 'rejected':
            handleRejectedFun(this._value)
            break
        }
      })
    }
    catch(errFun) {
      return this.then(undefined, errFun)
    }
    finally(callback) {
      return this.then(
        value => MyPromise.resolve(callback()).then(() => value),
        err => MyPromise.resolve(callback()).then(() => err)
      )
    }
    static resolve(val) {
      if(val instanceof MyPromise) return val
      return new MyPromise(resolve => resolve(val))
    }
    static reject(err) {
      return new MyPromise((resovle, reject) => reject(err))
    }
    static all(list) {
      return new MyPromise(resolve => {
        let arrPromise = []
        for(let [index, promiseItem] of list) {
          promiseItem.then(res => {
            arrPromise.push(res)
            if(arrPromise.length === list.length) resolve(arrPromise)
          })
        }
      })
    }
    static race(list) {
      return new MyPromise(resolve => {
        for(let [index, promiseItem] of list) {
          promiseItem.then(res => {
            resolve(res)
          })
        }
      })
    }
  }
  

防抖/节流实现

防抖的原理是,通过闭包缓存定时器,当缓存的定时器不为空时,则使用clearTimeout进行清除,然后再重新赋值为一个setTimeout,频繁操作中直到最后一步操作才会真正起效,前面的操作都会被清除掉

答案

  // 防抖(频繁操作最终只会执行一次)
  function debounce(fn, time) {
    let timeout = undefined
    return function() {
      let context = this
      let args = arguments
      if(!timeout) clearTimeout(timeout)
      timeout = setTimeout(() => {
        fn.apply(context, args)
      }, time)
    }
  }
  

节流的原理是,通过闭包缓存当前时间,当下一步重复操作的时间减去缓存时间大于参数时间时,那么就会直接执行函数,控制在规定的时间内执行函数

答案

  // 节流(频繁操作只会每隔一段时间操作一次)
  function throttle(fn, time) {
    let tempTime = Date.now()
    return function() {
      let currentTime = Date.now()
      if(currentTime - tempTime > time) {
        fn.apply(this, arguments)
        tempTime = currentTime
      }
    }
  }
  

函数柯里化实现

函数柯里化的原理,就是将多个参数的单一函数转化为单一参数的多个函数

在实现函数柯里化中,通过使用递归形式来将多个单一参数转换为多个参数为止。

如:sum(a, b, c, d) 相当于 sum(a)(b)(c)(d),也相当于 sum(a, b)(c)(d)

答案

  // 函数柯里化
  function curry(fn) {
    const judge = (...args) => 
      args.length >= fn.length
        ? fn(...args)
        : (...arg) => judge(...args, ...arg)
    return judge
  }
  
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant