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

相关推荐
J***Q29243 分钟前
Vue数据可视化
前端·vue.js·信息可视化
ttod_qzstudio2 小时前
深入理解 Vue 3 的 h 函数:构建动态 UI 的利器
前端·vue.js
芳草萋萋鹦鹉洲哦2 小时前
【elemen/js】阻塞UI线程导致的开关卡顿如何优化
开发语言·javascript·ui
_大龄3 小时前
前端解析excel
前端·excel
1***s6323 小时前
Vue图像处理开发
javascript·vue.js·ecmascript
槁***耿3 小时前
JavaScript在Node.js中的事件发射器
开发语言·javascript·node.js
一叶茶3 小时前
移动端平板打开的三种模式。
前端·javascript
前端大卫3 小时前
一文搞懂 Webpack 分包:async、initial 与 all 的区别【附源码】
前端
U***49833 小时前
JavaScript在Node.js中的Strapi
开发语言·javascript·node.js
Want5953 小时前
HTML音乐圣诞树
前端·html