这篇文章用来整理this
指向相关面试题,我们请看内容
##面试题一
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss(); // 绑定: 默认绑定, window -> window
person.sayName(); // 绑定: 隐式绑定, person -> person
(person.sayName)(); // 绑定: 隐式绑定, person -> person
(b = person.sayName)(); // 术语: 间接函数引用, window -> window
}
sayName();
上面代码执行之后依次打印window person person window
解释一下,这里的第一个window
是上面的person
对象里面的sayName
函数,那么我们这里调用的this
为什么不是指向我们preson
对象呢?原因是我们sayName
方法使用环境,将sayName
函数重新赋值给了一个变量sss
,我们调用sss()
执行sayName
方法,当我们调用sss
的时候我们当前的环境是在windows
中,所以我们可以理解,假如我们直接调用person.sayName()
才是打印person
对象,也就是我们第二个打印出person
的隐式绑定的方法。
第二个person
的由来:第二个person
我们知道当小括号包裹一段内容这段内容可以看做一个整体,并且先运行,所以我们可以将第一个小括号里的内容先运行出来,没错他就是我们的sayName
方法,众所周知,函数体在JavaScript中是堆内存,所以我们执行这里只是调用了这个内存地址,并不关person
的事,然后我们将这个内存地址运行的时候就需要在后面加个新的小括号了,这里讲的就是函数的调用。
最后一个window
打印的时候,是将我们的person.sayName
方法间接赋值给了一个全局变量b
并且执行,假如在最后我们打印b
变量会发现是ƒ () {console.log(this.name);}
和我们打印sss
的时候情况一模一样,所以就能体会到JavaScript
实际使用中的玄妙了吧。
面试题二
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 () {
// console.log(this) // 第一个表达式this -> person1
// console.log(this) // 第二个表达式this -> person2
// console.log(this) // 第三个表达式this -> person1
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
// 开始题目:
person1.foo1(); // 隐式绑定: person1
person1.foo1.call(person2); // 显式绑定: person2
person1.foo2(); // 上层作用域: window
person1.foo2.call(person2); // 上层作用域: window
person1.foo3()(); // 默认绑定: window
person1.foo3.call(person2)(); // 默认绑定: window
person1.foo3().call(person2); // 显式绑定: person2
person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
上面执行之后在控制台打印:person1 person2 window window window window person2 person1 person2 person1
person1.foo1()
这里打印person1
,由于是由person1
调用的foo1
函数,是隐式调用的方式,this
指向persion1
对象。所以这里的this
的name
就是person1
person1.foo1.call(person2)
这里为什么会打印出person2
呢,这是由于我们使用了call
显式绑定,person2
对象的name属性又是person2
,我们又知道显式绑定的优先级高于隐式绑定,所以才打印我们的person1
而不是person2
person1.foo2()
这里又为什么打印window
呢?原因就是我们的箭头函数,箭头函数是没有作用域的,所以 foo2: () => console.log(this.name)
中的this指向的就是window
对象,那么有人又要有疑问了,箭头函数没有作用域的话他指向的name不应该是person1
吗?不是的,在JavaScript中Object
类型没有他的作用域的,我们的方法才具备块级作用域。所以就打印我们的window
,这里的window是我们在顶级作用域定义的name
,假如我们顶级没定义name
的话,打印就的就是空。
person1.foo2.call(person2)
这里我们打印出来window
,是为什么呢?由于我们看到使用了call
显式调用数据,那不应该打印persion2
吗?不是我们不能调用persion2
,因为箭头函数没有作用域,这个特性要大于其他的特点。所以还是要乖乖打印顶级定义的name
变量。
person1.foo3()()
这里打印我们window
,原因是什么呢?person1.foo3
函数调用返回了一个函数,注意这里返回的函数是一个函数地址,也就是把函数所对应的栈内存地址返回了,第二个括号调用我们的占内存地址,也就是我们的堆内存。由堆内存执行this。这里执行的this所对应的是我们的顶级作用域window,打印出的也就是变量name
了
person1.foo3.call(person2)()
这里我们调用的不应该打印我们person2
对应的值吗?不是的,原因是foo3调用返回是一个方法,不能用call
即使使用了call
所改变的也不是我们person1
函数的name
。所以还是打印的结果和person1.foo3()()
一样都是window
。
person1.foo3().call(person2)
这里打印出来的就是我们的person2
,为什么是这个结果呢,由于我们将person1.foo3
调用了,并且使用call关键词显示绑定为person2
对应的name
person1.foo4()()
这里输出我们的person1
,因为返回的是一个箭头函数,又因为箭头函数是没有作用域的,所以这里的this就是指向我们的person1
,打印我们的person1
person1.foo4.call(person2)()
这里person1.foo4
使用了显示调用call
为什么在这里可以使用person2
的name,并且把他打印出来,person1.foo4
返回的是箭头函数,也就是我们内存地址,在没有调用箭头函数之前将person2对象绑定到person1.foo4
函数中。紧接着调用,就能打印出person2
对应的值
person1.foo4().call(person2)
这里打印出person1
,原因是箭头函数的问题,箭头函数没有作用域所以也就不能绑定this
,显式绑定就失去了作用,依然打印person1
不打印person2
面试题三
var name = 'window'
/*
1.创建一个空的对象
2.将这个空的对象赋值给this
3.执行函数体中代码
4.将这个新的对象默认返回
*/
function Person(name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
// person1/person都是对象(实例instance)
var person1 = new Person('person1')
var person2 = new Person('person2')
// 面试题目:
person1.foo1() // 隐式绑定: person1
person1.foo1.call(person2) // 显式绑定: person2
person1.foo2() // 上层作用域查找: person1
person1.foo2.call(person2) // 上层作用域查找: person1
person1.foo3()() // 默认绑定: window
person1.foo3.call(person2)() // 默认绑定: window
person1.foo3().call(person2) // 显式绑定: person2
person1.foo4()() // 上层作用域查找: person1(隐式绑定)
person1.foo4.call(person2)() // 上层作用域查找: person2(显式绑定)
person1.foo4().call(person2) // 上层作用域查找: person1(隐式绑定)
上面代码执行后打印:person1 person2 person1 person1 window window person2 person1 person2 person1
person1.foo1()
这里打印我们的person1
,为什么打印person1
,由上面代码看出我们的person1是由一个方法调用出来的,这个类的参数是person1,Person
类中的this就是指向我们的方法,我们person1
调用foo()
,这里foo1
是有自己的作用域的,但是在自己作用域中没找到name
,就到上级作用域找,找到person1
person1.foo1.call(person2)
打印person2
是为什么呢?由于我们的foo1使用的是默认绑定打印this.name
,call
显式绑定person2
,显式绑定优先级要高于隐式绑定,所以我们当然要调用person2
的this.name
打印
person1.foo2()
这里主要是由于我们的箭头函数没有作用域,直接去上级找this.name
打印person1
person1.foo2.call(person2)
这里打印person1
, person1.foo2
是一个箭头函数,没有作用域,所以调用call
也然并卵
。还是打印person1
中的name
person1.foo3()()window
为什么呢?person1.foo3
返回的是一个方法,这个方法又在最顶层首先调用并且再次调用person1
方法window
为什么呢?person1.foo3
返回的是一个方法,这个方法又在最顶层首先调用并且再次调用person1
方法
** person1.foo3.call(person2)()**
这里也是返回window
,原因是person1.foo3
并没调用函数就直接call
绑定,返回的依然是window
,依旧是默认值
person1.foo3().call(person2)
这里打印我们的person2
,显式绑定和上面的操作不同这里调用foo3
方法之后call
,可以改变name
的值,用了显示绑定。
person1.foo4()()
这里打印person1
,返回的是箭头函数没有作用域,直接上层查找返回person1
,属于隐式绑定
person1.foo4.call(person2)()
这里person1.foo4
调用person2
函数,虽然说person1.foo4
返回的是一个箭头函数但是我们将person2
绑定到了person1.foo4
上,在调用之后this.name
改变了,打印出我们的person2
person1.foo4().call(person2)
这里打印person1
,方法person1.foo4
先调用,因为是箭头函数所以call
绑定就失效了。还是调用隐式绑定的person1
面试题四
var name = 'window'
/*
1.创建一个空的对象
2.将这个空的对象赋值给this
3.执行函数体中代码
4.将这个新的对象默认返回
*/
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
person1.obj.foo1.call(person2)() // 默认绑定: window
person1.obj.foo1().call(person2) // 显式绑定: person2
person1.obj.foo2()() // 上层作用域查找: obj(隐式绑定)
person1.obj.foo2.call(person2)() // 上层作用域查找: person2(显式绑定)
person1.obj.foo2().call(person2) // 上层作用域查找: obj(隐式绑定)
上述代码执行后将会打印出来:window window person2 obj person2 obj
person1.obj.foo1()()
这里有一对象person1
调用obj
对象,然而obj
返回了一个函数,这个函数打印了this.name
,调用obj
对象又调用foo1
,使用了默认绑定,返回的是顶层定义的name
.
person1.obj.foo1.call(person2)()
这里打印的为什么和上面的一样呢?原因其实很简单我们在一个作用域调用另外一个作用域返回的方法,这里的作用域使用call
显式绑定也就不起作用了,因为foo1
不是用this.foo1 =
定义的。这里使用的默认绑定返回window
person1.obj.foo1().call(person2)
这里打印person2
,为啥就起作用了,person1.obj.foo1
是一个内存地址,内存地址调用之后,将返回的方法绑定到了person2
上,这里用了显示绑定,返回person2
person1.obj.foo2()()
这里打印了obj
,person1.obj.foo2
返回了一个箭头函数,箭头函数没有作用域,直接在上层作用域查找,打印obj
person1.obj.foo2.call(person2)()
这里将person2
绑定到person1.obj.foo2
方法上,使用了显式绑定,打印person2
person1.obj.foo2().call(person2)
这里先调用person1.obj.foo2
,然后再绑定person2
这里绑定失败,所以打印obj
使用了隐式绑定
感谢大家观看,我们下次见