共计 6411 个字符,预计需要花费 17 分钟才能阅读完成。
仅以此文留念这些年失败的面试和逝去的头发。。
第 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 题的教训,咱们很容易就认为 true
和 1
都会被转成字符串,但实际上 JS 中 true == 1
, false == 0
,所以 true
会被转成 1
再执行加法。
留神:
true
只等于1
,false
只等于0
,等于其余数字是不成立的,如true == 2
为false
。
第 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 == 1
,false == 0
,5 < 6 === true
而 1 < 7
,所以 1 为 true
;7 > 6 === true
而 1 > 5 === false
,所以 2 为 false
。
第 6 题:0.1 + 0.2 = ?
答案:
0.30000000000000004
解析:
问题的要害不在于答案外面有几个 0,而在于它不等于 0.3!Javascript 中的数字应用的是 64 位双精度浮点型(可参考 ECMAScript 标准)。如同十进制不能准确示意 1/3
对应的小数,二进制也没有方法准确示意 0.1
、0.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
应用的机会越来越少,let
、const
成为支流,本题如果想打印 0 1 2 3 4
,只有把 var
换成 let
就行了。
第 10 题:求打印后果
function foo() {
return x;
function x() {}
}
console.log(typeof foo());
答案:
'function'
解析:
本题考查的是对函数晋升的了解。个别定义一个函数有两种形式:
- 函数申明(function declaration):
function fn(){ ...}
- 函数表达式(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
解析:
本题次要考查箭头函数的特点。箭头函数没有本人的 this
和 arguments
,而是援用的外层作用域中的,而全局没有定义 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 中,
0ms
和1ms
是等价的,因为0
会被转成1
(可参考 Node 源码),所以在 node 中运行后果是'a' 'b'
- Chrome 和 node 相似,后果也是
'a' 'b'
- Firefox 中会打印
'b' 'a'
该题属于“回”字有多少种写法那一类的,并无多大的理论价值 ????。
第 15 题: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);
});
第 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 编码的单元值序列进行升序排列。比方 2
和 12
的 UTF-16 编码别离为 50
和 49, 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
后果为 true
。Number.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;
答案:
至多有四种办法:
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
,能够省略)
第 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;
}
好了,先想到这些。如果本文对你有帮忙,给个赞吧!