详解javaScript变态25题

好的JavaScript测试题目应该是:门外汉见了皱眉,行家见了疑惑题目是不是简单了点,同时考察点覆盖全面。//zxx: 我目前还没有这个能耐设计出如此优秀的题目。

这里要介绍的”Another JavaScript quiz“(by james)中的题目不是属于变态题目,而是确实属于变态题目,不过是表面上的,很多内容确实可能会遇到的。综合评价下就是:面试价值不及格,学习价值是很赞的,因此,探讨分享很有意义。

“Another JavaScript quiz“中有25个很简洁的JavaScript测试题,全部都是考察返回值的,例如1 && 3的返回值是?

25道题目全部完成后,可以点击下图所示的按钮,检查你的正确率以及那些题目出错了。

我自己快速测试了下,如果每题算4分的话,我的成绩是56分,不及格,可见自己对JS的学习以及理解还有一段路要走。

您也可以做一下这些题目,完成之后,若有疑惑,可以参考我下面纯个人的理解 – 部分理解可能不准确,欢迎指正。同时,网上应该有其他一些前辈的解释,您也可以参考参考。

二、公子,不急,慢慢来嘛

(1) 1 && 3 //返回的是?
结果是3.
&&是几乎无人不知,表示“与”。男方要娶水灵灵的妹子,七大姑八大婆都要同意,否则继续锻炼右手吧。
上面1 && 3本质上等同于大姑妈 && 三姑妈。

从左往右,如果“大姑妈”通过,继续“三姑妈”;否则直接返回“大姑妈”。因为“三姑妈”后面没有其他亲戚了,因此直接返回“三姑妈”。

在JavaScript中, 1 == true // true,因此,1 && 3等同于“大姑妈”通过了,返回最后一个检测的亲戚“三姑妈”,也就是这里的结果3.

因此,实际上,我们平时的 if (1 && 3) {} 等同于 if (3) {}.

如果这里的题目换成0 && 3返回的是?那结果是如何呢?

因为0 == false // true, 因此走不到“三姑妈”这一关,直接返回了0. 也就是if (0 && 3) {} 等同于 if (0) { /* 不会执行 */ }.

实用性
可以避免if嵌套。例如,要问页面上某个dom绑定点击事件,我们需要先判断这个dom元素存不存在,我们可能会这样做:
var dom = document.querySelector(“#dom”);
if (dom) { dom.addEventListener(“click”, function() {}); }

实际上,我们可以使用&&做一些简化:
var dom = document.querySelector(“#dom”);
dom && dom.addEventListener(“click”, function() {});

(2) 1 && “foo” || 0 //返回的是?

很简单的一道题,结果是”foo”.

这里出现了一个新的关系符||, 表示“或”的意思。男方要娶干巴巴的妹子,七大姑八大婆只要一个说好就可以了。比方说1 || 3表示:如果大姑妈说可以,则成了,返回大姑妈;如果大姑妈不允许,再看看三姑妈的意思。

因此,if (1 || 3) {}实际等同于if (1) {}; if (0 || 3) {} 等同于if (3) {}.

实用性
||可以让我们使用一种更快捷简易的方式为参数添加默认值。例如,写jQuery插件的时候,可选参数是可以缺省的,此时实际上值为undefined,会让后面的参数extend产生困扰。因此,我们会经常见到类似这样的代码:
$.fn.plugin = function(options) {
options = options || {};
// …
};

显然,根据上面的理解,1 && “foo”返回的是”foo”, 而”foo” || 0, 因”foo” == true为true, 自然返回是“大姑妈” – “foo”. 这就是最终结果”foo”的由来。

(3) 1 || “foo” && 0 //返回的是?

结果是1.
据说逻辑与运算符(&&)优先级要大于逻辑或(||)。如果是这样,那这里的结果返回应该是这样子的:1 || “foo” && 0 → 1 || 0 → 1.

(4) (1, 2, 3) //返回的是?

结果是3.
这里考察的是逗号运算符,也称多重求值。逗号运算符据说是优先级最低的运算符。
一般表现形式是:大姑妈,二姑妈,三姑妈,……N姑妈。
运算规则如下:折腾大姑妈,折腾二姑妈,折腾三姑妈,……折腾N姑妈,最后返回的N姑妈的返回值。

因此,这里1, 2, 3就是折腾了1,折腾了2,折腾了3并且返回了3的返回值。因此,结果是3.

好,现在提问:alert(1, 2, 3);的弹出值是?
结果是3……………………是不可能的!正确结果是1.

不要困扰。原因很简单,alert身后的()实际上也是一种运算符,这里指函数调用,优先级相当的高。这里,1, 2, 3已经被奴役成alert方法(弹出第一个参数值)的参数了,因此,弹出的是1.
如果是alert((1, 2, 3));则弹出的就是3了。

实用性
举个很简单的例子,i, j两个变量同时递增,可能我们会这样写:
var i=1;
var j=1;
i+=1;
j+=2;

我们可以借助逗号运算符获得更好地阅读体验,至于性能是否有提高,我个人并不清楚,就算有,差异也是可以忽略的。
var i=1, j=1;
i+=1, j+=2;

(5) x = { shift: [].shift };
x.shift();
x.length; //返回的是?

结果是IE6/IE7 undefined, 其他浏览器0. 不过,个人观点,IE6/IE7其实并不属于这个题目的考察浏览器。因此,我们着重讨论为何IE8+等浏览器为何结果是0.

首先,我们了解下数组的shift方法。 我个人一般把pop/push归为一对基友,移除尾部元素和尾部添加元素,都是做扫尾工作的。shift/unshift当作另外一对基友,移除头部元素和头部添加元素,都是在头部打点的。记住,单词字符个数少的(pop, shift)都是做删除的,这样记忆就不会混淆了。

从外表上看,shift方法移除数组中的第一个元素并返回这个元素。如果这个数组是个空数组,则返回undefined. 一般而言,shift操作会改变后面所有数组项在内存中的地址,因此,相比pop方法要慢得多。

从内在来看,shift还能给对象自动添加length属性。Mozilla开发者中心(MDC)Mozilla开发者网络(MDN – Mozilla Developer Network)的解释是这样子的:

shift is intentionally generic; this method can be called or applied to objects resembling arrays. Objects which do not contain a length property reflecting the last in a series of consecutive, zero-based numerical properties may not behave in any meaningful manner.

简体中文表示就是(这里的释义自己只有80%确认,若有不准确,请极力指证):
shift可以有意泛化(变身成鸭子);该方法可以被类数组对象call或者apply. 对象如果没有length属性,可能会以无意义的方式在最后反射一系列连续,基于0的数值属性。

什么意思呢?我们看下面这个更容易理解的问题:
x = {};
[].shift.call(x);
x.length; //返回的是?

结果是0. //zxx: IE6/IE7 undefined.

x对象原本木有length属性,在被数组shift方法call后,添加了一个值为0的length属性。此为数组shift方法的泛化性,专业术语为泛型(generic)。其基友方法,例如pop, push等都是如此。

OK, 回到原题,实际上,x.shift()的调用等同于[].shift.call(x), 不明白?看下面的一步一步分析。
var x = {
shift: function() {
console.log(this === x); // true
}
};
x.shift();

因此,在
x = { shift: [].shift }
条件下,
x.shift执行的就是[].shift的执行,只不过,[].shift()函数中的this上下文就是x(因为this===x),就等同于直接的[].shift.call(x)调用。

这条题目中x对象的shift属性名实际上是用来干扰,提高解答难度的刻意命名。我们使用其他命名,结果也是一样的。
x = { shit: [].shift };
x.shit();
x.length; // 0

(6) {foo:1}[0] //返回的是?
结果是[0], 或者这种表现形式0 { 0 : 0 } – 来自IE控制台.

不要试图使用alert或者控制台console.log输出,这只会返回不一样的结果undefined,哦?为何会有这等差异?
出题者james在”Labelled blocks, useful?“中有这样的解释:
Since JavaScript doesn’t have block scope, using a block anywhere other than in the conventional places (if/while etc.) is almost totally pointless. However, as I mentioned, we could use them to annotate and contain logically related pieces of code…

意思是说:
因为JavaScript没有块作用域,所以,如果语句块不是常规使用,如if/while等,其几乎就是打酱油的。甚至,我们可以利用这个特性注释或者包含相关的逻辑片段代码…

我们有必要好好理解这里“打酱油的”意思,这里的“打酱油”并不是指{}块中语句是打酱油,而是其本身就是个酱油。嘛意思,实例说明一切:
2 // 返回值为2
{2} // 返回值为2
str = “string” // 返回值为string
{ str = “string” } // 返回值为string
foo: 1 // 返回值为1
{ foo: 1 } // 返回值为1

也就是说花括号几乎就是皇帝的新衣。因此,这里的答案就不难理解了,{foo:1}[0]实际上就是foo:1; [0]. 返回的就是[0]本身。
注意这里反复出现的措辞“几乎”。“几乎酱油”的潜台词是有时候还能顶个臭皮匠。james举了个在块中使用break语句的例子,如下:
var x = 1;
foo: {
x = 2;
break foo;
x = 3;
}
x === 2; // true

非这种情况的break/continue只能在switch语句以及循环中使用。很有意思吧,我反正是学习了!

(7) [true, false][+true, +false] //返回的是?

结果是true.
首先,我们了解下+true和+false是个什么东西。

想必都清楚+1与-1是什么东东。指的是正数1与负数1. 同时,稍微对JS有了解的人也清楚,true == 1, false == 0. 因此,实际上,[+true, +false]就是[+1, +0].

[true, false]为数组,后面的[+true, +false]实际为索引,然而索引只需要一个值,因此,[+true, +false]返回的实际是我们上面提到的逗号运算——返回最后一个值,也就是+0, 也就是0.

因此,本题的问题其实是:[true, false][0] //返回的是?
旺财估计都已经明白了,就不多说了。

猜你喜欢

转载自blog.csdn.net/moyanxuan_1993_2_24/article/details/51727164
今日推荐