js 正则 包含字符 国外某网站给出了44道JS难题,只做对了17道

  来源 |

  国外某网站给出了44道JS难题,试着做了下,只做对了17道。这些题涉及面非常广,涵盖JS原型、函数细节、强制转换、闭包等知识,而且都是非常细节的东西,透过这些小细节可以折射出很多高级的JS知识点。

  你可以通过传送门()先去测试一下你的水平,然后回来看看我的解析。为了详细解释这些细节,我也查阅了不少资料,弥补了很多JS知识盲点。

  1. parseInt 遇上 map

  <pre class="code-snippet__js" data-lang="javascript">["1", "2", "3"].map(parseInt)` // A. ["1", "2", "3"]// B. [1, 2, 3]// C. [0, 1, 2]// D. other`</pre>

  答案是D。实际上返回的结果是 [1, NaN, NaN] ,因为 parseInt 函数只需要两个参数 parseInt(value, radix) ,而 map 的回调函数需要三个参数 callback(currentValue, index, array)。

  MDN文档中指明 parseInt 第二个参数是一个2到36之间的整数值,用于指定转换中采用的基数。如果省略该参数或其值为0,则数字将以10为基础来解析。

  如果该参数小于2或者大于36,则 parseInt 返回 NaN。此外,转换失败也会返回 NaN。

  现在来分析问题。parseInt("1", 0) 的结果是当作十进制来解析,返回 1;parseInt("2", 1) 的第二个参数非法,返回 NaN;parseInt("3", 2) 在二进制中,"3" 是非法字符,转换失败,返回 NaN。

  参考资料:

  2. 神奇的null

  <pre class="code-snippet__js" data-lang="javascript">[typeof null, null instanceof Object]` // A. ["object", false]// B. [null, false]// C. ["object", true]// D. other`</pre>

  答案是A。在MDN关于 null 的文档中也特别指出来了,typeof null 的结果是 "object",它是ECMAScript的bug,其实应该是 "null"。但这个bug由来已久,在JavaScript中已经存在了将近二十年,也许永远不会修复,因为这牵扯到太多的Web系统,修复它会产生更多的bug,令许多系统无法正常工作。而 instanceof 运算符是用来测试一个对象在其原型链构造函数上是否具有 prototype 属性,null 值并不是以 Object 原型创建出来的,所以 null instanceof Object 返回 false。

  参考资料:

  3. 愤怒的reduce

  <pre class="code-snippet__js" data-lang="javascript">[ [3,2,1].reduce(Math.pow), [].reduce(Math.pow) ]` // A. an error// B. [9, 0]// C. [9, NaN]// D. [9, undefined]`</pre>

  答案是A。MDN文档中关于 Array.prototype.reduce() 写得很清楚:

  如果数组为空并且没有提供initialValue, 会抛出TypeError 。如果数组仅有一个元素(无论位置如何)并且没有提供initialValue,或者有提供initialValue但是数组为空js 正则 包含字符,那么此唯一值将被返回并且callback不会被执行。

  参考资料:

  4. 该死的优先级

  <pre class="code-snippet__js" data-lang="kotlin">var val = smtg ;`console.log( Value is + (val === smtg ) ? Something : Nothing ); // A. Value is Something// B. Value is Nothing// C. NaN`// D. other</pre>

  答案是D。实际上输出 "Something",因为 + 的优先级比条件运算符 condition ? val1 : val2 的优先级高。

  参考资料:

  5. 神鬼莫测之变量提升

  <pre class="code-snippet__js" data-lang="javascript">var name = World! ;`(function () { if (typeof name === undefined ) { var name = Jack ; console.log( Goodbye + name); } else { console.log( Hello + name); }})(); // A. Goodbye Jack// B. Hello Jack// C. Hello undefined// D. Hello World`</pre>

  答案是A。看如下MDN官方文档的解释:

  在 JavaScript中, functions 和 variables 会被提升。变量提升是JavaScript将声明移至作用域 scope (全局域或者当前函数作用域) 顶部的行为。

  这意味着你可以在声明一个函数或变量之前引用它,或者可以说:一个变量或函数可以在它被引用之后声明。

  所以,上面的代码与下面这段代码是等价的:

  <pre class="code-snippet__js" data-lang="javascript">var name = World! ;`(function () { var name; if (typeof name === undefined ) { name = Jack ; console.log( Goodbye + name); } else { console.log( Hello + name); }})();`</pre>

  参考资料:

  6. 死循环陷阱

  <pre class="code-snippet__js" data-lang="swift">var END = Math.pow(2, 53);`var START = END - 100;var count = 0;for (var i = START; i 0 // A. false// B. true// C. error`// D. other</pre>

  答案是B。看规范描述吧:

  MIN_VALUE属性是 JavaScript 里最接近 0 的正值,而不是最小的负值。

  MIN_VALUE的值约为 5e-324。小于 MIN_VALUE

  ("underflow values") 的值将会转换为 0。

  因为 MIN_VALUE是 Number 的一个静态属性,因此应该直接使用:Number.MIN_VALUE,而不是作为一个创建的 Number实例的属性。

  参考资料:

  23. 谨记优先级

  <pre class="code-snippet__js" data-lang="cs">[1 < 2 < 3, 3 < 2 < 1]` // A. [true, true]// B. [true, false]// C. error// D. other`</pre>

  答案是A。的优先级都是从左到右,所以 1 < 2 < 3 会先比较 1 < 2,这会得到 true,但是 < 要求比较的两边都是数字,所以会发生隐式强制转换,将 true 转换成 1,所以最后就变成了比较 1 < 3,结果显然为 true。同理可以分析后者。

  参考资料:

  24. 坑爹中的战斗机

  <pre class="code-snippet__js" data-lang="javascript">// the most classic wtf`2 == [[[2]]] // A. true// B. false// C. undefined`// D. other</pre>

  答案是A。根据ES5规范,如果比较的两个值中有一个是数字类型,就会尝试将另外一个值强制转换成数字,再进行比较。而数组强制转换成数字的过程会先调用它的 toString方法转成字符串,然后再转成数字。所以 [2]会被转成 "2",然后递归调用,最终 [[[2]]] 会被转成数字 2。

  25. 小数点魔术

  <pre class="code-snippet__js" data-lang="swift">3.toString();`3..toString();3...toString(); // A. "3", error, error// B. "3", "3.0", error// C. error, "3", error// D. other`</pre>

  答案是C。点运算符会被优先识别为数字常量的一部分,然后才是对象属性访问符。所以 3.toString() 实际上被JS引擎解析成 (3.)toString(),显然会出现语法错误。但是如果你这么写 (3).toString(),人为加上括号,这就是合法的。

  26. 自动提升为全局变量

  <pre class="code-snippet__js" data-lang="javascript">(function() {` var x = y = 1;})();console.log(y);console.log(x); // A. 1, 1// B. error, error// C. 1, error// D. other`</pre>

  答案是C。很经典的例子,在函数中没有用 var 声明变量 y,所以 y 会被自动创建在全局变量 window下面,所以在函数外面也可以访问得到。而 x 由于被 var 声明过,所以在函数外部是无法访问的。

  27. 正则表达式实例

  <pre class="code-snippet__js" data-lang="javascript">var a = /123/;`var b = /123/;a == b;a === b; // A. true, true// B. true, false// C. false, false`// D. other</pre>

  答案是C。每个字面的正则表达式都是一个单独的实例,即使它们的内容相同。

  28. 数组也爱比大小

  <pre class="code-snippet__js" data-lang="bash">var a = [1, 2, 3];`var b = [1, 2, 3];var c = [1, 2, 4]; a == b;a === b;a > c;a < c; // A. false, false, false, true// B. false, false, false, false// C. true, true, false, true`// D. other</pre>

  答案是A。数组也是对象,ES5规范指出如果两个对象进行相等比较,只有在它们指向同一个对象的情况下才会返回 true,其他情况都返回 false。而对象进行大小比较,会调用 toString 方法转成字符串进行比较,所以结果就变成了字符串 "1,2,3" 和 "1,2,4" 按照字典序进行比较了(你若不信,可以重现两个变量的 toString 方法,进行测试)。

  29. 原型把戏

  <pre class="code-snippet__js" data-lang="javascript">var a = {};`var b = Object.prototype; [a.prototype === b, Object.getPrototypeOf(a) == b] // A. [false, true]// B. [true, true]// C. [false, false]`// D. other</pre>

  答案是A。对象是没有 prototype 属性的,所以 a.prototype 是 undefined,但我们可以通过 Object.getPrototypeOf 方法来获取一个对象的原型。

  30. 构造函数的函数

  <pre class="code-snippet__js" data-lang="javascript">function f() {}`var a = f.prototype;var b = Object.getPrototypeOf(f);a === b; // A. true// B. false// C. null`// D. other</pre>

  答案是B。这个解释起来有点绕口,我们先来看另外一段代码:

  <pre class="code-snippet__js" data-lang="javascript">function Person() {}`var p = new Person(); var a = p.__proto__;var b = Object.getPrototypeOf(p);var c = Person.prototype;console.log(a === b, a === c, b === c);// true, true, true var d = Person.__proto__;var e = Object.getPrototypeOf(Person);var f = Function.prototype;console.log(d === e, d === f, e === f);// true, true, true`</pre>

  首先你要明白,任何函数都是 Function 的实例,而p是函数 Person 的实例,Object.getPrototypeOf 会获取构造当前对象的原型。所以 Object.getPrototypeOf(p) === Person.prototype,而 Object.getPrototypeOf(Person) === Function.prototype,所以答案就很明显了。

  我解释的不是很好,如果读者有更好的解释,欢迎评论。

  31. 禁止修改函数名

  <pre class="code-snippet__js" data-lang="javascript">function foo() {}`var oldName = foo.name;foo.name = "bar";[oldName, foo.name]; // A. error// B. ["", ""]// C. ["foo", "foo"]`// D. ["foo", "bar"]</pre>

  答案是C。函数名是禁止修改的,规范写的很清楚,所以这里的修改无效。

  参考资料:

  32. 替换陷阱

  <pre class="code-snippet__js" data-lang="javascript">"1 2 3".replace(/d/g, parseInt);` // A. "1 2 3"// B. "0 1 2"// C. "NaN 2 3"// D. "1 NaN 3"`</pre>

  答案是D。如果 replace 方法第二个参数是一个函数,则会在匹配的时候多次调用,第一个参数是匹配的字符串,第二个参数是匹配字符串的下标。所以变成了调用 parseInt(1, 0)、parseInt(2, 2)和parseInt(3, 4),结果你就懂了。

  参考资料:

  33. Function的名字

  <pre class="code-snippet__js" data-lang="javascript">function f() {}`var parent = Object.getPrototypeOf(f);console.log(f.name);console.log(parent.name);console.log(typeof eval(f.name));console.log(typeof eval(parent.name)); // A. "f", "Empty", "function", "function"// B. "f", undefined, "function", error// C. "f", "Empty", "function", error`// D. other</pre>

  答案是C。根据第30题的解释,我们知道代码中的 parent 实际上就是 Function.prototype,而它在控制台中输出为:

  <pre class="code-snippet__js" data-lang="javascript">function () {` [native code]`}</pre>

  它的 name 属性是"",所以你 eval("")是得不到任何东西的。

  34. 正则测试陷阱

  <pre class="code-snippet__js" data-lang="javascript">var lowerCaseOnly = /^[a-z]+$/;`[lowerCaseOnly.test(null), lowerCaseOnly.test()] // A. [true, false]// B. error// C. [true, true]`// D. [false, true]</pre>

  答案是C。test 方法的参数如果不是字符串,会经过抽象 ToString操作强制转成字符串,因此实际上测试的是字符串"null"和"undefined"。

  35. 逗号定义数组

  <pre class="code-snippet__js" data-lang="cs">[,,,].join(", ")` // A. ", , , "// B. "undefined, undefined, undefined, undefined"// C. ", , "// D. ""`</pre>

  答案是C。JavaScript允许用逗号来定义数组,得到的数组是含有3个 undefined 值的数组。MDN关于 join 方法的描述:

  所有的数组元素被转换成字符串,再用一个分隔符将这些字符串连接起来。如果元素是undefined 或者null, 则会转化成空字符串。

  参考资料:

  36. 保留字 class

  <pre class="code-snippet__js" data-lang="kotlin">var a = {class: "Animal", name: "Fido"};`console.log(a.class); // A. "Animal"// B. Object// C. an error`// D. other</pre>

  答案是D。实际上真正的答案取决于浏览器。class 是保留字,但是在Chrome、Firefox和Opera中可以作为属性名称,在IE中是禁止的。另一方面,其实所有浏览器基本接受大部分的关键字(如:int、private、throws等)作为变量名,而class是禁止的。

  37. 无效日期

  <pre class="code-snippet__js" data-lang="javascript">var a = new Date("epoch");` // A. Thu Jan 01 1970 01:00:00 GMT+0100(CET)// B. current time// C. error// D. other`</pre>

  答案是D。实际结果是 Invalid Date,它实际上是一个Date对象,因为 a instance Date 的结果是 true,但是它是无效的Date。Date对象内部是用一个数字来存储时间的,在这个例子中,这个数字是 NaN。

  38. 神鬼莫测的函数长度

  <pre class="code-snippet__js" data-lang="php">var a = Function.length;`var b = new Function().length;console.log(a === b); // A. true// B. false// C. error// D. other`</pre>

  答案是B。实际上a的值是1,b的值是0。还是继续来看MDN文档关于 Function.length 的描述吧!

  Function构造器的属性:

  Function 构造器本身也是个Function。他的 length 属性值为 1 。该属性 Writable: false, Enumerable: false, Configurable: true。

  Function原型对象的属性:

  Function原型对象的 length 属性值为 0 。

  所以,在本例中,a代表的是 Function 构造器的 length 属性,而b代表的是 Function 原型的 length 属性。

  参考资料:

  39. Date的面具

  <pre class="code-snippet__js" data-lang="javascript">var a = Date(0);`var b = new Date(0);var c = new Date();[a === b, b === c, a === c]; // A. [true, true, true]// B. [false, false, false]// C. [false, true, false]`// D. [true, false, false]</pre>

  答案是B。先看MDN关于Date对象的注意点:

  需要注意的是只能通过调用 Date 构造函数来实例化日期对象:以常规函数调用它(即不加 new 操作符)将会返回一个字符串,而不是一个日期对象。另外,不像其他JavaScript 类型,Date 对象没有字面量格式。

  所以a是字符串,b和c是Date对象,并且b代表的是1970年那个初始化时间,而c代表的是当前时间。

  参考资料:

  40. min与max共舞

  <pre class="code-snippet__js" data-lang="swift">var min = Math.min();`var max = Math.max();console.log(min < max); // A. true// B. false// C. error// D. other`</pre>

  答案是B。看MDN文档,对 Math.min的描述:

  如果没有参数,结果为Infinity。

  对 Math.max 的描述:

  如果没有参数,结果为-Infinity。

  参考资料:

  41. 警惕全局匹配

  <pre class="code-snippet__js" data-lang="javascript">function captureOne(re, str) {` var match = re.exec(str); return match && match[1];} var numRe = /num=(d+)/ig, wordRe = /word=(w+)/i, a1 = captureOne(numRe, "num=1"), a2 = captureOne(wordRe, "word=1"), a3 = captureOne(numRe, "NUM=1"), a4 = captureOne(wordRe, "WORD=1"); [a1 === a2, a3 === a4] // A. [true, true]// B. [false, false]// C. [true, false]// D. [false, true]`</pre>

  答案是C。看MDN关于 exec 方法的描述:

  当正则表达式使用 "g" 标志时,可以多次执行 exec 方法来查找同一个字符串中的成功匹配。当你这样做时,查找将从正则表达式的 lastIndex 属性指定的位置开始。

  所以a3的值为 null。

  参考资料:

  42. 最熟悉的陌生人

  <pre class="code-snippet__js" data-lang="javascript">var a = new Date("2014-03-19");`var b = new Date(2014, 03, 19);[a.getDay() == b.getDay(), a.getMonth() == b.getMonth()] // A. [true, true]// B. [true, false]// C. [false, true]// D. [false, false]`</pre>

  答案是D。先看MDN关于Date的一个注意事项:

  当Date作为构造函数调用并传入多个参数时,如果数值大于合理范围时(如月份为13或者分钟数为70),相邻的数值会被调整。比如 new Date(2013, 13, 1)等于new Date(2014, 1, 1),它们都表示日期2014-02-01(注意月份是从0开始的)。其他数值也是类似,new Date(2013, 2, 1, 0, 70)等于new Date(2013, 2, 1, 1, 10),都表示时间2013-03-01T01:10:00。

  此外,getDay 返回指定日期对象的星期中的第几天(0~6),所以,你懂的。

  参考资料:

  43. 匹配隐式转换

  <pre class="code-snippet__js" data-lang="javascript">if("http://giftwrapped.com/picture.jpg".match(".gif")) {` console.log("a gif file");} else { console.log("not a gif file");} // A. "a gif file"// B. "not a gif file"// C. error// D. other`</pre>

  答案是A。看MDN对 match 方法的描述:

  如果传入一个非正则表达式对象,则会隐式地使用 new RegExp(obj)

  将其转换为正则表达式对象。

  所以我们的字符串 ".gif" 会被转换成正则对象 /.gif/,会匹配到 "/gif"。

  参考资料:

文章由官网发布,如若转载,请注明出处:https://www.veimoz.com/1355
0 评论
380

发表评论

!