一道超经典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

总结

到此这篇关于js面试题Foo.getName()的文章就介绍到这了,更多相关js面试题Foo.getName()内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

你可能感兴趣的:(一道超经典js面试题Foo.getName()的故事)