一道超经典js面试题Foo.getName()的故事
下面是一道超经典的JS面试题。
蕴含了静态属性与实例属性,变量提升,this指向,new一个函数的过程
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();
输出一下结果
Foo.getName(); //2 getName(); //4 Foo().getName(); //1 getName(); //1 new Foo.getName(); //2 new Foo().getName(); //3 new new Foo().getName(); //3
一、解析:
1.Foo.getName()
我们先看此题的上半部分做了什么,首先定义了一个叫Foo的函数,之后为Foo创建了一个叫getName的静态属性存储了一个匿名函数,之后为Foo的原型对象新创建了一个叫getName的匿名函数。之后又通过函数变量表达式创建了一个getName的函数,最后再声明一个叫getName函数。
第一问的 Foo.getName 自然是访问Foo函数上存储的静态属性,自然是2
二、解析:
2.getName()
为何输出是4,这里考的是变量提升与函数声明提升。我们知道使用var声明变量会存在变量提升的情况,比如下面的例子中,即使在声明前使用变量a也不会报错,举例:
console.log(a)// undefined var a = 1; console.log(a)// 1
因为声明提前会让声明提升到代码的最上层,而赋值操作停留在原地,所以上面代码等同于:
var a console.log(a)// undefined a = 1; console.log(a)// 1
而函数声明(注意是函数声明,不是函数表达式或者构造函数创建函数)也会存在声明提前的情况,即我们可以在函数声明前调用函数:
fn() // 1 function fn() { console.log(1); }; fn() // 1 //因为函数声明提前,导致函数声明也会被提到代码顶端,所以等同于 function fn() { console.log(1); }; fn() // 1 fn() // 1
那这样就存在一个问题了,变量声明会提升,函数声明也会提升,谁提升的更高呢?在你不知道的JavaScript中明确指出,函数声明会被优先提升,也就是说都是提升,但是函数比变量提升更高,所以题目中的两个函数顺序可以改写成:
function getName() { console.log(5); }; var getName; getName = function () { console.log(4); };
这样就解释了为什么是输出4。
三、解析:
3.Foo().getName()
其实可以看出来,我们在执行Foo()函数的时候getName这个变量提升到外部的全局作用域中了,因为在js中,如果对于一个变量没用用var 或者 let等声明的话,他就默认是全局属性,就是window对象的一个属性。所以在这里我们的全局的getName又被改了
因为我们Foo()执行的时候返回了this而这里的this就是window对象 我们需要知道的是在浏览器中所有全局的声明都是window对象的属性和方法,所以这里我们调用this.getName()就会返回1了。
四、解析:
4.getName()
这里输出1已经毫无悬念,上一分析中,getName的值在Foo执行时被修改了,所以再调用getName一样等同于window.getName(),同样是输出1。
五、解析:
5.new Foo.getName()
首先还是先看运算符优先级吧,我自个看完的结果是【new Foo() > Foo() > new Foo】,先运算方式2的Foo.getName() 结果为“2”,再new一个Foo实例对象,因此这里new的过程就相当于单纯把Foo.getName执行了一遍输出2。
六、解析:
6.new Foo().getName()
这里考了new基本概念,首先这个调用分为两步,第一步new Foo()得到一个实例,第二步调用实例的getName方法。
我们知道new一个构造函数的过程大致为,以构造函数原型创建一个对象(继承原型链),调用构造函数并将this指向这个新建的对象,好让对象继承构造函数中的构造器属性,如果构造函数没有手动返回一个对象,则返回这个新建的对象。
所以在执行new Foo()时,先以Foo原型创建了一个对象,由于Foo.prototype上事先设置了一个getName方法(输出3的那个),所以这个对象可通过原型访问到这个方法,其次由于Foo内部也没提供什么构造器属性,最终返回了一个this(这个this指向实例),因此这里的this还是等同于我们前面概念提到的以Foo原型创建的对象,可以尝试输出这个实例,除了原型上有一个getName方法就没有其它任何属性,因此这里输出3。
七、解析:
7.new new Foo().getName()
相当于new(new Foo().getName())
先执行new Foo().getName()由6部知道了输出3,再创建Foo.prototype.getName()的实例返回。结果为3
总结
上一篇:javascript 原型与原型链的理解及应用实例分析
栏 目:JavaScript代码
本文标题:一道超经典js面试题Foo.getName()的故事
本文地址:http://www.codeinn.net/misctech/224256.html