this
文章来自:
https://github.com/LinDaiDai/niubility-coding-js/blob/master/JavaScript/this/再来40道this面试题酸爽继续.md
内容概要
- this的默认绑定
- 隐式绑定
- 隐式绑定的隐式丢失问题
- 显示绑定
- 显示绑定的其他用法
- new绑定
- 箭头函数绑定
前期准备
在正式阅读之前,你需要知道this的5种绑定方式:
- 默认绑定(非严格模式下this指向全局对象,严格模式下this会绑定到undefined)
- 隐式绑定(当函数引用有上下文对象时,如obj.foo()的调用方式,foo内的this指向obj)
- 显式绑定(通过call()或者apply()方法直接指定this的绑定对象,如foo.call(obj))
- new绑定
- 箭头函数绑定(this的指向由外层作用域决定的)
1、默认绑定
先介绍一种最简单的绑定方式吧:默认绑定。
也就是我们常说的:在非严格模式下this指向的是全局对象window,而在严格模式下会绑定到undefined。
1.1题目一
var a = 10;
function foo() {
console.log(this.a);
}
foo();
我们知道在使用var创建变量的时候(不在函数里),会把创建的变量绑定到window上,所以此时a是window下的属性。
而函数foo也是window下的属性。
因此上面的代码其实就相当于是这样:
window.a = 10;
function foo() {
console.log(this.a);
}
window.foo();
在这里,调用foo()函数的是window对象,且又是在非严格模式下,所以foo()中的this的指向是window对象,因此this.a会输出10。
答案:
10
1.2题目二
改造下题目一,看看在严格模式下。
(想要开启严格模式,只要在需要开启的地方写上"use strict")
"use strict";
var a = 10;
function foo() {
console.log('this1', this);
console.log(window.a);
console.log(this.a);
}
console.log(window.foo)
console.log('this2', this)
foo()
需要注意的点:
- 开启了严格模式,只是说使得函数内的this指向undefined,它并不会改变全局中this的指向。因此this1中打印的是undefined,而this2还是window对象。
- 另外,它也不会阻止a被绑定到window对象上。
所以最后的执行结果:
f foo() {...}
'this2' Window{...}
'this1' undefined
10
Uncaught TypeError: Cannot read property 'a' of undefined
1.3题目三
let a = 10;
const b = 20;
function foo() {
console.log(this.a)
console.log(this.b)
}
foo();
console.log(window.a)
如果把var改成了let或者const,变量是不会被绑定到window上的,所以此时会打印出三个undefined。
答案:
undefined
undefined
undefined
1.4题目四
var a = 1;
function foo() {
var a = 2;
console.log(this);
console.log(this.a);
}
foo()
这里我们很容易就知道,foo()函数内的this指向的是window,因为是window调用的foo。
但是打印出的this.a呢?注意,是this.a,不是a,因此是window下的a。
并且由于函数作用域的原因我们知道window下的a还是1。
因此答案为:
Window{...}
1
1.5题目五
把题目1.4改造一下
var a = 1;
function foo() {
var a = 2;
function inner() {
console.log(this.a)
}
inner()
}
foo()
其实这里和1.4很像,不过一看到函数内的函数,就很容易让人联想到闭包,然后就脱口而出,答案是2。
审题可得仔细啊,这里问的是this.a,而在inner中,this指向的还是window。
答案:
1
2.隐式绑定
这里有一个简单的规则,this永远指向最后调用它的那个对象。谁最后调用的函数,函数内的this指向的就是谁(不考虑箭头函数)。
2.1题目一
function foo() {
console.log(this.a)
}
var obj = { a: 1, foo }
var a = 2
obj.foo()
(var obj = { foo }就相当于var obj = { foo: foo })
这道题中,函数foo()虽然是定义在window下,但是在obj对象中引用了它,并且将它重新赋值到obj.foo上。
且调用它的是obj对象,因此打印出来的this.a应该是obj中的a。
换个角度想,上面的代码就相当于下面这样:
var obj = {
a: 1,
foo: function() {
console.log(this.a)
}
}
var a = 2
obj.foo()
在这里foo函数内的this指向的就是obj,和题目效果一样。
答案是:
1
有人会有疑问了,obj.foo()不就相当于window.obj.foo()吗?
那foo()内的this到底是算window呢,还是obj呢?
请注意我前面说的,是最后调用函数的对象,显然,obj要比window更后面一点。
3、隐式绑定的隐式丢失问题
隐式丢失其实就是被隐式绑定的函数在特定的情况下会丢失绑定对象
有两种情况容易发生隐式丢失问题:
- 使用另一个变量来给函数取别名
- 将函数作为参数传递时会被隐式赋值,回调函数丢失this绑定
3.1题目一
使用另一个变量来给函数取别名会发生隐式丢失。
function foo() {
console.log(this.a)
}
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo
obj.foo()
foo2()
执行这段代码会打印啥呢?
在这里我们已经知道了,obj.foo()中this的指向是obj的,所以obj.foo()执行的时候,打印出来的是obj对象中的a, 也就是1.
但是foo2它不也是obj.foo吗?我只不过是用了一个变量foo2来盛放了它而已。所以你是不是认为它打印的也是1呢?
其实不是的,它打印出的是window下的a。
答案:
1
2
这是因为虽然foo2指向的是obj.foo函数,不过调用它的却是window对象,所以它里面this的指向是window。
其实也就相当于window.foo2(),如果你不信的话,可以看下面一题。
3.2题目二
让我们在一个新的变量obj2中也定义一个foo2看看:
function foo() {
console.log(this.a)
}
var obj = { a: 1, foo }
var a = 2
var foo2 = obj.foo
var obj2 = { a: 3, foo2: obj.foo }
obj.foo()
foo2()
obj2.foo2()
答案:
1
2
3
- obj.foo()中的this指向调用者obj。
- foo2()发生了隐式丢失,调用者是window,使得foo()中的this指向window。
- obj2.foo2()发生了隐式丢失,调用者是obj2,使得foo()中的this指向obj2。
3.3题目三
再就是如果你把一个函数当成参数传递时,也会被隐式赋值,发生意想不到的问题。
来看看这道题目:
function foo() {
console.log(this.a)
}
function doFoo(fn) {
console.log(this)
fn()
}
var obj = { a: 1, foo }
var a = 2
doFoo(obj.foo)
这里我们将obj.foo当成参数传递到doFoo函数中,在传递过程中,obj.foo()函数内的this发生了改变,指向了window。
因此结果为:
Window{...}
2
注意,我这里说的是obj.foo()函数,而不是doFoo()。doFoo()函数内的this本来就是指向window的,因为这里是window调用的它。
但是你不要以为是doFoo()函数内的this影响了obj.foo(),不信你看下一题。
3.4题目四
现在我们不用window调用doFoo,而是放在对象obj2里,用obj2调用:
function foo() {
console.log(this.a)
}
function doFoo(fn) {
console.log(this)
fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }
obj2.doFoo(obj.foo)
现在调用obj2.doFoo()函数,里面的this指向的应该是obj2,因为是obj2调用的它。
但是obj.foo打印出来的a依然是2,也就是window下的。
执行结果为:
{ a: 3, doFoo: f }
2
所以说,如果你把一个函数当成参数传递到另一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。
一样的代码,试试严格模式下:
"use strict"
function foo () {
console.log(this.a)
}
function doFoo (fn) {
console.log(this)
fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }
obj2.doFoo(obj.foo)
执行结果:
{ a: 3, doFoo: f }
Uncaught TypeError: Cannot read property 'a' of undefined
4、显式绑定
功能如其名,就是强行使用某些方法,改变函数内this的指向。
通过call()、apply()或者bind()方法直接指定this的绑定对象,如foo.call(obj)。
这里有几个知识点需要注意:
- 使用.call()或者.apply()的函数是会直接执行的
- bind()是创建一个新的函数,需要手动调用才会执行
- .call()和apply()用法基本类似,不过call接收若干个参数,而apply接收的是一个数组
4.1题目一
function foo() {
console.log(this.a)
}
var obj = { a: 1 }
var a = 2
foo()
foo.call(obj)
foo.apply(obj)
foo.bind(obj)
第一个foo()很好理解,这不就是默认绑定吗?
而第二个和第三个foo都使用了call或apply来改变this的指向,并且是立即执行的。
第四个foo,仅仅是使用了bind创建了一个新的函数,且这个新函数也没用别的变量接收并调用,因此不会执行。
答案:
2
1
1
这里提一嘴,如果call, apply, bind接收到的第一个参数是空或者null,undefined的话,则会忽略这个参数。
例如:
function foo() {
console.log(this.a)
}
var a = 2
foo.call()
foo.call(null)
foo.call(undefined)
输出的值:
2
2
2
4.2题目二
了解了显示绑定的基本使用之后,让我们来看看它的妙用。
首先,是这个例子:
var obj1 = {
a: 1
}
var obj2 = {
a: 2,
foo1: function() {
console.log(this.a)
},
foo2: function() {
setTimeout(function() {
console.log(this)
console.log(this.a)
}, 0)
}
}
var a = 3
obj2.foo1()
obj2.foo2()
对于obj.foo1(), 我们很清楚,它就是2
但是对于obj.foo2呢?在这个函数里,设置了一个定时器,并要求我们打印出this和this.a。
想想我前面说过的话,谁调用的函数,函数内的this指向的就是谁。
而对于匿名函数,它里面的this在非严格模式下始终指向的都是window。
所以最终结果是:
2
Window{...}
3
4.3题目三
面对上面这种情况我们就可以使用call, apply或者bind来改变函数中this的指向,使它绑定到obj1上,从而打印出1。
var obj1 = {
a: 1
}
var obj2 = {
a: 2,
foo1: function() {
console.log(this.a)
},
foo2: function() {
setTimeout(function() {
console.log(this)
console.log(this.a)
}.call(obj1), 0)
}
}
var a = 3
obj2.foo1()
obj2.foo2()
执行结果:
2
{ a: 1 }
1
这里是将.call运用到了setTimeout里的回调函数上,并不是运用到obj2.foo2()上。
有人会问,下面的写法不可以吗?
obj2.foo2.call(obj1)
注意:如果是这种写法的话,改变的就是foo2函数内的this的指向了,但是我们知道,foo2函数内this的指向和setTimeout里函数的this是没有关系的,因为调用定时器的始终是window。
并且这里使用.bind()也是可以的,因为定时器里的函数在时间到了之后本就是会自动执行的。
4.4题目四
我们不用定时器,把它干掉,换成一个函数:
var obj1 = {
a: 1
}
var obj2 = {
a: 2,
foo1: function() {
console.log(this.a)
},
foo2: function() {
function inner() {
console.log(this)
console.log(this.a)
}
inner()
}
}
var a = 3
obj2.foo1()
obj2.foo2()
这个也是函数包裹着函数(可以理解为foo2是obj2的方法,属于obj2, inner()是普通函数,属于window对象)
所以结果为:
2
Window{...}
3
如果给inner()函数显式绑定的话:
inner.call(obj1)
结果为:
2
{ a: 1 }
1
4.5题目五
其实在实际面试中,面试官喜欢这样考:
看看这道题输出结果:
function foo() {
console.log(this.a)
}
var obj = { a: 1 }
var a = 2
foo()
foo.call(obj)
foo().call(obj)
结果:
2
1
2
foo()会正常打印出window下的a,也就是2
foo.call(obj)由于显式绑定了this,所以会打印出obj下的a,也就是1
foo().call(obj)开始会执行foo()函数,打印出2,但是会对foo()函数的返回值执行.call(obj)操作,可以foo()函数的返回值是undefined,因此就会报错了。
4.6题目六
既然4.5是因为函数没有返回值才报的错,那现在加上返回值看看:
function foo() {
console.log(this.a)
return function() {
console.log(this.a)
}
}
var obj = { a: 1 }
var a = 2
foo()
foo.call(obj)
foo().call(obj)
结果:
2
1
2
1
- 第一个数字2自然是foo()输出的,虽然foo()函数也返回了一个匿名函数,但是并没有调用它呀,只有写成foo()(),这样才算是调用匿名函数。
- 第二个数字1是foo.call(obj)输出的,由于.call()是紧跟着foo的,所以改变的是foo()内this的指向,并且.call()是会使函数立即执行的,因此打印出1,同理,它也没有调用返回的函数。
- 第三个数字2是foo().call(obj)先执行foo()时打印出来的,此时foo()内this还是指向window。
- 在执行完foo()之后,会返回一个匿名函数,并且后面使用了.call(obj)来改变这个匿名函数的this指向并调用了它,所以输出了1。
4.7题目七
想想我们把call换成bind会怎么样呢?
先来回忆一下它们的区别:call是会直接执行函数的,bind是返回一个新函数,但不会执行。
function foo() {
console.log(this.a)
return function() {
console.log(this.a)
}
}
var obj = { a: 1 }
var a = 2
foo()
foo.bind(obj)
foo().bind(obj)
结果:
2
2
- foo()正常执行,打印出2
- 但是foo.bind(obj)却不会执行,它返回的是一个新函数
- foo().bind(obj)只会执行前面的foo()函数,打印出2,.bind(obj)只是将foo()返回的匿名函数显式绑定this而已,并没有调用。
4.8题目八
对于这种函数内返回的函数,一定要记住谁最后调用的它,this就指向谁。
function foo() {
console.log(this.a)
return function() {
console.log(this.a)
}
}
var obj = { a: 1 }
var a = 2
foo.call(obj)()
就像这道题,foo()函数内的this虽然指定了obj,但是调用最后也就是执行完foo.call(obj)之后,再进行调用匿名函数的确实window。
所以结果为:
1
2
(请注意这个例子,后面它会与箭头函数做对比)
4.9题目九
一直都在做函数返回函数的题目,让我们来看看把它们加到对象里,会有哪些有趣的题目吧。
var obj = {
a: 'obj',
foo: function() {
console.log('foo', this.a)
return function() {
console.log('inner', this.a)
}
}
}
var a = 'window'
var obj2 = { a: 'obj2' }
obj.foo()()
obj.foo.call(obj2)()
obj.foo().call(obj2)
- obj.foo()自然是打印出foo: obj和inner: window,这个没什么疑惑的。
- obj.foo.(obj2)()其实也没啥可疑惑的了,打印出foo: obj2和inner: window(类似4.8)。
- 那么obj.foo().call(obj2)就更没啥可疑惑的了,打印出foo: obj和inner: obj2。
4.10题目十
现在我们加一些参数看看
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)
结果:
6
6
- 开始调用obj.foo(a)将2传入foo函数并赋值给形参b,并且由于闭包原因,使得匿名函数能访问到b,之后调用匿名函数的时候,用call()改变了this的指向,使得匿名函数内this.a为3,并传入最后一个参数1,所以第一行输出的应该是3+2+1,也就是6。
- 而第二行,obj.foo.call(obj2)这里江南foo函数的this指向了obj2,同时并没有传递任何参数,所以b开始是undefined的,但是又因为有一句b = b || this.a,使得b变为了3,同时最后一段代码(1),是在调用匿名函数,且这个匿名函数内的this应该是指向window的,因此输出也为3+2+1,为6。
5、显示绑定的其他用法
除了上面那几道题的用法之外,我们还可以有一些其它的用法。
例如,我们可以在一个函数内使用call来显式绑定某个对象,这样无论怎样调用它,其内部的this总是指向这个对象。
5.1题目一
function foo1() {
console.log(this.a)
}
var a = 1
var obj = {
a: 2
}
var foo2 = function() {
foo1.call(obj)
}
foo2()
foo2.call(window)
这里的foo2函数内部的函数foo1我们使用call来显式绑定obj,就算后面再用call来绑定window也没有用了。
结果:
2
2
5.2
function foo1(b) {
console.log(`${this.a} + ${b}`)
return this.a + b
}
var a = 1
var obj = {
a: 2
}
var foo2 = function() {
return foo1.call(obj, ...arguments)
}
var num = foo2(3)
console.log(num)
答案:
'2 + 3'
5
5.3题目三
接下来是一个比较冷门的知识。
相信大家对forEach、map、filter都不陌生吧,它们是JS内置的一些函数,但是你知道它们的第二个参数也是能绑定this的吗?
来看看下面的题目吧:
function foo(item) {
console.log(item, this.a)
}
var obj = {
a: 'obj'
}
var a = 'window'
var arr = [1, 2, 3]
// arr.forEach(foo, obj)
// arr.map(foo, obj)
arr.filter(function(i) {
console.log(i, this.a)
return i > 2
}, obj)
答案:
1 "obj"
2 "obj"
3 "obj"
如果我们没有传递第二个参数obj的话,this.a打印出来的肯定是window下的a了,但是传入了之后将obj显示绑定到第一个参数函数上。
总结
- this永远指向最后调用它的那个对象
- 匿名函数(也就是普通函数,不是作为对象的方法,例如某个方法中的函数)的this永远指向window
- 使用.call()或者.apply()的函数是会直接执行的
- bind()是创建一个新的函数,需要手动调用才会执行
- 如果call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数
- forEach、map、filter函数的第二个参数也是能显示绑定this的。
6.new绑定
下面我们来看看另一种this的绑定形式,也就是new绑定。
使用new来调用一个函数,会构造一个新对象并把这个新对象绑定到调用函数中的this。
例如第一题。
6.1题目一
使用new来调用Person, 构造了一个新对象person1并把它(person1)绑定到Person调用中的this。
function Person(name) {
this.name = name
}
var name = 'window'
var person1 = new Person('zhangsan')
console.log(person1.name)
答案:
zhangsan
6.2题目二
构造函数中不仅可以加属性,也可以加方法:
function Person(name) {
this.name = name,
this.foo1 = function() {
console.log(this.name)
},
this.foo2 = function() {
return function() {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
person1.foo1()
person1.foo2()()
答案:
'person1'
''
- 第一个this.name打印的肯定是person1对象中的name,也就是构造person1对象时传递进去的person1字符串。
- 第二个this.name打印的应该就是window下的name了,但是这里window对象中并不存在name属性,所以打印出的是空。
6.3题目三
使用new函数创建的对象和字面量形式创建出来的对象好像没有什么大的区别,如果对象中有属性是函数类型的话,并且不是箭头函数,那么解法都一样。在后面说到箭头函数的时候就有区别了。
先看看下面这道题:
var name = 'window'
function Person(name) {
this.name = name
this.foo = function() {
console.log(this.name)
return function() {
console.log(this.name)
}
}
}
var person2 = {
name: 'person2',
foo: function() {
console.log(this.name)
return function() {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
person1.foo()()
person2.foo()()
在这道题中,person1.foo和person2.foo就没有什么区别。
打印出来的结果为:
'person1'
'window'
'person2'
'window'
6.4题目四
当new绑定结合显式绑定,例如call函数的话,解起来其实也不难。
来看下面的题目:
var name = 'window'
function Person(name) {
this.name = name
this.foo = function() {
console.log(this.name)
return function() {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person2.foo.call(person2)()
person2.foo().call(person2)
在做这类题的时候,你就把Person生成的person1脑补成:
var person1 = {
name: 'person1',
foo: function() {
console.log(this.name)
return function() {
console.log(this.name)
}
}
}
所以答案很容易就出来了:
'person2'
'window'
'person1'
'person2'
解题分析:
- person1.foo.call(person2)()将foo()函数内的this指向了person2,所以打印出person2,而内部返回的匿名函数是由window调用的,所以打印出window。
- peron1.foo().call(person2)是将匿名函数的this显式绑定到了person2上,所以打印出来的是person2
7、箭头函数绑定
在上面,我们有学到一个诀窍:this永远指向最后调用它的那个对象。
但是对于箭头函数就不是这样了,它里面的this是由外层作用域来决定的,且指向函数定义时的this而非执行时。
“它里面的this是由外层作用域来决定的”啥意思呢?来看看这句话:
箭头函数中没有this绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则,this为undefined。
而且指向函数定义时的this而非执行时这句话可以等会看题目7.4。
再来看看箭头函数可以分为哪几类题目来说吧,这是目录:
- 字面量对象中普通函数与箭头函数的区别:只有一层函数的题目
- 字面量对象中普通函数与箭头函数的区别:函数嵌套的题目
- 构造函数对象中普通函数和箭头函数的区别:只有一层函数的题目
- 构造函数对象中普通函数和箭头函数的区别:函数嵌套的题目
- 箭头函数结合.call的题目
7.1题目一
var obj = {
name: 'obj',
foo1: () => {
console.log(this.name)
},
foo2: function() {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
var name = 'window'
obj.foo1()
obj.foo2()()
这道题就非常具有代表性,它明确了箭头函数内的this是由外层作用域决定的。
-
对于obj.foo1()函数的调用,它的外层作用域是window,对象obj当然不属于作用域了(我们知道作用域只有全局作用域window和局部作用域函数)。所以会打印window。
-
obj.foo2()(),首先会执行obj.foo2(),这不是个箭头函数,所以它里面的this是调用它的obj对象,因此打印出obj,而返回的匿名函数是一个箭头函数,它的this由外层作用域决定,那也就是foo2咯,那就是它的this会和foo2函数里的this一样,就也打印出了obj。
让我们来拆分一下看看区别。
7.2题目二
字面量对象中普通函数与箭头函数的区别:只有一层函数的题目
var name = 'window'
var obj1 = {
name: 'obj1',
foo: function() {
console.log(this.name)
}
}
var obj2 = {
name: 'obj2',
foo: () => {
console.log(this.name)
}
}
obj1.foo()
obj2.foo()
解题分析:
- 不使用箭头函数的obj1.foo()是由obj1调用了,所以this.name为obj1。
- 使用箭头函数的obj2.foo()的外层作用域是window,所以this.name为window。
答案:
'obj1'
'window'
7.3题目三
字面量对象中普通函数与箭头函数的区别:函数嵌套的题目
如果用普通函数和箭头函数来做一层嵌套关系的话,一共有四种情况,让我们把每种情况都考虑一遍:
var name = 'window'
var obj1 = {
name: 'obj1',
foo: function() {
console.log(this.name)
return function() {
console.log(this.name)
}
}
}
var obj2 = {
name: 'obj2',
foo: function() {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
var obj3 = {
name: 'obj3',
foo: () => {
console.log(this.name)
return function() {
console.log(this.name)
}
}
}
var obj4 = {
name: 'obj4',
foo: () => {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
obj1.foo()()
obj2.foo()()
obj3.foo()()
obj4.foo()()
解题分析:
- obj1.foo()()两层都是普通函数,类似于题目4.6,分别打印出obj1和window
- obj2.foo()()外层为普通函数,内层为箭头,类似于题目7.1,都是打印出obj2。
- obj3.foo()()外层为箭头函数,内层为普通函数,箭头函数的this由外层作用域决定,因此为window,内层普通函数由调用者决定,调用它的是window,因此也为window。
- obj4.foo()()两层都是箭头函数,第一个箭头函数的this由外层作用域决定,因此为window,第二个箭头函数的this也由外层作用域决定,它的外层作用域是第一个箭头函数,而第一个箭头函数的this是window,因此内层的this也是window。
答案:
obj1.foo()() // 'obj1' 'window'
obj2.foo()() // 'obj2' 'obj2'
obj3.foo()() // 'window' 'window'
obj4.foo()() // 'window' 'window'
7.4题目四
构造函数中普通函数和箭头函数的区别:一层函数的题目
var name = 'window'
function Person(name) {
this.name = name
this.foo1 = function() {
console.log(this.name)
}
this.foo2 = () => {
console.log(this.name)
}
}
var person2 = {
name: 'person2',
foo2: () => {
console.log(this.name)
}
}
var person1 = new Person('person1')
person1.foo1()
person1.foo2()
person2.foo2()
- person1.foo1()是个普通函数,this由最后调用它的对象决定,即person1。
- person1.foo2()为箭头函数,this由外层作用域决定,且指向函数定义时的this而非执行时,在这里它的外层作用域是函数Person,且这个是构造函数,并且使用了new来生成了对象person1,所以此时this的指向是为person1。
- person2.foo2()字面量创建的的对象person2中的foo2是个箭头函数,由于person2是直接在window下创建的,你可以理解为它所在的作用域就是在window下,因此person2.foo2()内的this应该是window。
答案:
'person1'
'person1'
'window'
7.5题目五
构造函数对象中普通函数和箭头函数的区别:函数嵌套题目
var name = 'window'
function Person(name) {
this.name = name
this.foo1 = function() {
console.log(this.name)
return function() {
console.log(this.name)
}
}
this.foo2 = function() {
console.log(this.name)
return () => {
console.log(this.name)
}
}
this.foo3 = () => {
console.log(this.name)
return function() {
console.log(this.name)
}
}
this.foo4 = () => {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
person1.foo1()()
person1.foo2()()
person1.foo3()()
person1.foo4()()
解题分析:
- person1.foo1()()两层都是普通函数,这个不再重复说了,打印出person1和window。(类似题目6.2)
- person1.foo2()()第一层普通函数,它的this是由最后调用它的对象决定也就是person1,第二层为箭头函数,它的this由外层作用域决定,也就是foo2这个函数,因此也为person1。
- person1.foo3()()第一层为箭头函数,this由外层作用域决定,因此为person1,第二层为普通函数,由最后调用者决定,因此为window。
- person1.foo4()()两层都是箭头函数,this由外层作用域决定,所以都是person1。
答案:
person1.foo1()() // 'person1' 'window'
person1.foo2()() // 'person1' 'person1'
person1.foo3()() // 'person1' 'window'
person1.foo4()() // 'person1' 'person1'
7.6题目六
箭头函数结合.call的题目
箭头函数的this无法通过bind、call、apply来直接修改,但是可以通过改变作用域中this的指向来间接修改。
var name = 'window'
var obj1 = {
name: 'obj1',
foo1: function() {
console.log(this.name)
return () => {
console.log(this.name)
}
},
foo2: () => {
console.log(this.name)
return function() {
console.log(this.name)
}
}
}
var obj2 = {
name: 'obj2'
}
obj1.foo1.call(obj2)()
obj1.foo1().call(obj2)
obj1.foo2.call(obj2)()
obj1.foo2().call(obj2)
解题分析:
- obj1.foo1.call(obj2)()第一层为普通函数,并且通过.call改变了this指向为obj2,所以会打印出obj2,第二层为箭头函数,它的this和外层作用域中的this相同,因此也是obj2。
- obj1.foo().call(obj2)第一层打印出obj1,第二层为箭头函数,使用了.call想要修改this的指向,但是并不能成功,因此.call(obj2)对箭头函数无效,还是打印出obj1。
- obj1.foo2.call(obj2)()第一层为箭头函数,并且想要通过.call(obj2)改变this指向,但是无效,且它的外层作用域是window,所以会打印出window,第二层为普通函数,this是最后调用者window,所以也会打印出window。
- obj1.foo2().call(obj2)第一层为箭头函数,外层作用域是window,打印出window,第二层为普通函数,且使用了.call(obj2)来改变this指向,所以打印出了obj2。
答案:
obj1.foo1.call(obj2)() // 'obj2' 'obj2'
obj1.foo1().call(obj2) // 'obj1' 'obj1'
obj1.foo2.call(obj2)() // 'window' 'window'
obj1.foo2().call(obj2) // 'window' 'obj2'
在这道题中,obj1.foo1.call(obj2)就相当于是通过改变作用域简介改变箭头函数内this的指向。
总结
箭头函数需要注意的点:
- 它里面的this是由外层作用域来决定的,且指向函数定义时的this而非执行时
- 字面量创建的对象,作用域是window,如果里面有箭头函数属性的话,this指向的是window
- 构造函数创建的对象,作用域是可以理解为是这个构造函数,且这个构造函数的this是指向新对象的,因此this指向这个对象。
- 箭头函数的this是无法通过bind、call、apply来直接修改的,但是可以通过作用域中this的指向来间接修改。

浙公网安备 33010602011771号