2020前端面试复习-js部分-call/apply/bind的应用

94 阅读3分钟

call,apply,bind应用

this的几种情况

基于call,apply,bind来改变函数中的this

  1. Function.prototype->call,apply,bind所有的函数都可以调用这三个方法

    1. call fn先基于__proto__找到Function.prototype.call,把call方法执行的时候,call方法内部实现了一些功能,会把fn执行,并且让fn中的this变为第一个实参值
    2. apply的作用和细节上和call是一样的,只是传递给函数实参的方式不一样
    fn.call(obj,10,20);
    fn.apply(obj,[10,20]);
    
    function sum(){
        let arr=[].slice.call(arguments)
        return arr.reduce((item,result)=>item+result)
    }
    sum(1,2,3)->6
    

    重写call

    let obj = {
        name:'xxx',
        age:11
    };
    function fn(x,y){
        console.log(this);
        return x+y
    }
    let result = fn.call(obj,10,20)
    console.log(result)
    

    call实现思路:把需要执行的函数fn,和需要改变this指向的的obj关联在一起->obj.xxx=fn,此时我们只需要obj.xxx()就相当于把fn执行,而且函数中的this就是obj

    let obj = {
        name:'xxx',
        age:11,
        fn:fn
    };
    function fn(x,y){
        console.log(this);
        return x+y
    }
    let result = obj.fn(10,20)
    console.log(result)
    

    优化

    1. 临时给context设置的属性不能和原始对象中的属性冲突
    2. 参数的处理
      1. context不传递或者传递null,最后要改的this都会是window
      2. 必须保证context都是引用数据类型值(不论传递给什么类型)
    Function.prototype.call = function call(context,...params){
        //this->fn 当前执行的函数
        //context->obj 需要改变的this
        //params->[10,20]需要函数传递的实参信息
        context == null ? context = window :null
        !/^(Object|function)$/.test(typeof context) ? context = Object(context) :null
        let key = Symbol('key'),
            result
        context[key] = this;
        let result = context[key](...params);
        delete context[key];
        return result;
    }
    

    重写bind

    let obj = {
        name:'xxx',
        age:11
    };
    function fn(x,y){
        console.log(this);
        return x+y
    }
    document.body.click=fn.bind(obj,10,20)
    
    document.body.onclick = function(ev){
        fn.call(obj,10,20,ev)
    }
    

    我们期望,不论是事件触发, 还是定时器到时见,执行对应的方法时,可以改变方法中的this,以及给方法传递实参信息

    1. 直接用下面的方法操作是不可以的:call/apply在处理时会把函数立即执行,也就是在时间绑定或者设置定时器的时候,fn就执行了,而不是等待事件触发或者定时器到时见之后再执行 '立即处理的思想‘
    document.body.onclick=fn.call(obj,12,20)
    

    预先处理思想(柯里画函数)

    1. 绑定方法的时候先绑定一个匿名函数,事件触发或者到达时间,先把匿名函数执行,在执行匿名函数的时候,再把我们需要执行的fn执行,此时就可以基于call、apply改变this了
    2. bind相对于call、apply老说,并不会把函数立即执行,只是事先处理了要改变的this和参数,一切的执行还是按照原有的时间或者出发节点进行
    3. document.body.onclick=fn.call(obj,10,20)
    let obj = {
        name:'xxx',
        age:11
    };
    function fn(x,y,ev){
        console.log(this,ev,x,y);
        return x+y
    }
    document.body.onclick = function(){
        //this->body
        fn.call(obj,10,20,ev);
    }
    setTimeout(function(){
        //this->window
        fn.call(obj,10,20,ev);
    },1000)
    
    //原理:闭包“柯里化”
    Function.prototype.bind = function bind(context,...params){
        //this->fn最后要执行的函数
        //context->obj 最后要改变的this
        //params->[10,20] 最后要传递的参数
        let that = this
        return function proxy(...args){
            params = params.concat(args)
            return that.call(context,...params);
        }
    }
    

    箭头函数改变this

    let obj = {
        name:'xxx',
        age:11,
        fn:function(){
            //this->obj
            let that = this
            <!-- return function(){
                //this->window
                //如果需要改变obj.name
                that.name= 'jsy'
                console.log(this)
            } -->
            return ()=>{
                this.name = 'jsy'
                console.log(this)
            }
        }
    };
    let f = obj.fn();
    f()//