当前位置: 首页 > news >正文

深入理解并实践call、apply、bind三大金刚

✋🏻前言

又到了金三银四的跳槽潮,面试官真的时不时会问到call、apply、bind的相关问题,利用一点碎片化的时间,我整理了如下...

目录

✋🏻前言

call

入参

返回值

底层

apply

入参

返回值

底层 

bind

入参

返回值

底层 

总结


call

call(context, arg1, arg2 ... argn);
入参

context:this的指向,非严格模式下null和undefined会变成window对象,基础数据类型会转化为引用类型

arg1...argn 可选值

返回值

对应函数返回值

function fn11(arg1, arg2) {
  console.log(arg1, arg2);
  console.log(this);
  return "我是return值";
}
const self1 = { x: "1111" };
const res1 = fn11.call(self1, 1, 2, 3);
console.log(res1);

如果是箭头函数,则结果不一样

const fn1 = (arg1, arg2) => {
  // 箭头函数this指向不同;
  console.log(arg1, arg2);
  console.log(this);
};
const self1 = { x: "1111" };
const res1 = fn1.call(self1, 1, 2, 3);
console.log(res1);

 有关于this的指向问题,可以看我这篇文章

JS中的this 归纳了所有可能影响this指向改变的情况,不过这里不是我们讨论的重点,所以接下来我都不使用箭头函数

底层

知道了语法、用法以及表现效果,我们来手写一个call

Function.prototype.myCall = function (context, ...arg) {
  // ...arg表示不知道有多少参数
  context = context == null || context === undefined ? window : context;
  context = changeContext(context);
  // 定义一个属性_fn存储传入的函数this
  console.log("myCall中的this:", this);
  context._fn = this;
  const result = context._fn(...arg); // 不使用...的话arg是一个数组
  // 用完删掉,防止污染
  delete context._fn;
  return result;
};

context表示this指向,...arg表示的是动态的参数,因为call支持不定个数的参数,所以我们选用它

第一行排除null和undefined的情况

function changeContext(ctx) {
  if (typeof ctx === "string") {
    return new String(ctx);
  } else if (typeof ctx === "number") {
    return new Number(ctx);
  } else if (typeof ctx === "boolean") {
    return new Boolean(ctx);
  } else if (typeof ctx === "symbol") {
    return new Symbol(ctx);
  } else if (typeof ctx === "bigint") {
    return new BigInt(ctx);
  } else {
    return ctx;
  }
}

第二行将基本数据类型转化为引用数据类型,不然后续也无法属性赋值(这块代码后续都有用到)

这块是console输出

知道了this,于是我们将定义一个内部属性_fn赋值为this,然后执行这个context._fn的方法,传入参数...arg

  • 为什么是...arg:因为是ES6的结构语法,如果是arg的话就是一个数组,无法满足call的第二个参数
  • 为什么不直接用this(...arg):因为这样会导致this的指向改变
Function.prototype.myCall = function (context, ...arg) {
  // ...arg表示不知道有多少参数
  context = context == null || context === undefined ? window : context;
  context = changeContext(context);
  // 定义一个属性_fn存储传入的函数this
  console.log("myCall中的this:", this);
  const result = this(...arg); // 不使用...的话arg是一个数组
  return result;
};
这边打印的就是window对象了

 所以这边我们需要“中转一下”

接着我们就需要把我们定义的_fn删除(这是一个好习惯)

最后返回函数执行结果result,来看最后的打印吧

apply

apply(context, args);
入参

context:this的指向,非严格模式下null和undefined会变成window对象,基础数据类型会转化为引用类型

args 可选值

返回值

对应函数返回值

function fn2(arg1, arg2) {
  console.log(arg1, arg2);
  console.log(this);
}
const self2 = [222];
const res2 = fn2.apply(self2, [1, 2, 3]);
console.log(res2);

apply和call很相似,我们直接来看底层

底层 
Function.prototype.myApply = function (context, arg) {
  context = context == null || context === undefined ? window : context;
  context = changeContext(context);
  // 定义一个属性_fn存储传入的函数this
  context._fn = this;
  const result = context._fn(...arg);
  // 用完删掉,防止污染
  delete context._fn;
  return result;
};

可以看到,唯一的改变点就是入参变成了arg,因为传入的arg是一个数组 | 类数组

然后需要注意的是context._fn中的参数还是...arg结构的结果,在前面的使用部分我们也会看到,arg1、arg2分为对应的1,2而不是[1,2],undefined   

所以底层的知识很重要,根据现象知道本质

bind

bind(context, arg1, arg2 ... argn);
入参

context:this的指向,非严格模式下null和undefined会变成window对象,基础数据类型会转化为引用类型

arg1...argn 可选值

返回值

新建一个函数,此函数是原始函数的副本

function fn3(arg1, arg2) {
  console.log(arg1, arg2);
  console.log(this);
  console.log(typeof this);
}
const self3 = 33;
const res3 = fn3.bind(self3, 1, 2, 3);
console.log(res3);
res3();
很明显的看出将基础类型转化成了引用类型
底层 
Function.prototype.myBind = function (context, ...bindArg) {
  context = context == null || context === undefined ? window : context;
  context = changeContext(context);
  const _that = this;
  return function (...newArg) {
    // return _that.call(context, ...bindArg, ...newArg);

    context._fn = _that;
    const result = context._fn(...[...bindArg, ...newArg]);
    // 用完删掉,防止污染
    delete context._fn;
    return result;
  };
};

入参方面和call一样,只不过返回值方面需要修改

首先,返回一个方法,方法的参数也是不定的

然后有两种实现效果

  1. 直接使用call方法,然后把context,解构后的bindArg和解构后的newArg传入
  2. 第二种方式和上面call、apply的相同,注意函数调用的入参,需要先合并再解构,最后返回函数执行结果

总结

我们花了一篇博文从基础到使用到底层彻底讲通call、apply、bind的相关内容,期间也不可避免的涉及了箭头函数和this指向问题,相信我把所有能配置到的情况都列出来了


现在,你可以无比自信的和面试侃侃而谈了,因为你比他专业~~

如果觉得有收获,麻烦给个赞和关注。你的鼓励是我写作的动力,大家一起学习一起进步。

相关文章:

  • 【python|二分|leetcode441】一题搞清楚二分区间问题---闭区间、左闭右开、左开右闭、全开区间
  • DeepSeek引领端侧AI革命,边缘智能重构AI价值金字塔
  • PPT:仓储管理与WMS
  • 在VMware Workstation Pro上轻松部署CentOS7 Linux虚拟机
  • 数据篇| App爬虫入门(一)
  • datax-coud部署
  • Flutter中stream学习
  • PyTorch调试与错误定位技术
  • Dify Web 前端独立部署指南(与后端分离,独立部署)
  • 如何在c# 项目中使用redis
  • 前端大屏展示可视化-地图的绘制(天地图)
  • 基于springboot+vue的佳途旅行分享预约平台
  • Linux之磁盘管理
  • ThinkPHP框架中各模块通过MVC架构和扩展机制协同工作形成完整的请求处理流程
  • 三:FFMPEG拉流读取模块的讲解
  • 【力扣】2629. 复合函数——函数组合
  • macOS 如何进入安全模式
  • 阿里千问大模型(Qwen2.5-VL-7B-Instruct)部署
  • 【每日学点HarmonyOS Next知识】拖拽调整列表顺序、tab回弹、自定义弹窗this、状态变量修饰枚举
  • TypeScript接口:结构化类型的契约之道
  • ddns做网站/新闻今天最新消息
  • 可以做片头的网站/成都网站建设技术支持
  • 网站 not found/最新nba排名
  • 东台专业做网站的公司/国家免费技能培训官网
  • 哪里有个人卖房网站/网站优化系统
  • 网站特效网/成都seo技术