JS中call、apply、bind

一、原生call、apply、bind的使用

        函数对象的方法,需要通过函数对象来调用。

        (1)相同:改变函数体内 this 的指向

        三者第一个参数都是this要指向的对象,如果没有这个参数或参数为undefined或null,则默认指向全局window。

        在 ES6 的箭头函数下, call 和 apply、bind 将失效 ;

箭头函数的特点:

1. 体内的 this 对象, 就是定义时所在的对象, 而不是使用时所在的对象

2. 箭头函数不可以当作构造函数来用,也就是说不可以使用 new 命令, 否则报错

3. 箭头函数不可以使用 arguments 对象,该对象在函数体内不存在,可以用 Rest 参数代替

4. 不可以使用 yield 命令, 因此箭头函数不能用作 Generator 函数

        (2)不同之处:

        接受参数的方式不一样。apply是数组,而call和bind接收是参数列表;

        apply和call是一次性传入参数,而bind的返回值也可以传入参数

       apply、call 立即执行, bind不会立即执行,而是返回一个改变了上下文 this 后的函数,便于稍后调用。而原函数 中的 this 并没有被改变,依旧指向原来该指向的地方。

let obj={
    age:10,
    getAge:function (num1,num2,msg){
        console.log(this.age+'-----'+(num1+num2)+'----'+msg)
        return this.age
    },
    test:(num1,num2,msg)=>{
        console.log(this.age+'-----'+(num1+num2)+'----'+msg)
        return this.age
    }
}
let obj2 = {
    age: 16
}
obj.getAge()
obj.getAge.call(obj2,12,23,'男')//16-----35----男
obj.getAge.apply(obj2,[12,2,'男'])//16-----14----男
obj.getAge.bind(obj2,12,20,'男')()//16-----32----男
//bind的返回值也可以传入参数
obj.getAge.bind(obj2, 1,2)('女')//16-----3----女

//箭头函数下失效
obj.test.call(obj2,12,23,'男')//undefined------35----男
obj.test.apply(obj2,[12,2,'男'])//undefined------14----男
obj.test.bind(obj2,12,20,'男')()//undefined------32----男
//bind的返回值也可以传入参数
obj.test.bind(obj2, 1,2)('女')//undefined------3----女

        当前实例通过原型链的查找机制,找到function.prototype上的call、apply、bind方法。

        (3)补充

  • call 的性能要比apply好一点(尤其是当函数传递参数超过3个的时候),call 用扩展运算符可以代替apply 
  • bind 返回的函数不可以作为构造函数!

二、手写实现call

        原生的call方法在原型链上,因此在Function原型链上绑定自己写的myCall方法。

        实现思路:

        (1)判断调用mycall的是不是函数,如果不是,抛出异常;

        (2)判断是否传入了第一个参数context,即要指定的this值,如果没有传入,则默认为window全局对象;

        (3)this为要调用mycall的函数,将其赋给context.fn;

        (4)通过参数伪数组arguments将context后面的参数取出来,并传给context.fn获得执行结果result;

        (5)最后删除context.fn,返回result。

function Person(a,b,c,d){
    return{
        name:this.name,
        a:a,b:b,c:c,d:d
    }
}
var egg={name:"小明"}
Function.prototype.MyCall=function (context){
    if(typeof this !== 'function'){
        throw new TypeError ('Error')
    }
    //context就是我们要this要指向的对象。如果context不存在的话,默认指向window
    var obj=context||window;
    //把要执行的函数绑定到context的属性上,满足mycall函数内的this指向fn
    obj.fn=this;
    //----写法1---用扩展符号----
    //...arguments可以将一个类数组转为一个数组,
    // 删掉第一个参数,第一个参数是携带this指向的对象,第二个参数开始才是传入函数需要的参数。
    // const args = [...arguments].slice(1);
    // var result = obj.fn(...args) // 执行函数
    //------写法2----
    var newArguments=[];
    for(let i=1;i<arguments.length;i++){
        newArguments.push('arguments['+i+']');
    }//[ 'arguments[1]', 'arguments[2]', 'arguments[3]', 'arguments[4]' ]
    // eval() 函数作用:可以接受一个字符串str作为参数,并把这个参数作为脚本代码来执行。
    // 数组和字符串相加时,数组会调用toString方法
    var result = eval("obj.fn("+newArguments+")");
    //------------
    delete obj.fn;//执行之后删除这个函数,不能改写对象
    return result;
}

var res1=Person.MyCall(egg,'你好','嘿嘿','哈哈','小小')
console.log("MyCall:",res1)//MyApply: { name: '小明', a: '你好', b: '嘿嘿', c: '哈哈', d: '小小' }

 三、手写实现apply

Function.prototype.MyApply=function (context){
    if(typeof this !== 'function'){
        throw new TypeError ('Error')
    }

    var obj=context||window,result;
    obj.p=this;
    if(!arguments[1]){//如果没有数组
        result =obj.p();
    }else{
        //----写法1---用扩展符号----
        // result = obj.p(...arguments[1])//或者arguments[1]改为arr
        //------写法2----
        var newArguments=[];
        let arr = [...arguments[1]]
        for(let i=0;i<arr.length;i++){
            newArguments.push('arr['+i+']');
        }
        result = eval("obj.p("+newArguments+")");
    }
    delete obj.p;
    return result;
}

var res2=Person.MyApply(egg,['你好','嘿嘿','哈哈','小小'])
console.log("MyApply:",res2)//MyApply: { name: '小明', a: '你好', b: '嘿嘿', c: '哈哈', d: '小小' }

四、手写实现bind

        bind方法配合new使用时,this值会失效,因此此处对返回函数判断this是否是new 的实例对象,如果是,将apply的this绑定到新实例(此处为Person的实例)上面。

Function.prototype.MyBind=function (obj){
    if(typeof this !== 'function'){
        throw new TypeError ('Error')
    }
    obj = obj || window;
    var that = this;//保存当前调用的函数,此处为Person
    var arr=Array.prototype.slice.call(arguments,1);
    //var arr=[...arguments].slice(1);//写法2  第一个括号传的参数['你好', '嘿嘿']
    var o=function (){};//用原型式继承的理念来进行继承避免过多修改
    var newf = function (){
        var arr2=Array.prototype.slice.call(arguments),
            arrSum=arr.concat(arr2);//全部参数
        // const arrSum=args.concat(...arguments);//写法2 第二个括号传的所有参数
        if(this instanceof o){//this为newf的实例,此时函数newf的原型对象已经更改为空函数的实例了
            return that.apply(this,arrSum);//将apply的this绑定到新实例上面
        }else{
            return that.apply(obj,arrSum);//否则就按照原本的obj对象绑定
        }
    };
    //将person原型对象赋值给res3这个函数的原型对象,使得res的实例b可以用到person原型对象的属性
    o.prototype=that.prototype;//o的原型对象修改为这里this(函数Person)的原型对象
    newf.prototype=new o;//将新函数的原型对象(也就是函数Person的原型对象)作为空函数o的实例进行串联
    return newf;
}

var res3=Person.MyBind(egg,'你好','嘿嘿');
var b=new res3('hahha',"22")// bind方法配合new使用时,this值会失效
console.log("MyBind:",b)//MyBind: { name: undefined, a: '你好', b: '嘿嘿', c: 'hahha', d: '22' }

var res4=Person.MyBind(egg,'你好','嘿嘿','呵呵',"22")();
console.log("MyBind:",res4)//MyBind: { name: '小明', a: '你好', b: '嘿嘿', c: '呵呵', d: '22' }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值