手写call和apply

作者: 超人s | 来源:发表于2020-09-11 17:14 被阅读0次

    1. 什么是call和apply

    传送门:理解与使用js中的apply()和call()
    至于为什么要手写代码,不仅仅是为了面试,也是为了帮助我们更好的理解代码逻辑,深入其中原理最好的方法。

    2. 手写call

    写个call方法,跟写一个项目是一样的。都要先分析其需求的功能点,需求分析要到位,再去逐个击破。
    首先,我们需要知道call是函数原型上的一个方法,它的作用是绑定对象和参数,并执行函数。使用方法如下:

    function.call(thisObj, arg1, arg2, ...)
    

    开始实现:

    1. 我们就给我们手写的call方法叫myCall,接受的第一个参数 thisObj 是目标函数执行时的目标对象的值,从第二个可选参数arg1开始的其他参数,将作为目标函数执行时的实参。
    2. 需要分析call函数都有哪些‘需求’
      • 需要判断是否严格模式(this作用域的指向问题,非严格模式需要对thisObj特殊处理)
      • 如何判断严格模式
      • 如果thisObj不是对象类型怎么处理,因为我们是劫持绑定对象
        代码如下:
    // 首先apply是原型链Function.prototype上的一个方法
    Function.prototype.myCall = function() {
      // 通过arugments拿到所有参数
      // 第一个参数是绑定的this对象
      var thisObj = arguments[0];
      // 判断是否严格模式
      var isStrictMode = (function(){return this === undefined}())
      if (!isStrictMode) {
        // 如果在非严格模式下,thisObj的值是null或undefined,需要将thisObj置为全局对象
        if (thisObj === null || thisObj === undefined) {
          // 获取全局对象时兼顾浏览器环境和Node环境
          thisObj = (function(){return this}())
        } else {
          // 非对象类型,需要转换类型
          var tthisObjType = typeof thisObj
          if (thisObjType === 'number') {
           thisObj = new Number(thisObj)
          } else if (thisObjType === 'string') {
            thisObj = new String(thisObj)
          } else if (thisObjType === 'boolean') {
            thisObj = new Boolean(thisObj)
          }
        }
      }
      // 从索引1开始的剩余参数
      var invokeParams = [...arguments].slice(1);
      // 接下来要调用目标函数,那么如何获取到目标函数呢?
      // 实际上this就是目标函数,因为myCall是作为一个方法被调用的,this当然指向调用对象,而这个对象就是目标函数
      // 这里做这么一个赋值过程,是为了让语义更清晰一点
      var invokeFunc = this;
      // 此时如果thisObj对象仍然是null或undefined,那么说明是在严格模式下,并且没有指定第一个参数或者第一个参数的值本身就是null或undefined,此时将目标函数当成普通函数执行并返回其结果即可
      if (thisObj === null || thisObj === undefined) {
        return invokeFunc(...invokeParams)
      }
      // 否则,让目标函数成为thisObj对象的成员方法,然后调用它
      // 直观上来看,可以直接把目标函数赋值给对象属性,为了key值唯一,防止覆盖掉thisObj对象的原有属性,可以创建一个唯一的属性名,使用Symbol处理
      var symbolPropName = Symbol(thisObj)
      thisObj[symbolPropName] = invokeFunc
      // 返回目标函数执行的结果
      return thisObj[symbolPropName](...invokeParams)
    }
    
    

    上代码测试一下:

    Math.max.myCall(null, 1,2,3,4,5)
    // 5
    
    function mycallTets(a, b) {
      var args = [].slice.myCall(arguments)
      console.log(arguments, args)
    }
    mycallTets(1, 2)
    
    var obj = {
      name: 'jack'
    };
    var name = 'ross';
    function getName() {
      return this.name;
    }
    getName();
    getName.myCall(obj);
    
    

    可以看到,效果是有的。但是毕竟是手写的,跟源码还是有区别的,考虑情况可能会有不到位的地方,会有bug场景。

    3. 手写apply

    当我们写了call之后,apply就方便许多了,很多逻辑基本一致。我们只需要注意两个方法的差异点:第二个参数为数组,下面就不写注释了,直接上代码:

    Function.prototype.myApply = function(thisObj, params) {
      var isStrict = (function(){return this === undefined}())
      if (!isStrict) {
        var thisObjType = typeof thisObj
        if (thisObjType === 'number') {
         thisObj = new Number(thisObj)
        } else if (thisObjType === 'string') {
          thisObj = new String(thisObj)
        } else if (thisObjType === 'boolean') {
          thisObj = new Boolean(thisObj)
        }
      }
      var invokeFunc = this;
      // 处理第二个参数
      var invokeParams = Array.isArray(params) ? params : [];
      if (thisObj === null || thisObj === undefined) {
        return invokeFunc(...invokeParams)
      }
      var symbolPropName = Symbol(thisObj)
      thisObj[symbolPropName] = invokeFunc
      return thisObj[symbolPropName](...invokeParams)
    }
    

    简单测试一下:

    Math.max.myApply(null, [1, 2, 4, 8])
    // 8
    

    OK,大功告成!

    相关文章

      网友评论

        本文标题:手写call和apply

        本文链接:https://www.haomeiwen.com/subject/gvkmektx.html