浅谈一下this的指向问题

168 阅读3分钟

this JavaScript 语言的一个关键字。会随着执行环境的变化而变化。

  1. 在全局环境下,this始终指向的是全局对象window,无论是否在严格模式下

  2. 普通函数内部的this(默认绑定)

    // 非严格模式
    function fn1() {
      return this
    }
    fn1()   // window
    
    // 严格模式下
    function fn1() {
      'use strict';   // 使用严格模式
      return this
    }
    fn1()   // undefined
    

    嵌套函数中的this指向

    var name = 'Mike'
    var obj = {
      name: 'Kobe',
      fn1: function() {
        console.log(this.name)
        function fn2() {
          console.log(this.name)
        }
        return fn2     
      }}
    // Kobe
    // Mike
    

    所以,非严格模式下this指向window,严格模式下是undefined

    **对于多层嵌套的对象,匿名函数或者普通函数指向的依然是window,**所以fn2的name应该是window的

    var name = 'Mike'
    var obj = {
      name: 'Kobe',
      fn1: function() {
        console.log(this.name)
      }
    }
    var fn = obj.fn1
    fn()   // Mike
    

    新建了变量fn,直接等于fn1这个函数,所以this的指向是window。对于使用变量给函数变名是相当于把this指向了window。

  3. 构造函数中的指向

    function Person2(name) {
      this.name = name
    }
    var person2 = new Person2('Mike')
    person2.name   // Mike
    

    构造函数中的this与被创建的新对象绑定

    function Person(name) {
      this.name = name
    }
    Person.prototype.getName = function() {
      return this.name
    }
    var Kobe = new Person('kobe')
    // kobe
    

    构造函数的也是指向新创建的实例

  4. 显式绑定(通过call,bind,apply的方式显式改变this指向)

    var obj = {
      name: 'Kobe',
      fn1: function(){
        console.log(this.name)
        console.log(argument)
      }
    }
    var param = {name: 'Mike'}
    obj.fn1.call(param, 1, 2)   // Mike  Arguments(3) [1, 2, 3]
    obj.fn1.apply(param, [1, 2])  // // Mike  Arguments(3) [1, 2, 3]
    

    如果将null或者undefined传入,值都会被忽略,应用的还是默认绑定

    var foo = {
      name: 'Kobe'
    }
    var name = 'Mike'
    function fn1() {
      console.log(this.name)
    }
    foo.call(null)   // Mike
    
  5. 箭头函数

    var name = 'Kobe'
    var obj = {
      name: 'Mike',
      fn1: function() {
        console.log(this.name)
      }
      fn2: () => {
        console.log(this.name)
      }
    }
    obj.fn1()   // 'Mike'
    obj.fn2()   // 'Kobe'
    

    fn2箭头函数,指向作用域上一层是obj,所以打印出来的是window下面的name。箭头函数体内的this对象,指向的是外层作用域(离被调用函数最近的对象)

下面是一些练习题可供我们回顾复习一下

  • 题目一

    var name = 'Kobe'
    function fn1() {
      console.log(this.name)
    }
    (function() {
      'use strict'
      fn1()
    })
    // Kobe
    

因为在调用fn1函数的依然是window,所以能正确打印出Kobe。但如果是this.fn1()就会报错,在里面严格模式,使用的this会是undefined

  • 题目二

    var obj = {
      name: 'Kobe',
      getName: function() {
        console.log('getName: ', this.name)
        return function() {
          console.log('fn: ', this.name)
        }
      }
    }
    var name = 'window'
    var obj1 = { name: 'Mike' }
    obj()()   // getName: Kobe   fn: window
    obj.getName.call(obj1)()   // getName: 'Mike'   fn: window
    obj.getName().call(obj1)()   // getName: 'Kobe'   fn: Mike
    

通过call来改变this的指向

  • 题目三

    var name = 'Kobe'
    function Person(name) {
      this.name = name
      this.obj = {
        name: 'obj'
        fn1: function() {
          return function() {
            console.log(this.name)
          }
        },
        fn2: function() {
          return () =>  {
            console.log(this.name)
          }
        }
      }
    }
    
    var p1 = new Person('p1')
    var p2 = new Person('p2')
    p1.obj.fn1()()     // 'Kobe'
    p1.obj.fn1.call(p2)()   // 'Kobe'
    p1.obj.fn1().call(p2)   // 'p2'
    
    p1.obj.fn2()()     // 'obj'
    p1.obj.fn2.call(p2)()    // 'p2'
    p1.obj.fn2().call(p2)    // 'obj'
    

匿名函数指向的是window,可以通过call去修改this指向。箭头函数是没有this,所以无法修改this指向。只能根据上层函数的this指向来决定。

总结一下

  • 函数如果是在new中调用,this绑定的是新创建的对象
  • 匿名函数的this指向永远是window
  • 在某个上下文对象中调用,this绑定的是上下文对象
  • 默认绑定的是window,严格模式下绑定到的是undefined
  • 如果传入undefined和null作为this的绑定对象传入,值会被忽略
  • 如若是箭头函数的话,this指向继承的外层代码this
  • 隐式绑定丢失的原因:把函数当作参数传递或者使用变量给函数起别名