JavaScript——this指向问题详解

一、非箭头函数this指向

排除箭头函数,其他情况下this指向遵循下面规则

  • 在全局上下文中this指向全局对象(通常是window对象)。

    需要注意的是:

    • 严格环境下,全局下的this指向window, 而在严格环境下是undefined
    • setTimeout等都是挂载在window下的,因此setTimeout中的this,不管怎么写都指向window
  • 在对象方法中this指向调用该方法的对象。

  • 函数独立调用时this指向window,独立调用可以理解为,它没有上级。

    可以思考一下如下代码:

    let obj = {
      a: function () {
        function b() {
          console.log(this);
        }
        b();
      },
    };
    

    输出的结果是window,因为b函数是没有上级的,不像a函数作为方法调用,它的上级是obj

  • 立即执行函数【也就是定义后立即执行的匿名函数】的this指向window

    可以思考一下如下代码:

    let obj = {
      a: (function () {
        console.log(this);
      })(),
      b: function c() {
        (function () {
          console.log(this);
        })();
      },
    };
    
    obj.a;
    obj.b();
    

    上面两个都输出window,因为都是执行函数,只不过a成了对象的属性,b成了对象的方法。

  • 在构造函数中this指向新创建的实例对象。

    需要注意的是:

    • 如果是function的构造函数,不加new的话会当成方法使用而不是构造函数,因此加new指向新构造的实例,不加new指向调用该方法的对象。
  • 在事件处理函数中this通常指向触发事件的DOM元素。

function f1() {
  console.log(this);
}
function f2() {
  "use strict";  // 严格模式
  console.log(this);
}
function f3() {
  this.name = "zhangsan";
  console.log(this); 
}
f1();  // window
f2();  // undefined
f3();  // window  当成方法调用了而不是构造函数
new f3();  // f3新构造的实例对象

二、箭头函数this指向

箭头函数是JavaScript中的一个特殊情况。它们不会创建自己的this上下文,而是继承外部函数的this。这使得箭头函数的this是静态的,不会随着调用方式而改变

三、隐式绑定导致的this丢失

在某些特殊情况下会存在this丢失的问题,常见的就是将调用函数作为参数传递或者变量赋值给另外一个变量,此时this指向window

下面看代码:

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

let b = obj.a;
obj.a();
b();

这里obj.a()运行的this指向obj对象,而b()运行的this指向window,因此b变量导致this丢失。上面的代码就相当于:

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

let b = function () {
  console.log(this);
};
obj.a();
b();

四、经典面试题

题1

var a = 'a'
let b = 'b'
function foo() {
	console.log(this.a)
	console.log(this.b)
}
foo()

此题先输出a再输出b,因此var将变量a提升至顶级,并挂载到了window上,而let``不存在变量提升,不会被挂载到window`上。

题2

const obj1 = {
  fn: function () {
    return this;
  },
};

const obj2 = {
  fn: function () {
    return obj1.fn();
  },
};

const obj3 = {
  fn: function () {
    var fn1 = obj1.fn;
    return fn1();
  },
};

console.log(obj1.fn()); // obj1
console.log(obj2.fn()); // obj1
console.log(obj3.fn()); // window

此题第一个输出很简单,指向调用fn()的对象obj1

第二个输出就等同于在obj2对象内调用obj1对象的fn,因此fn方法指向调用它的obj1

第三个输出出现隐式绑定导致的this丢失,因此就相当于在obj3fn方法的作用域内新建了一个fn1函数,再在此作用域内独立执行函数,因为是独立执行,所以输出window

题3

const obj = {
  foo: () => {
    console.log(this);
  },
};

obj.foo();

此题输出window,因此foo为箭头函数,因此this指向上一级的上下文this,即obj所在的上下文window

题4

let obj = {
  say: function () {
    var f1 = () => {
      console.log(this);
    };
    f1();
  },
  pro: {
    getPro: () => {
      console.log(this);
    },
    getPro2: function () {
      console.log(this);
    },
  },
};
let o = obj.say;
o(); // window
obj.say(); // obj
obj.pro.getPro(); // window
obj.pro.getPro2(); // pro

第一个输出发生隐式绑定导致this丢失,又因为f1是箭头函数,所以它的this继承o()this,因此输出window

第二个输出由于f1是箭头函数,所以它的this继承say()this,因此输出obj

第三个暂时没理解

第四个对象。

题5

var a = 1;
a = 2;
window.a = 3;
function foo() {
  let a = 4;
  this.a = 5;
  setTimeout(function () {
    console.log(a);
  }, 10);
  setTimeout(function () {
    console.log(this);
    console.log(this.a);
  }, 20);
  setTimeout(function () {
    console.log(a);
  }, 30);
  setTimeout(function () {
    console.log(this.a);
  }, 40);
}
foo()

输出顺序是 4 5 4 5

分步解析:

  1. 首先var a = 1在全局对window上挂载了a: 1
  2. a = 2重新赋值了window上的a属性
  3. window.a = 3同理
  4. 执行foo()这段代码,,这里没加new,所以把它当成函数执行,foo内的this指向window,所以执行this.a = 5后,window上的a被修改为5

这样就很容易理解了,第一个输出没加this,因此输出局部变量a ,等于4,第二个输出加了this,因此输出window上的a,等于5,后面同理

你可能感兴趣的:(JavaScript,面试,javascript,开发语言,ecmascript,前端,面试,vue.js,vue)