Skip to content

一些有意思的JS面试题

Posted on:February 14, 2021
  1. parseInt 遇上 map

    ["1", "2", "3"].map(parseInt);
    
    // A. ["1", "2", "3"]
    // B. [1, 2, 3]
    // C. [0, 1, 2]
    // D. other

    答案 D,实际返回结果为[1, NaN, NaN],parseInt 需要两个参数 parse(value,radix),map 的回调函数需要三个参数 map(currentValue, index, array)。parseInt 的第二个参数是一个 2 到 36 之间的整数值,用于指定转换中采用的基数。如果该参数省略或为 0,则数字将以 10 作为基数来解析。如果该参数小于 0 或大于 36,则 parseInt 返回 NaN,转换失败也返回 NaN。parseInt(“1”, 0)的结果被当作十进制来解析,返回 1;parseInt(“2”, 1)的第二个参数小于 2,返回 NaN;parseInt(“3”, 2)在二进制中,“3”是非法字符,转换失败,返回 NaN。


  1. 愤怒的 reduce

    [[3, 2, 1].reduce(Math.pow), [].reduce(Math.pow)];
    
    // A. an error
    // B. [9, 0]
    // C. [9, NaN]
    // D. [9, undefined]

    答案是 A,如果数组为空或没有 initialValue,会抛出 TypeError


  1. 死循环陷进

    var END = Math.pow(2, 53);
    var START = END - 100;
    var count = 0;
    for (var i = START; i <= END; i++) {
      count++;
    }
    console.log(count);
    
    // A. 0
    // B. 100
    // C. 101
    // D. other

    答案是 D,在 js 中,2^53 是最大的值,没有比这更大的值了,所以 2^53 + 1 == 2^53,所以这个死循环无法终止。


  1. 过滤器魔法

    var ary = [0, 1, 2];
    ary[10] = 10;
    ary.filter(function (x) {
      return x === undefined;
    });
    
    // A. [undefined x 7]
    // B. [0, 1, 2, 10]
    // C. []
    // D. [undefined]

    答案是 C,filter 中的 callback 函数只会在已赋值的索引上被调用,对于哪些已经被删除或从未被赋值的索引不会被调用。这道题中,array 实际是[0,1,2,undefined * 7, 10],所以这 7 个值为 undefined 的元素不会被 callback 调用,剩下的元素不满足 x === undefined 的条件,所以返回空数组。


  1. 数组原型是数组

    Array.isArray(Array.prototype);
    
    // A. true
    // B. false
    // C. error
    // D. other

    答案是 A,一个鲜为人知的事实:其实 Array.prototype 也是一个数组。这点在 MDN 文档中提到过。


  1. 一言难尽的强制转换

    var a = [0];
    if ([0]) {
      console.log(a == true);
    } else {
      console.log("wut");
    }
    
    // A. true
    // B. false
    // C. "wut"
    // D. other

    答案是 B。== 相等中,如果有一个操作数是布尔类型,会先把他转成数字,所以比较变成了 [0] == 1;同时规范指出如果其他类型和数字比较,会尝试把这个类型转成数字再进行宽松比较,而对象(数组也是对象)会先调用它的 toString() 方法,此时 [0] 会变成 “0”,然后将字符串 “0” 转成数字 0,而 0 == 1 的结果显然是 false。


  1. 撒旦之子“==”

    [] == [];
    
    // A. true
    // B. false
    // C. error
    // D. other

    答案是 B。ES5 规范指出:如果比较的两个对象指向的是同一个对象,就返回 true,否则就返回 false,显然,这是两个不同的数组对象。


  1. 加号“+”和减号“-”

    "5" + 3;
    "5" - 3;
    
    // A. "53", 2
    // B. 8, 2
    // C. error
    // D. other

    答案是 A。“5” + 2 = “52” 很好理解,+ 运算符中只要有一个是字符串,就会变成字符串拼接操作。你不知道的是,- 运算符要求两个操作数都是数字,如果不是,会强制转换成数字,所以结果就变成了 5 - 2 = 3。


  1. 统统算我的

    function sidEffecting(ary) {
      ary[0] = ary[2]; // ary = [10, 1, 10]
    }
    
    function bar(a, b, c) {
      c = 10;
      sidEffecting(arguments);
      return a + b + c;
    }
    
    bar(1, 1, 1);
    
    // A. 3
    // B. 12
    // C. error
    // D. other

    答案是 D。 实际上结果是 21。在 JavaScript 中,参数变量和 arguments 是双向绑定的。改变参数变量,arguments 中的值会立即改变;而改变 arguments 中的值,参数变量也会对应改变。


  1. 反转世界

    var x = [].reverse;
    x();
    
    // A. []
    // B. undefined
    // C. error
    // D. window

    答案是 D。 MDN 规范关于 reverse 的描述: reverse 方法颠倒数组中元素的位置,并返回该数组的引用。而这里调用的时候没有制定数组,所以默认的 this 就是 window,所以最后结果返回的是 window。


  1. 自动提升为全局变量

    (function () {
      var x = (y = 1);
    })();
    console.log(y);
    console.log(x);
    
    // A. 1, 1
    // B. error, error
    // C. 1, error
    // D. other

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


  1. 禁止修改函数名

    function foo() {}
    var oldName = foo.name;
    foo.name = "bar";
    [oldName, foo.name];
    
    // A. error
    // B. ["", ""]
    // C. ["foo", "foo"]
    // D. ["foo", "bar"]

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


  2. 逗号定义数组

    [, , ,].join(", ");
    
    // A. ", , , "
    // B. "undefined, undefined, undefined, undefined"
    // C. ", , "
    // D. ""

    答案是 C。 JavaScript 允许用逗号来定义数组,得到的数组是含有 3 个 undefined 值的数组。MDN 关于 join 方法的描述:所有的数组元素被转换成字符串,再用一个分隔符将这些字符串连接起来。如果元素是 undefined 或者 null, 则会转化成空字符串。


  1. 保留字 class

    var a = { class: "Animal", name: "Fido" };
    console.log(a.class);
    
    // A. "Animal"
    // B. Object
    // C. an error
    // D. other

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


  1. 神鬼莫测的函数长度

    var a = Function.length;
    var b = new Function().length;
    console.log(a === b);
    
    // A. true
    // B. false
    // C. error
    // D. other

    答案是 B。 实际上 a 的值是 1,b 的值是 0。

    Function 构造器的属性:

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

    Function 原型对象的属性:

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


  1. Date 的面具

    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]

    答案是 B。 需要注意的是只能通过调用 Date 构造函数来实例化日期对象:以常规函数调用它(即不加 new 操作符)将会返回一个字符串,而不是一个日期对象。另外,不像其他 JavaScript 类型,Date 对象没有字面量格式。所以 a 是字符串,b 和 c 是 Date 对象,并且 b 代表的是 1970 年那个初始化时间,而 c 代表的是当前时间。


  1. mix 和 min

    var min = Math.min();
    var max = Math.max();
    console.log(min < max);
    
    // A. true
    // B. false
    // C. error
    // D. other

    答案是 B。 对 Math.min,如果没有参数,结果为 Infinity。对 Math.max,如果没有参数,结果为-Infinity。


  1. 重复声明变量

    function foo(a) {
      var a;
      return a;
    }
    
    function bar(a) {
      var a = "bye";
      return a;
    }
    
    [foo("hello"), bar("hello")];
    
    // A. ["hello", "hello"]
    // B. ["hello", "bye"]
    // C. ["bye", "bye"]
    // D. other

    答案是 B。 一个变量在同一作用域中已经声明过,会自动移除 var 声明,但是赋值操作依旧保留,结合前面提到的变量提升机制。