总结This指向

前言

  • this 是 JavaScript 的关键字
  • 是 当前环境 执行期上下文对象 的 一个属性
  • this明确指向的时机:执行期
  • this 在不同环境、不同作用域下,表现不同

全局this

🌈获取全局对象this

  • web 下的全局 this: window、self、frames、this
  • node 下的全局 this: global、globalThis
  • worker 下的全局 this: self
  • 通用:globalThis

web

  • 全局 this = window
  • 严格模式下
    • 未通过 var 声明的变量会报错
    • 函数自调用中 this 为 undefined
1
2
3
4
5
6
7
var a = 1;
var b = function() { console.log('fun b中的this:', this) }
c = {};
console.log('this === window', this === window);
console.log('window.a === a:', window.a === a);
b();
console.log('window.c === this.c:', window.c === this.c);

严格模式下:

1
2
"use strict";
c = 123;
1
2
3
"use strict";
var b = function() { console.log('fun b中的this:', this) }
b();
1
2
3
4
5
6
7
8
function test() {
"use strict";
return this;
}

window.test(); // window

// 谁调用函数,函数内部的this指向谁

node

  • 下面两种方式才能把变量挂载到 global 上
    • global.a
    • a (不通过 var、let、const,这些声明的变量会挂载到当前模块中而不是全局)
  • 严格模式下
    • 函数自调用中 global 为 undefined
1
2
3
4
5
6
7
8
9
10
var a = 1;
b = function() { console.log(this) };
global.c = 2;
console.log(a);
console.log(b);
console.log(c);
console.log(global.a);
console.log(global.b);
console.log(global.c);
b();
1
2
3
"use strict";
var b = function() { console.log(this) };
b(); // undefined

🌈class 中 this

  • super 做了什么:
    • 调用父类的 constructor
    • 生成 this ,并把 this 指向 子类
  • 如果没有调用 super
    • 不会调用父类的 constructor 也就不生成父类的 this
  • 子类 constructor 中,访问 this 前,必须得首先调用 super
  • 类中是严格模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Father {
constructor(age) {
this.age = age;
}
swim() {
console.log('Go swimming!!!');
}
}

class Son extends Father {
constructor() {
super(23);
this.hobby = 'travel';
console.log(this.age);
}
study() {
console.log(this);
this.swim();
}
}

const son = new Son();
son.study();

不能调换super顺序,得首先调用 super:

1
2
3
4
5
6
class Son extends Father {
constructor() {
this.hobby = 'travel';
super(23);
}
}

原因是按照正常顺序:

  1. super() 被调用
  2. 调用父类 constructor
  3. 实例化父类 this,给父类 this 挂上 age 属性
  4. 再执行子类 constructor 中代码
  5. 先把父类 this 指向子类实例化的 this
  6. 给子类 this 添加上 hobby 属性
  7. 现在子类上有两个属性了,分别是 age、hobby

而如果先执行子类 constructor 中代码,后执行 super,就会导致先实例化了子类 this,再实例化父类 this,这样逻辑就不对了。

类之间操作this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Father {
get fruit() {
return 'apple';
}
eat() {
console.log('I am eating an ' + this.fruit);
}
}

class Son {
get fruit() {
return 'orange';
}
}

const father = new Father();
const son = new Son();

father.eat(); // apple
son.eat = father.eat;
son.eat(); // orange

如果希望son调用eat时,还是拿的 father 中的 apple,可以在constructor中bind固定this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Father {
constructor() {
// 让函数内部的this指向固定
// 这样无论再把eat赋给谁,this都不会改变了
this.eat = this.eat.bind(this);
}
get fruit() {
return 'apple';
}
eat() {
console.log('I am eating an ' + this.fruit);
}
}

class Son {
get fruit() {
return 'orange';
}
}

const father = new Father();
const son = new Son();

father.eat(); // apple
son.eat = father.eat;
son.eat(); // apple

🌈 call、apply、bind 的 this

  • call、apply、bind 都能改变 this 指向
1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
a: 1
}
var a = 2;

function test(b, c) {
console.log(this);
}

test();
test.call(obj, 3, 4);
test.apply(obj, [3, 4]);
  • bind 绑定只能生效一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj = {
a: 1
}
var obj2 = {
a: 100
}

function test(b, c) {
console.log(this, b, c);
}

var test1 = test.bind(obj, 3, 4);
test1();
var test2 = test1.bind(obj2, 3, 4);
test2();
1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
a: 1
}
var obj2 = {
a: 100
}

function test(b, c) {
console.log(this, b, c);
}

var test1 = test.bind(obj2, 3, 4).bind(obj, 3, 4);
test1();

箭头函数中 this

  • 箭头函数本身没有 this
  • 箭头函数内部 this 指向外层作用域 this
  • 箭头函数中 this 不是在执行期确定,而是声明时就已确定,而且无法更改
  • 箭头函数忽略任何形式的this指向的改变(apply,call,bind)
  • 箭头函数无法通过 new 实例化

不管是否是严格模式,全局下箭头函数的this都为window

1
2
3
4
5
6
7
8
9
const test = () => {
console.log(this);
}
test(); // window
"use strict";
const test = () => {
console.log(this);
}
test(); // window

箭头函数忽略任何形式的this指向的改变(apply,call,bind):

1
2
3
4
5
6
7
8
9
10
11
var obj = {
a: 1
}

const test = () => {
console.log(this.a);
}
test.call(obj); // undefined
test.apply(obj); // undefined
var test1 = test.bind(obj);
test1(); // undefined

箭头函数中 this 不是在执行期确定,而是声明时就已确定,而且无法更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var obj = {
a: 1
}

obj.test = () => {
console.log(this);
}
obj.test(); // window
var obj = {
a: 1
}

obj.test = function() {
var t = () => {
console.log(this); // obj
}
t();
}
obj.test();
var obj = {
a: 1
}

obj.test = function() {
var t1 = () => {
var t2 = () => {
console.log(this); // obj
}
t2();
}
t1();
}
obj.test();
var obj = {
a: 1
}

obj.test = function() {
var t1 = function() {
var t2 = () => {
console.log(this);
}
t2();
}
t1();
}
obj.test();

对象中的 this

  • this指向的基本原则:谁调用,this指向谁
  • 对象方法内部的this:指向最近作用域的引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var obj = {
a: 1,
b: 2,
test: function() {
console.log(this.a); // 1
},
test2: test2,
c: {
d: 4,
test3: function() {
console.log(this);
console.log(this.d);
}
}
}
function test2() {
console.log(this.b);
}
obj.test();
obj.test2();
obj.c.test3();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var obj2 = {
a: 1,
b: 2,
test3: function() {
function t() {
// 这个函数t是孤立的,不是 obj2 内部的成员
// 并不存在 obj2.t, t()自调用,默认this为window
console.log(this);
}
t(); // window
}
}
obj2.test3();
var a = 8;
var o = {
a: 10,
b: {
fn: function() {
console.log(this.a);
}
}
}
o.b.fn(); // undefined

原型this

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {}
obj.__proto__ = {
e: 20
}
console.log(obj.e); // 20 原型继承
var obj = Object.create({
test4: function() {
console.log(this.a + this.b);
}
});
obj.a = 1;
obj.b = 2;
obj.test4(); // 3

🌈构造函数 return this

  • new做了什么

    • 实例化一个空对象
    • 把需要初始化的属性和方法挂载此对象上
    • 隐式的返回此 this
  • 如果显示的 return 一个复杂类型的值,会把默认隐式return的实例化对象覆盖

1
2
3
4
5
6
7
8
9
10
11
12
function Test() {
this.a = 1;
this.b = 2;
console.log(this); // {a:1,b:2}

return {
c: 3,
d: 4
}
}
var test1 = new Test();
console.log(test1); // {c:3,d:4}

🌈事件处理函数中的this

  • onclick、addEventListener事件处理函数内部的this总是指向被绑定的DOM元素
1
2
3
4
5
6
7
8
9
10
<body>
<button id="btn">点我</button>
</body>
var btn = document.getElementById('btn');
btn.onclick = function() {
console.log(this); // btn元素
}
btn.addEventListener('click', function() {
console.log(this); // btn元素
}, false);

直接在元素上绑定,this还是btn:

1
<button id="btn" onclick="console.log(this)">点我</button>

改变this指向:

如果绑定箭头函数,this就是window:

1
<button id="btn" onclick="(function() { console.log(this) })()">点我</button>

Promise中回调函数this还是window

练习

1
2
3
4
5
6
7
8
9
10
var a = 8;
var o = {
a: 10,
b: {
fn: function() {
console.log(this.a);
}
}
}
o.b.fn();