一道有趣的JS编程题

186 阅读2分钟

求下列代码的输出。

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();

我们来一一分析下吧。首先我们要明白,通过var声明的变量和函数的声明都会被抬升,在代码正式执行之前就会被解析。所以我们先找到函数声明和通过var声明的变量。得到如下结果

Foo = function () {
    getName = function () { alert (1); };
    return this;
}
getName = undefined
getName = function () { alert (5);}

然后从头到尾开始执行代码。

第一问
Foo.getName();

上面这行代码,访问的是Foo函数的静态属性,显然答案是2.

第二问
getName();

这里刚开始会把function (){alert(5)}赋给getName,但由于后来代码执行过程中把function (){alert(4)}赋给了getName,所以这里的输出结果为4.

第三问
Foo().getName();

这里先执行Foo函数,由于是在全局环境调用的Foo,所以它返回的this指向window,相当于是求window.getName(),这里要注意,在执行Foo()时,在函数体内执行了getName = function () { alert (1); 而由于这里getName并没有被声明,所以它会返回到上一级去找getName,刚好全局环境中有定义这个变量,所以就把function () { alert (1)赋给了全局变量getName,所以当执行Foo().getName()时返回1.

第四行
getName();

根据上面的分析,这里就显然返回结果1.

第五问
new Foo.getName();

这里考察JS运算符的优先级。 我也是查了资料才知道,带参数的new和成员访问 ….… 的优先级相同,高于不带参数的new和函数调用()。因此第五问就转化为

new (Foo.getName)( ),此时因为有个括号,所以变成了new有参数列表,直接执行new,再函数调用,所以这里是将getName作为构造函数来执行,所以弹出2.

第六问
new Foo().getName();

这里有参列表和 . 的执行顺序是一样的,所以按顺序执行就可。注意这里Foo作为构造函数会有返回值,而返回值this在构造函数中本来就代表当前实例化对象,最终Foo函数返回实例化对象。 之后调用实例化对象的getName函数,因为在Foo构造函数中没有为实例化对象添加任何属性,当前对象的原型对象(prototype)中寻找getName函数。所以弹出3.

第七问
new new Foo().getName();

最终执行顺序为new ((new Foo()).getName)(),先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new,所以最终结果为3。