今天逛知乎,看到这么一道JavaScript面试题,觉得考察的知识点还挺多的,故而记录下自己的做题过程。
题目如下
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();
考察知识点
- 变量定义提升
- 函数声明提升
- this指向问题
- 原型链
- 作用域链
- 全局变量
- 运算符优先级
- 构造函数返回值问题
解题步骤
题目中声明了Foo()
函数、getName
函数表达式、getName()
函数。由于变量声明提升和函数声明提升问题,题目应该转换为如下代码:
function Foo() {
getName = function () { alert (1); };
return this;
}
var getName; //提升变量声明
function getName() { alert (5);} // 提升函数声明,覆盖getName变量
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
getName = function () { alert (4);}; //alert(5)覆盖了getName变量,此处alert(4)又覆盖了alert(5)
第一问: Foo.getName()
这个没有任何争议,直接访问Foo
函数的静态属性getNmae
。alert的值为:2。
第二问: getName()
由于变量声明提升和函数声明提升的缘故,getName()
最终的值为function(){alert(4)}
。alert的值为:4。
注意:变量只提升声明不提升赋值。看如下代码:
var sayAge = function(){
console.log("hhh");
}
function test(){
console.log("test");
}
// 经历变量和函数声明提升后实际为:
var sayAge;
function test(){
console.log("test");
}
sayAge = function(){
console.log("hhh");
}
第三问: Foo().getName()
执行Foo
函数,返回值再调用getName()。
这里Foo()的执行对后面的分析影响很大。Foo函数体内给getName变量赋值,由于函数体内没有getName变量,所以去上层作用域中找,找到了getName变量,赋值为function(){alert(1)}
。
此时题目代码已经为:
function Foo() {
getName = function () { alert (1); };
return this;
}
var getName;
function getName() { alert (5);}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
getName = function () { alert (1);};
Foo()
的返回值为this
,这里this指向window
。
所以Foo().getName()
其实就是window.getName()
,此时全局的getName已经被Foo()
更改为alert(1)。
alert的值为:1。
第四问: getName()
经历了第三问,第四问也就呼之欲出了。直接在全局运行getName()
函数。alert的值为:1。
第五问: new Foo.getName()
这里有个关键点是:点操作符和new操作符谁优先?其实凭第一感觉,我们能分清是先点后new。只是有时候太关注反而不相信自己了。
所以这里其实等价于:
new (Foo.getName())
// 详细点就是:
new (function () { alert (2);})
alert的值为:2。
第六问: new Foo().getName()
这个和第五问有点不同的是,Foo执行隔断了点运算符的优先级。实际等价于:
(new Foo()).getName()
这里涉及一个构造函数是否返回值的问题。 传统语言中,比如Java,正常情况下构造函数是不能有返回值的。而JavaScript中返回值可有也可无。
- 没有返回值时,正常实例化对象
- 返回值为基本类型时,和没有返回值的作用效果一样
- 返回值为引用类型时,实际返回值就是这个引用类型的值。
这里new Foo()
返回的this
。this
在构造函数中本身就是指代实例对象。所以new Foo().getName()
就等价于实例.getName()
。由于实例本身没有getName()
,所以访问原型上的getName()
。
alert的值为:3。
第七问: new new Foo().getName()
该问等价于:
new (new Foo()).getName();
// 结合第六问,等价于
new 实例.getName();
// 等价于:
new function () { alert (3);}
alert的值为:3。
最终答案
//答案:
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3