• 深入理解this和call、bind、apply对this的影响及用法


      首先看一道网易的面试题:

    var a = {
        a:"haha",
        getA:function(){
            console.log(this.a);
        }    
    }
    var b = {
            a:"hello"
        }
    var getA = a.getA;
    var getA2 = getA.bind(a);
    function run(fn){
        fn();
    }
    //分别输出
    a.getA();//haha
    getA();//window下面的a对象
    run(a.getA);//window下面的a对象
    getA2.call(b);//haha

      这里考察了三个点:形参实参的理解、this的指向、call和bind对this指向的影响。

    一、this指向问题

      关于this指向的问题,上一篇:理解JavaScript里this关键字有比较好的总结。

      (1)这里有三种简单情况:

      1、如果函数中的this没有调用它的对象,那么this指向的就是window(注意:严格模式下这种情况的this会为空,即undefined

      2、如果函数中的this被不包含子对象的对象所调用,那么this指向的就是调用它的对象。

      3、如果函数中的this被包含多级对象的对象调用,this指向的也只是它上一级的对象,如下例:

    var demoObj = {
        a:1,
        b:{
            fun:function(){
                console.log(this.a); 
            }
        }
    }
    demoObj.b.fun();//undefined,因为this指向demoObj.b,b里面没有a

      这里this不是指向demoObj对象,而是指向demoObj.b对象,这里找不到demoObj.b对象里的a,所以会输出undefined。 

      (2)还有三种特殊情况:

      1、还是上面的例子,改一下调用函数的方式,如下。

    var demoObj = {
        a:1,
        b:{
            fun:function(){
                console.log(this.a); 
            }
        }
    }
    var newFun = demoObj.b.fun;
    newFun();//undefined,this指向window

      这里还是得到undefined,但是this的指向却是window,这里的undefined是因为没找到window对象里的a,才输出的undefined。虽然函数fun是被对象b所调用,但是在将fun赋值给变量newFun的时候并没有执行,newFun的上级对象window,所以最终执行时指向的是window。

      2、构造函数用new实例对象时对this的影响。

    function Fun(){
        this.name = "haha";
    }
    var stu = new Fun();
    console.log(stu.name); //haha

      这里之所以对象stu.name可以输出haha,是因为new关键字就是创建一个对象实例,这个stu对象中包含了this.name这个属性,相当于复制但却没有执行。在执行时调用这个函数Fun的是对象stu,所以this指向的就是对象stu。

      用new操作符创建对象时发生的事情:

      第一步: 创建一个Object对象实例。
      第二步: 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)。
      第三步: 执行构造函数中的代码(这里的执行并不是真的让this指向哪里,而是为这个新对象添加属性)。
      第四步: 返回新生成的对象实例

      原本的构造函数是window对象的方法,如果不用new操作符而直接调用,那么构造函数的执行对象就是window,即this指向了window。现在用new操作符后,this就指向了新生成的对象。理解这一步至关重要。

      3、有return的函数在new时对this的影响(正常的构造函数是没有return语句),我们先看下面的几个例子。

    //例1
    function Fun()  
    {  
        this.name = 'haha';  
        return {};  
    }
    var stu = new Fun();  
    console.log(stu.name); //undefined,stu为{}
    //例2
    function Fun()  
    {  
        this.name = 'haha';  
        return function(){};
    }
    var stu = new Fun();  
    console.log(stu.name); //undefined
    //例3
    function Fun()  
    {  
        this.name = 'haha';  
        return 123;
    }
    var stu = new Fun();  
    console.log(stu.name); //haha,stu为Fun的实例
    //例4
    function Fun()  
    {  
        this.name = 'haha';  
        return undefined;
    }
    var stu = new Fun();  
    console.log(stu.name); //haha

      可以看出:如果return的是一个对象,那么this会指向返回的对象,如果return的不是一个对象,那么this还是指向函数的实例。

      但是return的是null时比较特殊。虽然null也是对象,但是this还是指向函数的实例。

    //例5
    function Fun()  
    {  
        this.name = 'haha';  
        return null;
    }
    var stu = new Fun();  
    console.log(stu.name); //haha

    二、call、bind、apply对this指向的影响

      call和apply只有参数不同,这里就只讨论call,因为call和bind参数使用方法是一样的。

      1、call是动态的改变this的指向,即换个对象执行原对象方法的方法,并立即执行;

      2、bind是静态改变this的指向,并返回一个修改后的函数。

      就拿开始的题目最后一个输出来说:

      如果只是使用call的话:在执行到这两句时动态改变了this的指向,所以call(b)的输出hello,call(a)的输出haha。

    getA.call(b);//hello
    getA.call(a);//haha

      接下来看有bind影响的:

    var getA2 = getA.bind(a);

      这里getA其实是a.getA,那么getA.bind(a)将this指向a,其实还是返回了a.getA函数赋值给了getA2。注意:其实函数没有变化,但是内部已经将this指向了a

    getA2.call(b);//haha
    //相当于a.getA.call(b);

      此时无论call里是a还是b,都会输出haha,因为内部的this已经被bind绑定指向bind里面的a了。怎么理解呢,看下面示例:

    var getA2 = getA.bind(a);
    var getA3 = getA.bind(b);
    getA2.call(b);//haha
    getA3.call(b);//hello
    
    var getA2 = getA.bind(a);
    var getA3 = getA2.bind(b);
    getA2.call(b);//haha
    getA3.call(b);//haha

      使用bind静态指定this:第一次使用bind后,this就被固定为bind的参数了,call、apply、bind均无法改变。

      如果new一下,this就是指向当前构造函数的实例;其他情况,this一直被静态绑定为a

    var a = {
        a:"haha",
        getA:function(){
            this.c = "12345678";
            console.log(this,this.a);
        }    
    }
    var b = {
            a:"hello"
        }
    var getA = a.getA;
    var getA2 = getA.bind(a);
    var getA3 = getA2.bind(b);
    var getObj = new getA3();//new一下,this就是当前构造函数的实例
    getA3();//其他情况,this一直被静态绑定为a

      总的来说,call方法是在调用时改变this并立即执行这个函数,bind方法可以先改变函数中的this,之后对应的函数可以在需要的时候再调用。

    三、call、bind、apply用法:

      1、fun.apply(context,[argsArray])

      立即调用fun,同时将fun函数原来的this指向传入的新context对象,实现同一个方法在不同对象上重复使用

      context:传入的对象,替代fun函数原来的this;

      argsArray:一个数组或者类数组对象,其中的数组参数会被展开作为单独的实参传给 fun 函数,需要注意参数的顺序。 

      2、fun.call(context,[arg1],[arg2],[…])

      同apply,只是参数列表不同,call的参数需要分开一个一个传入。如果不知道参数个数,则使用apply。

      使用:

    Math.max()    //只接收单独的参数,通过下面的方法可以在数组上面使用max方法:
    Math.max.apply(null, array);    //会将array数组参数展开成单独的参数再传入
    Array.prototype.push.apply(arr1,arr2);    //将一个数组拆开push到另一个数组中;不用apply则会将后续数组参数当成一个元素push进去。
    Array.prototype.slice.call(arguments);    //在类素组对象上使用slice方法
    function isArray(obj){
        return Object.prototype.toString.call(obj) === '[object Array]' ;
    }    //验证是否是数组

      3、fun.bind(context,[arg1],[arg2],[…])

      使fun方法执行的context永不变。静态指定this

      arg1:要传递到新函数的参数列表

      返回一个函数供后续调用,其函数体和原函数fun一样,但新函数的this指向新传入的context对象。新函数会具有bind方法指定的初始参数arg1/arg2...,后续调用新函数时的实参要往已有参数的后面排。就是科里化方式,绑定默认的参数,后面的参数可以变化。

      使用:

    //原来的函数有4个参数
    var displayArgs = function (val1, val2, val3, val4) {
        console.log(val1 + " " + val2 + " " + val3 + " " + val4);
    }
    var emptyObject = {};
    // 生成新函数时bind方法指定了2个参数,则新函数会带着这个两个实参
    var displayArgs2 = displayArgs.bind(emptyObject, 12, "a");
    // 调用时传入另2个参数,要在bind方法传入的2个实参后面
    displayArgs2("b", "c");
    // Output: 12 a b c

      bind的参数可以在执行的时候再次添加,但是要注意的是,参数需要按照形参的顺序添加

    var demoObj = {
        name:"haha",
        fun:function(a,b,c){
            console.log(a,b,c);
        }
    }
    var newFun = demoObj.fun;
    var newFun2 = newFun.bind(demoObj,5);
    newFun2(7,9);//5,7,9
  • 相关阅读:
    原型模型
    V模型
    瀑布模型
    微服务的特点 优点 缺点
    ip地址的分类
    DSSA特定领域软件体系结构
    Git操作 :从一个分支cherry-pick多个commit到其他分支
    【原理】从零编写ILI9341驱动全过程(基于Arduino)
    Arduino驱动ILI9341彩屏(一)——颜色问题
    STL库学习笔记(一)——什么是STL?
  • 原文地址:https://www.cnblogs.com/goloving/p/7259988.html
Copyright © 2020-2023  润新知