题目
| 
 | 
 | 
答案
| 
 | 
 | 
1. Foo.getName();
先看此题的上半部分做了什么,首先定义了一个叫 Foo 的函数,之后为 Foo 创建了一个叫 getName 的静态属性存储了一个匿名函数,之后为 Foo 的原型对象新创建了一个叫 getName 的匿名函数。之后又通过函数变量表达式创建了一个 getName 的函数,最后再声明一个叫 getName 函数。
第一问的 Foo.getName 自然是访问 Foo 函数上存储的静态属性,自然是2。
2. getName();
第二问,直接调用 getName 函数。既然是直接调用那么就是访问当前上文作用域内的叫 getName 的函数,所以跟1 2 3都没什么关系。此题有无数面试者回答为5。此处有两个坑,一是变量声明提升,二是函数表达式。
变量声明提升
| 
 | 
 | 
提升后
函数表达式
var getName 与 function getName 都是声明语句,区别在于 var getName 是函数表达式,而 function getName 是函数声明。
函数表达式最大的问题,在于js会将此代码拆分为两行代码分别执行。
| 
 | 
 | 
所以最终函数声明的x覆盖了变量声明的x,log输出为x函数。
同理,原题中代码最终执行时的是
3. Foo().getName();
第三问的 Foo().getName() 先执行了 Foo 函数,然后调用 Foo 函数的返回值对象的 getName 属性函数。Foo 函数的第一句getName = function () { alert (1); }; 是一句函数赋值语句,注意它没有var声明,所以先向当前 Foo 函数作用域内寻找 getName 变量,没有。再向当前函数作用域上层,即外层作用域内寻找是否含有 getName 变量,找到了,也就是第二问中的 alert(4) 函数,将此变量的值赋值为 function(){alert(1)} 。
此处实际上是将外层作用域内的 getName 函数修改了。
注意:此处若依然没有找到会一直向上查找到window对象,若window对象中也没有getName属性,就在window对象中创建一个getName变量。
之后 Foo 函数的返回值是 this,简单的讲,this 的指向是由所在函数的调用方式决定的。而此处的直接调用方式,this 指向 window 对象。Foo 函数返回的是 window 对象,相当于执行 window.getName() ,而 window 中的 getName 已经被修改为 alert(1),所以最终会输出 1。
此处考察了两个知识点,一个是变量作用域问题,一个是 this 指向问题。
4. getName();
直接调用 getName 函数,相当于 window.getName() ,因为这个变量已经被 Foo 函数执行时修改了,遂结果与第三问相同,为 1。
5. new Foo.getName();
第五问 new Foo.getName() ,此处考察的是js的运算符优先级问题。
点(.)的优先级高于new操作,遂相当于是
所以实际上将 getName 函数作为了构造函数来执行,遂弹出 2。
6. new Foo().getName();
new Foo().getName() ,首先看运算符优先级括号高于new,实际执行为
遂先执行 Foo 函数,而 Foo 此时作为构造函数却有返回值,所以这里需要说明下js中的构造函数返回值问题。
构造函数的返回值
在传统语言中,构造函数不应该有返回值,实际执行的返回值就是此构造函数的实例化对象。
而在js中构造函数可以有返回值也可以没有。
- 没有返回值则按照其他语言一样返回实例化对象。 
- 若有返回值则检查其返回值是否为引用类型。如果是非引用类型,如基本类型 string, number, boolean, null, undefined则与无返回值相同,实际返回其实例化对象。 
- 若返回值是引用类型,则实际返回值为这个引用类型。 
原题中,返回的是 this,而 this 在构造函数中本来就代表当前实例化对象,遂最终 Foo 函数返回实例化对象。
之后调用实例化对象的 getName 函数,因为在 Foo 构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象 prototype 中寻找 getName ,找到了。
遂最终输出 3 。
7. new new Foo().getName();
new new Foo().getName() 同样是运算符优先级问题。
最终实际执行为
先初始化 Foo 的实例化对象,然后将其原型上的 getName 函数作为构造函数再次 new 。
遂最终结果为 3 。
这里引用 @于明昊 的评论,更详细的解释了第7问
这里确实是 (new Foo()).getName(),但是跟括号优先级高于成员访问没关系,实际上这里成员访问的优先级是最高的,因此先执行了 .getName ,但是在进行左侧取值的时候, new Foo() 可以理解为两种运算:new 带参数(即 new Foo())和函数调用(即 先Foo() 取值之后再 new),而 new 带参数的优先级是高于函数调用的,因此先执行了 new Foo(),或得 Foo 类的实例对象,再进行了成员访问 .getName 。
运算符优先级
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
作者:小小沧海
出处:http://www.cnblogs.com/xxcanghai/
本文地址:http://www.cnblogs.com/xxcanghai/p/5189353.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
