好的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] //返回的是? 旺财估计都已经明白了,就不多说了。 |