这里收录一道阿里的变态面试题!!!
function Foo() {
getName = function() {
console.log(1);
};
return this;
}
Foo.getName = function() {
console.log(2);
};
Foo.prototype.getName = function() {
console.log(3);
};
var getName = function() {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
首先公布一下运行结果,你也可以拷贝然后到浏览器测试:
接下来我们具体分析一下代码:
预编译
在代码尚未执行时,会发生预编译阶段。
预编译阶段大致上主要分为两个阶段:
- 变量声明提升
- 函数声明整体提升
此时会生成一个的全局的GO
对象用来保存全局变量:
(之后都将采用function -> n
的方式表示是一个函数,n
为打印结果。)
GO: {
Foo: function,
getName: function -> 5
}
代码执行
Foo.getName = function() -> 2
Foo.prototype.getName = function -> 3
getName = function -> 4
这时候全局GO
中的getName
值就发生了变化:
GO: {
Foo: function -> 1,
getName: function -> 4
}
函数执行
Foo.getName()
这个函数打印结果很显然是2
。
getName()
该函数执行直接访问GO
中的getName
方法,所以打印4
。
Foo().getName()
首先执行Foo
函数内部代码,这时全局GO
中的getName
函数被重新赋值:
GO: {
Foo: function -> 1,
getName: function -> 1
}
然后执行reurn this
,此时this
指向window
,所以该函数实际上是调用window.getName()
,打印结果为1
。
getName()
这个函数执行直接在全局范围内执行,所以打印结果也是1
。
new Foo.getName()
在执行这个函数与下一个函数new Foo().getName()
之前,我们需要知道,new
时加不加括号的区别,(其实就是一个运算符的优先级问题),在这之前我已经记录过JavaScript运算符优先级,这里直接截图展示:
可以看出:
new(带参数列表)
运算的优先级与成员访问
运算的优先级相同,并且运算时从左到右。成员访问
运算的优先级高于new(无参数列表)
运算的优先级。
搞清楚这一点后我们再看new Foo.getName()
这个执行顺序,首先执行Foo.getName()
然后在执行new
运算符。
执行Foo.getName()
打印结果为2
,new
不会改变打印结果。
new Foo().getName()
知道了运算符优先级后,这个函数执行顺序应该是先进行new Foo()
操作,得到一个Foo
的实例,然后这个实例调用getName()
方法,调用的方法应该是原型上的方法,所以打印结果为3
。
new new Foo().getName()
这个函数执行顺序为先执行new Foo()
创建出一个实例,记作xxx
,就变成了new xxx.getName()
,之后执行顺序为先执行xxx.getName()
,同样访问了原型上的方法,打印3
,最后的new
运算符不改变打印结果。
测验
这道题到这里就结束了,下面有一道类似的题可供大家测验:
function A() {
alert(1);
}
function Func() {
A = function() {
alert(2);
};
return this;
}
Func.A = A;
Func.prototype = {
A: () => {
alert(3);
}
};
首先这道题在最后会报错的,但是不会影响得到打印结果。
弹出框依次打印:"1"
,"1"
,"2"
,"1"
,"3"
,"3"
。
直接注意的是这里使用的是alert()
函数打印,该函数会默认调用toString()
方法,所以打印结果都为字符串类型的值。