美文网首页
call, apply - 2020-11-05

call, apply - 2020-11-05

作者: 勇敢的小拽马 | 来源:发表于2020-11-05 11:07 被阅读0次

0. call

var person = {
  fullName: function(txt) {
     console.log(txt + this.firstName + " " + this.lastName);
   }
 }
var person1 = {
   firstName:"John",
   lastName: "Doe"
} 
person.fullName.call(person1, "Hello, ");  // 输出"Hello, John Doe"

上面的call做了这几件事情,分为三步:

  1. person.fullName.call(person1)这个函数调用时候的this指向变了,如果只是使用person.fullName()的话,是隐式绑定,this应该指向person。但是使用person.fullName.call(person1)之后变成了显式绑定,this绑定传入的第一个参数,即person1。
  2. 从call函数传入的第二个参数开始,作为person.fullName的参数传入。
  3. 不更改person和person1的任何属性和方法。person这一边,如果你再次调用person.fullName(),不会打印任何和person1相关的信息;而person1这一边,并没有增加或者更改它自身的任何方法。

弄懂了这几件事,我们可以开始动手写自己的call函数

1.

我们在调用person.fullName.call(person1)的时候,this应该指向person1。解决方法就是在person1上添加一个方法,让这个方法等于person.fullName.。我们来写一下:

Function.prototype.myOwnCall = function(context) {
  context.fn = this;
  context.fn();
}

调用一下person.fullName.myOwnCall(person1, "Hello, ");

输出结果是:undefinedJohn Doe

这里,我们的context就是person1,this就是person.fullName这个函数的定义,我们在context上添加了一个fn方法,让它等于fullName这个函数,然后调用这个函数。

另外,如果看call的文档可以发现,如果第一个参数传入的是null的情况下,this会指向window,那么我们需要在函数刚开始的时候,让

context = context || window

等等,如果fullName有返回值怎么办?比如,我们把person的fullName改成

fullName: function(txt) {
return txt + this.firstName + " " + this.lastName;
}

这太容易了,直接把fn执行结果保存下来,直接返回就可以了。

很好,尽管参数还没有传进去,但完成了第一步的要求,代码如下:

Function.prototype.myOwnCall = function(context) {
  context = context || window
  context.fn = this;
  var result = context.fn();
  return result;
}

2.

从call函数传入的第二个参数开始,作为person.fullName的参数传入。从第二个参数开始,分别可以用arguments[1],arguments[2]……表示。有多少个arguments我们提前并不知道。怎么把它们传入fn()呢?

熟悉ES6的朋友们很容易想到,我直接传入…Array.from(arguments).slice(1)不就可以了么。可以是可以,不过如果面试官不让你使用这些新特性怎么办?

这里就要使用到eval函数。eval函数可以计算传入的字符串,然后执行其中的JS代码,使用eval之后,代码如下。

Function.prototype.myOwnCall = function(context) {
  context = context || window
  context.fn = this;

  var args = [];
  for (var i = 1; i < arguments.length; i++) {  
    args.push("arguments[" + i + "]");
  }
  var result = eval("context.fn(" + args + ")");
  return result;
}

3.

不更改person和person1的任何属性和方法。

对于person来说,我们的代码并没有更改任何属性或者方法。但对于person1,我们增加了一个fn方法,因此,要把这个方法在运行之后删掉:

delete context.fn;

但是,又出现了一个问题,如果person1本身就有一个方法叫做fn怎么办?那不是调用call之后,就会把它本身这个方法删掉了么?有的朋友会说,那起一个复杂点的函数名,保证其他人不会起这么少见的名称不就完了么?不行,这也不能保证万无一失。

怎么办?我们可以用Math.random()随机生成一个id,如果这个id已经存在于person1上,那么再生成一个id。好了,最终的程序是这样的:

Function.prototype.myOwnCall = function(context) {
  context = context || window;
  var uniqueID = "00" + Math.random();
  while (context.hasOwnProperty(uniqueID)) {
    uniqueID = "00" + Math.random();
  }
  context[uniqueID] = this;

  var args = [];
  for (var i = 1; i < arguments.length; i++) {  
    args.push("arguments[" + i + "]");
  }
  var result = eval("context[uniqueID](" + args + ")");
  delete context[uniqueID];
  return result;
}

apply

而apply则十分类似,过程不再赘述,只需要注意一下,第二个参数是否存在就可以:

Function.prototype.myOwnApply = function(context, arr) {
  context = context || window
  var uniqueID = "00" + Math.random();
  while (context.hasOwnProperty(uniqueID)) {
    uniqueID = "00" + Math.random();
  }
  context[uniqueID] = this;

  var args = [];
  var result = null;
 
  if (!arr) {
    result = context[uniqueID]();
  } else {
    for (var i = 0; i < arr.length; i++) { 
      args.push("arr[" + i + "]");
    }
    result = eval("context[uniqueID](" + args + ")");
  }
  delete context[uniqueID];
  return result;

}

相关文章

网友评论

      本文标题:call, apply - 2020-11-05

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