自己写的call apply 源码奇葩操作引发的思考?

案例背景

本人在复习资料中 意外看到了几年前写的 call 源码,奇葩思路传递了一个字符串, 不多说直接上代码

Function.prototype.mycall = function(...args) {
      const fn = this;
      if (typeof fn !== 'function') throw new TypeError('call 必须被函数调用');
      let target = args.shift();//获取第一个参数
      if (!target || typeof target !== 'object') target = window; //不传参默认只想 window
      target._fn = fn;
      const res = target._fn(args);
      delete target._fn;
      return res;
};
const obj = { name: 'mike' };
function echo() {
  console.log(this.name);
}

echo.call('123'); //源生的打印出 undefined
echo.mycall('123'); //我自己的打印出 ''
复制代码

发现使用源生的 call 打印出来的是 undefined 使用自己的 mycall 打印了 ”

第一个坑

name 是 window 对象的关键字

引用 MDN 上的定义

Window.name:获取/设置窗口的名称。

window.name 会调用 toString 将赋给它的值转换成对应的字符串表示。

第二个坑

猜想是否是 this 指向出问题(源生的 call 可能不是单纯的把 this 指向于第一个参数)

我们来打印一下 this

Function.prototype.mycall = function(...args) {
  const fn = this;
  if (typeof fn !== 'function') throw new TypeError('call 必须被函数调用');
  let target = args.shift();//获取第一个参数
  if (!target || typeof target !== 'object') target = window; //不传参默认只想 window
  target._fn = fn;
  const res = target._fn(args);
  delete target._fn;
  return res;
};
const obj = { name: 'mike' };
function echo() {
  console.log(this);
  // console.log(this.name);
}

echo.call('123'); //源生的打印出 undefined
echo.mycall('123'); //我自己的打印出 ''
复制代码

结果出乎意料

String {"123"}//源生的call
Window {window: Window, self: Window, document: document, name: "", location: Location, …}//mycall
复制代码

让我们来修复这个问题

Function.prototype.mycall = function(...args) {
  const fn = this;
  if (typeof fn !== 'function') throw new TypeError('call 必须被函数调用');
  let target = args.shift();//获取第一个参数
  if (!target) target = window; //不传参默认指向 window

  //如果不是 object 需要使用 new 构造出对应的构造器原型对象的实例 修复指向问题
  /*======关键代码======== */
  if (typeof target !== 'object') {
    const type = Object.prototype.toString.call(target).slice(8, -1);
    target = new type.constructor(target);
  }
  /*======关键代码======== */

  target._fn = fn;
  const res = target._fn(args);
  delete target._fn;
  return res;
};
复制代码

完事,测试正常

完整代码

Function.prototype.mycall = function(...args) {
  const fn = this;
  if (typeof fn !== 'function') throw new TypeError('call 必须被函数调用');
  let target = args.shift();//获取第一个参数
  if (!target) target = window; //不传参默认指向 window

  //如果不是 object 需要使用 new 构造出对应的构造器原型对象的实例 修复指向问题
  /*======关键代码======== */
  if (typeof target !== 'object') {
    const type = Object.prototype.toString.call(target).slice(8, -1);
    target = new type.constructor(target);
  }
  /*======关键代码======== */
  target._fn = fn;
  const res = target._fn(args);
  delete target._fn;
  return res;
};
const obj = { name: 'mike' };
function echo() {
  console.log(this.name);
}
echo.call('123'); //源生的打印出 undefined
echo.mycall('123'); //我自己的打印出 undefined
复制代码

遗留的小 bug

  • 自己写的 mycall 会在 echo 打印 this 的时候多出一个 _fn 属性

欢迎大佬 指正 完成 py 交易

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享