JavaScript 中的 this 指向算是 比较令人头疼的问题之一了。this 是一个指向当前执行上下文的关键字,在不同的上下文中指向不同的对象,具体取决于函数被调用的方式。
下面, 让我们花费 10 分钟的时间来一次 this 指向的旅程。
this 指向原则
首先,我们需要明确下 this 的指向原则:
1. 如果函数被当做方法来调用,那它的 this 就是调用它的对象。
var a = 0;
let obj = {
a: 1,
fun() {
console.log(this.a);
}
};
obj.fun(); // 1
obj.fun.call(window); // 0
2. 如果函数(非箭头函数)被当做函数来调用,那么 this 值要么是全局对象(非严格模式),要么是 undefined(严格模式)。
非严格模式:
var a = 0;
function fun() {
var a = 1;
function foo() {
console.log(this.a);
}
foo();
}
fun(); // 0
严格模式:
'use strict';
var a = 0;
function fun() {
var a = 1;
function foo() {
console.log(this.a);
}
foo();
}
fun(); // 报错,因为 this 是 undefined
3. 箭头函数不会创建自己的 this,它的 this 来自于外层作用域。箭头函数不能通过 call、apply、bind 等方法来改变它的 this 指向,但可以通过改变外层作用域的 this 指向来改变自身的 this 指向。
var a = 0;
let obj = {
a: 1,
fun: () => {
console.log(this.a);
}
};
let obj2 = {
a: 2
};
obj.fun(); // 0
obj.fun.call(obj2); // 0
4. 构造函数中的 this 指向它的实例对象。
function Person(name) {
this.name = name;
this.say = function () {
console.log(this.name);
};
}
const jack = new Person('jack');
jack.say(); // jack 解析:say中的this指向调用它的对象, 也就是实例对象jack
几道面试题
OK,上面几个原则已经记住了,那我们开始下面的问题:
1. 题目 1
let a = 1;
const b = 2;
function foo() {
console.log(this.a);
console.log(this.b);
}
foo();
console.log(window.a);
答案:
undefined
undefined
undefined
解析:
- let/const 修饰的变量不会挂载到全局上,因此
window.a
是undefined
。 - 函数中的 this 指向
window
, 因此this.a
,this.b
相当于window.a
,window.b
,都是undefined
.
2. 题目 2
var name = 'window';
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name);
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name);
};
},
foo4: function () {
return () => {
console.log(this.name);
};
}
};
var person2 = { name: 'person2' };
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
答案&解析:
var name = 'window';
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name);
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name);
};
},
foo4: function () {
return () => {
console.log(this.name);
};
}
};
var person2 = { name: 'person2' };
person1.foo1(); // person1 解析:方法中的this指向调用它的那个对象
person1.foo1.call(person2); // person2 解析:call改变this指向为person2
person1.foo2(); // window 解析:箭头函数没有自己的this,它的this来源于外层作用域,也就是window
person1.foo2.call(person2); // window 解析:箭头函数不能通过call来改变this指向
person1.foo3()(); // window 解析:person1.foo3()是一个函数,非严格模式下,函数的this指向window.
person1.foo3.call(person2)(); // window 解析:person1.foo3.call(person2)改变了foo3的this指向,返回仍然是一个函数,this指向widnow
person1.foo3().call(person2); // person2 解析:person1.foo3()是一个函数,call改变了该函数的this指向为person2
person1.foo4()(); // person1 解析:箭头函数没有自己的this,它的this来源于外层作用域,也就是foo4,foo4的this指向调用它的那个对象,也就是person1
person1.foo4.call(person2)(); // person2 解析:call改变了foo4的this指向为person2, 箭头函数的this指向也是person2
person1.foo4().call(person2); // person1 解析:箭头函数不能通过call来改变this指向,它的this来源于外层作用域,也就是person1
3. 题目 3
var name = 'window';
function Person(name) {
this.name = name;
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name);
};
},
foo2: function () {
return () => {
console.log(this.name);
};
}
};
}
var person1 = new Person('person1');
var person2 = new Person('person2');
person1.obj.foo1()();
person1.obj.foo1.call(person2)();
person1.obj.foo1().call(person2);
person1.obj.foo2()();
person1.obj.foo2.call(person2)();
person1.obj.foo2().call(person2);
答案&解析:
var name = 'window';
function Person(name) {
this.name = name;
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name);
};
},
foo2: function () {
return () => {
console.log(this.name);
};
}
};
}
var person1 = new Person('person1');
var person2 = new Person('person2');
person1.obj.foo1()(); // window 解析:函数的this指向全局,也就是window
person1.obj.foo1.call(person2)(); // window 解析:函数的this指向全局,也就是window
person1.obj.foo1().call(person2); // person2 解析:call改变this的指向为person2, person2.name 是 'person2'
person1.obj.foo2()(); // obj 解析:箭头函数的this来源于外层作用域,也就是foo2,foo2的this指向调用它的那个对象,也就是obj
person1.obj.foo2.call(person2)(); // person2 解析:箭头函数的this来源于外层作用域,也就是foo2。call改变foo2的this指向为person2
person1.obj.foo2().call(person2); // obj 解析:箭头函数的this无法通过call来改变
4. 题目 4
var obj = {
a: 1,
foo: function (b) {
b = b || this.a;
return function (c) {
console.log(this.a + b + c);
};
}
};
var a = 2;
var obj2 = { a: 3 };
obj.foo(a).call(obj2, 1);
obj.foo.call(obj2)(1);
答案和解析:
-
obj.foo(a).call(obj2, 1)
obj.foo(a)
返回的是一个函数,函数中引用了外层函数的变量 b, 这里是 2。由于形成了闭包,这个 b 会长存在内存中。
function(c) {
console.log(`${this.a} + ${b} + ${c}`);
}
然后,通过 call 改变该函数的指向为 obj2, 因此 this.a 等同于 obj.a,也就是 2.
因此答案是:3 + 2 + 1
-
obj.foo.call(obj2)(1)
obj.foo.call(obj2)
改变 foo 的 this 指向为 obj2, 且没有传参。因此 b = this.a 也就是 obj2.a。也就是 3。b 长存内存,是 3
因此返回的函数是:
function(c) {
console.log(`${this.a} + ${b} + ${c}`);
}
然后指向函数,参数是 1。函数的 this 指向 window。this.a 等同于 window.a,也就是 2
因此答案是:2 + 3 + 1
最后
如果你做完这几道题不过瘾,可以去刷一下这篇博客 再来 40 道 this 面试题酸爽继续(1.2w 字用手整理)。
本文中的大多数问题都来自于该博客。40 道面试题都答对,this 指向 so easy。
参考文档: