题目
|
|
答案
|
|
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
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。