前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >那些年遇到的刁钻JavaScript面试题(可防踩坑)

那些年遇到的刁钻JavaScript面试题(可防踩坑)

作者头像
MudOnTire
发布2020-07-26 20:07:12
5780
发布2020-07-26 20:07:12
举报
文章被收录于专栏:MudOnTireMudOnTireMudOnTire

仅以此文纪念这些年失败的面试和逝去的头发。。

第 1 题:console.log(2 + '2')

答案:'22'

解析: 这是比较常规的面试题了,主要考察的是 JavaScript 中的隐式类型转换。在 JS 中 + 主要有两个作用:数字相加和字符串拼接,当 + 两边不都为数字时会把它们都转为字符串再拼接,所以第一个 2 会先被转成 '2' 再与第二个 '2' 拼接。

第 2 题:console.log(2 - '2')

答案:0

解析:+ 不同,- 没有操作字符串而只有 “减法” 的功能,当 - 两边有非数字时会先把其转换成数字再相减。所以,本题中的 '2' 先被转成数字 2,最终 2 - 2 等于 0。当操作数没法转换成数字时则会导致结果为 NaN,比如 'foo' - 2 = NaN

*/% 的行为也和 - 类似。

第 3 题:console.log(true + 1)

答案:2

解析: 有了第1题的经验,我们很容易就认为 true1 都会被转成字符串,但实际上 JS 中 true == 1, false == 0 ,所以 true 会被转成 1 再执行加法。

注意:true 只等于 1false 只等于 0,等于其他数字是不成立的,如 true == 2false

第 4 题:console.log(NaN === NaN)

答案:false

解析: NaN 表示一个不为数字的值(Not a number)。我们只需要记住:NaN和所有值都不等,包括它自己,不管是用 == 还是 === 判断!判断一个值是否为 NaN 只能用 isNaN() 或者 Number.isNaN()

第 5 题:1. console.log(5 < 6 < 7) 2. console.log(7 > 6 > 5)

答案:(1) true;(2) false

解析: 根据第三题的经验 true == 1false == 05 < 6 === true1 < 7,所以 1 为 true7 > 6 === true1 > 5 === false,所以 2 为 false

第 6 题:0.1 + 0.2 = ?

答案:0.30000000000000004

解析: 问题的关键不在于答案里面有几个 0,而在于它不等于 0.3!Javascript 中的数字使用的是 64 位双精度浮点型(可参考 ECMAScript 规范)。如同十进制不能精确表示 1/3 对应的小数,二进制也没有办法精确表示 0.10.2 这种小数,比如 0.1 转成二进制为:0.0001100110011001100110011001100110011001100110011001101,是无限循环的小数,而因为计算机不可能无限分配内存去存储这个数,一般只能精确到多少位,所以造成精度丢失在所难免,最终导致计算结果有所偏差。

第 7 题:[1, 2, 3] + [4, 5, 6] = ?

答案:1,2,34,5,6

解析: 本题主要考察隐式类型转换和数组转字符串,我们已经知道 + 两边如果不都为数字则会把它们转成字符串再拼接,而 [1, 2, 3].toString() === '1,2,3',因为最终结果为 '1,2,3' + '4,5,6' === '1,2,34,5,6'

如果我们想要进行数组拼接可以:

[1, 2, 3].concat([4, 5, 6]);

// 或者使用spread opertator
[...[1, 2, 3], ...[4, 5, 6]];

第 8 题:求打印结果

(function () {
  var a = b = 100;
})();

console.log(a);
console.log(b);

答案:报错(Uncaught ReferenceError: a is not defined

解析: 由于赋值表达式是从右往左执行的,相当于 var a = (b = 100);,所以先执行 b = 100,由于函数体中并没有局部变量 b,所以会定义一个全局变量 b 并赋值 100;接着执行 a = b,会把b的值赋值给局部变量 a。执行 console.log(a) 时会直接报错,因为全局作用域中并没有定义 a,由于报错导致程序中断所以 console.log(b) 没有执行。如果把 console.log(b) 放在前面就是先打印 100 再报错了。

使用严格模式('use strict')可以避免b这种意外全局变量的创建。

第 9 题:求打印结果

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, 0);
}

答案:5 5 5 5 5

解析: 这是个经典问题,考察对作用域和 event loop 的理解。用 var 定义的变量的作用域是函数作用域(functional scope),所以上面代码相当于:

var i;

for (i = 0; i < 5; i++) {
  ...
}

最终 console.log(i) 里面的 i 引用的是外面的那个,而由于event loop机制,setTimeout 的回调函数会被push到task queue里面等call stack里面的for循环结束了再执行,此时 i 已经变成了5,因此最终打印结果变成5个5。随着ES6的普及,var 使用的机会越来越少,letconst 成为主流,本题如果想打印 0 1 2 3 4,只要把 var 换成 let 就行了。

第 10 题:求打印结果

function foo() {
  return x;

  function x() {}
}

console.log(typeof foo());

答案:'function'

解析: 本题考察的是对函数提升的理解。一般定义一个函数有两种方式:

  1. 函数声明(function declaration):function fn(){ ... }
  2. 函数表达式(function expression):var fn = function(){ ... }

其中函数声明会存在函数提升的现象,而函数表达式则没有(函数表达式会存在变量提升,但初始化并不会被提升,所以不具有函数提升的效果),即JS在编译阶段会把对函数的定义提升到作用域顶部(实际上并不会修改代码结构,而是在内存中进行处理),所以本题的代码等价于:

function foo() {
  function x() {
    console.log(`hi`);
  }
  
  return x;
}

所以,结果打印 function。函数提升的主要作用是可以在函数定义之前就进行调用。

第 11 题:求打印结果

var x = 1;
function x(){}

console.log(typeof x);

答案:'number'

解析: 本题还是考察变量提升和函数提升,以及它们的优先级。函数提升的优先级要高于变量提升,所以函数被提升到作用域最顶部,接下来才是变量定义,因此本题等价于:

function x(){}
var x;
x = 1;

console.log(typeof x);

所以,x 最终是数字。

第 12 题:求打印结果

const fn = () => arguments;

console.log(fn("hi"));

答案:报错 Uncaught ReferenceError: arguments is not defined

解析: 本题主要考察箭头函数的特点。箭头函数没有自己的 thisarguments,而是引用的外层作用域中的,而全局没有定义 arguments 变量,所以报错。

在箭头函数中如果要访问参数集,建议使用 Rest parameters:(...args) => { }

第 13 题:求打印结果

const fn = function () {
  return
  {
    message: "hello";
  }
};

console.log(fn());

答案:undefined

解析:

在 JavaScript 中,如果 return 关键词和返回值之间存在换行符(Line Terminator),则 return 后面会自动插入 ';',参考 ASI (Automatic semicolon insertion)。所以本题代码等同于:

const fn = function () {
  return;
  {
    message: "hello";
  }
};

console.log(fn());

结果因此为 undefined

第 14 题:求打印结果

setTimeout(() => {
  console.log("a");
}, 1);

setTimeout(() => {
  console.log("b");
}, 0);

答案:有可能是 'a' 'b',也有可能是 'b' 'a',取决于 js 运行环境。

  • 在 Node.js 中,0ms1ms是等价的,因为 0 会被转成 1(可参考Node源码),所以在 node 中运行结果是 'a' 'b'
  • Chrome 和 node 类似,结果也是 'a' 'b'
  • Firefox 中会打印 'b' 'a'

该题属于“回”字有多少种写法那一类的,并无多大的实际价值 ?。

第 15 题:event.targetevent.currentTarget 的区别

答案:event.target 是真正触发 event 的元素,而 event.currentTarget 是绑定 event handler 的元素。

例如:

<div id="container">
  <button>click me</button>
</div>
const container = document.getElementById("container");
container.addEventListener("click", function (e) {
  console.log("target =", e.target);
  console.log("currentTarget =", e.currentTarget);
});
target
target

第 16 题:求打印结果

function(){
  console.log('hi');
}()

答案:报错:Uncaught SyntaxError: Function statements require a function name

解析: 本题主要考察对 IIFE 语法的理解。本题代码等价于:

function(){
  console.log('hi');
}

()

所以报语法错误,而正确的 IIFE 语法应该是 (function(){...})()

第 17 题:求打印结果

const arr = [1, 2, 3];
arr[-1] = -1;
console.log(arr[arr.indexOf(100)]);

答案:-1

解析: 本题主要考察对 JavaScript 对象的理解和数组的 indexOf() 方法。首先,数组本质还是一个 JavaScript 对象,那就可以设置 属性,就算数组的索引没有 -1,但 -1 仍可作为对象的 key 存在,所以 x[-1] = -1 没有问题。接着,indexOf() 方法所要查找的值如果在数组中不存在则返回 -1,所以最终相当于求 console.log(arr[-1]),得到最终答案为 -1

第 18 题:求数组排序后的结果

const arr = [5, 22, 1, 14, 2, 56, 132, 88, 12];
console.log(arr.sort());

答案:[1, 12, 132, 14, 2, 22, 5, 56, 88]

解析:

本题主要考察对数组 sort() 方法的理解。 sort() 默认是把元素转成字符串,再比较 UTF-16 编码的单元值序列进行升序排列。比如 212 的 UTF-16 编码分别为 5049, 50,而 49 < 50,所以 12 排在 2 之前。

如果想按照实际的数字大小升排列需要传入一个比较函数:

// 升序
arr.sort((a, b) => a - b);
// 降序
arr.sort((a, b) => b - a);

第 19 题:求 x 的值使下列等式同时为 true

x * x === 0;
x + 1 === 1;
x - 1 === -1;
x / x === 1;

答案:Number.MIN_VALUE

解析:

Number.MIN_VALUE 是 JavaScript 能表示的最小的正数,也是最接近 0 的值,所以很多行为和 0 类似,例如前 3 条等式,但是它毕竟不是 0,所以可以作为除数,因此等式 4 也成立。与之相对的还有 Number.MAX_VALUE,是 JavaScript 中能表示的最大数。

第 20 题:console.log(9999999999999999)

答案:10000000000000000

解析:

看到答案有点懵,直觉告诉我们这肯定又和 JavaScript 中的某些最大数的限制有关。没错,JS 中有个 Number.MAX_SAFE_INTEGER,它的值为 2^53 - 1,即 9007199254740991。这个数的存在还是因为 JS 使用的 64 位双精度浮点型数,它能表示的区间仅仅为 -(2^53 - 1) ~ 2^53 - 1,超过这个区间的数就不“安全”了,不安全表现为无法准确的表示和比较这些数,比如 Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 结果为 trueNumber.isSafeInteger() 可以用来判断一个数是否 “安全”。

当我们需要使用更大的数时建议使用 BigInt

第 21 题:原型链的顶层是什么?

答案:null

解析:

一般认为原型链顶层是 Object.prototype,但其实 Object.prototype 还是有 __proto__ 的内部属性的,而 Object.prototype.__proto__ 等于 null。所以答案为 null 更为准确。

参考 Annotated ECMAScript 5.1 - Properties of the Object Prototype Object

第 22 题:如何阻止给一个对象设置属性

比如:

const obj = {};

// todo: 让 obj.p = 1 无效

obj.p = 1;

答案: 至少有四种方法:

  1. Object.freeze(obj)
  2. Object.seal(obj)
  3. Object.preventExtensions(obj)
  4. Object.defineProperty(obj, 'p', { writable: false })

解析:

  1. Object.freeze() 最为严格,它会完全禁止对象做任何修改,包括:增加新属性、修改已有属性、修改其原型
  2. Object.seal() 的规则宽松一点:允许修改 writable 的属性,但不允许新增和删除属性,且已有属性都会被标记为不可配置的(non-configurable)
  3. Object.preventExtensions() 更加宽松,可以阻止对象新增属性和修改其 __proto__(不能给 __proto__ 重新赋值)
  4. Object.defineProperty() 将属性 p 定义为不可写的,因此无法再给 p 设置新的值(writable 默认为 false,可以省略)

第 23 题:判断一个字符串是否为回文(palindrome,翻转过后和原来相等),忽略大小写

基础算法题,至少有2种方法:

解法1:将数字转成字符串,再转成数组,翻转后再比较:

function palindrome(str) {
  str = str.toLowerCase();
  return str.split("").reverse().join("") === str;
}

解法2:for循环,头尾比较

function palindrome(str) {
  for (var i = 0; i < str.length / 2; i++) {
    const left = str[i];
    const right = str[str.length - 1 - i];
    if (left.toLowerCase() !== right.toLowerCase()) return false;
  }

  return true;
}

这道题的升级版,是判断一个数字是否为回文,且不能将数字转成字符串。思路是通过取余的方法获取到每一位的数字,再构造一个反过来的数和原数进行比较:

function palindrome(num) {
  let copy = num;
  let currentDigit = 0;
  let reversedNum = 0;
  do {
    currentDigit = copy % 10;
    reversedNum = reversedNum * 10 + currentDigit;
    copy = parseInt(copy / 10);
  } while (copy !== 0);

  return num === reversedNum;
}

好了,先想到这些。如果本文对你有帮助,给个赞吧!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第 1 题:console.log(2 + '2')
  • 第 2 题:console.log(2 - '2')
  • 第 3 题:console.log(true + 1)
  • 第 4 题:console.log(NaN === NaN)
  • 第 5 题:1. console.log(5 < 6 < 7) 2. console.log(7 > 6 > 5)
  • 第 6 题:0.1 + 0.2 = ?
  • 第 7 题:[1, 2, 3] + [4, 5, 6] = ?
  • 第 8 题:求打印结果
  • 第 9 题:求打印结果
  • 第 10 题:求打印结果
  • 第 11 题:求打印结果
  • 第 12 题:求打印结果
  • 第 13 题:求打印结果
  • 第 14 题:求打印结果
  • 第 15 题:event.target 和 event.currentTarget 的区别
  • 第 16 题:求打印结果
  • 第 17 题:求打印结果
  • 第 18 题:求数组排序后的结果
  • 第 19 题:求 x 的值使下列等式同时为 true
  • 第 20 题:console.log(9999999999999999)
  • 第 21 题:原型链的顶层是什么?
  • 第 22 题:如何阻止给一个对象设置属性
  • 第 23 题:判断一个字符串是否为回文(palindrome,翻转过后和原来相等),忽略大小写
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档