重写一下call方法

144 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情
在js的日常使用中,我们经常会改变this的指向,其中call便是其中经常用到的一个方法。为了能让自己更加深刻的理解call方法,决定参考一些资料,自己去重新写一下call方法。

内置call方法

代码

function fn(x, y){
  console.log(this, x, y)
}
let obj = {
  name: 'xh'
}
fn.call(obj, 10, 30)

输出结果是 {name: 'xh'} 10 30

思路,我们只需要考虑如何将this和需要执行的函数关联起来就可以了,而我如果重写call方法,必然要覆盖原有的内置call方法,这就需要在Function的圆形上进行。而将this和要执行的函数关联到一起,只需要将要执行的函数作为this的一个属性就好了。

重写call方法

Function.prototype.call = function call(content, ...params){
  const self = this;//习惯比直接使用this
  content['fn'] = self;
  content['fn'](...params)
}

function fn(x, y){
  console.log(this, x, y)
}
let obj = {
  name: 'xh'
}
fn.call(obj, 10, 30)

输出结果是{name: 'xh', fn: ƒ} 10 30
哈哈,不错,和预期差不多。

优化

1.this的指向内容多了一个属性fn,既然多了,那么我们就需要删除掉它。

Function.prototype.call = function call(content, ...params){
  const self = this;
  content['fn'] = self;
  content['fn'](...params);
  delete content['fn'];//函数执行完毕后删除相关属性
}

再次执行函数得到结果入下图

截屏2022-04-24 23.23.10.png
虽然依然显示fn属性,但是将对象展开后发现fn消失了,这是因为展开显示的是最新的对象内容,而外层显示的是在console的时候对象对应的内容。虽然展示不是很理想,但勉强能接受了。

  1. 返回值 现在新的方法还没有返回值,如果fn函数存在返回值,那么我新写的call方法就会出现undefined了,那么就给call加上返回值
Function.prototype.call = function call(content, ...params){
  const self = this;
  let res = null;
  content['fn'] = self;
  res = content['fn'](...params);
  delete content['fn'];
  return res;//增加返回值
}
  1. this什么时候指向window呢? 在只考虑浏览器环境下,当call的第一个参数为undefined或者null的时候,this就会指向window,加油!
Function.prototype.call = function call(content, ...params){
  content == null ? content = window : null;//当content是null或者undefined的时候,将content赋值为window
  const self = this;
  let res = null;
  content['fn'] = self;
  res = content['fn'](...params);
  delete content['fn'];
  return res;
}
  1. 如果obj中本来就存在fn属性呢?如果按照现有函数执行的话,会被先替换再删除掉的。这怎么处理呢?咦~,symbol似乎是个好东西。
Function.prototype.call = function call(content, ...params){
  content == null ? content = window : null
  const self = this;
  let res = null;
  let newAttr = Symbol('newAttr')//使用symbol代替fn,防止属性重名
  content[newAttr] = self;
  res = content[newAttr](...params);
  delete content[newAttr];
  return res;
}
  1. 如果call函数的第一个参数我传入的是非正常对象呢?比如输入字符串“abc”
    如果输入字符串“abc”,那么函数执行过程中一定会报错,因为只有“引用类型”能 content['fn'] 这样使用。那么如何让字符串等基本数据类型也可以使用呢?这里有两个方法,一个是使用构造函数的方法去创建,就是使用new方法,但问题是要区分不同的基础数据类型,比如new Number();new String()...,而且symbol不能使用new创建;另外一个办法就是使用Object()方法。
Function.prototype.call = function call(content, ...params){
  content == null ? content = window : null
  !/^(object|function)$/.test(typeof content) ? content = Object(content) : null;
  const self = this;
  let res = null;
  let newAttr = Symbol('newAttr')
  content[newAttr] = self;
  res = content[newAttr](...params);
  delete content[newAttr];
  return res;
}

利用正则表达式和Object()方法,将基础数据类型转化成可以使用 content['fn'] 方式获得属性的数据。

最后测试

在我能想到的一些问题上都得到了解决,可能会存在疏漏,欢迎指正。下面是代码执行截图

截屏2022-04-24 23.54.13.png