this指向练习题

普通方法和对象方法 this 指向结论:

  1. this的指向,是在函数被调用的时候确定的

  2. 函数调用时,看其是否被某个对象所拥有,假如被某个对象拥有,那么函数中的this,指向的是其拥有的对象

    例如:obj.fn()。fn() 函数被 obj 所拥有,那么 fn 里面的 this,指向的是 obj

  3. 如果函数独立调用,那么该函数内部的this,则指向undefined。在非严格模式中,当this指向undefined时,它会被自动指向全局对象。

    例如aaa() 是独立调用,那么aaa函数里面的this会指向undefined,在非严格模式下面指向的是全局对象

基础练习题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var value = 1;

var foo = {
value: 2,
bar: function () {
return this.value;
}
}

//示例1
console.log(foo.bar()); // 2
//示例2
console.log((foo.bar)()); // 2
//示例3
console.log((foo.bar = foo.bar)()); // 1
//示例4
console.log((false || foo.bar)()); // 1
//示例5
console.log((foo.bar, foo.bar)()); // 1
// 注意后面3个例子,其实都是bar函数, 已经脱离了foo context
bar == (foo.bar = foo.bar); // true foo.bar = foo.bar是一个语句,调用时执行该语句返回一个function,已丢失foo,又因为全局调用,所以this被赋值为当前全局对象。
bar == (foo.bar,foo.bar); // true
bar == (false || foo.bar); // true

独立调用,非严格模式下 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
// demo1
var a = 20;
function fn() {
console.log(this.a);
}
fn();

// demo02
var a = 20;
function fn() {
function foo() {
console.log(this.a);
}
foo();
}
fn();

// demo3
// 为了能够准确判断,我们在函数内部使用严格模式,因为非严格模式会自动指向全局
function fn() {
'use strict';
console.log(this);
}

fn(); // fn是调用者,独立调用
window.fn(); // fn是调用者,被window所拥有

// demo04
function foo() {
console.log(this.a)
}

function active(fn) {
fn(); // 真实调用者,为独立调用
}

var a = 20;
var obj = {
a: 10,
getA: foo
}

active(obj.getA);

obj.c 里面的 this.a 是赋值语句,并没有调用,this 是在调用时被决定的,现在只是定义,默认指向全局对象,this.a === window.a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// demo01
var a = 20;
var obj = {
a: 10,
c: this.a + 20,
fn: function () {
return this.a;
}
}

console.log(obj.c);
console.log(obj.fn());

// demo02
var a = 20;
function getA() {
return this.a;
}
var foo = {
a: 10,
getA: getA
}
console.log(foo.getA()); // 10

参考文章

前端基础进阶(七):全方位解读this

进阶练习题1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

解析

可以看到题目中存在函数声明和变量声明,它们会被提升,变成下面

1
2
3
4
5
6
7
8
9
function Foo() {
getName = function () { alert (1); };
return this;
}
function getName() { alert (5);} // 函数声明比变量声明提前
var getName // 由于上面函数已声明 getName,相同的变量名声明会被直接忽略
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
getName = function () { alert (4);}; // 覆盖

开始执行语句:

  1. Foo.getName(); 找Foo函数找到getName方法 找到Foo.getName = function () { alert(2); }; 打印输出2

  2. getName(); 由上面声明提升的分析得,执行getName = function () { alert (4);}; 打印输出4

  3. Foo().getName();

    运算符的优先级(点.的优先级高),但是因为()括号无法.点调用,所以先将Foo函数执行完再去执行.getName()方法。

    等价于(Foo()).getName(); 先看Foo函数,一个全局变量getName,一个return this,所以此时全局变量的getName再次被重新赋值。

    Foo返回this,返回的就是调用Foo函数的对象,window。所以(Foo()).getName() 等价于window,getName(),getName = function () { alert(1); };,打印输出1

  4. getName(); 与上题一样,getName = function () { alert(1); };,打印输出1

  5. new Foo.getName(); 因为.点的优先级高,所以等价于 new (Foo.getName)(),Foo.getName = function () { alert (2);};,new(Foo.getName)() = new ( function () { alert(2); };){},因为new函数调用时,会执行这个函数,所以打印输出2

  6. new Foo().getName(); 实际为 (new Foo()).getName(),先执行 new Foo(), 结果产生一个新的实例对象,并且继承了Foo()这个构造函数中的getName方法,找到 Foo.prototype.getName = function () { alert(3); }; 打印输出3

  7. new new Foo().getName(); new ((new Foo()).getName)() => new (foo.getName)() => new (function () { alert(3); };)() 打印输出3

参考文章

一道常被人轻视的前端JS面试题

js经典面试题之Foo.getName

js面试题Foo.getName()的故事

进阶练习题2

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
var name = 'window'

var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }

person1.show1()
person1.show1.call(person2)

person1.show2()
person1.show2.call(person2)

person1.show3()()
person1.show3().call(person2)
person1.show3.call(person2)()

person1.show4()()
person1.show4().call(person2)
person1.show4.call(person2)()

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
person1.show1() // person1,隐式绑定,this指向调用者 person1 
person1.show1.call(person2) // person2,显式绑定,this指向 person2

person1.show2() // window,箭头函数绑定,this指向外层作用域,即全局作用域
person1.show2.call(person2) // window,箭头函数绑定,this指向外层作用域,即全局作用域

person1.show3()() // window,默认绑定,这是一个高阶函数,调用者是window
// 类似于`var func = person1.show3()` 执行`func()`
person1.show3().call(person2) // person2,显式绑定,this指向 person2
person1.show3.call(person2)() // window,默认绑定,调用者是window

person1.show4()() // person1,箭头函数绑定,this指向外层作用域,即person1函数作用域
person1.show4().call(person2) // person1,箭头函数绑定,
// this指向外层作用域,即person1函数作用域
person1.show4.call(person2)() // person2

通过构造函数来创建一个对象,并执行相同的4个show方法

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
/**
* 非严格模式
*/

var name = 'window'

function Person (name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () => console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () => console.log(this.name)
}
}

var personA = new Person('personA')
var personB = new Person('personB')

personA.show1()
personA.show1.call(personB)

personA.show2()
personA.show2.call(personB)

personA.show3()()
personA.show3().call(personB)
personA.show3.call(personB)()

personA.show4()()
personA.show4().call(personB)
personA.show4.call(personB)()

答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
personA.show1() // personA,隐式绑定,调用者是 personA
personA.show1.call(personB) // personB,显式绑定,调用者是 personB

personA.show2() // personA,首先personA是new绑定,产生了新的构造函数作用域,
// 然后是箭头函数绑定,this指向外层作用域,即personA函数作用域
personA.show2.call(personB) // personA,同上

personA.show3()() // window,默认绑定,调用者是window
personA.show3().call(personB) // personB,显式绑定,调用者是personB
personA.show3.call(personB)() // window,默认绑定,调用者是window

personA.show4()() // personA,箭头函数绑定,this指向外层作用域,即personA函数作用域
personA.show4().call(personB) // personA,箭头函数绑定,call并没有改变外层作用域,
// this指向外层作用域,即personA函数作用域
personA.show4.call(personB)() // personB,解析同题目1,最后是箭头函数绑定,
// this指向外层作用域,即改变后的person2函数作用域

题目一和题目二的区别是,personA 通过构造函数创建,new 绑定,产生了新的构造函数作用域,在 personA.show2() 中的箭头函数,this指向外层作用域,即personA函数作用域。

参考文章

从这两套题,重新认识JS的this、作用域、闭包、对象

【进阶3-2期】JavaScript深入之重新认识箭头函数的this

面试题 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var a = 100
function create() {
var a = 200
return function () {
console.log(a)
}
}
var fn = create();
fn()
// 200

var a = 100
function invoke(fn) {
var a = 200
fn()
}
function fn() {
console.log(a)
}
invoke(fn)
// 100