手写call和bind

本文介绍了如何手写实现JavaScript的call和bind方法,分析了它们的功能差异和参数处理方式。call与apply的主要区别在于参数传递,bind则会在保持函数不变的情况下绑定指定的上下文,并可接受预设参数。文章详细讲解了实现这两个方法的关键步骤,包括处理this指向、参数合并以及考虑new操作的影响。
摘要由CSDN通过智能技术生成

参考文章:手写apply

在上篇文章中我们完成了apply的手写实现,如果理解了apply的实现的话,那么call和bind的实现就比较简单了,他们的功能是类似的,只是传参和部分功能有点不一样,接下来我们分别实现这两个方法。

call实现

跟写apply的时候一样,我们在实现call之前先要清楚call的使用方式以及实现的效果,然后再自己去编写代码。

let name = "张三"
let personObj = {
    name: "李四",
}
function getPersonName(age,sex){
    console.log(this.name,age,sex)
}
getPersonName(18,'男')
// 张三 18 男
getPersonName.call(personObj,22,'男')
// 李四 22 男

我们发现call的功能跟apply是一样的,就只是传参的方式不同,apply是把函数需要的参数放在数组中作为第二个参数传递,而call是把需要的参数直接加在后面。所以说直接修改上篇文章中apply方法里面接收参数的地方就可以实现call了。

Function.prototype._call = function(target){
    (target == null || target == undefined) && (target = window);
    // 下面对基本数据类型的处理是可以通过Object(target)来实现的
    /* if(typeof target !== 'object'){
        if(typeof target == 'number') target = new Number(target)
        if(typeof target == 'boolean') target = new Boolean(target)
        if(typeof target == 'string') target = new String(target)
    }*/
    target = Object(target)
    let fn = Symbol();
    target[fn] = this
    // 将参数数组的第一项删除,剩下的就是函数需要的参数数组了
    let args = [...arguments].splice(1)
    let result;
    // 与apply中获取参数的方式不同,如果没有传参,args会是空数组,所以不用进行判断,可以直接调用函数传参
    result = target[fn](...args)
    delete target[fn]
    return result
}

bind实现

我们先来看一下bind的使用:

let name = "张三"
let personObj = {
    name: "李四",
}
function getPersonName(age,sex){
    console.log(this.name,age,sex)
}
let bind_person = getPersonName.bind(personObj,22,"男")
bind_person() // 李四 22 男

通过上面的使用方式可以发现:

  1. 函数通过bind修改this指向后会返回一个新的函数;

  1. 与apply、call不同,在bind的时候,函数不会执行;

  1. bind传参的方式与call一样。

所以在bind的函数中,我们需要定义一个函数并且最后要return出去:

Function.prototype._bind = function(target){
    let self = this    // 定义变量来存储调用bind的函数
    // 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
    let args = [...arguments].slice(1)    
    // 定义一个新的函数,并将这个函数return出去
    // 外部在执行这个函数的时候才去执行存储下的self并修改this指向
    let newFun = function(){
        // 在这里完成self(调用bind的函数)的执行以及修改this
        
    }
    return newFun
}

我们需要在bind中新定义的函数里面去完成函数的执行和this的绑定,那么可以直接用apply或者call去实现:

Function.prototype._bind = function(target){
    let self = this    // 定义变量来存储调用bind的函数
    // 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
    let args = [...arguments].slice(1)    
    // 定义一个新的函数,并将这个函数return出去
    // 外部在执行这个函数的时候才去执行存储下的self并修改this指向
    let newFun = function(){
        // 在这里完成self(调用bind的函数)的执行以及修改this
        // 同样的由于原函数存在有返回值的情况,此处也需要return
        return self.apply(Object(target),args)
    }
    return newFun
}

在bind的使用过程中,我们发现新得到的函数中也是可以传参的:

let name = "张三"
let personObj = {
    name: "李四",
}
function getPersonName(age,sex,school){
    console.log(this.name,age,sex,school)
}
let bind_person = getPersonName.bind(personObj,22)
bind_person("男","xxx学校") // 李四 22 男 xxx学校

所以在_bind中,我们需要对函数调用时传递的参数进行修改:

Function.prototype._bind = function(target){
    let self = this    // 定义变量来存储调用bind的函数
    // 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
    let args = [...arguments].slice(1)    
    // 定义一个新的函数,并将这个函数return出去
    // 外部在执行这个函数的时候才去执行存储下的self并修改this指向
    let newFun = function(){
        // args是调用bind时传的参数,arguments是调用return出去的新函数时传的参数
        // 将两种参数合并再传给self
        let allArgs = [...args,...arguments]
        // 在这里完成self(调用bind的函数)的执行以及修改this
        // 同样的由于原函数存在有返回值的情况,此处也需要return
        return self.apply(Object(target),allArgs)
    }
    return newFun
}

对于新得到的函数,我们是可以使用new调用,这个时候函数中的this指向的是new出来的实例,所以在使用apply修改this指向时,需要进行判断:

Function.prototype._bind = function(target){
    let self = this    // 定义变量来存储调用bind的函数
    // 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
    let args = [...arguments].slice(1)    
    // 定义一个新的函数,并将这个函数return出去
    // 外部在执行这个函数的时候才去执行存储下的self并修改this指向
    let newFun = function(){
        // args是调用bind时传的参数,arguments是调用return出去的新函数时传的参数
        // 将两种参数合并再传给self
        let allArgs = [...args,...arguments]
        // 在这里完成self(调用bind的函数)的执行以及修改this
        // 同样的由于原函数存在有返回值的情况,此处也需要return

        // 使用instanceof来判断使用返回的函数是否是用new调用的
        // 不是new调用的时候this才会指向context
        return self.apply(this instanceof newFun ? this : Object(target),allArgs)
    }
    return newFun
}

目前写的这个bind函数还存在一些问题,我们给原始函数的原型添加一些属性,再使用new创建一个bind处理后函数的实例,这个实例上面并没有之前原型上的属性:

let fun = function(){
    console.log(this.name)
}
let obj = {
    name: "张三"
}
fun.prototype.callName = function (){
    console.log("fun原型上的callName函数")
}
let Fun1 = fun.bind(obj)
let Fun2 = fun._bind(obj)
let fun1 = new Fun1()
let fun2 = new Fun2()
fun1.callName()    // fun原型上的callName函数
fun2.callName()    // Uncaught TypeError: fun2.callName is not a function

所以我们需要在返回函数之前将这个函数的prototype修改为绑定函数的prototype,这样实例就可以继承函数原型中的值:

Function.prototype._bind = function(target){
    let self = this    // 定义变量来存储调用bind的函数
    // 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
    let args = [...arguments].slice(1)    
    // 定义一个新的函数,并将这个函数return出去
    // 外部在执行这个函数的时候才去执行存储下的self并修改this指向
    let newFun = function(){
        // args是调用bind时传的参数,arguments是调用return出去的新函数时传的参数
        // 将两种参数合并再传给self
        let allArgs = [...args,...arguments]
        // 在这里完成self(调用bind的函数)的执行以及修改this
        // 同样的由于原函数存在有返回值的情况,此处也需要return

        // 使用instanceof来判断使用返回的函数是否是用new调用的
        // 不是new调用的时候this才会指向context
        return self.apply(this instanceof newFun ? this : Object(target),allArgs)
    }
    newFun.prototype = this.prototype
    return newFun
}

但是这样写又会出现一个问题,因为我们是直接把放回函数的原型指向了绑定函数的原型,这种写法只是一个简单的对象引用,是一个浅拷贝,会导致修改实例原型的时候,绑定函数的原型也会跟着变;同样的,修改绑定函数的原型,实例的原型也会变:

let fun = function(){
    console.log(this.name)
}
let obj = {
    name: "张三"
}
let Fun1 = fun._bind(obj)
let fun1 = new Fun1()
fun1.__proto__.callName = function(){
    console.log('666')
}
fun1.callName()    // 666
fun.prototype.callName()    // 666

所以我们可以添加一个中间变量函数,通过这个中间变量来维护他们之间的原型关系:

Function.prototype._bind = function(target){
    let self = this    // 定义变量来存储调用bind的函数
    // 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
    let args = [...arguments].slice(1)    
    // 定义一个新的函数,并将这个函数return出去
    // 外部在执行这个函数的时候才去执行存储下的self并修改this指向
    let newFun = function(){
        // args是调用bind时传的参数,arguments是调用return出去的新函数时传的参数
        // 将两种参数合并再传给self
        let allArgs = [...args,...arguments]
        // 在这里完成self(调用bind的函数)的执行以及修改this
        // 同样的由于原函数存在有返回值的情况,此处也需要return

        // 使用instanceof来判断使用返回的函数是否是用new调用的
        // 不是new调用的时候this才会指向context
        return self.apply(this instanceof newFun ? this : Object(target),allArgs)
    }
    // 定义一个空函数作为中间变量
    let fn = function(){}
    // 让这个中间变量的prototype指向绑定函数的prototype
    fn.prototype = this.prototype
    // 让返回函数的prototype指向这个中间变量的实例
    newFun.prototype = new fn()
    return newFun
}

以上就是bind函数的实现啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值