简洁的代码,详细的解释,每个前端er都要会的手写call方法

265 阅读4分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

前言

又是一年的金三银四,疯狂的手写剧情再度上演,接下来就让我们一起来实现下JavaScript里的call函数。

一、思路梳理

在实现手写call之前让我们先来梳理下call函数的使用场景。通常我们在使用call的时候,需要两个对象(所有的一切都是对象🙈):

  1. 想要执行的function(我们假设叫foo)。
  2. 希望能够调用foo的对象(我们假设叫obj)。

那我们在实现call函数的时候主要突破点就是能够获取到foo以及obj。那么接下来我们就带着这样的一个思路来实现call方法吧。

二、call方法的实现

function jCall(thisArg,...args) {
  // 传进来的是null和undefined时将this指向为window
  thisArg = thisArg ?? window

  // 确保传进来的是Object
  thisArg = Object(thisArg)

  // 生成一个唯一的key值,防止和传进来的对象的key重复
  const key = Symbol()
  thisArg[key] = this

  // 获得函数的执行结果
  const result = thisArg[key](...args)

  // 删除手动添加的属性,使thisArg回到最初的样子
  delete obj[key]

  // 返回函数的执行结果
  return result
}

Function.prototype.jCall = jCall

三、测试结果展示

function foo(x, y, z) {
  console.log(x + y + z, this);
}

const obj = {
  name: 'ZJoker',
}

foo.jCall()
foo.jCall(null,1,2,3)
foo.jCall(undefined,1,2,3)
foo.jCall(123,1,2,3)
foo.jCall('123',1,2,3)
foo.jCall(false,1,2,3)
foo.jCall([],1,2,3)
foo.jCall({},1,2,3)

console.log('---------我是分割线----------');

foo.call()
foo.call(null,1,2,3)
foo.call(undefined,1,2,3)
foo.call(123,1,2,3)
foo.call('123',1,2,3)
foo.call(false,1,2,3)
foo.call([],1,2,3)
foo.call({},1,2,3)

在这里插入图片描述

四、实现的详解

4.1 获取需要调用目标function的对象

  // 传进来的是null和undefined时将thisArg指向为window
  thisArg = thisArg ?? window

  // 确保传进来的是Object
  thisArg = Object(thisArg)

在思路梳理里我们提到的需要调用函数foo(目标function)的对象obj(目标对象)其实就是call方法的第一个参数,所以我们这里用形参thisArg来获取obj。

但是,对于使用call方法时是否传参或者传的第一个参数的类型是什么我们没有办法控制,所以我们要手动的处理下传过来的参数。

4.2 获取被调用的目标function

  // 生成一个唯一的key值,防止和传进来的对象的key重复
  const key = Symbol()
  thisArg[key] = this

通常情况下我们在使用call方法的时候,都是xxx.call()的方式,所以call方法的执行是被函数foo唤起的。也就是说call方法内的this指向的就是我们的目标function foo。

至此我们就已经完全的获取到目标function foo以及目标对象obj。不过这里有一种边界情况需要我们去处理。

为了能够让obj(形参thisArg)调用我们的foo,也就是将foo的this绑定到obj(call方法的实际意义,改变函数的this指向)。我们需要以obj.foo()的形式执行目标函数。所以我们给obj新增一个方法就是我们的foo。但是key如何起名,是一个值得考虑的问题,我们要避免和obj原有的方法重名,所以这里使用了Symbol来保证key值的唯一。

4.3 目标function参数的获取

  // 获得函数的执行结果
  const result = thisArg[key](...args)

  // 删除手动添加的方法,使thisArg回到最初的样子
  delete obj[key]

  // 返回函数的执行结果
  return result

在通常情况下我们的foo可能会有自己的参数,所以在call方法中,除了第一个参数以外的参数我们利用展开语法作为foo需要的参数传递给foo。

这里我们利用ES6中的剩余参数(Rest Parameters)的概念接收call方法中除了第一个参数以外的参数,即jCall函数中的形参...args。

同时利用ES6中的展开语法(Spread syntax)的概念将获得的剩余参数(args)传递到目标函数foo中,即下面一行代码中的...args

 thisArg[key](...args)

最后我们把手动添加到obj的方法foo删除,使thisArg回到最初的样子,并将obj.foo()的执行结果返回。

结语

以上就是手写call的实现过程,一个出自js小白的学习记录,不对的地方欢迎指正。

---------------我是分割线---------------

简洁的代码,详细的解释,每个前端er都要会的手写bind方法。

简洁的代码,详细的解释,每个前端er都要会的手写apply方法。

简洁的代码,详细的解释,每个前端er都要会的手写call方法