【手写系列】 手把手教你如何实现 call apply bind
大家好啊!这次我给大家带来的干货是如何自己实现call apply bind 这三个函数。
这三个函数大家应该都很熟悉,这三个函数是用于给普通函数进行this换绑的。
通过本文你能学到(复习)到什么知识📘
- 剩余参数(rest args)
- 普通函数和箭头函数的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