yliu

时来天地皆同力,运去英雄不自由

模拟实现call和apply


call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数

上面是 call 的定义,apply 与 call 类似只是第二个参数是一个数组而 call 接收的则是一个参数列表。

看一个例子

var obj = {
  name: "obj"
};
function foo() {
  return this.name;
}
foo.call(obj); //obj

上面通过 call 改变了 this 的指向,下面就是模拟实现

实现

call 和 apply 是 es5 的方法,既然是模拟实现那肯定不能用 es5 方法了,我们先分析一下怎么来指定 this,this 跟动态作用域类似是在执行时确定,那我们在指定 this 的属性上添加一个方法并且执行,那么这个方法的 this 就是指定 this

var obj = {
  name: "obj",
  foo: function foo() {
    return this.name;
  }
};

obj.foo();

上面解决了 this,下面来看下怎么实现参数的传递,call 方法可以传递任意参数列表,我们可以通过arguments来获取,它是一个类数组。

function getName() {
  console.log(arguments); //Arguments(5) [1, 2, 3, 4, 5]
}
function foo() {
  var args = [];
  for (var i = 0; i < arguments.length; i++) {
    args.push("arguments[" + i + "]");
  }
  eval("getName(" + args + ")");
}
foo(1, 2, 3, 4, 5);

OK,这两块已经搞定了,下面就是实现

call

Function.prototype.calls = function(con) {
  con.fn = this;
  // 获取参数
  var args = [];
  for (var i = 1; i < arguments.length; i++) {
    args.push("arguments[" + i + "]");
  }
  var result = eval("con.fn(" + args + ")");
  delete con.fn;
  return result;
};

上面删除属性是为了避免污染,这里的fn可以是任意属性名只要保证不重复就行了,不过仔细观察上面函数还是有问题

  1. call 的 this 可以是 null 或者 undefined
  2. 可以是字符串数字之类的,会转化为包装对象;
Function.prototype.calls = function(con) {
  if (con == null) {
    con = typeof window === "object" ? window : global;
  }
  con = Object(con);
  con.fn = this;
  // 获取参数
  var args = [];
  for (var i = 1; i < arguments.length; i++) {
    args.push("arguments[" + i + "]");
  }
  var result = eval("con.fn(" + args + ")");
  delete con.fn;
  return result;
};

测试一下

function foo() {
  return this.length;
}
console.log(foo.calls("obj")); // 3

撒花,这样就实现了 call

apply

与 call 十分类似,这里就直接贴代码了

Function.prototype.applys = function(con, arr) {
  if (con == null) {
    con = typeof window === "object" ? window : global;
  }
  con = Object(con);
  con.fn = this;
  var result;
  if (typeof arr === "object" && arr.length) {
    var args = [];
    for (var i = 0; i < arr.length; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("con.fn(" + args + ")");
  } else {
    result = eval("con.fn()");
  }
  delete con.fn;
  return result;
};

参考

  1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
  2. https://github.com/mqyqingfeng/Blog/issues/11