上次分享了一道题,大家反响不错,很开心自己写的东西有人愿意花时间去看,也给了自己莫大的鼓舞,其实做题虽然不比真正的编程,但是也能够让你发现一些你之前没有注意到的语言层面的问题。所以,这次再分享一道稍微有难度的JavaScript题目。
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();
请问上述代码在浏览器环境下,输出结果是多少? 揭晓一下最终答案:
2 4 1 1 2 3 3
前四道难度不是很大,主要是后三道,基本是全军覆没,感叹实在是太绕了了。后面慢慢分析了一下,逐个讲一下吧。 首先必须注意一个问题
function Foo() {
getName = function () {
console.log('1');
};
return this;
}
在函数内部声明的getName
变量,前面是不带有var
、let
,const
的,所以其实根据LHS(这个的介绍可以去的我博客看一下关于LHS和RHS的总结),声明的getName
是在全局范围内(也是就window
)。
其次需要明确你是否知道下面代码在浏览器中的执行结果:
var getName = function () {
console.log('4');
};
function getName() {
console.log(5);
}
getName();
上述代码的执行结果是:4
。原因是这样的,var
声明的变量和函数声明function
都会被提升,但是函数声明的提升的级别是比
var
要高的,所以上面的代码的实际执行结果是:
function getName() {
console.log(5);
}
var getName = function () {
console.log('4');
};
getName();
后一个函数表达式getName
覆盖了前面的函数声明getName
,实际执行的是函数表达式(也就是是为什么JavaScript永远不会有函数重载这么一说了),所以输出的是4
。
首先我给下面的代码添加一下必要的注释:
//函数声明
function Foo() {
//全局变量
getName = function () {
console.log('1');
};
return this;
}
//为函数添加属性getName,其类型是Function,所以这里也可以看出来,Function也是一种Object
Foo.getName = function () {
console.log('2');
};
//为Foo的原型添加方法getName
Foo.prototype.getName = function () {
console.log('3');
};
var getName = function () {
console.log('4');
};
function getName() {
console.log(5);
}
下面执行第一条语句:
Foo.getName();
函数Foo
本身并没有执行,执行的是函数的属性getName
,当然输出的是:2
.
接下来执行:
getName();
这是在全局范围内执行了getName()
,有两条对应的getName
的声明,根据前面我们所提到的提升的级别来看实际执行是函数表达式:
var getName = function () {
console.log('4');
};
所以输出的是4
。
接下来执行
Foo().getName();
首先看一下JavaScript的操作符优先级,从高到低排序
从上面可以看出来()
与.
优先级相同,所以Foo().getName()
从左至右执行。首先运行Foo()
,全局的getName
被覆盖成输出console.log('1')
,并且返回的this
此时代表的是window
。随后相当于执行的window.getName()
,那么输出的实际就是1
(被覆盖)。
下面到了
getName();
这个不用说了,执行的还是:1
(和上面一毛一样)。
下面到了三个最难的部分:
new Foo.getName();
对于这条语句的执行,有两种可能:
(new Foo).getName()
或
new (Foo.getName)()
但是我们根据操作符优先级表可以得知,其实上.
操作符要比new
优先级要高,所以实际执行的是第二种,所以是对
Foo.getName = function () {
console.log('2');
};
函数执行了new
操作,当然输出的是2
。
下面到了执行
new Foo().getName();
这个语句的可能性也有两种:
(new Foo()).getName();
或者
new (Foo().getName)();
那么应该是那种的呢?原来我以为会是第二种的执行方式,后面通过浏览器调试发现真实的执行的方式是第一种。我看到题目的作者是这么解释的:
首先看运算符优先级括号高于new。实际执行为(new Foo()).getName()。遂先执行Foo函数。
我觉得上面的解释是有问题的,对比上面两种执行方式,第一种是先执行new
,然后执行的是.
操作符,然后执行的是()
。第二种是先执行了()
,再执行的是.
,最后执行new
操作符。如果真的按照引用所说的用优先级的方式判别,其实恰恰应该执行的是第二种而不是第一种。
后来总算找到原因了,原来之前那个出现的比较多的JavaScript优先级的表并不完整,万能的MDN给出了最权威的JavaScript优先级表运算符优先级
我列举出最重要的部分(由高到低):
所以带参数的new
操作符是优先级最高的,这下就没有问题了,执行顺序确实应该是第一种。
那么按照(new Foo()).getName();
来执行,情况就就很简单了,(new Foo())
返回了新生成的对象,该对象没有getName()
方法,所以在prototype
中找到了getName()
方法。所以输出的是3
。
胜利就在眼前,我们看一下最后一问。
new new Foo().getName();
和上一步一样的方法,我们按照优先级表给分析一下这个语句到底是怎么执行的。
首先带参数的new
操作符优先级最高,第一步划分为:
new (new Foo().getName)();
第二步划分为:
new ((new Foo()).getName)();
所以执行(new Foo()).getName
这个函数是对应的Foo.prototype.getName,所以执行new (Foo.prototype.getName)()
肯定输出的是3
。
哈哈哈,这么难得题终于解决了,开心~总结一下吧,首先JavaScript知识最好去MDN去查,万一别的地方写错了真的是害人不浅。其次,如果在写代码的时候还是少利用操作符优先级这种东西,一旦不明确的地方就立刻用()
,代码的可阅读性真的是很重要!很重要!很重要!毕竟代码还是给人看~
如果有写的不正确的地方,欢迎大家指出,资历深浅,请多指教。欢迎大家去围观我的博客呀 https://mrerhu.github.io
我现在都用use strict ,
来自酷炫的 CNodeMD
好文章!
@bajian 谢谢~~~😘
@MrErHu Foo().getName(); ^
TypeError: Foo(…).getName is not a function
上述代码的执行结果是:4。原因是这样的,var声明的变量和函数声明function都会被提升,但是函数声明的提升的级别是比var要高的
这里提升级别谁高谁低我不知道,但是不是提升级别高低影响的,实际上无提升级别之分看起来并不会有什么问题。而是函数声明和变量声明被提升,变量赋值位置不变。
var getName;
function getName() {
console.log(5);
}
getName = function () {
console.log('4');
};
getName();
题目不错,妥妥的考察基本功。
作用域/原型链。
<p style=“text-align:right”><a href=“https://github.com/lumia2046/cnode”> — — 来自lumia2046-react-cnode</a></p>
<h1>厉害了</h1><p style=“text-align:right”><a href=“https://github.com/lumia2046/cnode”> — — 来自lumia2046-react-cnode</a></p>
相信我,PHP是最好的语言
<font color=red>厉害了</font>
<h1><font color=“green”>JavaScript 才是最好的语言</font></h1>
REACT_DEVTOOLS_GLOBAL_HOOK = parent.REACT_DEVTOOLS_GLOBAL_HOOK
new (Foo.prototype.getName)()
最后一个括号为什么为什么没有报 is not a function
new (Foo.prototype.getName)返回的不是一个对象吗是不是相当于 ({})()吗
node 执行结果和这个完全不一样。 不知道是不是因为es6
这东西以后估计会变的;写代码尽量不要有提权的代码; 这玩意痛苦
这里有出题人和她的解答
@nikewu 对,是我以前看过的题目,但是解答是我自己分析的,毕竟也是自己学习的过程。
之前一直收藏着没看,今儿偷空看一下,老实说,不够细心,遇到优先级不清楚的地方,犯了懒病…题目不错,对夯实基础有帮助,但是项目中别写这样的代码…
@slclub node 之所以表现不一样,是因为最上面 Foo 的函数声明在 node 中返回的 this ,并不是 window,这是由于node 的包装导致的
所以在 Foo().getName() 的时候就会报 not a function, Foo() 返回的 this 里没有这个方法。
放弃第三个打印测试的话,可以修改为Foo(), 这样第四个打印:getName() 就可以正常打出来 1
嗯,靠谱
@slclub 题目并不复杂 不过你这个问题问到点上了 不是es6的问题 是因为在浏览器环境下 在根作用域下面定义的变量或者方法会自动绑定到window下面,而node不是 明白这句话你就清楚了console.log(this == (function(){return this})())
赞一个