fezaoduke / fe-practice-hard

晚练课
69 stars 6 forks source link

第 52 期(W3C 标准-ECMAScript-上下文环境):模拟call方法 #55

Open wingmeng opened 5 years ago

wingmeng commented 5 years ago

ECMAScript 中的 call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。 下面我们编写一个函数 myCall 来模拟 call 方法,以此加深对 call 的理解。

使用 ES6 实现,非常简单:

// 将 myCall 定义在 Function 原型链上,这样所有的函数都可以共享这个方法
// context 为当前上下文环境
Function.prototype.myCall = function(context, ...args) {
  // 当传入 null 或 undefined 时,指向 window
  context = context || window;

  // 【重点】
  // 在当前上下文环境下创建一个临时函数,赋值为 this(当前调用 myCall 的函数)
  // 用以将当前调用 myCall 的函数“借用”到当前上下文环境中
  context._fn = this;

  // 将形参传入临时函数,获得执行结果
  let result = context._fn(...args);

  // 过河拆桥,删除临时函数
  delete context._fn;
  return result;
}

call 方法是 ES1.3 就有了的,用 ES6 模拟未免有点……所以:

使用 ES3 实现:

Function.prototype.myCall = function(context) {
  // 创建一个数组来保存参数,即考虑 bar.call(obj, a, b, c) 这样存在多参数的场景  
  var args = [];

  context = context || window;
  context._fn = this;

  // 接着要处理传参的问题,例如有多个参数的情况
  // arguments 是函数中的隐式参数,保存着传递递给函数的实参,是一个伪数组(像数组却没有数组方法)
  // 注意这里的 i 是从 1 开始的,因为 arguments[0] 是 context,排除在外
  for (var i = 1; i < arguments.length; i++) {
    // 以形参的形式将参数保存到 args 数组中,供下一步使用
    args.push('arguments[' + i + ']');
  }

  // 【重点】
  // 现在我们需要把临时函数和参数以字符串的形式拼接起来,然后用 eval 执行
  // 为啥要用拼接的方式?因为 ES3 中无法给函数传递不定参数
  var result = eval('context._fn(' + args + ')');

  delete context._fn;
  return result;
}