你不知道的this指向

555 阅读4分钟

一、this到底是什么

  • 学习this的第一步是明白this既不指向函数自身也不指向函数的词法作用域,this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

二、this的四种绑定规则

  • 默认绑定

    默认绑定一般发生在回调函数,函数直接调用;

    function fn() {
        console.log( this.a )
    }
    let a = 2;
    fn(); //2
    

    this.a被解析成了全局变量a,函数调用时应用了this的默认绑定,因此this指向全局对象。在代码中,fn是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

    如果使用严格模式(use strict),则不能将全局对象用于默认绑定,因此this会绑定到undefined

  • 隐式绑定

    通俗来说谁最终调用函数,this指向谁

    const obj = {
        name : 'lyq',
        getName () {
            console.log( this ); //obj
            console.log( this.name ); //lyq
        }
    };
    obj.getName();
    

    链式调用只有最后一层在调用位置中起作用

    var obj2 = {
        name:'lyq',
        foo() {
            console.log( this.name )
        }
    }
    var obj1 = {
        name:'zs',
        obj2:obj2
    }
    
    obj1.obj2.foo(); // lyq
    
    隐式丢失:

    一个最常见的this绑定问题是被隐式绑定的函数会丢失绑定对象,也就是你说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式

    var a = 100;
    
    var obj = {
        a : 2,
        foo() {
            console.log( this.a )
        }
    } 
    
    var bar = obj.foo;
    
    bar(); //100
    

    obj.foo 是引用属性,赋值给bar的实际上就是foo函数(即:bar指向foo本身,因此应用了默认绑定,指向全局属性a

  • 显示绑定call,apply,bind

    相对隐式绑定,this值在调用过程中会动态变化,可是我们就想绑定指定的对象,这时就用到了显示绑定。

    显示绑定主要是通过改变对象的prototype关联对象。具体使用上,可以通过这两个方法call(…)或apply(…)来实现(大多数函数及自己创建的函数默认都提供这两个方法)。

    function foo() {
        console.log( this.a )
    }
    
    var obj = {
        a : 2
    }
    
    foo.call( obj ) // 2
    
    

    call和bind是一样的,区别在于参数的设置上。

    硬绑定
    function foo() { 
        console.log( this.a );
    }
    
    var a = 2;
    
    var obj1 = { 
        a: 3,
    };
    
    var obj2 = { 
        a: 4,
    };
    
    var bar = function() {
      foo.call( obj1 );
    }
    
    setTimeout( bar, 100 ); // 3
    
    bar.call( obj2 );  //3
    
    

    这里需要注意下,虽然bar被显示绑定到obj2上,对于函数bar 中的this确实被绑定到了obj2,而foo因为通过foo.call( obj1 )已经显示绑定了obj1,所以在foo函数内,this指向的是obj1,不会因为bar函数内指向obj2而改变自身。所以打印的是obj1.a(即3)。

  • new绑定

    function foo(a) { 
        this.a = a;
    }
    
    var a = 2;
    
    var bar1 = new foo(3);
    console.log(bar1.a); // 3
    
    

    使用new来调用foo()时,会创造一个新对象并把它绑定到foo()调用中的this上。

    三、箭头函数this指向

    首先需要明确,箭头函数不适用以上this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this

    箭头函数this的定义:箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定 。

    var name = 'lyq';
    var obj = {
      name : 'zs',
      getName:function() {
        console.log( this.name )
      }
    }
    obj.getName(); //zs
    
    
    var name = 'lyq';
    var obj = {
      name : 'zs',
      getName:()=>{
        console.log( this.name )
      }
    }
    obj.getName(); //lyq
    

    所谓的定义时候绑定,就是this是继承自父执行上下文中的this,比如这里的箭头函数中的this.name,箭头函数本身所在的对象为obj,而obj的父执行上下文就是window,因此这里的this.name 实际上表示的是window.name,因此输出的是lyq。(this只有在函数被调用,或者通过构造函数new Object()的形式才会有this)

    注意简单对象(非函数)是没有执行上下文的!

    在setInterval和setTimeout中传入函数时,函数中的this会指向window对象。箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

    export default {
      data () {
        return {
          name: zs,
        }
      },
      methods: {
        setName () {
          setTimeout(() => {
            this.name = 0;
          }, 500);
        }
      }
    }
    
    

    此时函数的this指向的是定义它的时候的对象,也就是this指向了data内中对应的变量。如果使用function,this则会指向window,无法获得当前对象。