前言
实现手写bind方法,可以有助于了解原生bind方法的实现逻辑,能更好的使用bind方法
bind用法
先了解原bind方法是如何使用的
先写一个普通的函数
function fn(a,b,c,d){
console.log(a,b,c,d)
console.log(this)
return 'myreturn'
}
使用bind能够重新定义this的指向
let fn1 = fn.bind({age:'19'},2,3)
let ret = fn1(4,5)
console.log(ret);
结果如下
可以看出bind接收一个上下文的对象,以及剩余参数,并返回一个新的函数,新的函数能够接受另外的剩余参数
思路
自写的函数可以被任意函数调用,所以我们要在Function的原型中来写,比如
Function.prototype.myBind = function(){}
- 那么我们也可以写一个函数
- 接收的第一个参数是上下文对象
- 然后再接受剩余的参数
- 将this先赋值给一个变量,方便返回函数中调用
- 返回一个函数
- 返回函数中接受参数,然后合并两次的参数
- 再调用this赋值的那个变量,并将合并后的参数传进去
代码实现
编写代码
Function.prototype.myBind = function(ctx){
// 接受剩余参数
let args = Array.prototype.slice.call(arguments,1);
// 将this存起来,在返回函数中调用
let ff = this;
// 返回一个函数
return function myf(){
// 接收参数
let otherArgs = Array.prototype.slice.call(arguments);
// 合并两次的参数
let allArgs = args.concat(otherArgs);
return ff.apply(ctx,allArgs);
}
}
代码解释
line3:
arguments 是一个对应于传递给函数的参数的类数组对象。
arguments对象不是一个 Array 。它类似于Array,但除了 length 属性和索引元素之外没有任何Array属性。例如,它没有 pop 方法。参数1是为了排除ctx这个参数,所以从第二个开始处理
line5:
将this赋值给ff,是为了在返回函数中能够调用
line7:
返回一个函数
line9:
接收参数,并转为数组
line11:
将外层获取到的参数与本函数的参数合并在一起,这样才是完整的参数
line12:
调用ff函数,并将参数传进去,返回函数结果
调用call函数是因为如果不调用call,那么this的指向将是全局变量,所以需要call来指定this的指向
调用测试
let myfn = fn.myBind({a:1},2)
let res = myfn(8,9,7)
console.log(res)
输出结果
可以看到输出结果没什么问题
其他情况
如果我们这么调用,就会有问题
let myfn = fn.myBind({a:1},2)
let res = new myfn(8,9,7)
console.log(res)
目前没看出来问题对吧,那我们用原bind来试试
let myfn = fn.bind({a:1},2)
let res = new myfn(8,9,7)
console.log(res)
这样就能对比出问题了,原bind输出的内容和自写的不同
我们发现this指向不再是自己传入的参数而是fn本身
这是因为原bind中有一个判断,用来区分了调用方式
那么我们也要这么做,可以判断是否是通过new来调用,如果是new调用的话,我们也用new来调用就好了
Function.prototype.myBind = function(ctx){
// 接受剩余参数
let args = Array.prototype.slice.call(arguments,1);
// 将this存起来,在返回函数中调用
let ff = this;
// 返回一个函数
return function myf(){
// 接收参数
let otherArgs = Array.prototype.slice.call(arguments);
// 合并两次的参数
let allArgs = args.concat(otherArgs);
// 如果是通过new的方式调用,那么也以new的方式来调用;通过原型对比来判断是否是new调用
if(myf.prototype === Object.getPrototypeOf(this)){
return new ff(...allArgs);
}else{
return ff.apply(ctx,allArgs);
}
}
}
line7:
我们给这个匿名函数加上一个函数名,方便line13来获取原型
line13:
通过判断原型是否相等的方式,来判断是否是new调用
line14:
如果是new调用,我们也用new来调用就可以了
测试代码
let myfn = fn.myBind({a:1},2)
let res = new myfn(8,9,7)
console.log(res)
这样我们就写好了一个bind方法
总结
手写bind主要用到了
接受参数
arguments
类数组转数组
Array.prototype.slice.call
Function原型
Function.prototype
apply,call
ff.apply(ctx,allArgs);
Array.prototype.slice.call(arguments);
以及this指向的了解