likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 44

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

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

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

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

我们先从Call入手📃

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

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的原型上添加对应的方法。

Function.prototype.callByHand=function(){}

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

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

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)了,因为这里并不确定函数到底有多少个入参,剩余参数呢能接受所有的入参转换为一个数组,它能够很好的满足我们的需求,所以我们接下来这么写。

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

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

接下来介绍apply的实现📄

apply和call的区别仅仅在接受入参时有区别,apply是以数组形式接受函数的入参的 所以在实现方法上,没什么差异。

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的时候,只要返回函数就行。

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

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指向,大家喜欢哪种解法就用哪种就行

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,可以叫我雪碧,是一名在前端路上学习的菜鸡,关注我,带你一起学习前端的知识

转载自:https://juejin.cn/post/7259765338005930041
评论
请登录