写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。Github地址

理解this

  1. this是一个关键字,一个内置的引用变量;
  2. 在函数中都可以直接使用this;
  3. this代表调用函数的当前对象;
  4. 在定义函数时,this还没有确定,只有在函数执行时才动态确定(绑定)的。

重要的事情说三遍:this要在函数执行时才能确认,定义函数时无法确认。this要在函数执行时才能确认,定义函数时无法确认。this要在函数执行时才能确认,定义函数时无法确认。

为什么呢?因为this是执行上下文的一部分,而执行上下文需要在代码执行的时候产生,而不是定义的时候

this的工作原理(5种情况)

全局作用域内

当在全局作用域内使用this时,它将会指向全局对象,即window对象。 举个🌰:

function test(a) {
    this.a = a; // 相当于window.a = a;
}
test(3); // 这里相当于window.test(3)
// 所以test函数中的this指向了window
console.log(a); // 3 这里相当于与访问window.a
1
2
3
4
5
6

再来看个🌰:

// 这里的函数test实际是被Window对象调用的
function test() {
    var name = 'lisi'; // 这里是局部变量name
    console.log(this.name); // undefined
    console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
}
test(); //这里相当于window.test();
1
2
3
4
5
6
7

作为普通函数调用

当在全局作用域内调用函数时,this也会指向全局对象。 举个🌰:

var obj = {
    a: 0,
    b: 0,
    say: function(a, b) {
        var sayA = function(a) {
            console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
            this.a = a;
        };
        var sayB = function(b) {
            console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
            this.b = b;
        };
        sayA(a); // 不是通过new操作符来调用的,也没有通过dot(.)来调用
        sayB(b); // 不是通过new操作符来调用的,也没有通过dot(.)来调用
    }
}
obj.say(1, 1);
console.log(obj.a + '----' + obj.b); //0--0
console.log(window.a + '-----' + window.b); //1-----1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

解析:对于say方法中的sayA和sayB中的this来说:不是通过new操作符来调用的,也没有通过dot(.)来调用,因此this指向window。

作为对象方法调用

this指向调用该方法的对象。 举个🌰:

var obj = {
    name: 'lisi',
    sayName: function() {
        // this指向obj
        console.log(this.name); // lisi
    }
}
obj.sayName();
1
2
3
4
5
6
7
8

再来看个🌰:

var name = 'wangwu';
var obj = {
    name: 'lisi',
    sayName: function() {
        console.log(this.name); // lisi(this指向的也只是它上一级的对象)
    }
}
window.obj.sayName();
1
2
3
4
5
6
7
8

对象字面量obj = {},变量obj其实也是window对象的属性,所以可以这样来调用window.obj.sayName()。如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,所以这里this指向obj,而不是window。

var obj = {
    name: 'wangwu',
    objInner: {
        name: 'lisi',
        sayName: function() {
            console.log(this.name); // lisi
        }
    }
}
obj.objInner.sayName();
1
2
3
4
5
6
7
8
9
10

如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象。

var obj = {
    name: 'wangwu',
    objInner: {
        sayName: function() {
            console.log(this.name); // undefined
        }
    }
}
obj.objInner.sayName();
1
2
3
4
5
6
7
8
9

尽管对象objInner中没有属性name,这个this指向的也是对象objInner,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西

 var obj = {
    name: 'wangwu',
    objInner: {
        name: 'lisi',
        sayName: function() {
            console.log(this.name); // undefined
            console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
        }
    }
}
var fn = obj.objInner.sayName;
fn();
1
2
3
4
5
6
7
8
9
10
11
12

作为构造函数调用

在构造函数内部,this指向新创建的实例对象。 举个🌰:

function Person(name, age) {
    this.name = name;
    this.age = age;
}
var person1 = new Person('lisi', 18);
console.log(person1.name); // lisi
var person2 = Person('wangwu', 12);
// person2是undefined,因为Person函数没有返回值,默认返回undefined
// console.log(person2.name); // Uncaught TypeError: Cannot read property 'name' of undefined
console.log(window.name); // wangwu
// Person('wangwu', 12)相当于window.Person('wangwu', 12)
1
2
3
4
5
6
7
8
9
10
11

显式的设置this指向(call、apply、bind)

当使用call或者apply方法时,函数内的this将会被显式设置为函数调用的第一个参数。

function Test(a, b) {
    this.a = a;
    this.b = b;
    this.say = function(a, b) {
        this.a = a;
        this.b = b;
    }
}
var obj1 = new Test(1, 1);
var obj2 = {a: 2, b: 2};
obj1.say.call(obj2, 3, 3);
console.log(obj2.a + '----' + obj2.b); // 3----3
1
2
3
4
5
6
7
8
9
10
11
12

解析:apply和call这两个方法切换函数执行的上下文环境,即可以改变this指向。obj1.say.call(obj2, 3, 3)实际上是obj2.say(3, 3),所以say中的this就指向了obj2。

let fn = function(name, age) {
    console.log(name); // lisi
    console.log(this); // { a: 2 }
}.bind({a: 2});

fn('lisi', 12);
1
2
3
4
5
6

箭头函数this指向

箭头函数没有自己的this,看其外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window

const name = 'wangwu';
const obj = {
    name: 'lisi',
    sayName: function() {
        console.log(this); // {name: "lisi", sayName: ƒ}
        setTimeout(() => {
            console.log(this.name); // lisi
        }, 1000);
    }
};
obj.sayName();
1
2
3
4
5
6
7
8
9
10
11

相关题目

当this碰到return

function Fn() {
    this.username = '王五';
    return 1;
    // return undefined;
}
var a = new Fn();
console.log(a); // Fn {username: "王五"}
console.log(a.username); // 王五

function Fn1() {
    this.username = '王五';
    return function(){};
}
var b = new Fn1();
console.log(b); // ƒ (){}}
console.log(b.username); // undefined

function Fn2() {
    this.username = 'lisi';
    return {};
}
var obj = new Fn2();
console.log(obj); // Object{}
console.log(obj.username); // undefined

// 虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊
function Fn3() {
    this.username = '王五';
    return null;
}
var a = new Fn3();
console.log(a); // Fn3 {username: "王五"}
console.log(a.username); // 王五
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

如果返回值是一个对象或者函数,那么this指向的就是那个返回的对象;如果返回值不是一个对象,那么this还是指向函数的实例。

demo2

function Foo() {
    // 会覆盖全局的getName方法
    getName = function() {
        // console.log('----', this);
        console.log(1);
    }
    return this;
}
Foo.getName = function() { // 相当于静态方法
    console.log(2);
}
Foo.prototype.getName = function() {
    console.log(3);
}
var getName = function() {
    console.log(4);
}
// 会被覆盖
function getName() {
    console.log(5);
}

Foo.getName(); // 2
getName(); // 4
// Foo()当做普通函数调用,this指向window
Foo().getName(); // 1
getName(); // 1

// 设计js运算符优先级,参考mdn
new Foo.getName(); // 2
// Foo()当做构造函数调用,this指向当前实例对象,相当于调用Foo原型上的getName
new Foo().getName(); // 3
new new Foo().getName(); // 3
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

运算符优先级

demo3

const obj = {
    name: 'lisi',
    say() {
        console.log(this.name);
    },
    read: () => {
        console.log(this.name);
    }
}
obj.say(); // lisi
obj.read(); // undefined
1
2
3
4
5
6
7
8
9
10
11

解析:

  1. say是普通函数且前面有点(.),即调用者是obj,因此函数say中的this指向obj,所以输出lisi。
  2. read是箭头函数,this指向函数所在的作用域,当前的作用域为全局环境,即this指向window,所以输出undefined。

总结:

  1. 普通函数中的this指向函数的调用者,有个简便的方法就是看:函数前面有没有点(.),如果有点,那么就指向点前面的那个对象
  2. 箭头函数中的this指向函数所在的作用域:注意理解作用域,只有函数的{}才构成作用域,对象的{}以及if(){}都不构成作用域。
const obj = {
    say: function () {
        setTimeout(() => {
            console.log(this)
        });
    }
}
obj.say(); // obj 此时箭头函数的this指向为其包含函数say中的this指向
// say是普通函数,且前面有点,因此say中的this指向为obj
1
2
3
4
5
6
7
8
9

demo4

var length = 10;
function fn() {
 console.log(this.length);
}

const obj = {
    length: 5,
    method: function(fn) {
        fn(); // 10
        arguments[0](); // 2
    }
};

obj.method(fn, 1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. method这个函数传入了两个参数,一个参数为fn(),fn()为普通函数,this指向函数的调用者,此时指向全局(也可以看这个函数前面没有点),所以输出结果为10;
  2. arguments是函数的所有参数,是一个类数组的对象,arguments0,可以看成是arguments.0(),调用这个函数的是arguments,此时this就是指arguments,this.length就是angument.length,就是传入的参数的总个数2。

上述例子在node环境中的运行结果为undefined 2,var length = 10改成global.length = 10;是因为node环境下定义在全局的变量不会绑定到global,浏览器会自动将变量length绑定到全局对象window上

稍微改一下:

let length = 10; // 这里将var改为let
function fn() {
 console.log(this.length);
}

const obj = {
    length: 5,
    method: function(fn) {
        fn(); // 0(准确的说是当前浏览器窗口中iframe的个数)
        arguments[0](); // 2
    }
};

obj.method(fn, 1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

用let或者const来定义length,然后输出的是0和2。 这是因为:var在全局定义的变量会自动绑定到window对象上,但是const和let在全局定义的变量不会自动绑定到window对象上。改成le后,h打印window.length为0,是因为window.length表示当前页面有几个iframe,可以看一下MDN上关于window.length的定义。

再来改一下:

var length = 10;

function fn() {
    console.log(this.length);
}

const obj = {
    length: 5,
    method: function(fn) {
        fn(); // 10
        const fun = arguments[0];
        fun(); // 10  还是那句话,函数的this指向需要等到函数调用时才能确定,这里指向window
    }
};

obj.method(fn, 1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

demo5

window.val = 1;
var obj = {
    val: 2,
    say: function() {
        this.val *= 2;
        val *= 2;
        console.log(val);
        console.log(this.val);
    }
}
obj.say(); // 2 4
var func = obj.say;
func(); // 8 8
1
2
3
4
5
6
7
8
9
10
11
12
13

obj.say();执行这行代码时,this指的是obj,所以this.val => obj.val*=2,最后结果为4,val*=2 => window.val *= 2,最后结果是2。

func(),执行这行代码时,func()没有任何前缀,this指的是window.func();所以此时this指向的是window,this.val => window.val*=2,此时window.val为4,val*=2 => window.val *2,最后结果为8,最后console.log(this.val)与console.log(val)指的都是window.val,最后结果都是8。

补充知识点

浏览器默认的this为window。

function test() {
    console.log(this);
}
test(); // window
1
2
3
4

node.js中全局环境默认this为{},普通函数中默认this为global。

console.log(this); // {}
function test() {
   console.log(this === global); // true
}
test();
1
2
3
4
5

参考文档

  1. JavaScript 的 this 原理