一道关于new运算符的阿里面试题

239 阅读2分钟

直接附上题目:

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()打印结果为2new不会改变打印的结果。

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()方法,所以得到的结果都是字符串。