前言

实现手写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);

结果如下

JS中实现手写bind方法_剩余参数

可以看出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)

输出结果

JS中实现手写bind方法_原型链_02

可以看到输出结果没什么问题

其他情况

如果我们这么调用,就会有问题

let myfn = fn.myBind({a:1},2)
let res = new myfn(8,9,7)
console.log(res)

JS中实现手写bind方法_手写bind_03

目前没看出来问题对吧,那我们用原bind来试试

let myfn = fn.bind({a:1},2)
let res = new myfn(8,9,7)
console.log(res)

JS中实现手写bind方法_手写bind_04

这样就能对比出问题了,原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)

JS中实现手写bind方法_手写bind_05


这样我们就写好了一个bind方法

总结

手写bind主要用到了

接受参数

arguments

类数组转数组

Array.prototype.slice.call

Function原型

Function.prototype

apply,call

ff.apply(ctx,allArgs);

Array.prototype.slice.call(arguments);

以及this指向的了解