this指向问题一网打尽

697 阅读4分钟

唯有死记,才能活用😆 Javascript中的this,因其灵活的指向、复杂的使用场景一直是面试中的热点,不论是初级还是中高级开发者,这都是一道必考题。这个概念虽然基础,但是非常重要,是否能深刻理解this,是前端 Javascript中进阶的重要一环。this指向多变,很多隐蔽的bug都缘于它。与此同时,this强大灵活,如果能熟练驾驭,就会写出更简洁、优雅的代码。

社区上对于this的讲解虽然不少,但缺乏统一梳理。本文章,让我们直面this的方方面面,并通过例题真正领会与掌握this。this相关知识点如下

image.png

this指向了谁

结论∶this的指向,是在调用函数时根据执行上下文所动态确定的。
具体环节和规则,可以先「死记硬背」以下几条规律,后面来慢慢一一分析

  1. 在函数体中,简单调用该函数时(非显式隐式绑定下),严格模式下this绑定到 undefined,否则绑定到全局对象 window/globa
  2. 一般构造函数new调用,绑定到新创建的对象上;
  3. 般由call/ apply/bind方法显式调用,绑定到指定参数的对象上
  4. 一般由上下文对象调用,绑定在该对象上
  5. 箭头函数中,根据外层上下文绑定的this决定this指向。

实战例题分析

全局环境下的this

这种情况相对简单直接,函数在浏览器全局环境中被简单调用,非严格模式下this指向 window;在 use strict指明严格模式的情况下就是undefined。

我们来看例题,请描述打印结果

      const foo = {
        bar: 10,
        fn: function () {
          console.log(this);
          console.log(this.bar);
        },
      };
      var fn = foo.fn;
      fn();

打印: Window undefined
原因:因为这里 this 仍然指向的是 window。虽然 fn 函数在 foo 对象中作为方法被引用,但是在赋值给 fn1 之后,fn1 的执行仍然是在 window 的全局环境中。因此输出 window 和 undefined

      const foo = {
        bar: 10,
        fn: function () {
          console.log(this);
          console.log(this.bar);
        },
      };
      foo.fn();

打印: {bar: 10, fn: ƒ} 10
原因:因为这个时候this指向的是最后调用它的对象,在foo,fn()语句中this指向foo对象。

结论:在执行函数时,如果函数中的this是被上一级的对象所调用,那么this指向的就是上一级的对象;否则指向全局环境。

上下文对象调用中的 this

      const student = {
        name: "Lucas",
        fn: function () {
          return this;
        }
      };
      console.log(student.fn() === student);

打印:true

复杂点的

const person = {

    name: 'Lucas',

    brother: {

        name: 'Mike',

        fn: function() {

            return this.name

        }

    }

}

console.log(person.brother.fn())

打印: Mike

const person = {

    name: 'Lucas',

    brother: {

        name: 'Mike',

        fn: function() {

            return this.name

        }

    }

}

console.log(person.brother.fn())

打印: Mike
在这种嵌套的关系中,this 指向最后调用它的对象,因此输出将会是:Mike

bind/call/apply 改变 this 指向

一句话总结:
他们都是用来改变相关函数 this 指向的,但是 call/apply 是直接进行相关函数调用;bind 不会执行相关函数,而是返回一个新的函数,这个新的函数已经自动绑定了新的 this 指向,开发者需要手动调用即可。再具体的 call/apply 之间的区别主要体现在参数设定

image.png

构造函数和 this

问题一: 操作符调用构造函数,具体做了什么?

  • 创建一个新的对象; 
  • 将构造函数的 this 指向这个新对象; 
  • 为这个对象添加属性、方法等; 
  • 最终返回新对象。 代码表示:
var obj  = {}
obj.__proto__ = Foo.prototype
Foo.call(obj)

当然,这里对 new 的模拟是一个简单基本版的,更复杂的情况我们会在原型、原型链相关的《面向对象和原型》中讲述。
案列:

function Foo(){

    this.user = "Lucas"

    const o = {}

    return o

}

const instance = new Foo()
console.log(instance)
console.log(instance.user)

打印:{} undefined

function Foo(){

    this.user = "Lucas"

    return 1

}

const instance = new Foo()

console.log(instance.user)

打印:Lucas

结论:如果构造函数中显式返回一个值,且返回的是一个对象,那么this就指向这个返回的对象;如果返回的不是一个对象,那么this仍然指向实例

箭头函数中的 this 指向

结论: 箭头函数使用 this 不适用以上标准规则,而是根据外层(函数或者全局)上下文来决定。

const foo = {  

    fn: function () {  

        setTimeout(function() {  

            console.log(this)

        })
    }  
}  

console.log(foo.fn())

打印:Window 这道题中,this 出现在 setTimeout() 中的匿名函数里,因此 this 指向 window 对象。如果需要 this 指向 foo这个 object 对象,可以巧用箭头函数解决

const foo = {  

    fn: function () {  

        setTimeout(() => {  

            console.log(this)

        })

    }  

}

console.log(foo.fn())  

打印:{fn: ƒ}

this 优先级相关

我们常常把通过 callapplybindnew 对 this 绑定的情况称为显式绑定;根据调用关系确定的 this 指向称为隐式绑定。 那么显式绑定和隐式绑定谁的优先级更高呢?

开放列题分析

实现一个bind函数

Function.prototype.bind = Function.prototype.bind || function (context) {

    var me = this;

    var args = Array.prototype.slice.call(arguments, 1);

    return function bound () {

        var innerArgs = Array.prototype.slice.call(arguments);

        var finalArgs = args.concat(innerArgs);

        return me.apply(context, finalArgs);

    }

}