duyue6002 / Blog

:pencil2: Write here
http://duyue6002.github.io/Blog/#/
5 stars 1 forks source link

[面试]一道经典JS面试题 #8

Open duyue6002 opened 5 years ago

duyue6002 commented 5 years ago

JS半吊子又杀回江湖了,这次看到网上一道很火的JS面试题,看着看着就跪了。果然纯逻辑和考核心概念的题目很容易出错,现在就来分析分析。

题目:

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);};

// please output the results:
foo.getName();
getName();
foo().getName();
getName();
new foo.getName();
new foo().getName();
new new foo().getName();

眩晕了·····

要搞懂这题,主要是以下几个概念要明白,函数静态属性原型

首先,定义名为foo的函数。再为foo创建getName的静态属性,这个属性存储了匿名函数。接着为foo的原型对象创建getName的匿名函数。再创建一个名为getName的函数。最后声明一个叫getName的函数。

第1问

foo.getName();// 2

第2问

这题太坑,最重要的一点要明白变量声明提升函数表达式。变量声明提升就是说,所有声明变量和声明函数都会提升到当前函数的顶部。函数表达式是指var的方式来声明,这时js会将代码拆分为两行来执行,声明的会提前到顶部,赋值会在原位置。所以这题的执行顺序就变成:

function foo() {...};
var getName;// 提升变量声明
function getName() {...};// 提升函数声明,覆盖var的声明
foo.getName = ...;
foo.prototype.getName = ...;
getName = ...;// 赋值再次覆盖函数声明

所以这里返回的是4.

第3问

想的很简单,虽然答案对了,但知其然不知其所以然呀!过程是这样的:问foo().getName(),就是先执行foo函数再调用其返回值对象的getName属性函数。首先foo()是一个声明的函数,函数的第一句并没有声明getName属性,而是直接赋值了一个函数,这就要向当前函数作用域上层(即外层作用域)找getName变量,外部有声明(如果一直向上找到window对象都没有此属性,就会为window对象创建这个属性),就将此变量的值赋值为一个函数。注意,外层作用域声明的getName值就变味被赋值的函数!这就非常有意思了,getName值在整个过程中变了3次!最后foo()返回this,这里的this指向window对象。

所以此问返回的是1.

第4问

上问执行的相当于是this.getName(),其实就是本题的答案,还是返回1.

第5问

原来还有优先级的不同,成员访问要高于new的优先级,所以相当于new (foo.getName)();,返回2.

MDN关于优先级的文档:运算符优先级

第6问

看完优先级后,懵逼的我…这里是运算符高于new高于函数调用,故执行:(new foo()).getName()

这样原函数foo执行后返回this,this代表当前实例化对象,也就是foo函数返回实例化对象,再调用其实例化对象的getName函数,在构造函数中,并没有为实例化对象添加属性,所以从当前对象的原型对象(prototype)中寻找。这里有点绕,我在console下跑了后仔细看看发现是这么回事,new foo()后返回的实例化对象里面并没有构造函数里的getName属性函数,添加了prototype才找得到。

此处一个概念叫构造函数的返回值,就是指构造函数的实例化对象。分为以下3种情况:

  1. 本身函数就没有返回值,那new之后就返回实例化对象。

  2. 函数有返回值,但是是非引用类型,如基本类型,就跟1的情况相同。

  3. 返回值是引用类型,实际返回值就是那个引用类型。

    function f(){return {a:1};}
    // return undefined
    new f();
    // return Object {a:1}

此处返回3.

第7问

还是优先级问题,这里执行顺序为:new ((new foo()).getName(),先里面的foo函数,再new一个实例化对象,后面是进行成员访问.getName,最后再new。好复杂...已经晕了。其实这里没有括号,所以先成员访问,左侧取值时要注意。总之:括号>成员访问>new