经典面试题:手撕call/apply函数实现

171 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情

前言

前端面试中,手撕callapplybind函数的实现是非常常见的,今天我们就来看下callapply函数的自定义实现。

对自定义bind函数感兴趣的同学可以直接点击这里的传送门:经典面试题:手撕一个bind函数

call函数自定义实现

call函数定义:

call()  方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

举例:

// 声明Person类
class Person {
  // 声明私有属性name、age
  private name: string;
  private age: number;

  // 初始化时,传入name、age
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  /**
   * @method printInfo
   * @description 打印个人信息
   * @param sex string 传入性别信息
   */
  printInfo(sex: string) {
    console.log({
      name: this.name,
      age: this.age,
      sex: sex,
    });
  }
}

// 实例化类,创建对象
const p = new Person('Jhon', 20);

// 调用printInfo方法
p.printInfo('男'); // {name: 'Jhon', age: 20, sex: '男'}

// 调用系统方法call,改变this
p.printInfo.call({name: '韩梅梅', age: 10}, '女'); // {name: '韩梅梅', age: 10, sex: '女'}

自定义实现:

call方法需要注意的是this的指向、支持传入一个或多个参数,并且是立即返回函数执行结果。

因为JS中函数作用域是静态类型作用域,在函数声明的位置已经确定了~

function printThis () {
  // 如果是在dom中指向window,如果是nodejs中指向global
  console.log(this)
}

const man = {
  name: '小名',
  printThis () {
    // 指向的是man本身
    console.log(this)
  }
}

那如何将原函数的this指向变为call方法中传入的this呢,我们可以借助对象调用自身方法的用法,更改原函数的this指向。

Up Code ~ 上码 ~

/**
 * @method myCall
 * @description 自定义call函数的实现
 * @param context any 传入的this指向
 * @param args any[] 传入的参数
 */
// @ts-ignore
Function.prototype.myCall = function (context: any, ...args: any[]) {
  // 1. 处理context可能是null或者是undefined的情况
  if (context === undefined || context === null) {
    // 指向全局的this
    context = globalThis;
  }

  // 2. 处理context类型不是对象的情况
  if (typeof context !== 'object') {
    context = new Object(context);
  }

  // 定义唯一key
  const fnKey = Symbol();

  // 将原函数的本身this,赋值给context的fnKey属性上,因为fnKey是唯一不重复的,所以也不会影响context本身的属性
  context[fnKey] = this;
  
  // 调用原函数 context[fnKey]这种形式,将this指向了context本身
  // 传入传递的参数,接收结果
  const res = context[fnKey](...args);

  // 将在处理过程中新增加的fnKey属性再删除,不出现`脏代码`
  delete context[fnKey];
  
  // 返回最终的结果
  return res;
};

功能测试:

// @ts-ignore
p.printInfo.myCall({ name: '韩梅梅', age: 10 }, '女'); // {name: '韩梅梅', age: 10, sex: '女'}

嘎嘎好使~

apply函数自定义实现

其实apply和call函数基本上就是一致的,唯一的区别是apply支持传递的参数是以数组形式传入的,简单调整下

// @ts-ignore
Function.prototype.myApply = function (context: any, args: any[]) {
  // context的判断逻辑
  if (context === undefined || context === null) {
    // 指向全局的this
    context = globalThis;
  }

  // 判断context不是对象
  if (typeof context !== 'object') {
    context = new Object(context);
  }

  // 定义唯一key
  const fnKey = Symbol();

  // 添加对象的方法
  // 当前函数对象
  context[fnKey] = this;
  const res = context[fnKey](...args);

  delete context[fnKey];
  return res;
};

功能测试:

// @ts-ignore
p.printInfo.myApply({ name: '韩梅梅', age: 10 }, ['女']); // {name: '韩梅梅', age: 10, sex: '女'}

没有问题~~~

结语

以上就是胡哥今天给大家分享的内容,喜欢的小伙伴记得点赞收藏呀,关注胡哥有话说,学习前端不迷路,欢迎多多留言交流...