44个 Javascript 题附带解析

你不知道的,总会在这里找到答案,自己的强大才是真的强大。

Posted by 曲小强 on October 4, 2016

原题来自: javascript-puzzlers

大家可以先去做一下感受感受. 当初我的成绩是 19/44…

第 1 题

1
["1", "2", "3"].map(parseInt);

首先, map接受两个参数, 一个回调函数 callback, 一个回调函数的this值,其中回调函数接受三个参数 currentValue, index, array;而题目中, map 只传入了回调函数–parseInt.其次, parseInt 只接受两个两个参数 string, radix(基数).

  • 在没有指定基数,或者基数为 0 的情况下,JavaScript 作如下处理:
  • 如果字符串 string 以”0x“或者”0X“开头, 则基数是 16 (16 进制).
  • 如果字符串 string 以”0”开头, 基数是 8(八进制)或者 10(十进制),那么具体是哪个基数由实现环境决- 定。ECMAScript 5 规定使用 10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出radix参数的值。
  • 如果字符串 string 以其它任何值开头,则基数是 10 (十进制)。
  parseInt('1', 0);
  parseInt('2', 1);
  parseInt('3', 2);

所以答案是 [1, NaN, NaN]

第 2 题

1
[typeof null, null instanceof Object];

typeof 返回一个表示类型的字符串。 instanceofinstanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。 因为 typeof null === 'object' 自语言之初就是这样….

typeof 的结果请看下表:

1
2
3
4
5
6
7
8
9
10
  type          // result
  undefined     // "undefined"
  null          // "object"
  Boolean       // "boolean"
  Number        // "number"
  String        // "string"
  Symbol        // "symbol"
  Host object   // Implementation-dependent
  Function      // "function"
  Object        // "object"

所以答案 [object, false]

第 3 题

1
[[3, 2, 1].reduce(Math.pow), [].reduce(Math.pow)];
arr.reduce(callback[, initialValue])

reduce接受两个参数, 一个回调, 一个初始值. 回调函数接受四个参数 previousValue, currentValue, currentIndex, array 需要注意的是 如果数组为空且未提供初始值,则将引发类型错误。 所以第二个表达式会报异常. 第一个表达式等价于 Math.pow(3, 2) => 9; Math.pow(9, 1) =>9

答案 an error

第 4 题

1
2
var val = "smtg";
console.log("Value is " + (val === "smtg") ? "Something" : "Nothing");

简而言之 + 的优先级 大于 ?

所以原题等价于 'Value is true' ? 'Somthing' : 'Nonthing' 而不是 'Value is' + (true ? 'Something' : 'Nonthing')

答案 'Something'

第 5 题

1
2
3
4
5
6
7
8
9
var name = "World!";
(function() {
  if (typeof name === "undefined") {
    var name = "Jack";
    console.log("Goodbye " + name);
  } else {
    console.log("Hello " + name);
  }
})();

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

这题目相当于

1
2
3
4
5
6
7
8
9
10
var name = "World!";
(function() {
  var name; // 现在的name是undefined
  if (typeof name === "undefined") {
    name = "Jack";
    console.log("Goodbye " + name);
  } else {
    console.log("Hello " + name);
  }
})();

答案是 Goodbye Jack

第 6 题

1
2
3
4
5
6
7
var END = Math.pow(2, 53);
var START = END - 100;
var count = 0;
for (var i = START; i <= END; i++) {
  count++;
}
console.log(count);

js 中可以表示的最大整数不是 2 的 53 次方,而是 1.7976931348623157e+308。 2 的 53 次方不是 js 能表示的最大整数而应该是能正确计算且不失精度的最大整数,可以参见 js 权威指南。 9007199254740992 +1 还是 9007199254740992 ,这就是因为精度问题,如果 9007199254740992 +11 或者 9007199254740992 +111 的话,值是会发生改变的,只是这时候计算的结果不是正确的值,就是因为精度丢失的问题。

它进入无限循环

答案是 other

第 7 题

1
2
3
4
5
var ary = [0, 1, 2];
ary[10] = 10;
ary.filter(function(x) {
  return x === undefined;
});

答案是 []

我们来看一下 Array.prototype.filter 的 polyfill:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun /*, thisArg*/) {
    "use strict";
    if (this === void 0 || this === null) {
      throw new TypeError();
    }
    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== "function") {
      throw new TypeError();
    }
    var res = [];
    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++) {
      if (i in t) {
        // 注意这里!!!
        var val = t[i];
        if (fun.call(thisArg, val, i, t)) {
          res.push(val);
        }
      }
    }

    return res;
  };
}

我们看到在迭代这个数组的时候, 首先检查了这个索引值是不是数组的一个属性, 那么我们测试一下.

0 in ary; => true
3 in ary; => false
10 in ary; => true

也就是说 从 3 - 9 都是没有初始化的’坑’!, 这些索引并不存在与数组中. 在 array 的函数调用的时候是会跳过这些’坑’的.

第 8 题

1
2
3
4
var two = 0.2;
var one = 0.1;
var eight = 0.8;
var six = (0.6)[(two - one == one, eight - six == two)];

IEEE 754 标准中的浮点数并不能精确地表达小数

那什么时候精准, 什么时候不经准呢? 笔者也不知道…

答案 [true, false]

第 9 题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function showCase(value) {
  switch (value) {
    case "A":
      console.log("Case A");
      break;
    case "B":
      console.log("Case B");
      break;
    case undefined:
      console.log("undefined");
      break;
    default:
      console.log("Do not know!");
  }
}
showCase(new String("A"));

// switch 是严格比较, String 实例和 字符串不一样.

var s_prim = "foo";
var s_obj = new String(s_prim);

console.log(typeof s_prim); // "string"
console.log(typeof s_obj); // "object"
console.log(s_prim === s_obj); // false

字符串字面量和直接调用String()方法(不使用new调用构造函数)的结果是原始字符串。JS自动回转化原始字符串到String对象。所以可以在原始字符串上使用用String对象的方法。而在上下文中,在原始字符串的方法被调用或者从其中获取属性时,JS会自动包裹原始字符串然后调用方法或者获取属性。

答案是 Do not know!

第 10 题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function showCase2(value) {
  switch (value) {
    case "A":
      console.log("Case A");
      break;
    case "B":
      console.log("Case B");
      break;
    case undefined:
      console.log("undefined");
      break;
    default:
      console.log("Do not know!");
  }
}
showCase2(String("A"));

String(x) does not create an object but does return a string, i.e. typeof String(1) === "string"

还是刚才的知识点, 只不过 String 不仅是个构造函数 直接调用返回一个字符串哦.

答案 Case A

第 11 题

1
2
3
4
5
6
7
8
9
10
11
function isOdd(num) {
  return num % 2 == 1;
}
function isEven(num) {
  return num % 2 == 0;
}
function isSane(num) {
  return isEven(num) || isOdd(num);
}
var values = [7, 4, "13", -9, Infinity];
values.map(isSane);
7 % 2 => 1
4 % 2 => 0
'13' % 2 => 1
-9 % % 2 => -1
Infinity % 2 => NaN

需要注意的是 余数的正负号随第一个操作数.

答案 [true, true, true, false, false]

第 12 题

1
2
3
parseInt(3, 8);
parseInt(3, 2);
parseInt(3, 0);

第一个值为3没什么好说的。如果出现的数字不符合后面输入的进制,则为NaN,所以第二个值为NaN。而radix为0时的情况第一题下面有介绍,这里也是一样为默认10

答案 3, NaN, 3

第 13 题

1
Array.isArray(Array.prototype);

一个鲜为人知的实事: Array.prototype => [];

答案: true

第 14 题

1
2
3
4
5
6
var a = [0];
if ([0]) {
  console.log(a == true);
} else {
  console.log("wut");
}

解析:

  • Boolean([0]) === true
    • [0] == true
    • true 转换为数字 => 1
    • [0] 转化为数字失败, 转化为字符串 ‘0’, 转化成数字 => 0
    • 0 !== 1 a本身是一个长度为1的数组,而当数组不为空时,其转换成bool值为true。

而==左右的转换,会使用如果一个操作值为布尔值,则在比较之前先将其转换为数值的规则来转换,Number([0]),也就是0,于是变成了0 == true,结果自然是false,所以最终结果为B

答案:false

第 15 题

1
[] == [];

[] 是Object, 两个 Object 不相等

当==运算符当左右都是对象时,则会比较其是否指向同一个对象。而每次调用字面量创建,都会创造新的对象,也就是会开辟新的内存区域。所以指针的值自然不一样

答案是 false

第 16 题

1
2
"5" + 3;
"5" - 3;

+ 用来表示两个数的和或者字符串拼接, -表示两数之差.

- 会尽可能的将两个操作数变成数字, 而 + 如果两边不都是数字, 那么就是字符串拼接.

答案是 '53', 2

第 17 题

1
2
3
4
5
var arr = Array(3);
arr[0] = 2;
arr.map(function(elem) {
  return "1";
});

稀疏数组. 同第 7 题.

题目中的数组其实是一个长度为3, 但是没有内容的数组, array 上的操作会跳过这些未初始化的’坑’.

所以答案是 ["1", undefined × 2]

第 18 题

1
2
3
4
5
6
7
8
const a = {};
const b = { key: "b" };
const c = { key: "c" };

a[b] = 123;
a[c] = 456;

console.log(a[b]);

对象键自动转换为字符串。我们试图将一个对象设置为对象 a 的键,其值为123

但是,当对象自动转换为字符串化时,它变成了[Object object]。所以我们在这里说的a["Object object"] = 123是。然后,我们可以尝试再次做同样的事情。 c 对象同样会发生隐式类型转换。那么,a["Object object"] = 456

然后,我们打印a[b],它实际上是a["Object object"]。我们将其设置为456,因此返回456

答案: 456

第 19 题

1
2
3
4
5
6
7
8
9
function sidEffecting(ary) {
  ary[0] = ary[2];
}
function bar(a, b, c) {
  c = 10;
  sidEffecting(arguments);
  return a + b + c;
}
bar(1, 1, 1);

这是一个大坑, 尤其是涉及到 ES6 语法的时候

在调用函数时,函数内部的arguments维护着传递到这个函数的参数列表。它看起来是一个数组,但实际上它只是一个有length属性的Object,不从Array.prototype继承。所以无法使用一些Array.prototype的方法。

arguments对象其内部属性以及函数形参创建getter和setter方法,因此改变形参的值会影响到arguments对象的值,反过来也是一样

也就是说 arguments 是一个 object, c 就是 arguments[2], 所以对于 c 的修改就是对 arguments[2] 的修改.

所以答案是 21.

然而!!!!!!

当函数参数涉及到 any rest parameters, any default parameters or any destructured parameters 的时候, 这个 arguments 就不在是一个 mapped arguments object 了…..

1
2
3
4
5
6
7
8
9
function sidEffecting(ary) {
  ary[0] = ary[2];
}
function bar(a, b, c = 3) {
  c = 10;
  sidEffecting(arguments);
  return a + b + c;
}
bar(1, 1, 1);

答案是: 12 !!!!

第 20 题

1
2
3
var a = 111111111111111110000,
  b = 1111;
a + b;

JavaScript 数字的题,与第七题考察点相似。由于 JavaScript 实际上只有一种数字形式 IEEE 754 标准的 64 位双精度浮点数,其所能表示的整数范围为-2^53~2^53(包括边界值)。这里的 111111111111111110000 已经超过了2^53 次方,所以会发生精度丢失的情况。

答案还是 111111111111111110000

第 21 题

1
2
var x = [].reverse;
x();

这题考查的是函数调用时的 this 和 Array.prototype.reverse 方法。

The reverse method transposes the elements of the calling array object in place, mutating the array, and returning a reference to the array.

也就是说 最后会返回这个调用者(this), 可是 x 执行的时候是上下文是全局. 那么最后返回的是 window. 关于 this 指向问题

答案是 window

第 22 题

1
Number.MIN_VALUE > 0;

MIN_VALUE 属性是 JavaScript 中可表示的最小的数(接近 0 ,但不是负数),它的近似值为 5 x 10-324

有兴趣的话可以看一下这个MDN

答案是 true

列举 Number 的其它常量:

  • Number.MAX_VALUE:最大的正数
  • Number.MIN_VALUE:最小的正数
  • Number.NaN:特殊值,用来表示这不是一个数
  • Number.NEGATIVE_INFINITY:负无穷大
  • Number.POSITIVE_INFINITY:正无穷大

第 23 题

1
[1 < 2 < 3, 3 < 2 < 1];

运算符的运算顺序和隐式类型转换的题,从MDN 上运算符优先级,’<’运算符顺序是从左到右,所以变成了[true < 3, false < 1]

1
2
3
4
5
6
3 > 2 && 2 > 1;
// returns true

3 > 2 > 1;
// returns false because 3 > 2 is true, and true > 1 is false
// Adding parentheses makes things clear: (3 > 2) > 1

这个题等价于

 1 < 2 => true;
 true < 3 =>  1 < 3 => true;
 3 < 2 => false;
 false < 1 => 0 < 1 => true;

所以,这里首先通过Number()转换为数字然后进行比较,true 会转换成 1,而 false 转换成 0,就变成了[1 < 3, 0 < 1]

参考

答案是 [true, true]

第 24 题

1
2
// the most classic wtf
2 == [[[2]]];

又是隐式类型转换的题,

也就是说左右两边都被转换成了字符串,而字符串都是”2”

这里首先需要对==右边的数组进行类型转换,根据以下规则(来自justjavac 的文章《「译」JavaScript 的怪癖 1:隐式类型转换》):

  1. 调用 valueOf()。如果结果是原始值(不是一个对象),则将其转换为一个数字。
  2. 否则,调用 toString() 方法。如果结果是原始值,则将其转换为一个数字。
  3. 否则,抛出一个类型错误。

所以右侧被使用 toString()方法转换为”2”

然后又通过 Number(“2”)转换为数字 2 进行比较,结果就是 true 了

答案是 true

第 25 题

1
2
3
3.toString()
3..toString()
3...toString()

JavaScript 会在调用方法时对原始值进行包装,但在于这个点是小数点、还是方法调用的点,于是乎第一个就是error了,因为 JavaScript 解释器会将其第一个点认为是小数点。

第二个则很好说通了,第一个点解释为小数点,变成了(3.0).toString(),结果就是"3"

第三个也是,第一个点为小数点,第二个是方法调用的点,但是后面接的不是一个合法的方法名,于是乎就error

如果

1
2
var a = 3;
a.toString(); // "3"

这是因为在 js 中 1.1, 1., .` 都是合法的数字. 那么在解析 3.toString 的时候这个** . **到底是属于这个数字还是函数调用呢? 只能是数字, 因为 3.合法啊!

答案是 error, '3', error

第 26 题

1
2
3
4
5
(function() {
  var x = (y = 1);
})();
console.log(y);
console.log(x);

变量提升和隐式定义全局变量的题,也是一个 JavaScript 经典的坑…

y 被赋值到全局. x 是局部变量. 所以打印 x 的时候会报 ReferenceError: x is not defined

答案是 1, error

第 27 题

1
2
3
4
var a = /123/,
  b = /123/;
a == b;
a === b;

JavaScript 中的正则表达式依旧是对象,==运算符左右两边都是对象时,会比较他们是否指向同一个对象.

答案 false, false

第 28 题

1
2
3
4
5
6
7
var a = [1, 2, 3],
  b = [1, 2, 3],
  c = [1, 2, 4];
a == b;
a === b;
a > c;
a < c;

JavaScript 中 Array 的本质也是,所以前两个的结果都是 false,

而 JavaScript 中 Array 的’>’运算符和’<’运算符的比较方式类似于字符串比较字典序

会从第一个元素开始进行比较,如果一样比较第二个,还一样就比较第三个,如此类推,所以第三个结果为 false,第四个为 true。

答案 false, false, false, true

第 29 题

1
2
3
var a = {},
  b = Object.prototype;
[a.prototype === b, Object.getPrototypeOf(a) === b];

原型链的题(总会有的),考查的__proto__prototype的区别。首先要明确对象和构造函数的关系,对象在创建的时候,其__proto__会指向其构造函数的prototype属性

实例对象是没有prototype属性的,只有函数才有,所以a.prototype其实是undefined,第一个结果为 false

Object实际上是一个构造函数(typeof Object的结果为"function"),使用字面量创建对象和new Object创建对象是一样的,所以a.__proto__也就是Object.prototype,而Object.getPrototypeOf(a)与a.__proto__是一样的,所以第二个结果为true

答案 [false, true]

第 30 题

1
2
3
4
function f() {}
var a = f.prototype,
  b = Object.getPrototypeOf(f);
a === b;

f.prototype 是使用使用 new 创建的 f 实例的原型

而 Object.getPrototypeOf 是 f 函数的原型.

a === Object.getPrototypeOf(new f()) // true
b === Function.prototype // true

答案 false

第 31 题

1
2
3
4
function foo() {}
var oldName = foo.name;
foo.name = "bar";
[oldName, foo.name];

考察了函数的 name 属性,使用函数定义方式时,会给 function 对象本身添加一个 name 属性,保存了函数的名称,很好理解oldName为"foo"。name 属性时只读的,不允许修改,所以foo.name = "bar";之后,foo.name还是"foo"

答案 ['foo', 'foo']

第 32 题

1
"1 2 3".replace(/\d/g, parseInt);

String.prototype.replace、正则表达式的全局匹配和 parseInt,

首先需要确定 replace 会传给 parseInt 哪些参数。举个栗子:

1
2
3
4
5
6
7
"1 2 3".replace(/\d/g, function() {
  console.log(arguments);
});
//输出结果:
//["1", 0, "1 2 3"]
//["2", 2, "1 2 3"]
//["3", 4, "1 2 3"]

一共三个:

  1. match:正则表达式被匹配到的子字符串
  2. offset:被匹配到的子字符串在原字符串中的位置
  3. string:原字符串

这样就很好理解了,又回到之前 parseInt 的问题了,结果就是 parseInt(“1”, 10), parseInt(“2”, 2), parseInt(“3”, 4)

答案: 1, NaN, 3

第 33 题

1
2
3
4
5
6
function f() {}
var parent = Object.getPrototypeOf(f);
f.name; // ?
parent.name; // ?
typeof eval(f.name); // ?
typeof eval(parent.name); //  ?

第一 f.name 值为"f",第三 eval(“f”)则会输出 f 函数,所以结果为"function"

第二在自己的浏览器测试的时候是 '', 第四是 'undefined'

第 34 题

1
2
var lowerCaseOnly = /^[a-z]+$/;
[lowerCaseOnly.test(null), lowerCaseOnly.test()];

正则表达式的test()会自动将参数转换为字符串,原式就变成了[lowerCaseOnly.test("null"), lowerCaseOnly.test("undefined")]

答案:[true, true]

第 35 题

1
[, , ,].join(", ");
[,,,] => [empty × 3]

JavaScript 中使用字面量创建数组时,如果最末尾有一个逗号’,’,会背省略,所以这是个长度为三的稀疏数组(这是长度为三, 并没有 0, 1, 2 三个属性哦)(都是 undefined);

答案: ", , "

第 36 题

1
2
var a = { class: "Animal", name: "Fido" };
a.class;

经典坑中的一个,class 是关键字。根据浏览器的不同,结果不同:

chrome 的结果: “Animal”

Firefox 的结果:”Animal”

Opera 的结果:”Animal”

IE 8 以上也是: “Animal”

IE 8 及以下: 报错

所以答案不重要, 重要的是自己在取属性名称的时候尽量避免保留字. 如果使用的话请加引号 a['class']

第 37 题

1
var a = new Date("epoch");

简单来说, 如果调用 Date 的构造函数传入一个字符串的话需要符合规范, 即满足 Date.parse 的条件.

另外需要注意的是 如果格式错误 构造函数返回的仍是一个 Date 的实例 Invalid Date.

答案 Invalid Date

第 38 题

1
2
3
var a = Function.length,
  b = new Function().length;
a === b;

我们知道一个function(Function 的实例)的 length 属性就是函数签名的参数个数, 所以 b.length == 0.

另外 Function.length 定义为 1……

所以不相等……

答案 false

第 39 题

1
2
3
4
var a = Date(0);
var b = new Date(0);
var c = new Date();
[a === b, b === c, a === c];
  • 如果不传参数等价于当前时间.
  • 如果是函数调用 返回一个字符串.

答案 [false, false, false]

第 40 题

1
2
3
var min = Math.min(),
  max = Math.max();
min < max;

Math.min 的参数是 0 个或者多个。如果是多个参数很容易理解,返回参数中最小的。

如果没有参数,则返回InfinityInfinity 是 javascript 中全局对象的一个属性,在浏览器环境中就是 window 对象的一个属性,表示无穷大

Math.max() 没有传递参数时返回的是 -Infinity

因此 Math.min() 要比 Math.max() 大。

答案 false

第 41 题

1
2
3
4
5
6
7
8
9
10
11
function captureOne(re, str) {
  var match = re.exec(str);
  return match && match[1];
}
var numRe = /num=(\d+)/gi,
  wordRe = /word=(\w+)/i,
  a1 = captureOne(numRe, "num=1"),
  a2 = captureOne(wordRe, "word=1"),
  a3 = captureOne(numRe, "NUM=2"),
  a4 = captureOne(wordRe, "WORD=2");
[a1 === a2, a3 === a4];

因为第一个正则有一个 g 选项 它会‘记忆’他所匹配的内容, 等匹配后他会从上次匹配的索引继续, 而第二个正则不会

所以 a1 = ‘1’; a2 = ‘1’; a3 = null; a4 = ‘2’

答案: [true, false]

第 42 题

1
2
3
4
5
6
7
var a = new Date("2014-03-19"),
  b = new Date(2014, 03, 19);
[a.getDay() === b.getDay(), a.getMonth() === b.getMonth()];
a.getDay(); // 3
b.getDay(); // 6
a.getMonth(); // 2
b.getMonth(); // 3

new Date("2014-03-19") 如果传入的是一个符合规则的日期字符串(“2014-03-19”),得到的就是对应的标准时间对象 // Wed Mar 19 2014 08:00:00 GMT+0800 (中国标准时间)

new Date(2014, 03, 19) 如果传入的是三个,第三个识别为日期// Wed Mar 19 2014 08:00:00 GMT+0800 (中国标准时间)

答案:[false, false]

第 43 题

1
2
3
4
5
if ("http://giftwrapped.com/picture.jpg".match(".gif")) {
  ("a gif file");
} else {
  ("not a gif file");
}

tring.prototype.match 接受一个正则, 如果不是, 按照 new RegExp(obj) 转化. 所以 . 并不会转义 那么 /gif 就匹配了 /.gif/

答案: 'a gif file'

第 44 题

1
2
3
4
5
6
7
8
9
function foo(a) {
  var a;
  return a;
}
function bar(a) {
  var a = "bye";
  return a;
}
[foo("hello"), bar("hello")];

在两个函数里, a作为参数其实已经声明了, 所以 var a; var a = 'bye' 其实就是 a; a ='bye'

答案: ["hello", "bye"]

后言

我只是一个分享者,代码的搬运工、一个追求者、一个学习者,有不同的意见或新的想法,提出来,我们一起研究。

鲁迅曾经说过知识遍地,拾到了就是你的

类似文章: 悦老板



回到顶部 返回
顶部