手写call函数

83 阅读2分钟

先从一道小小的面试题说起

call函数做了什么?

于是乎翻开我们自己的面试宝典

  • 改变了函数内部的this指向
  • 执行该函数

如何做到以上两点

示例1 :

let country = {
  leader : "大魏吴王",
}

function slogan(){
  console.log(this.leader)
}

slogan.call(country)

现在我想改变slogan函数内部的this指向并且执行该函数该怎么做?

示例2 :

let country = {
  leader : "大魏吴王",
  slogan : function(){
      console.log(this.leader)
  }
}
country.slogan()

没毛病啊!

思考与写入过程

如此看我们的问题就变成了如何将示例1的代码变成示例2这种形式

这个问题跟把大象放冰箱需要多少步是一样的

  1. 赋值函数到对象中
  2. 执行对象中的函数

按照我们思考的过程一步步来....

  1. 首先重写call方法要写在哪里?

这个问题我们要明确谁掉用了call的方法,含无疑问是函数,用示例1中的例子说就是slogan。所以我们要把它写在函数的原型上

示例3:

  Function.prototype.call2 = function(context) {
    //  context就是传入的对象  
}
  1. 怎么取到函数,并把它复制到对象中?

来看示例3中call2中的this指向:

我们知道this是函数运行时的环境:

结合示例1和示例3我们可以大致的画出他的内存图:

call.png

从图中我们看到当前的环境依旧是slogan所以说示例3中call2中的this依旧是函数本身

由此我们要完成第二部赋值函数到对象中

  Function.prototype.call2 = function(context) {
    //  context就是传入的对象  
    context.fn = this;
    context.fn();
    delete context.fn;// 还没完执行过后我们要删除fn
}

关于为什么要删除fn我们看一下这个例子

var obj = {
    value: 1
};
function foo(o) {
    o.value = 2;
    console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2

如果不删除它就会影响原有的对象,这与引用类型在内存中的存储方式有关在这里就不细说了

  1. 最后我们需要考虑this的特性
  • 能接受很多参数
  • 传入null时this指向window
  • 函数可以有有返回值 最后就是这样的
 Function.prototype.call2 = function (context,...args) {.
    var context = context || window;
    context.fn = this;
    let result = context.fn(...args)
    delete context.fn
    return result;
}