【手写系列】 手把手教你如何实现 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,可以叫我雪碧,是一名在前端路上学习的菜鸡,关注我,带你一起学习前端的知识

相关推荐
To_OC4 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC4 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic4 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy4 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志5 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble6 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长6 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯7 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜7 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司8 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能