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

简介: 前端面试中,手撕call、apply、bind函数的实现是非常常见的,今天我们就来看下call和apply函数的自定义实现。对自定义bind函数感兴趣的同学可以直接点击这里的传送门:经典面试题:手撕一个bind函数

前言


前端面试中,手撕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: '女'}


没有问题~~~



相关文章
|
2天前
|
存储 SQL 数据库
面试题20: 存储过程和函数的区别
面试题20: 存储过程和函数的区别
|
2天前
|
前端开发 UED
【面试题】async/await 函数到底要不要加 try catch ?
【面试题】async/await 函数到底要不要加 try catch ?
|
2天前
|
自然语言处理 前端开发
阿里面试官:如何给所有的async函数添加try/catch?
阿里面试官:如何给所有的async函数添加try/catch?
|
2天前
|
存储 编译器 程序员
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
113 0
|
2天前
|
算法 Java C++
数据结构与算法面试题:实现一个函数 fill(int[] a, int n, int v),使其将大小为 n 的数组 a 填满为 v。
数据结构与算法面试题:实现一个函数 fill(int[] a, int n, int v),使其将大小为 n 的数组 a 填满为 v。
17 0
|
2天前
|
算法 C++
数据结构和算法面试题:实现一个函数,将一棵二叉树转换为它的镜像。(递归或者非递归实现)
数据结构和算法面试题:实现一个函数,将一棵二叉树转换为它的镜像。(递归或者非递归实现)
15 0
|
2天前
|
算法 Java C++
数据结构与算法面试题:实现一个函数,判断一个链表是否为回文链表。(提示:反转后半部分链表比对前半部分)
数据结构与算法面试题:实现一个函数,判断一个链表是否为回文链表。(提示:反转后半部分链表比对前半部分)
23 0
|
2天前
|
Java Spring 容器
面试题:怎样为组件在创建的时候指定执行一个函数,在销毁的时候也先执行一个函数
面试题:怎样为组件在创建的时候指定执行一个函数,在销毁的时候也先执行一个函数
32 0
|
2天前
|
前端开发 UED
【面试题】 async/await 函数到底要不要加 try catch ?
【面试题】 async/await 函数到底要不要加 try catch ?
【面试题】 async/await 函数到底要不要加 try catch ?
|
2天前
|
前端开发 Java C++
【面试题】calc()计算函数的作用和理解
【面试题】calc()计算函数的作用和理解
【面试题】calc()计算函数的作用和理解