迫于

迫于在写 React 事件处理中的 this 时,写到 this 突然卡壳了,一顿冷静分析之后发现自己对于 this 的理解又忘了一些,于是痛定思痛,重新读了 《You-Dont-Know-JS》中关于 this 的文章。


函数的执行环境

首先上一段代码

function fn(){
    console.log(this.a)
}
let obj = {
    a: 1,
    fn: fn
}

var a = 'window'

fn()  //window
obj.fn()  //1

首先我们要明确一点 ,this 不是编写代码时就绑定的,而是运行时绑定的。 比如 fn() 在调用时,所处的环境是 global,所以他的 this 就是 windowobj.fn() 在调用时,所处的环境是在对象 obj 内,所以他的 this 指向 obj 对象

当一个函数被调用时,会建立一个称为执行环境的活动记录。这个记录包含函数是从何处(调用栈 —— call-stack)被调用的,函数是 如何 被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的 this 引用。

所以,this是根据调用点(函数如何被调用)来为每次函数调用建立的绑定


调用点的规则

  • 默认绑定: 首先是独立函数调用。在这种倾向下,this 规则是在没有其他规则适用时的默认规则。

    function fn(){
      console.log(this)
    }
    
    fn()  //window
    

    fn() 被独立调用,此时的 this 是默认绑定,所以 this 指向了 window

  • 隐含绑定 这种规则是:调用点是否拥有一个对象环境。

    function fn(){
      console.log(this.a)
    }
    
    let obj = {
      a: 1,
      fn: fn
    }
    
    obj.fn()  //1
    

    首先,fn被声明然后作为引用属性添加到 obj 上,我们知道,对象、函数、数组等都是引用类型,存在于堆内存中,以传值(地址)的方式被变量引 用,所以 obj 并不包含或者拥有函数 fn

    obj.fn() 的调用点以 obj 环境来引用函数,所以,函数在被调用的时间节点上拥有对象环境 obj

    对象属性引用链的最后一层是影响调用点的

    function fn() {
      console.log( this.a );
    }
    
    let obj2 = {
      a: 42,
      fn: fn
    };
    
    let obj1 = {
      a: 2,
      obj2: obj2
    };
    
    obj1.obj2.fn(); // 42
    
  • 隐含丢失 隐含丢失:隐含绑定丢失了他的绑定,如何丢的?看代码

    function fn(){
      console.log(this.a)
    }
    
    let obj = {
      a: 1,
      fn: fn
    }
    
    let foo = obj.fn
    
    var a = 'window'
    
    foo()  //window
    

    这段代码,将 foo 引用了 obj.fn,但是在 foo(),他依然属于函数独立调用,所以 this 指向全局作用域的 a

    function fn(){
      console.log(this.a)
    }
    
    function bar(f){
      f()
    }
    
    let obj = {
      a: 1,
      fn: fn
    }
    
    var a = 'window'
    
    bar(obj.fn)  //  window
    

    这段代码中,将 obj.fn 作为回调函数传入 bar 中,但是 bar 调用回调函数的方式依然属于 函数独立调用,所以 this 指向 全局作用域的 a

    function fn(){
      console.log(this.a)
    }
    
    let obj = {
      a: 1,
      fn: fn
    }
    
    var a = 'window'
    
    setTimeout(obj.fn, 1000)  //  window
    

    这段代码跟上一段一样。

  • 箭头函数 箭头函数的 this 不是在运行时生效的,而是在定义这个箭头函数时生效的

    let obj = {
      fn(){
        setTimeout(function(){
          console.log(this)
        }, 1000)
      },
      arrow(){
        let a = () => {console.log(this)}
        setTimeout( a, 1000)
      }
    }
    
    obj.fn()  //window
    obj.arrow()  //对象obj
    

    obj.fnsetTimeout 中的回调函数相当于函数独立调用,this 指向 windowobj.arrow 中的 setTimeout 中的回调函数是一个 箭头函数,而箭头函数的 this 时函数定义生效时所在的对象,也就是 objarrow 中的 a 引用了箭头函数,而这个箭头函数是在 arrow 执行时定义的,所以所指向的对象环境为 obj