learn icon indicating copy to clipboard operation
learn copied to clipboard

JavaScript深入之call和apply的模拟实现

Open yangtao2o opened this issue 4 years ago • 0 comments

call 和 apply 的模拟实现

call

call()在使用一个指定的 this 值和若干个指定的参数值的前提下,调用某个函数或方法。该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

使用 call 方法调用函数并且指定上下文的 'this'

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

使用 call 方法调用父构造函数

function Person(name, age) {
  this.name = name;
  this.age = age;
}
function Tao(name, age, job) {
  Person.call(this, name, age);
  this.job = job;
}
var tao = new Tao("yangtao", 27, "Teacher");

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

  • 将函数设为对象的属性
  • 执行该函数
  • 删除该函数
// 类似于:
var foo = {
  value: 1,
  bar: function() {
    return this.value;
  }
};
foo.bar(); // 1

// 第一步
foo.fn = bar;
// 第二步
foo.fn();
// 第三步
delete foo.fn;

第一版:绑定 this

Function.prototype.mycall = function(context) {
  context.fn = this;
  context.fn();
  delete context.fn;
};
// 测试一下
var foo = {
  value: 1
};

function bar() {
  console.log(this.value);
}

bar.mycall(foo); // 1

// 如下所示
var foo = {
  value: 1,
  bar: function() {
    console.log(this.value);
  }
};
foo.bar();

第二版:给定参数

Function.prototype.mycall = function(context, name, age) {
  context.fn = this;
  context.fn();
  var args = [];
  for (var i = 1, l = arguments.length; i < l; i++) {
    args.push("arguments[" + i + "]");
  }
  eval("context.fn(" + args + ")");
  delete context.fn;
};

第三版:传参为 null 和返回结果

Function.prototype.mycall = function(context) {
  var context = context || window;
  //获取调用call的函数,用this可以获取
  context.fn = this;
  var args = []; // ["arguments[1]", "arguments[2]"]
  for (var i = 1, l = arguments.length; i < l; i++) {
    args.push("arguments[" + i + "]");
  }
  // 把传给call的参数传递给了context.fn函数
  // context.fn(args.join(','));
  // context.fn(...args)
  var result = eval("context.fn(" + args + ")");
  delete context.fn;
  return result;
};

第四版:考虑 context,以及 context.fn 的可能性

Function.prototype.myCall = function(context) {
  // 这一步如果不强制是 object 类型,可以省略
  if (typeof context != "object") {
    throw new Error("Arguments error");
  }

  context = context || window;
  
  var args = [];
  var result;

  if ("fn" in context && context.hasOwnProperty("fn")) {
    var fn = context.fn;
    var fnFlag = true;
  }

  context.fn = this;

  for (var i = 1, l = arguments.length; i < l; i++) {
    args.push("arguments[" + i + "]");
  }

  result = eval("context.fn(" + args + ")");

  if (fnFlag) {
    context.fn = fn;
  } else {
    delete context.fn;
  }

  return result;
};

apply

apply()call(),只不过将多个参数值,以数组的形式传入而已。

用 apply 将数组添加到另一个数组:

var arr = ["a", "b"];
var arr2 = [1, 2];
arr.push.apply(arr, arr2);
console.log(arr); // ["a", "b", 1, 2]

使用 apply 和内置函数:

var nums = [1, 10, 3, 6, 2];
var max = Math.max.apply(null, nums); // 10
var min = Math.min.apply(null, nums); // 1

// ES6 写法:
var max = Math.max(...nums); // 10
var min = Math.min(...nums); // 1
Function.prototype.myapply = function(context, arr) {
  if (typeof context !== "object") {
    throw new Error("Arguments error");
  }
  context = context || window;
  context.fn = this;

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

原文链接:JavaScript 深入之 call 和 apply 的模拟实现

yangtao2o avatar Mar 08 '20 04:03 yangtao2o