一道颇有难度的JavaScript题

上次分享了一道题,大家反响不错,很开心自己写的东西有人愿意花时间去看,也给了自己莫大的鼓舞,其实做题虽然不比真正的编程,但是也能够让你发现一些你之前没有注意到的语言层面的问题。所以,这次再分享一道稍微有难度的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变量,前面是不带有varlet,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去查,万一别的地方写错了真的是害人不浅。其次,如果在写代码的时候还是少利用操作符优先级这种东西,一旦不明确的地方就立刻用(),代码的可阅读性真的是很重要!很重要!很重要!毕竟代码还是给人看~

时间: 2025-01-05 03:13:47

一道颇有难度的JavaScript题的相关文章

一道有意思的C语言编程题

最近在看经典的C语言入门书籍K&R,虽然是一本入门书籍,可是其中的精妙之处却需要慢慢体会.其中的经典题很多,仔细琢磨一定会收获良多. 今天看到这样一道题:编写一个删除C语言程序中所有的注释语句.感觉颇有意思,与大家一起分享一下: 我的思路: 找到注释的起始符号 \ 判断紧接着的输入字符,如果是*或者是\,则说明后面全是注释,跳过即可,否则照样输出 其他则直接输出 疑问: 所配套的答案书中提出要考虑引号后面的内容以做出响应,不是很明白这是为什么.个人认为无需考虑引号的影响也能将注释去除,希望有高手

一道很有意思的java线程题

这几天看结城浩的<java多线程设计模式>,跟着做一些习题,有几道题目很有意思,记录下自己的体会. 首先是题目(在原书212页,书尾有解答): public class Main { public static void main(String[] args) { try { Blackhole.enter(new Object()); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Blac

一道int与二进制加减题

int dis_data = 32769; if( dis_data > 0x7fff)  dis_data -= 0xffff; printf("%d\n",dis_data); 上面的dis_data 输出值会是多少?  初一看可能还看不出来,那就计算一下: 0x7fff转换为十进制为 32767,显然 dis_data > 0x7fff, 所以要执行 dis_data -= 0xffff;这一句代码. 现在dis_data = 32769, 那么 0xffff转换为十

上海华为的一道关于指针方面的编程题(C/C++)

int A[nSize],其中隐藏着若干0,其余非0整数,写一个函数int Func(int* A, int nSize),使A把0移至后面,非0整数移至数组前面并保持有序,返回值为原数据中第一个元素为0的下标. 尽可能不使用辅助空间且考虑效率及异常问题,注释规范且给出设计思路 注:我的方法的复杂度为O(n),大家如果有其它方法希望可以交流一下. /* author: jiangxin Blog: http://blog.csdn.net/jiangxinnju */ #include <ios

一道关于CSS选择器优先级的题

<span style="font-size:14px;"><html> <head> <style type="text/css"> #redP p { /* 权值 = 100+1=101 */ color:#F00; /* 红色 */ } #redP .red em { /* 权值 = 100+10+1=111 */ color:#00F; /* 蓝色 */ } #redP p span em { /* 权值 =

一道看似dp实则暴力的题 Zombie&#39;s Treasure Chest

 Zombie's Treasure Chest 本题题意:有一个给定容量的大箱子,此箱子只能装蓝宝石和绿宝石,假设蓝绿宝石的数量无限,给定蓝绿宝石的大小和价值,要求是获得最大的价值 题解:本题看似是dp中的背包问题,但是由于数据量太大,用dp肯定会超时,所以只能寻找另外一种思路,可以用贪心加暴力,先求出两种宝石大小的最小公倍数com,然后将N/com-com,与N%comkanchengs看成是两个部分(想想应该明白).将前一个部分,放入单位价值量最高的那个,对于后面那个部分直接将S1的数量从

一道很不错的字符分割题

刚在stackoverflow看到一道很不错的问题,遂拿来分享之. 题目要求:我有一个很长的字符串: String s1="This is my world. This has to be broken." 我要把上面的字符串打乱以固定的长度(例如10)使得输出为: This is my world. Thi s has to b e broken. 但是我想让输出包含原来的字符的同时不不分开一个词使得输出如下: This is my world. This has to be bro

实战一道2级综合编程模拟题

请编写程序,生成随机密码.具体要求如下:???????????????????????????????????????????????????????????????????????????????????????????????? (1)使用 random 库,采用 0x1010 作为随机数种子.???????????????????????????????????????????????????????????????????????????????????????????????? (2)

做一道 高一 求 函数 值域 的 题

网友 暮色星辰ing (Suzuha)   在 数学吧 发了一个 帖,  提问了一道 题,  这道题 是 g(x) = 5 / ( 2^x + 1 ) - 2   ,    x ∈ [ 0, 2 ]    ,     y = [ 2 + g(x) ]  [ 1 / g ( -x ) - 2 ]     ,     求 y 的 值域    . 我做了一下, 我化简 得到 的 函数式 是   y = -25 * (2^x - 1) / [ (2^x + 1) (3 * 2^x - 2) ]  ,