理解严格模式下JavaScript的this指向的变化

2,805 阅读5分钟

全局代码中的this

  • 全局中的代码不管是否处于严格模式下,它的this都执行Window

    console.log(this) // Window
    

箭头函数的this

  • 箭头函数的this由上下文的词法作用域决定,即在哪定义的就指向哪里的this
  () => {
      console.log(this) // window 或 global,在全局定义指向全局
  }
  obj = {
      fn: () => {
          console.log(this) // obj 在obj对象中定义,但是指向全局(箭头函数时,obj无法确认环境)
      }
  }

普通函数中的this

  • 直接调用函数(在全局或者其他函数内)

    • 在非严格模式下,this默认指向全局变量(window或global)

      ```js
      // 在浏览器中,全局对象是window, 在NODE中是global
      function test() {
        console.log(this) // Window 或者 global
        (function(){
           console.log(this) // Window 或者 global
        })
      }
      test() 
      ```
      
      • 在严格模式下,this将保持进入执行环境的值,所以,如果没有指定环境,则默认undefined
      function test() {
        console.log(this) // undefined    
        (function(){
           console.log(this) // undefined
        })
      }
      test() 
      
      • 特殊点:当函数作为回调函数时,需要注意是否隐式绑定了所属对象,例如:当作为setTimeout的参数时,就默认将函数绑定了window对象
      // setTimeout是window的方法,可以使用window.setTimeout调用
      // 调用函数时,对象访问大致路线:window -> setTimeout -> 获取参数中test的引用参数 -> 执行 test 函数
      function test() {
         console.log(this) 
      }
      setTimeout(test, 100); // window 或 global
      
  • 作为对象方法调用则指向当前对象

    • 注意:如下列代码中的将对象的方法赋值给其他变量在调用,this将按上面的普通方法直接调用函数规则处理
    var a = 'global'
    function name() {
         console.log('name', this.a) // 通过普通调用方法直接调用,在严格模式下this为undefined,运行报错,在非严格模式下global
    }
    var obj = {
     fn: function () {
         console.log('inner', this.a) // obj 通过对象obj调用,this指向该对象与模式没有关系
         name() 
     },
     a: 'obj'
    }
    obj.fn()
    var newFn = obj.fn()
    newFn() // 严格模式为undefined,非严格为window或者global,按普通方法直接调用的规则处理
    function test(fn){
       fn()  
    }
    test(obj.fn) // 相当于将fn=obj.fn, 严格模式为undefined,非严格为window或者global
    newObj = {fn: 2}
    test(newObj.fn=obj.fn) // 相当于 fn = newObj.fn = obj.fn, 严格模式为undefined,非严格为window或者global
    
  • 执行new操作时,构造函数的this指向正在构造的新对象

    function TEST(){
        this.a = 'obj';
        console.log(this) //new操作时 TEST { a: 'obj'} this指向正在构造的对象(返回的对象)
    }
    var o = new TEST();
    console.log(o); //  TEST { a: 'obj'}  this指向刚刚构造的对象
    
    • 对es6的class Person {} 使用new操作时,this执行它调用的环境
    class MyTest {
      constructor (callback) {
        this.callback = callback
        callback() // 指向window
      }
      func () {
        this.callback() // 指向MyTest
      }
    }
    // let的值不会绑定到window上,无法用window.name访问
    let name = 'global'
    function Test () {
      console.log(this, this.name)
    }
    new Test() // window 使用let输出 '', 使用var输出global
    let c = new MyTest(Test) // window 使用let输出 '', 使用var输出global
    c.func() // MyTest{} undefined
    
    • 其他情况下Constructor的执行与普通函数没有区别,谁调用它则指向谁
    // 当函数直接调用的时候,在非严格模式下,this指向window;严格模式为undefined
    // 当函数作为对象的属性调用的时候,this指向这个对象;
    // p1.constructor指向Person的构造函数(Person()函数本身),
    // 在p1.constructor()时,Person是作为p1的属性调用的,所以this指向p1;
    // 当调用p2 = p1.constructor;p2();时,其实就相当于直接调用Person();所以this指向window
    var name = 'global'
    function Person() {
        this.name = 'person';
        console.log(this);
    }
    var p1 = new Person();
    p1.constructor();        //  Person {name: "person"}
    var p2 = p1.constructor;
    p2();   // window
    
  • 设置call/apply/bind后调用,则指向其第一个参数的this,如果为空则在严格模式下指向undefined,在非严格模式下指向window或global

    // 语法
    // 1、函数.apply(对象, [参数列表]) 
    // 2、函数.call(对象, arg1,arg2,arg3…argn)
    // 3、函数.bind(对象)
    // 'use strict'
    var a = 'global'
     function name() {
         console.log('name', this.a)
     }
     function name2() {
         console.log('name2', this)
     }
     var obj = {
         a: 'obj'
     }
     name.call(obj) // obj this指向通过call绑定的obj对象
     // name2.call() // 严格模式为undefined,非严格模式为widow或global
    
  • 作为一个dom事件处理函数,它的this指向添加处理事件的元素(一些浏览器在使用非addEventListener的函数动态添加监听函数时不遵守这个约定)

    // html
    <div id="A">
      <div id="B"></div>
    </div>
    // javascript
    var a = document.getElementById('A');
    var b = document.getElementById('B');    
    function logs (e) {
        console.log(e.target, e.target===this); // 当e.target与e.currentTarget相等时为true
        console.log(e.currentTarget, e.currentTarget===this); // 总是true
    }
    a.addEventListener('click', logs, false);
    // 点击A时,
    // 输出 A节点信息,true \n A节点信息, true
    // 点击B时,
    // 输出 B节点信息,false \n A节点信息, true
    // currentTarget表示实际绑定处理事件的元素
    // target表示触发事件的元素(如点击B)
    // 所以处理事件中的this指向实际添加处理事件的元素
    
  • 作为一个内联事件处理函数,

    • 当代码被内联on-event 处理函数调用时,它的this指向监听器所在的DOM元素,与模式没有关系
    <button onclick="alert(this.tagName.toLowerCase());">Show this</button>
    
    • 当代码包裹在内部函数中时,在非严格模式指向window或global对象(即非严格模式下调用的函数未设置this时指向的默认对象),在严格模式下为undefined
    <button onclick="alert((function(){return this})());">Show inner this</button>
    

闭包的this指向

闭包的执行相当于是返回一个函数,然后将这个返回的函数执行,因此和普通函数的直接调用相同

var box={
    user: 'zs',
    getThis:function(){
        return function(){
            return this;   
        };
    }
}
console.log(box.getThis()()); // 指向全局,非严格为window,严格为undefined