【手写系列】 手把手教你如何实现 call apply bind

大家好啊!这次我给大家带来的干货是如何自己实现call apply bind​​ 这三个函数。

这三个函数大家应该都很熟悉,这三个函数是用于给普通函数进行this​​换绑的。

通过本文你能学到(复习)到什么知识📘

  1. 剩余参数(rest args)
  2. 普通函数和箭头函数的this指向。

我们先从Call入手📃

我们先从JS提供的Call​​来观察下,它的运行是什么样子的。

javascript 复制代码
const obj = {
  a: "我的a",
};

function test(n1,n2) {
  console.log("这是哪里的a", this.a,n1+n2);
}
 
test.call(obj,10,20);

// 输出结果: 这是哪里的a 我的a 30

它接受一个this对象​​,原来的函数参数是通过追加的形式,一个个的加在call​​的函数内的。

那么我们要想要实现一个和这个差不多的功能,我们要怎么去做。

首先,call​​ 是一个Function的实例方法,所以我们要实现和它一样的功能,也需要在Function的原型上添加对应的方法。

javascript 复制代码
Function.prototype.callByHand=function(){}

1️⃣这样我们就实现了第一步,我们自己定义的函数也能调用callByHand​​了 ,恭喜恭喜🎇

2️⃣接下来,我们需要换绑this​​,那么我们就需要改变函数的执行上下文环境​​,如果大家对this的指向不是很明白,可以在评论区留言,我会在出文章专门介绍,这里暂时不赘述这部分内容。

ini 复制代码
Function.prototype.callByHand = function (bindThis) {
  let fn = this;
  bindThis.fn = fn;
  bindThis.fn();
};

这里简单说两句,定义callByHand​​的时候,一定要使用普通函数,因为普通函数的执行上下文是在函数执行的那一刻所决定的,和它定义的位置无关。

箭头函数不一样,它的执行上下文是由定义位置所决定的,身处在哪个作用域下面,它的this​​ 也就指向哪个作用域。

所以我们看,在这里面,this​​​ 实际上就是指向调用callByHand​​​的test​​​函数,然后,我们给bindThis​​​添加了一个fn​​​属性,将我们的函数赋值给它,使用bindThis.fn()​​​去执行的时候,test​​​函数的执行上下文就是obj​​​,那么我们在test​​​函数内就能找到对应的a了,就是在obj中找到的。

3️⃣ 然后,我们来看下如何接受函数的参数呢?

这里就要用到剩余参数(restArg)了,因为这里并不确定函数到底有多少个入参,剩余参数呢能接受所有的入参转换为一个数组,它能够很好的满足我们的需求,所以我们接下来这么写。

ini 复制代码
Function.prototype.callByHand = function (bindThis, ...args) {
  let fn = this;
  bindThis.fn = fn;
  bindThis.fn(...args);
};

好,到此为止,你已经实现了call的基本功能了,这么看下来是不是实现很简单呢?

接下来介绍apply的实现📄

apply和call的区别仅仅在接受入参时有区别,apply是以数组形式接受函数的入参的

所以在实现方法上,没什么差异。

ini 复制代码
const test = function (n1, n2) {
  console.log("计算结果", this.a, n1 + n2);
};

const obj = {
  a: 999,
};

Function.prototype.applyByHand = function (bindThis, args) {
  let fn = this;
  bindThis.fn = fn;
  bindThis.fn(...args);
};

test.apply(obj, [10, 20]); // 计算结果 999 30
test.applyByHand(obj, [10, 20]); // 计算结果 999 30

✔是不是很简单呢,看了之后,希望你能学会。

最后介绍bind的实现📑

bind的实现方式与前两者就不同了,call和apply在执行后,原函数就会被执行

但是bind并不会,他仅仅是绑定,而不执行函数。

因为不执行原函数,只是返回函数,所以我们在实现bind的时候,只要返回函数就行。

好,在我们知道这个思路后,我们来看下面代码,实际上也是差不多的形式。

ini 复制代码
const test = function (n1, n2) {
  console.log("计算结果", this.a, n1 + n2);
};

const obj = {
  a: 999,
};

Function.prototype.bindByHand = function (bindThis, ...args) {
  const newFn = (...params) => {
    let fn = this;
    bindThis.fn = fn;
    if (args.length !== 0) bindThis.fn(...args);
    else bindThis.fn(...params);
  };

  return newFn;
};

const newFn = test.bind(obj, 30, 40);
newFn(10, 20);

const newFn2 = test.bindByHand(obj, 30, 40);
newFn2(10, 20);

注意点:我这里使用的箭头函数返回包装后的函数,是利用了箭头函数的this指向与执行时没有关系,所以它的this还是指向了obj

还有第二种解法,就是利用闭包,这样就拿到了对应的this指向,大家喜欢哪种解法就用哪种就行

ini 复制代码
Function.prototype.bindByHand = function (bindThis, ...args) {
  let fn = this;  
  const newFn = (...params) => {	  
    bindThis.fn = fn;
    if (args.length !== 0) bindThis.fn(...args);
    else bindThis.fn(...params);
  };

  return newFn;
};

因为bind函数可以传入参也可以不传,所以我稍微判断了下。优先级是bind函数的入参优先级最高。

💯💯💯恭喜你,看到这里你已经掌握call apply bind​​的手写啦,真棒啊~~~

最后哦,call apply bind​​的一些异常参数判断,我没有写,这个就作为大家的额外作业了,学会基础写法之后,相信你们在这基础上加异常判断也是易如反掌

PS:像如果传递的this是null或者undefined,apply入参是数组,传入基础类型怎么办。

最后再和大家打个招呼,我是CodeSpirit,可以叫我雪碧,是一名在前端路上学习的菜鸡,关注我,带你一起学习前端的知识

相关推荐
xjt_090110 分钟前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农22 分钟前
Vue 2.3
前端·javascript·vue.js
夜郎king1 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳1 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵2 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星2 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_2 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝2 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions2 小时前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发2 小时前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法