JavaScript 基础知识整理之实现call和apply

336 阅读2分钟

call

call和apply是什么?

call()和apply方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。简单来说就是改变函数this的方法

举个栗子:

var foo = {
    value: 1
};
function test(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
test.call(foo, 'kevin', 18);
// kevin
// 18
// 1

对上call方法的定义:

1.call改变了this指向,指向了foo,传入指定的['kevin', 18]参数
2.test函数执行

实现call第一步

试想当调用 call 的时候,把test函数成为foo对象的一个属性,然后调用完再用delete删除不就可以了?

所以我们模拟的步骤可以分为:

1.将函数设置为对象的一个属性
  foo.fn = test
2.执行函数
  foo.fn()
3.删除函数
  delete foo.fn

根据这个思路,我们模拟写下callTest函数第一个版本

//第一版
Function.prototype.callTest = function (obj) {
  obj.fn = this
  obj.fn()
  delete obj.fn;
}

//测试
var foo = {
  value:1
}
function test() {
  console.log(this.value)
}
test.callTest(foo)  //1

实现call第二步(传递参数)

要实现参数传递,主要有两个问题:

1.传入的参数并不确定
2.把参数数组放到要执行的函数的参数里面去并执行

传入的参数不确定,我们可以用arguments来解决,参数数组是从arguments的第二项到最后一项。将参数数组放进执行函数参数中并执行可以用eval方法

Function.prototype.callTest = function (obj) {
  obj.fn = this;
  var args = []
  var len = arguments.length;
  for (var i = 1; i < len; i++){
    args.push('arguments[' + i + ']');
  }
  eval('obj.fn(' + args + ')')
  delete obj.fn
}

实现call最后一步

最后还有两个情况:

1.参数为null的时候指向window
2.函数可以有返回值
Function.prototype.callTest = function (context) {
  var obj = context || window;
  obj.fn = this
  var args = []
  var len = arguments.length
  for (var i = 1; i < len; i++){
    args.push('arguments[' + i +']');
  }
  var result = eval('obj.fn(' + args + ')');
  delete obj.fn
  return result
}

apply 实现和call差不多一样,只是将arguments传参变成数组

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}