仅以此文记念这些年失败的面试和逝去的头发。。html
console.log(2 + '2')
答案:
'22'
解析:
这是比较常规的面试题了,主要考察的是 JavaScript 中的隐式类型转换。在 JS 中 +
主要有两个做用:数字相加和字符串拼接,当 +
两边不都为数字时会把它们都转为字符串再拼接,因此第一个 2
会先被转成 '2'
再与第二个 '2'
拼接。node
console.log(2 - '2')
答案:
0
解析:
和 +
不一样,-
没有操做字符串而只有 “减法” 的功能,当 -
两边有非数字时会先把其转换成数字再相减。因此,本题中的 '2'
先被转成数字 2
,最终 2 - 2
等于 0
。当操做数无法转换成数字时则会致使结果为 NaN
,好比 'foo' - 2 = NaN
。git
*
、/
、%
的行为也和-
相似。
console.log(true + 1)
答案:
2
解析:
有了第1题的经验,咱们很容易就认为 true
和 1
都会被转成字符串,但实际上 JS 中 true == 1
, false == 0
,因此 true
会被转成 1
再执行加法。github
注意:true
只等于1
,false
只等于0
,等于其余数字是不成立的,如true == 2
为false
。
答案:
false
解析:
NaN 表示一个不为数字的值(Not a number)。咱们只须要记住:NaN
和全部值都不等,包括它本身,不论是用 ==
仍是 ===
判断!判断一个值是否为 NaN
只能用 isNaN()
或者 Number.isNaN()
。面试
console.log(5 < 6 < 7)
2. console.log(7 > 6 > 5)
答案:(1)true
;(2)false
解析:
根据第三题的经验 true == 1
,false == 0
,5 < 6 === true
而 1 < 7
,因此 1 为 true
;7 > 6 === true
而 1 > 5 === false
,因此 2 为 false
。算法
0.1 + 0.2 = ?
答案:
0.30000000000000004
解析:
问题的关键不在于答案里面有几个 0,而在于它不等于 0.3!Javascript 中的数字使用的是 64 位双精度浮点型(可参考 ECMAScript 规范)。如同十进制不能精确表示 1/3
对应的小数,二进制也没有办法精确表示 0.1
、0.2
这种小数,好比 0.1
转成二进制为:0.0001100110011001100110011001100110011001100110011001101
,是无限循环的小数,而由于计算机不可能无限分配内存去存储这个数,通常只能精确到多少位,因此形成精度丢失在所不免,最终致使计算结果有所误差。express
[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]];
(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
这种意外全局变量的建立。
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
使用的机会愈来愈少,let
、const
成为主流,本题若是想打印 0 1 2 3 4
,只要把 var
换成 let
就好了。
function foo() { return x; function x() {} } console.log(typeof foo());
答案:
'function'
解析:
本题考察的是对函数提高的理解。通常定义一个函数有两种方式:
function fn(){ ... }
var fn = function(){ ... }
其中函数声明会存在函数提高的现象,而函数表达式则没有(函数表达式会存在变量提高,但初始化并不会被提高,因此不具备函数提高的效果),即JS在编译阶段会把对函数的定义提高到做用域顶部(实际上并不会修改代码结构,而是在内存中进行处理),因此本题的代码等价于:
function foo() { function x() { console.log(`hi`); } return x; }
因此,结果打印 function
。函数提高的主要做用是能够在函数定义以前就进行调用。
var x = 1; function x(){} console.log(typeof x);
答案:
'number'
解析:
本题仍是考察变量提高和函数提高,以及它们的优先级。函数提高的优先级要高于变量提高,因此函数被提高到做用域最顶部,接下来才是变量定义,所以本题等价于:
function x(){} var x; x = 1; console.log(typeof x);
因此,x
最终是数字。
const fn = () => arguments; console.log(fn("hi"));
答案:报错
Uncaught ReferenceError: arguments is not defined
解析:
本题主要考察箭头函数的特色。箭头函数没有本身的 this
和 arguments
,而是引用的外层做用域中的,而全局没有定义 arguments
变量,因此报错。
在箭头函数中若是要访问参数集,建议使用 Rest parameters:
(...args) => { }
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
。
setTimeout(() => { console.log("a"); }, 1); setTimeout(() => { console.log("b"); }, 0);
答案:有多是'a' 'b'
,也有多是'b' 'a'
,取决于 js 运行环境。
0ms
和 1ms
是等价的,由于 0
会被转成 1
(可参考Node源码),因此在 node 中运行结果是 'a' 'b'
'a' 'b'
'b' 'a'
该题属于“回”字有多少种写法那一类的,并没有多大的实际价值 😢。
event.target
和 event.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); });
function(){ console.log('hi'); }()
答案:报错:
Uncaught SyntaxError: Function statements require a function name
解析:
本题主要考察对 IIFE 语法的理解。本题代码等价于:
function(){ console.log('hi'); } ()
因此报语法错误,而正确的 IIFE 语法应该是 (function(){...})()
。
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
。
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 编码的单元值序列进行升序排列。好比 2
和 12
的 UTF-16 编码分别为 50
和 49, 50
,而 49 < 50
,因此 12
排在 2
以前。
若是想按照实际的数字大小升排列须要传入一个比较函数:
// 升序 arr.sort((a, b) => a - b); // 降序 arr.sort((a, b) => b - a);
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 中能表示的最大数。
答案:
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
结果为 true
。Number.isSafeInteger()
能够用来判断一个数是否 “安全”。
当咱们须要使用更大的数时建议使用 BigInt。
答案:null
解析:
通常认为原型链顶层是 Object.prototype
,但其实 Object.prototype
仍是有 __proto__
的内部属性的,而 Object.prototype.__proto__
等于 null
。因此答案为 null
更为准确。
参考 Annotated ECMAScript 5.1 - Properties of the Object Prototype Object
好比:
const obj = {}; // todo: 让 obj.p = 1 无效 obj.p = 1;
答案:
至少有四种方法:
Object.freeze(obj)
Object.seal(obj)
Object.preventExtensions(obj)
Object.defineProperty(obj, 'p', { writable: false })
解析:
Object.freeze()
最为严格,它会彻底禁止对象作任何修改,包括:增长新属性、修改已有属性、修改其原型Object.seal()
的规则宽松一点:容许修改 writable
的属性,但不容许新增和删除属性,且已有属性都会被标记为不可配置的(non-configurable)Object.preventExtensions()
更加宽松,能够阻止对象新增属性和修改其 __proto__
(不能给 __proto__
从新赋值)Object.defineProperty()
将属性 p
定义为不可写的,所以没法再给 p
设置新的值(writable
默认为 false
,能够省略)基础算法题,至少有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; }
好了,先想到这些。若是本文对你有帮助,给个赞吧!