JavaScript | 手写apply、bind(面试篇)

146 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情

一、前言

apply和call的手写差不多,因为他们的基本概念差不多,都是改变函数执行时的上下文,或者改变函数调用时的this指向,只是在于他们的传参不同。而bind又与call的基本功能类似,传参方式也一致,但是它不会像call和apply一样直接调用,它只是改变this指向并返回改变this指向以后的函数不会自己调用。

展开说说

  1. call给函数传参的时候,是作为call的第二个参数以后所有的参数 逗号隔开:call(thisArg,arg1,arg2.....)
  2. apply给函数传参的时候,函数的参数全部放在数组中,作为apply的第二个参数:apply(thisArg,[arg1,arg2...]) 3.bind和call一样:bind(thisArg,arg1,arg2.....)只改变this指向、返回函数、不默认调用

二、手写apply

因为call和apply只是参数的不同,详细拆分讲解可以参考上一篇手写call:手写call(面试篇)

1.apply的基本使用

   var obj = {name:'lucky'}
   function fn(a,b){
     console.log(this,a+b) //obj,3
    }
   fn.apply(obj,[1,2])

2. 手写apply

   var obj = {name:'lucky'}
   Function.prototype.myApply = function(context){
      var type = typeof context;
      //判断改变后的上下文对象是null和undefined的时候 this应该指向window
      if (context === null || context === undefined) {
            context = window;
       }
       //如果改变后的上下文对象是基本包装类型,则this指向其包装对象
        switch (type) {
            case "number":
                context = new Number(context);
                break;
            case "boolean":
                context = new Boolean(context);
                break;
            case "string":
                context = new String(context);
                break;
        }
         //获取第二个以后的参数,就是fn的参数
            var arg = arguments[1];
         //如果arg不存在 则返回空数组
           arg = arg ? arg : []; 
   /***
    梳理:
        1.fn1.myApply调用,所以这里的this指向的就是fn1,
        给context扩展一个方法,这个方法就是fn1:context[key]=this
        2.context就是改变之后的上下文对象
        3.然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context
   **/
        //给context扩展的方法名要是一个独一无二的值,防止覆盖原有方法
        var key = Date.now().toString(36);
        //context扩展的方法
        context[key] = this;
        //然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context
        var result = eval("context[key](" + arg.toString() + ")");
        
        //此时改变之后的上下文对象context就会多一个方,使用完成之后要删除掉这个方法
        delete context[key];

        return result;

   }
   
    function fn1(a,b){
     console.log(this,a+b) 
    }
   
    fn1.myApply(obj, [1, 2]);
    fn1.myApply(null, [1, 2]);
    fn1.myApply(undefined, [1, 2]);
    fn1.myApply(1, [1, 2]);
    fn1.myApply("str", [1, 2]);
    fn1.myApply(true, [1, 2]);

验证结果

apply.jpg

三、手写bind

1.bind基本使用

bind与call、apply不同,不是直接调用,而是改变this指向之后并返回改变后的函数(举例绑定事件)

    document.onclick = function () {
            //bind绑定与var that = this 同理
            //var that = this
            setTimeout((function () {
                //console.log(that)//document
                console.log(this) //document
            }).bind(this), 100)
        }

2.手写bind

思路:改变this指向,并且把函数返回出去

  1. 简易写法(先写出核心思路)
/**
 思路梳理:
 1.返回函数:return function{}
 2.改变this:在返回函数里拿到当前函数调用call(apply)改变this指向
 3.result()调用就是调用内部返回函数
**/
    //myBind返回一个被改变this指向的函数
    Function.prototype.myBind = function(){
    //返回一个函数
     return function(){
         fn.call(obj,1,2)
      }
  }
  var obj = {name:'lucky'}
  function fn(a,b){
    console.log(this,a+b) //obj,3
  }
  var result = fn.myBind(obj,1,2)
  result()
  1. 完整写法
/**
 思路梳理:
 1.返回函数:return function{}
 2.改变this:利用apply(call)改变this指向
 3.result()调用就是调用内部返回函数
**/
    //myBind返回一个被改变this指向的函数
    Function.prototype.myBind = function(context){
    //用_this保存当前的this 也就是调用myBind的函数(fn)
     var _this = this;
    //拿参数
     var arg = Array.from(arguments).slice(1);
     arg = arg ? arg : [];
    //返回一个函数
    return function () {
    //利用apply改变this
      return _this.apply(context, arg);
    }
  }
  var obj = {name:'lucky'}
  function fn(a,b){
    console.log(this,a+b) 
  }
  var result = fn.myBind(obj,1,2) //obj,3
  //因为是内部调用的apply所以也不用考虑绑定对象的传值,会自动包装
  var result = fn.myBind(null,1,2) //window,3
  result()

好了,以上就是本篇文章的分享,感谢阅读!