1

缘由

开通文章是为了能够有个地方长篇大论今天遇到的问题
由于提了一个问题(见 这里),被人嘲讽。但是这个嘲讽我的人(@xiaoboost )的答案并不对,他的答案只是根据结果解释能够得出这个结果的执行。至于为什么以及JavaScript在执行过程中进行了哪些判断并没有进行详细的解释或者说完整的解释。其余的几个朋友要么鄙视这道题、要么鄙视我把运算符优先级牵扯进来。最让我纳闷的是大家都认为这里面不牵扯到运算符的优先级判断。
在此,我以自己的理解来解释一下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()

我的答案:

首先,JS中会对变量声明和函数声明在编译阶段进行提升,所以实际代码会表现成这样:

// 此处是变量提升的演示,是在编译阶段进行的
var getName;
function Foo() {
  getName = function () {
    alert(1);
  };
  return this;
}
function getName() {
  alert(5);
}

// 此处则只有到了执行阶段才会执行
Foo.getName = function () {
  alert(2);
};
Foo.prototype.getName = function () {
  alert(3);
};
getName = function () {
  alert(4);
};

其次,除了第六问和第七问,所有有成员访问运算符(也叫属性访问器).的表达式,.的优先级都是最高,没有一个表达式有圆括号,有圆括号的要么是函数调用,要么是new 运算(对象创建表达式)的组成部分。

这里专门说明没有圆括号是因为看到这个问题原作者自己的分析文章将new Foo().getName()中的new Foo()先执行解释为由于圆括号的优先级高于.。附上问题原作者自己的分析文章:http://www.cnblogs.com/xxcang...

以下为各问题的数字输出以及为什么:

第一问

2,直接调用Foo函数的getName方法。

第二问

4,变量声明和函数声明会在编译阶段被提升。此时,函数声明会覆盖变量声明。但是到了执行阶段,如果变量有赋值操作,那么变量会因赋值而覆盖之前的函数声明。因此第二问的getName()实际执行的是赋值了匿名函数function () {alert(4)}的函数表达式。

第三问

1,.运算符优先级最高,按照.运算符的关联性从左往右先计算左操作数,Foo()是一个函数调用表达式,函数内部在执行时,由于函数内部没有查找到局部变量getName,因此引擎会沿着作用域链向上查找getName变量。在全局作用域中找到getName变量(同时也是window的属性/方法),给它赋值一个新的匿名函数function(){alert(1)}returnthis此时指向当前方法所属的对象window,因此,计算右操作数时,就是调用了window对象上的getName方法。而前面左操作数Foo()中已经给getName变量重新赋值了一个匿名函数,因此会出现新赋值函数所弹出的数字1。

第四问

1,getName()已经因为前面的Foo()的调用而赋值了新的匿名函数,因此弹出数字1。

第五问

2,new可以与Foo结合组成一个没有参数的对象创建表达式,但是这样它的优先级低于'.',因此这里.运算符优先级高于new和函数调用(),整个表达式则可以理解为:new (Foo.getName)(),当Foo.getName执行完毕会返回一个匿名函数function(){alert(2)},此时会与new运算符、()组成一个新的表达式:new function(){alert(2)}(),这是一个对象创建表达式,这个表达式在执行执行的时候,构造函数部分function(){alert(2)}会被执行,结果就是2。另外,对象创建表达式在没有传入参数的情况下可以省略括号,因此原题中的new Foo.getName();可以把后面的括号省略掉new Foo.getName,结果一样。

第六问

3,带有参数的对象创建表达式(new constructor())和成员访问表达式的优先级是一样,这里就牵扯到应该理解成
(new Foo()).getName()还是new (Foo().getName)()。如果是new (Foo().getName)(),那么在执行Foo()时,它是一个函数调用,优先级低于带参数的new,因此这样不行。那么只能是(new Foo()).getName()new Foo()实例化一个对象,然后通过.访问getName属性,对象本身没有这个属性,顺着原型链查找到Foo.prototype中有这个属性,并且赋值了一个匿名函数。最后通过函数调用运算符调用它,得到3。

第七问

3,可以看做 new((new Foo()).getName)()(new Foo()).getName同上面的结果一样得到Foo.prototype.getName一个匿名函数:function () {alert(3)},这个匿名函数与前面的new以及后面的()组成一个新的对象创建表达式new function () {alert(3)} (),这个表达式在执行时,会调用其中的构造函数function (){alert(3)},因此会弹出3。

以上是我对这个问题的解释,其中第六问得到@zonxin 的解答,再次感谢,附上地址:https://segmentfault.com/q/10...

最后,附上我被嘲讽并且被4个人'踩'的问题地址,如果觉得我的解答是正确的,麻烦帮我'平反'╥﹏╥...;如果有错误的地方,请留言指出,我会十分感谢。
我的原问题地址:https://segmentfault.com/q/10...


Rachel
361 声望8 粉丝

产品设计一枚,初入前端深海,还请各位前辈多多指教