持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第二天,点击查看活动详情
最近刷到了几道this相关的面试题,感觉还挺有意思的,拿出来和大家分享一下,顺带也复习一下this绑定相关的知识点。
this绑定的优先级
this绑定的优先级一般是这样排序的:
new绑定 > 硬绑定(bind) > 显式绑定(apply/call) > 隐式绑定 > 默认绑定(兜底规则)
并且函数的this与函数声明的位置无关,只与函数的调用方式有关。箭头函数本身没有this,如果在箭头函数中使用this,那么会继承外层函数调用的this绑定,如果没有的话,会找到全局作用域下的this,也就是window。
知道绑定的优先级后,一起来看看这几道面试题吧:
面试题一:
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
//写出每次打印的内容
function sayName() {
var fn = person.sayName;
fn();
person.sayName();
(person.sayName)();
(fn1 = person.sayName)();
}
sayName();
这里调用sayName函数后,执行函数体代码,首先是将person对象中的sayName方法赋值给了fn函数,然后对fn函数进行了独立函数调用,独立函数调用是应用默认绑定规则的,所以这里首先会打印window。
第二行打印通过.点操作符对person对象中的sayName方法进行调用,那么会应用隐式绑定的规则,所以会打印person。
然后通过小括号()对person.sayName进行了包裹,再进行调用。但是.操作符优先级本身就比()小括号高,所以这里还是会打印person。
最后通过小括号()包裹了一个fn1 = person.sayName的表达式,这个表达式的返回值就是person中的sayName方法对应的函数。这一行就相当于对这个函数也进行了独立函数调用,所以也会打印window。
所以面试题一的答案就是window/person/person/window。你答对了吗
面试题二:
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);
这里第一段代码直接通过点.操作符调用了person1的foo1方法,会应用隐式绑定的规则,打印person1。然后通过call方法对person1的foo1方法进行调用,会应用显式绑定的规则,打印person2。
第二段代码通过点.操作符调用了person1的foo2方法,但是由于foo2是箭头函数,自身没有this,所以沿着作用域会向上查找,找到全局的this,打印window。同样的call调用对箭头函数也是无效的,所以后面也会打印window
第三段代码第一行先通过person1.foo3(),拿到foo3的返回值也是一个函数,然后对这个函数进行了独立函数调用,应用默认绑定的规则,打印window。第二行是对person1的foo3函数进行了call调用,foo3的this指向person2,但是跟foo3的返回值中的函数没什么关系,所以还是会打印window。第三行先通过person1.foo3()拿到返回值函数,然后对返回值函数进行call调用,打印person2。
第四段代码第一行通过person1.foo4(),拿到foo4的返回值是一个箭头函数,然后调用该箭头函数,因为箭头函数自身没有this,所以沿着函数调用的作用域向上查找,找到foo4的作用域,foo4是通过person1.进行调用的,所以应用隐式绑定,打印person1。第二行通过call调用了foo4,所以箭头函数找到foo4的this时,会打印person2。第三行对箭头函数自身使用call无效,找到foo4时,还是打印person1。
所以面试题二的答案是:
person1/person2
window/window/
window/window/person2
person1/person2/person1
面试题三:
var name = 'window'
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)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('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)
第一段代码第一行,通过点.操作符调用了person1的foo1方法,隐式绑定,打印person1。第二行,通过call调用foo1,显式绑定,打印person2。
第二段代码第一行,通过点.操作符调用person1的foo2方法,但是foo2是箭头函数,自身没有this,沿作用域向上查找,在构造函数Person中找到this为person1,打印person1。第二行通过call调用箭头函数无效,还是打印person1。
第三段代码第一行,通过person1.foo3()拿到返回值是一个函数,然后进行调用,这里也相当于独立函数调用,默认绑定,打印window。第二行,通过call调用person1的foo3,foo3的this指向person2,但是这和返回值函数也没什么关系,还是相当于独立函数调用,打印window。第三行,通过call调用返回值函数,显式绑定,打印person2。
第四段代码第一行通过person1.foo4(),拿到foo4的返回值是一个箭头函数,然后调用该箭头函数,因为箭头函数自身没有this,所以沿着函数调用的作用域向上查找,找到foo4的作用域,foo4是通过person1.进行调用的,所以应用隐式绑定,打印person1。第二行通过call调用了foo4,所以箭头函数找到foo4的this时,会打印person2。第三行对箭头函数自身使用call无效,找到foo4时,还是打印person1。
所以面试题三的答案是:
person1/person2
person1/person1
window/window/person2
person1/person2/person1
面试题四:
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)
第一段代码第一行,通过person1.obj.foo1()拿到foo1的返回值函数,进行独立函数调用,这里也是打印window。第二行,通过call调用foo1拿到返回值函数,这里foo1的this是person2,但是和返回值函数没什么关系。对返回值函数来说还是独立函数调用,打印window。第三行通过call调用返回值函数,显式绑定,打印person2。
第二段代码第一行,通过person.obj.foo2()拿到foo2的返回值是一个箭头函数,调用该箭头函数。箭头函数自身没有this,沿作用域向上查找,找到foo2的作用域,foo2是通过obj点.操作符调用的,所以这里会找到obj,打印obj。第二行通过call把foo2的this显式绑定为person2,箭头函数调用时,找到foo2的this,打印person2。第三行通过call调用箭头函数无效,箭头函数向上找到foo2的this还是obj,所以打印obj。
所以面试题四的答案是:
window/window/person2
obj/person2/obj
总结
上面四道面试题循序渐进的考察了this的几种绑定规则,你都做对了吗? 其实只要搞清楚this的几种绑定规则以及它们的优先级,在实际开发中遇到this都能合理的运用。
再回顾下优先级: new绑定 > 硬绑定(bind) > 显式绑定(apply/call) > 隐式绑定(对象调用)> 默认绑定(兜底规则)
并且函数的this与函数声明的位置无关,只与函数的调用方式有关。箭头函数本身没有this,如果在箭头函数中使用this,那么会继承外层函数调用的this绑定,如果没有的话,会找到全局作用域下的this,也就是window。