call,apply,bind改变this指向及实现原理

1.使用方法

一个小例子,解释call,apply,bind的使用

var value = '外面的值'
function consoleLogVal(x,y){
    console.log(this,"this指向")
    console.log(this.value)
    console.log(`x:${x},y:${y}`)
}
var callObj = {
    value:'callObj'
}
var bindObj = {
    value:'bindObj'
}
var applyObj = {
    value:'applyObj'
}

consoleLogVal('normal1','normal2');

consoleLogVal.call(callObj,'call1','call2');

consoleLogVal.bind(bindObj,'bind1','bind2')();

consoleLogVal.apply(applyObj,['apply1','apply2'])

综上所述,我们可以知道:

  • call,apply,bind可以改变this指向
  • 普通调用时,this指向全局window
  • 使用call,apply,bind后,this指向改为传递的第一个参数
  • call,bind传递参数是逗号分隔,一直向后传递,apply传递参数以数组形式
  • call,apply直接调用,bind方法需要使用立即调用函数形式

2.原理解析

2.1 call实现

// 实现代码
Function.prototype.myCall = function(ctx,...args){
    let handler = Symbol(); // 生成一个唯一的值,用来作为要绑定对象的属性key,存储当前调用call方法的函数
    
    if(typeof this !== 'function'){
        throw this+'.myCall is not a function'
    }
    // 若第一个参数为引用类型或者null
    if(typeof ctx === 'object' || ctx === 'function'){
        // 如果为null,则this为window
        ctx = ctx || window
    }else{
        // 如果为undefined,则this绑定为window
        if(typeof ctx === 'undefined'){
            ctx = window
        }else{
            // 基本类型包装 1 => Number(1)
            ctx = Object(ctx)
        }
    }
    // this为当前调用call方法的函数
    ctx[handler] = this;
    // 执行这个函数,这时这个函数内部this绑定为ctx,存储函数执行后的返回值
    let result = ctx[handler](...args)
    // 删除对象上面的函数
    delete ctx[handler]
    // 返回值
    return result
}

// 使用示例:
function consoleLogVal(x,y){
    console.log(this,"this指向")
    console.log(this.value)
    console.log(`x:${x},y:${y}`)
}
var callObj = {
    value:'callObj'
}
consoleLogVal.myCall(callObj,'call1','call2');

2.2 apply实现

Function.prototype.myApply = function(ctx,argsArr){
    let handler = Symbol();  
    if(typeof this !== 'function'){
        throw this+'.myApply is not a function!'
    }
    let args = [];
    // 判断apply函数传递的参数是不是数组形式
    if(typeof argsArr === 'object' || typeof ctx === 'function' || typeof argsArr === 'undefined'){
        args = Array.isArray(argsArr)? argsArr :[];
    } else {
        // 如果为基本类型,如果是undefined,则无效,其它类型则抛出错误。
        throw 'TypeError: CreateListFromArrayLike called on non-object'
    }

    if(typeof ctx === 'object'){
        ctx = ctx || window
    }else{
        if(typeof ctx === 'undefined'){
            ctx = window
        }else{
            ctx = Object(ctx)
        }
    }

    ctx[handler] = this; // 在对象中加入这个方法

    // 执行该方法,得到返回值
    let result = ctx[handler](...args)

    // 删除新增的方法,恢复本来样貌的对象
    delete ctx[handler]

    return result
    
}

var applyObj = {
    value:'applyObj'
}
consoleLogVal.myApply(applyObj,['apply1','apply2'])

2.2 bind实现

我们根据使用结果可知,在使用bind函数时,需要自执行一下,所以返回的是一个函数

Function.prototype.myBind=function(context,...args) {
    // 这里的this为调用bind方法的函数。
    let thisFunc=this;
    // 如果调用bind的变量不是Function类型,抛出异常。
    if(typeof thisFunc!=='function') {
        throw new TypeError('Function.prototype.bind - '+
            'what is trying to be bound is not callable');
    }
    // 定义一个函数boundF
    // 下面的”新函数“ 均为函数调用bind方法之后创建的函数。
    let boundF=function() {
        // 这里的 arguments 为函数经过bind方法调用之后生成的函数再调用时的实参列表
        let boundFAgrs=arguments;
        // 把调用bind方法时除第一个参数外的参数集合与新函数调用时的参数集合合并。当做参数传递给call方法
        let totalAgrs=[...args,...arguments];
        
        // 如果调用新函数时存在新的this,并且新的this不是全局对象,那么我们认为这里想要更改新函数this的绑定。因此让新函数的内部this绑定为当前新的this。
        
        thisFunc.call(this && this !== window ? this : context,...totalAgrs);
    }
    //通过原型链继承的方式让原函数的原型和新函数的原型,都在通过new关键词构造的新对象的原型链上
    // b instanceof 原函数  -> true
    // b instanceof 新函数  -> true
    var F=function() {};
    F.prototype=thisFunc.prototype;
    boundF.prototype=new F();

    return boundF;
}


var bindObj = {
    value:'bindObj'
}
consoleLogVal.myBind(bindObj,'bind1','bind2')();

学习来源,这篇文章真的超级好

学习用法,希望可以帮助到你,
我是Ably,你无须超越谁,只要超越昨天的自己就好~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值