【手写】call、apply、bind

call

// 首先明确原生call的作用
//fn.call(a, ...args) //作用就是a.fn(...arg) 让a来调用fn,this指向a
// 返回值就是执行目标函数后的返回值,所以要在内部执行该函数
Function.prototype.myCall = function (context = window, ...args) {
    // 让context的默认值为window, ...args表示剩余所有参数 args是所有参数的数组

    // 函数内部this指向外部调用该函数的对象,此处场景下指向fn.call的fn
    // 给context添加一个属性
    context.fn = this
    console.log('args',args);
    // 运行目标函数,得到返回值,args是数组,还是要用扩展运算符分出来
    let result = context.fn(...args)

    // 删除fn属性,否则context属性会越来越多
    delete context.fn

    return result
}


// 上面的方法形参里有arg ,其实可以用arguments属性调用
Function.prototype.myCall2 = function (context = window) {
    // arguments是函数的内置属性,表示传递进来的所有参数
    // 就算没有在括号里传入参数,也可以通过该属性得到其他途径传入的(跟this一样,由js内部传递进去)
    // 比如父组件给子组件的, arguments是一个伪数组,下面的操作是变成真数组并且index从1开始才是参数
    // index为0的是传进来的context

    let args = [...arguments].slice(1)
    context.fn = this
    // 使用args的时候要把数组拆开
    let result = context.fn(...args)
    delete context.fn
    return result
}

// 还有种情况是node.js环境下,全局变量不是window,而是global
// 有可能context上面已经有了fn属性,如果,就会覆盖原本的属性
Function.prototype.myCall2 = function (context) {
    if(context === undefined || context === null) {
        // globalThis是es11的新关键字,指当前环境下的全局变量
        context = globalThis
    }

    // arguments是函数的内置属性,表示传递进来的所有参数
    // 就算没有在括号里传入参数,也可以通过该属性得到其他途径传入的(跟this一样,由js内部传递进去)
    // 比如父组件给子组件的, arguments是一个伪数组,下面的操作是变成真数组并且index从1开始才是参数
    // index为0的是传进来的context

    let args = [...arguments].slice(1)

    // 用Symbol加上一个唯一的属性
    let key = Symbol('key')
    context[key] = this
    // 使用args的时候要把数组拆开
    let result = context[key](...args)
    delete context[key]
    return result
}

apply

// apply的参数是数组,所以...args应该是一个完整的数组,到时候直接传入就行
Function.prototype.myApply = function(context=window ,...args){
    // args是数组包了一层数组
    context.fn = this
    // 把参数从args里取出来,是一个数组形式,再扩展运算符打开
    let result = context.fn(...args[0])
    // let result = context.fn(...arguments[1])
    delete context.fn
    return result
}


// apply的参数是数组,所以参数直接写args就行,写...args有点麻烦
Function.prototype.myApply2 = function(context=window ,args){
    if(context ===undefined||context===null){
        context = globalThis
    }
    let key = Symbol('key')
    context[key] = this
    // 把参数从args里取出来,是一个数组形式,再扩展运算符打开
    let result = context[key](...args)

    delete context[key]
    return result
}

bind

bind与上面两个不同,是返回的一个函数,属于函数柯里化,所以要判断当返回的函数是构造函数的时候,要可以进行new操作,参考文章1 文章2

比如
f3 = f1.bind(f2),
f4 = new f3(),

这里的f4既不是f1也不是f2,是一个全新的实例对象
new操作后得到的新对象,要是一个不同于f1和f2

//fn.bind(a, ...args) //返回的是一个函数,需要自己去调用,a.fn(...arg)() 让a来调用fn,this指向a
Function.prototype.myBind1 = function (context, ...outerArgs) {
    let self = this
    // 返回一个函数
    return function (...innerArgs) {
        return self.call(context, ...outerArgs, ...innerArgs)
    }
}

// 手写bind不能用箭头函数
Function.prototype.myBind2 = function (context, ...outerArgs) {
    if (typeof this !== 'function') {
        throw new TypeError('error')
    }
    let self = this

    // 返回一个函数
    return function (...innerArgs) {
        return self.call(context, ...outerArgs, ...innerArgs)
    }
}


Function.prototype.myBind3 = function (context, ...outerArgs) {
    // this->func context->obj outerArgs->[10,20]
    let self = this

    // 返回一个函数
    return function F(...innerArgs) { //返回了一个函数,...innerArgs为实际调用时传入的参数
        // 考虑F如果是一个构造函数,那么返回出去,就有可能被new F()
        // 如果F是构造函数,那么说明调用bind的this即self也是构造函数

        /* 
            为什么要用new
            参考   https://blog.csdn.net/weixin_39570777/article/details/111388167?ops_request_misc=&request_id=&biz_id=102&utm_term=%E6%89%8B%E5%86%99bind%E4%B8%ADnew&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-6-.first_rank_v2_pc_rank_v29&spm=1018.2226.3001.4187
            当使用bind的函数是一个构造函数时,使用完毕后应该返回去的同样也是一个构造函数
            如此,返回去的函数应该是可以进行new 的
            new里面发生的步骤,先回顾一下,
                首先创建一个空对象,把构造函数的原型对象绑定到空对象的__proto__上
                这个空对象就是被返回的实例对象,
                然后执行构造函数并把this绑定这个实例,即用实例调用构造函数,得到返回结果,可能有也可能没用
                最后如果结果是一个对象,就返回这个对象,如果不是对象就返回这个实例

            那么在此情况下,必须要考虑返回去的函数是可以成功进行new操作的
            当外部在 new F()的时候,调用该返回的F函数,要得到一个实例对象,或者一个F运行后的返回对象
            new内部要把this交给内部新生成的实例对象
            内部会有这段代码 F.call(f) , 此时this指向f,f instanceof F为true
            所以this instanceof F 为true
            那么就要返回

        
        */
        if (this instanceof F) {
            // 这下面的new 内部会返回一个新对象,执行调用bind的原构造函数的结果
            return new self(...outerArgs, ...innerArgs)
            // 或者,此处的this为new 内部的F 的实例对象,是一个全新的对象,既不是调用bind的对象,也不是调用bind后指定的对象,
            // 换句话说f3 = f1.bind(f2), f4 = new f3(),这里的f4既不是f1也不是f2,是一个全新的实例对象
            return self.call(this, ...outerArgs, ...innerArgs)
        }
        // 把func执行,并且改变this即可
        return self.apply(context, [...outerArgs, ...innerArgs]) //返回改变了this的函数,参数合并
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值