前言
这是一道十分经典的面试题,涵盖了从函数的基本概念、运算符优先级,到作用域链、原型链、this 关键字、new 关键字等根底知识点考查,能够说能残缺答对 JS 根底才算过了关,本文就带大家回顾并分析这道面试题,应该是全网最具体的文章了,这次彻底搞懂它。
// a
function Foo () {getName = function () {console.log(1);
}
return this;
}
// b
Foo.getName = function () {console.log(2);
}
// c
Foo.prototype.getName = function () {console.log(3);
}
// d
var getName = function () {console.log(4);
}
// e
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()
这一问首先考查的是 函数的基本概念 :在 JS 中函数是 第一类对象 ,也被称作 ” 一等公民 ”,这是因为 函数领有对象所领有的全副性能。所以这里的 Foo.getName()
能够看作是调用了 Foo
对象上的属性,在题目中的 b 处有其定义,故后果输入 2。
2. getName()
这里调用的 getName
在上下文中被定义了两次,一次是通过变量申明,一次是函数申明,故这一问考查的是 变量申明晋升与函数申明晋升 ,申明提前会让申明晋升到代码的最上层,而函数再一次施展了它 ” 一等公民 “ 的特权: 函数申明晋升比变量申明晋升更高,所以这一问理论执行代码可看作:
function getName() {console.log(5);
};
var getName;
getName = function () {console.log(4);
};
两者申明独特晋升之后,变量的赋值操作最初执行,所以调用 getName()
输入的后果是 4。
3. Foo().getName()
和第一问相比看似只多了个括号,理论考查的内容齐全不一样。
咱们先温习一下 JS 中的 运算符优先级,这是下来全副解题的根底。
-> MDN – 运算符优先级汇总表。
首先成员拜访运算从左到右执行,所以咱们要先看 Foo()
函数做了什么,依据题目 a 处的定义:
function Foo () {getName = function () {console.log(1);
}
return this;
}
执行 Foo()
之后为 getName
赋值一个函数(留神这里的 getName
并没有 var
关键字,所以还考查了 作用域链 的知识点),JS 在遇到未声明的变量时会向上一级一层层查找,后面咱们晓得了 变量申明会晋升,在全局作用域下 getName
是曾经被申明的了,所以执行 Foo()
的作用其实就是把全局的 getName
又赋值了新函数。
而 Foo()
自身返回了 this
,所以这一问又变成了「this.getName()
输入什么?」。这里当然也就考查了 this 关键字 的知识点,这里在全局上下文执行,this
指代window
,也就是调用了 getName()
,执行后果是后面 Foo()
赋予的新函数,所以输入了 1。
4. getName()
因为题目条件是程序执行,所以这里通过了第三问之后全局 getName
曾经被批改过了,在上一问曾经解析完,这里毫无疑问执行输入是 1。
5. new Foo.getName()
乍一看认为是要考查 new 关键字 了,其实并没有,它还是考查了下面提到的 运算符优先级,依据优先级咱们能够得出,Foo.getName()
是会先执行的,执行完只是输入了第一问的后果,再对其执行 new
没有意义,最初输入的还是 2。
6. new Foo().getName()
这里开始考查 new 关键字 的概念,但咱们还是要先说说这一问波及的 运算符优先级 问题,可能你看过其它文章解析这一问的时候会说等价于 (new Foo()).getName()
,可你晓得为什么会是这样吗?为什么第 5 问是先执行 Foo.getName()
而这一问却是先执行 new Foo()
呢?
这是因为 new
运算在优先级上有两种模式:
- 带参数列表:
new … (…)
优先级 18 - 无参数列表:
new …
优先级 17
如果优先级不同那么按 优先级最高的运算符先执行,不必思考联合性。
比方
1 + 1 * 2
执行起来就是1 + (1 * 2)
如果优先级雷同则按联合性执行
比方赋值运算联合性是 ” 从右到左 ”,所以
a = b = 1
理论为a = (b = 1)
所以这就解释了为什么这一问会是 new Foo()
先执行,咱们画个图看下:
在上一问里成员拜访优先级是 18,new
(无参列表)优先级是 17,优先级不同,则高优先级先执行,所以上一问先执行 Foo.getName()
;
而这一问里 new
(带参列表)优先级与成员拜访同属 18,优先级雷同,并行下看联合性,new
带参时联合性不相干,所以间接执行,成员拜访联合性从左到右,所以先拿出 Foo()
执行,于是得出了下面等价于 (new Foo()).getName()
的论断。
接下来就是考查 new 相干概念了,new Foo()
以 Foo
为原型创立了一个新对象,这个实例自身并没有 geiName
这个办法,然而题目 c 处在 Foo
函数的原型上挂载过一个 getName
办法,最终实例会通过 原型链 拜访到 Foo.prototype.getName()
这个办法,后果输入 3。
温习
new
关键字做了什么:
- 创立新对象并将
.__proto__
指向构造函数的.prototype
- 将
this
指向新创建的对象- 返回新对象
温习 原型链 知识点:
每个函数实例对象都有一个
__proto__
属性,__proto__
指向了prototype
,当拜访实例对象的属性或办法,会先从本身构造函数中查找,如果找不到就通过__proto__
去原型中查找。
7. new new Foo().getName()
考查的上一问其实曾经讲完了,还是一样画张图:
所以得出理论执行的是:new(new Foo().getName())
new Foo().getName()
在上一问可知实例最终拜访 Foo
原型链上的办法,最终为创立 new(Foo.prototype.getName())
的实例返回,后果输入 3。
以上就是文章的全部内容,心愿对你有所帮忙!如果感觉文章写得不错,能够点赞珍藏,也欢送关注,我会继续更新更多前端有用的常识与实用技巧,我是茶无味 de 一天,心愿与你独特成长~