直接附上题目:
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
}
然后执行return this,此时this指向window,所以该函数实际上是调用window.getName(),打印结果为1。
getName()
这个函数直接在全局范围内执行,所以打印结果也是1。
new Foo.getName()
在执行这个函数与下一个函数new Foo().getName()之前,我们需要知道,new时加括号与否的区别(这其实是一个优先级的问题),到这里我们可以查看mdn中关于优先级的总结。
这里附加上需要的部分:
可以看出:
new(带参数列表)运算的优先级与成员访问运算的优先级相同,并且运算顺序为从左到右。成员访问运算的优先级高于new(无参数列表)运算的优先级。
搞清楚这一点后我们再看new Foo.getName(),首先执行Foo.getName()然后再执行new运算符。
执行Foo.getName()打印结果为2,new不会改变打印的结果。
new Foo().getName()
知道了运算符优先级关系后,这个函数的执行顺序为先new Foo(),得到一个Foo的实例,然后通过这个实例调用getName()方法,调用的方法应该是原型prototype上的方法,所以打印结果为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()方法,所以得到的结果都是字符串。