前言
我在之前的文章中经常会提到bind这个函数,那么bind究竟是个什么东西呢?本文将带大家深度剖析bind函数,并且带大家手写一个bind
bind
bind是用来改变this指向的一个函数(在前几篇文章中,我们讨论了this,在这里就不多赘述(this到底是谁的舔狗???😍 - 掘金 (juejin.cn))。
先来看官方文档是如何描述的
Function
实例的bind()
方法创建一个新函数,当调用该新函数时,它会调用原始函数并将其this
关键字设置为给定的值,同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面。
也就是说,bind()中可以传入n个参数,让this指向第一个参数。
我们先来看一个例子:
js
const obj = {
name: 'Alice',
greet: function () {
console.log(`Hello, ${this.name}!`);
},
};
// 调用obj的greet方法,此时this值将指向obj对象
obj.greet(); // 输出:Hello, Alice!
const greet = obj.greet;
// 直接调用greet函数,此时this值将指向全局对象(window)
greet(); // 输出:Hello, undefined
// 使用bind函数将greet函数绑定到obj对象上
const boundGreet = obj.greet.bind(obj);
// 调用绑定后的函数,此时this值将永久地绑定到obj对象
boundGreet(); // 输出:Hello, Alice!
在this的文章中,我们提到可以通过对象绑定来绑定this,所以obj.greet()
是可以访问到name属性的,
但是我们将obj.greet
赋值给greet的时候,发生了隐式丢失,这里输出"Hello, undefined"
const boundGreet = obj.greet.bind(obj);
当我们使用bind绑定之后,this绑定在obj上,此时输出为"Hello, Alice!"
由此观之,bind绑定的使用极其重要!
手写一个bind
既然bind如此重要,那么我们就来手写一个bind
- 思考
先来思考一下,我们的bind该怎么样完成?
- 函数可以调用我的bind方法
- 可以传入多个参数
- 将this绑定到第一个参数
我们需要完成的功能基本就是这三个了,接下来我们将开始完成这些功能
- 动手
- 函数可以调用我的myBind方法
要让我们的myBind方法可以让任何一个函数调用,我们只需要将其写在函数原型上即可
js
Function.prototype.myBind = function(){}
- 可以传入多个参数
当我们要向里面传入多个参数时,该怎么办呢?我们可以使用...args,注意第一个参数是要绑定到的 那个参数
js
Function.prototype.myBind = function(context,...args){}
- 将this绑定到第一个参数
最简单的两步已经完成,我们的重点在第三步,将this绑定到第一个参数
我们又该怎么做呢?
1)首先我们要先判断,是否是函数调用我们的方法
js
if(typeof this!== "function"){
throw new TypeError("error")
}
看到这里,你估计会说:what?我们不是写在函数的原型上吗?只有函数才能调用我,那我这里还判断个球? 好兄弟,你怕是忘记了箭头函数这个老六了,箭头函数 sayHello
没有自己的 this
值,所以让箭头函数调用 bind方法是没有效果的。
2)当我们没有传入参数时怎么办
js
context = context || window//闭包之中
当我们没有传入参数时,就让this指向全局的window
3)记住,此刻我们的this指向的是我们调用myBind的那个函数,在下面,我们会调用他,所以在这里需要先将this保留下来
js
let that = this;
4)最后,我们应该根据不同的调用来完成this绑定,在这里我们return出一个fn方法
如果新函数 fn
使用 new
关键字调用,即通过构造函数的方式调用,那么它会创建一个新的实例,并将 this
值设置为新实例。在这种情况下,fn
函数会调用原始函数,并传递所有的参数,包括原始参数和新参数。这样,在实例化时就可以使用 new
关键字调用被绑定的函数,而且 this
值将被正确地设置为新实例。
否则,如果新函数 fn
是通过普通函数调用方式调用的,那么它会使用 apply
方法来调用原始函数,并将 this
值手动绑定到指定的 context
上,同时传递所有的参数。
js
return function fn(...innerArgs){
//this丢失
if(this instanceof fn){
return new that(...args,...innerArgs)
}
return that.apply(context,[...args,...innerArgs])
//原先的函数执行,this 手动指定为context
}
完整的myBind在这里
js
Function.prototype.myBind = function(context,...args){
//函数 this
// this()
if(typeof this!== "function"){
throw new TypeError("error")
}
context = context || window//闭包之中
let that = this;
return function fn(...innerArgs){
//this丢失
if(this instanceof fn){
return new that(...args,...innerArgs)
}
return that.apply(context,[...args,...innerArgs])
//原先的函数执行,this 手动指定为context
}
}
我们来调用一下
js
Function.prototype.myBind = function(context,...args){
//函数 this
// this()
if(typeof this!== "function"){
throw new TypeError("error")
}
context = context || window//闭包之中
let that = this;
return function fn(...innerArgs){
//this丢失
if(this instanceof fn){
return new that(...args,...innerArgs)
}
return that.apply(context,[...args,...innerArgs])
//原先的函数执行,this 手动指定为context
}
}
function sayHello(x, y, z) {
console.log("函数中的",this);
console.log("你好!",this.name);
console.log(x + y + z);
}
const arrowFn = ()=>{}
let person = {
name:"坤坤"
}
// arrowFn.myBind()
let bindFn = sayHello.myBind(person,1,2);
console.log(bindFn(3));
确实,我们将this绑定到了坤坤身上...
结尾
手写源码还是有点废脑子的😭,但是当大家理解了以后,多刻意练习,还是难不倒聪明不绝顶的掘友们的!