当前位置 博文首页 > 哇喔WEB:手写call、apply、bind

    哇喔WEB:手写call、apply、bind

    作者:哇喔WEB 时间:2021-01-16 22:25

    ??关于this的指向性和执行上下文等问题本人也写过相关文章对其进行详细的解析,但是这些文章中好像遗忘了什么?对,那就是如何改变this指向的作用域,所以也就引出今天的主角——callapplybind;这“三兄弟”是Function原型上的三个改变调用函数作用域/上下文的函数,具体如何使用可以直接查看MDN,今天只研究如何实现这三种方法。

    一、call的实现

    ??call() 方法:让call()中的对象调用当前对象所拥有的function。例如:test.call(obj,arg1,arg2,···) 等价于 obj.test(arg1,arg2,···);在手写实现call()方法前我们先进行分析,test调用call方法可以看作将test方法作为obj的一个属性(方法)调用,等obj.test()执行完毕后,再从obj属性上删除test方法:

    • 1、将函数设置为对象的属性;
    • 2、处理传入的参数;
    • 3、执行对象上设置的函数;
    • 4、删除对象上第一步设置的函数;

    myCall:

    function test(a, b) {
      console.log(a);
      console.log(b);
      console.log(this.c);
    }
    
    let obj = {
      c: "hello",
    };
    
    //myCall
    Function.prototype.myCall = function () {
      //声明传入上下文为传入的第一个参数,如果没有传参默认为global(node环境),如果是浏览器环境则为 window;
      let context = arguments[0] || global; 
      //将调用myCall方法函数(this)设置为 声明的传入上下文中的fn函数;
      context.fn = this;
      //对函数参数进行处理
      var args = [];
      for (let index = 0; index < arguments.length; index++) {
        index > 0 && args.push(arguments[index]);
      }
      //执行fn,也就是调用myCall方法的函数
      context.fn(...args);
    	//执行完毕后删除传入上下文的fn,不改变原来的对象
      delete context.fn;
    };
    
    test.myCall(obj, "a", 123);
    console.log(obj)
    

    打印的结果:

    a
    123
    hello
    { c: 'hello' }
    

    从结果可以看出:testthis.c输出为hello,说明thisobj;最后输出的obj也没有改变。

    二、apply的实现

    ??apply() 方法作用和call()完全一样,只是apply的参数第一个为需要指向的对象,第二个参数以数组形式传入。例如:test.apply(obj,[arg1,arg2,···]) 等价于 obj.test(arg1,arg2,···)

    myApply:

    //myApply
    Function.prototype.myApply = function(){
      let context = arguments[0] || global;
      context.fn = this;
      var args = arguments.length > 1 ? arguments[1] : [];
      context.fn(...args);
      delete context.fn;
    }
    
    test.myApply(obj, ["world", 123]);
    console.log(obj)
    

    打印的结果:

    world
    123
    hello
    { c: 'hello' }
    

    三、bind的实现

    ??bind方法:创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。例如:let fn = test.bind(obj,arg1,arg2,···); fn() 等价于 let fn = obj.test; fn(arg1,arg2,···);实现思路为:

    • 1、将函数设置为对象的属性;
    • 2、处理传入的参数;
    • 3、返回函数的定义/引用;
    • 4、外层执行接收的函数;
    • 5、删除对象上第一步设置的函数;

    myBind:

    Function.prototype.myBind = function(){
      //1.1、声明传入上下文为传入的第一个参数,如果没有传参默认为global(node环境),如果是浏览器环境则为 window;
      let context = arguments[0] || global;
      //1.2、将调用myBind方法函数(this)设置为 声明的传入上下文中的fn函数;
      context.fn = this;
      //2.1、对调用myBind的函数参数进行处理
      var args = [];
      for (let index = 0; index < arguments.length; index++) {
        index > 0 && args.push(arguments[index]);
      }
    	//3、声明和定义函数变量F,用于返回给外层
      let F = function  (){
        //2.2、对再次传入的参数进行处理,追加到
        for (let index = 0; index < arguments.length; index++) {
          args.push(arguments[index]);
        }
        //4.2、执行实际的调用myBind方法函数
        context.fn(...args);
        //5、执行完毕后删除传入上下文的fn,不改变原来的对象
        delete context.fn;
      }
      return F;
    }
    
    var f = test.myBind(obj, "a")
    //4.1、执行返回的函数
    f(9527);
    

    打印的结果:

    a
    9527
    hello
    { c: 'hello' }
    

    四、总结

    ??关于实现js中一些常见的方法属于面试中的常问问题,可能刚开始接触的时候会一筹莫展。知道和理解其中的原理能够在日常开发中更如鱼得水,面对面试也不成问题。另外,学会以目的(实现的功能)为导向一层一层反推,总结出实现的思路就能按照步骤直接实现或者曲线实现。