写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。Github地址。
理解this
- this是一个关键字,一个内置的引用变量;
- 在函数中都可以直接使用this;
- this代表调用函数的当前对象;
- 在定义函数时,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
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();
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
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();
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();
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();
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();
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();
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)
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
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);
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();
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); // 王五
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
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
2
3
4
5
6
7
8
9
10
11
解析:
- say是普通函数且前面有点(.),即调用者是obj,因此函数say中的this指向obj,所以输出lisi。
- read是箭头函数,this指向函数所在的作用域,当前的作用域为全局环境,即this指向window,所以输出undefined。
总结:
- 普通函数中的this指向函数的调用者,有个简便的方法就是看:函数前面有没有点(.),如果有点,那么就指向点前面的那个对象;
- 箭头函数中的this指向函数所在的作用域:注意理解作用域,只有函数的{}才构成作用域,对象的{}以及if(){}都不构成作用域。
const obj = {
say: function () {
setTimeout(() => {
console.log(this)
});
}
}
obj.say(); // obj 此时箭头函数的this指向为其包含函数say中的this指向
// say是普通函数,且前面有点,因此say中的this指向为obj
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);
2
3
4
5
6
7
8
9
10
11
12
13
14
- method这个函数传入了两个参数,一个参数为fn(),fn()为普通函数,this指向函数的调用者,此时指向全局(也可以看这个函数前面没有点),所以输出结果为10;
- 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);
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);
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
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
2
3
4
node.js中全局环境默认this为{},普通函数中默认this为global。
console.log(this); // {}
function test() {
console.log(this === global); // true
}
test();
2
3
4
5