彻底搞懂JavaScript中的this指向问题

前端 · 2020-07-27 ·

JavaScript中的关键词this是个非常重要的语法点。理解this之前,明确以下两点。

  • this永远指向一个对象
  • this的指向取决于函数调用的位置

首先this永远指向一个对象,很好理解,不管在什么地方使用this,它必然会指向某个对象,难点在于在JavaScript之中,一切皆对象(运行环境也是对象),当代码运行时,会产生一个对应的执行环境,在这个执行环境中,所有的变量会被事先提出来(变量提升),代码从上往下开始执行(执行上下文:当前代码的运行环境),在 JavaScript 中,运行环境主要包含了全局环境和函数环境,由于执行上下文的切换,让this的指向就随着执行上下文的变化而变化,很难事先确定到底指向哪个对象。

在一个执行上下文中,最重要的三个属性分别是变量对象(Variable Object)、作用域链(Scope Chain)和 this 指向。

this在全局作用域中

var whereThis = 'I am this';
console.log("1.1 "+whereThis); // 1.1 I am this

this.whereThis = 'I am redefined';
console.log("1.2 "+whereThis); // 1.2 I am redefined

在全局作用域/全局环境( global scope | global context )中,this指向的就是全局对象。

  • 在浏览器里,指向 window 对象
  • 在Node.js里,指向 global 对象

this在函数(function)中

var obj = {
  func1: function () { 
      console.log("2 "+this.data1) 
  },
  data1: "objData1"
};

var func1 = obj.func1;// 变量 func1 保存的是一个函数引用(内存地址),是 obj 里面 func1 的内存地址
var data1 = "globalData1";

obj.func1() // 2 objData1;
func1() // 2 globalData1; 等同于 window.func1()

由于函数内部使用了 this 关键字,this关键字在不同的执行上下文(函数运行时所在的环境)指向不同。对于 obj.func1() 来说,func1 运行在 obj 环境,所以 this 指向 obj;对于 func1() 来说,func1 运行在全局环境,所以 this 指向全局环境。

var tree = {
    size: 'normal',
    treeInfo: function() {
        console.log(this == tree);
        console.log("3 "+ this.size);
    }
};

tree.treeInfo(); // true, 3 normal 

var bigTree = {
    size: 'big'
};

bigTree.treeInfo = tree.treeInfo;
bigTree.treeInfo(); // false, 3 big

this 的指向从来不是静态的,它总是在你调用一个函数,但尚未执行内部函数前被指定,实际上 this 是被调用的函数的父作用域提供的。所以更重要的是,我们要看清函数调用时是怎么写的。

当一个函数被调用时,应该里面看()调用左边的部分。

  • 如果 () 的左边是一个引用,函数 this 的指向就是这个引用所属的对象
  • 否则 this 指向的就是全局对象(window | global)
function func2() {
    console.log(this);
}
func2(); // window
// 这里,this指向的是全局对象window。我们先看()的左边是func2,func2 属于 window,相当于window.func2()
// 那么func2属于谁呢?func2属于全局对象,所以this指向的就是全局对象。

var obj2 = {
    func2: function() {
        console.log(this);
    },
    data2:"obj2data2"
}
obj2.func2(); // obj2
// 这里,this指向的是obj2,先看()左边是func2,func2属于obj2,所以func2里的this指向的就是obj2

this在构造函数中

function Person() {
    // 变量作用域为函数内部,外部无法访问,防止了变量名冲突和污染
    this.name = "Constructor Function";
    this.sayName = function() {
        console.log("Constructor Function");
    }
}
// 外部无法访问内部变量
var a = new Person();
console.log(a.name)

构造函数中的this指向调用该构造函数所创建的实例对象。对象a 可以调用Person里面的name,是因为new关键字可以改变this的指向,将这个this指向对象a,(我们用变量a创建了一个Person的实例,相当于复制了一份Person到对象a里面)。之前写过JavaScript中的new这个语法糖到底做了啥?

function Constructor() {
    that = this;
}

// console.log(that); // 报错:that is not defined

var instanceA = new Constructor();
console.log( that == instanceA ); // 结果为true

var instanceB = new Constructor();
console.log( that == instanceA ); // 结果为false
console.log( that == instanceB ); // 结果为true

当构造函数碰到return时,这时候的this指向就不一定是指向函数的实例了。

当return返回以下的值:undefined、null、boolean、number等基础类型,并不会代替 new 调用的默认返回对象。

当return返回以下的值:{}、[]、RegExp、Date、Function,均会代替new 默认返回的对象。这里返回的值,都是对象,复杂类型。

%