以下是我根据面试经历整理的 60 道 JavaScript 读代码类面试题,涵盖变量作用域、闭包、原型链、异步机制、类型转换等核心知识点。每道题附有解析,助你在面试中脱颖而出。
🧠 JavaScript 读代码面试题精选(共 30 题)
1. 变量提升与作用域
题目:
function test() {
console.log(a);
var a = 10;
}
test();
输出:
undefined
解析:
变量 a
在函数作用域内声明,虽然赋值在 console.log
之后,但由于变量提升,声明被提升到函数顶部,初始值为 undefined
。
2. 闭包与循环
题目:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
输出:
3
3
3
解析:
var
声明的变量在函数作用域内共享,循环结束后 i
的值为 3,所有的箭头函数共享这个值。
3. 箭头函数与 this
题目:
const obj = {
name: 'Alice',
greet: () => {
console.log(this.name);
}
};
obj.greet();
输出:
undefined
解析:
箭头函数不绑定自己的 this
,其 this
值取决于外部作用域。在此例中,this
指向全局对象,this.name
为 undefined
。
4. 原型链继承
题目:
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child() {
Parent.call(this);
this.name = 'Child';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
const child = new Child();
child.sayName();
输出:
Child
解析:
Child
通过 Parent.call(this)
继承了 Parent
的属性,并通过原型链继承了 Parent
的方法。child.sayName()
调用的是继承自 Parent.prototype
的方法,this.name
为 'Child'
。
5. 异步执行顺序
题目:
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
输出:
Start
End
Promise
Timeout
解析:
同步代码先执行,输出 'Start'
和 'End'
。Promise
的回调属于微任务,setTimeout
的回调属于宏任务。事件循环机制决定微任务优先于宏任务,因此 'Promise'
在 'End'
之后,'Timeout'
最后执行。
6. 类型转换
题目:
console.log([] + []);
console.log([] + {});
console.log({} + []);
console.log({} + {});
输出:
""
"[object Object]"
"[object Object]"
"[object Object][object Object]"
解析:
在加法操作中,操作数为对象时,会先调用其 toString
方法转换为字符串。空数组的 toString
返回空字符串,空对象的 toString
返回 "[object Object]"
。
7. typeof
运算符
题目:
console.log(typeof null);
console.log(typeof undefined);
console.log(typeof NaN);
输出:
object
undefined
number
解析:
typeof null
返回 'object'
是 JavaScript 的历史遗留问题。undefined
类型为 'undefined'
,NaN
是一种数字类型,typeof NaN
返回 'number'
。
8. ==
与 ===
的区别
题目:
console.log(0 == '0');
console.log(0 === '0');
输出:
true
false
解析:
==
会进行类型转换,字符串 '0'
转为数字 0
,所以 0 == '0'
为 true
。===
不进行类型转换,类型不同,结果为 false
。
9. NaN
的比较
题目:
console.log(NaN == NaN);
console.log(NaN === NaN);
输出:
false
false
解析:
NaN
与任何值都不相等,包括它自身。要判断一个值是否为 NaN
,应使用 isNaN()
或 Number.isNaN()
。
10. parseInt
的陷阱
题目:
console.log(parseInt('08'));
console.log(parseInt('08', 10));
输出:
0
8
解析:
在某些旧版本的 JavaScript 中,parseInt
解析以 '0'
开头的字符串时,会默认按八进制解析,'08'
中的 '8'
不是八进制数字,解析结果为 0
。指定基数为 10
可避免此问题。
11. 数组的 length
属性
题目:
const arr = [1, 2, 3];
arr.length = 5;
console.log(arr);
输出:
[1, 2, 3, <2 empty items>]
解析:
将数组的 length
属性设置为大于当前长度的值,会在数组末尾添加空项。
12. arguments
对象
题目:
function test(a) {
arguments[0] = 5;
return a;
}
console.log(test(10));
输出:
5
解析:
在非严格模式下,函数的参数与 arguments
对象的对应元素共享内存,修改 arguments[0]
会影响参数 a
的值。
13. setTimeout
的作用域
题目:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
输出:
0
1
2
解析:
let
声明的变量具有块级作用域,每次循环都会创建一个新的 i
,因此每个 setTimeout
回调中的 i
值不同。
14. this
的绑定
题目:
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name);
}
};
const greet = obj.greet;
greet();
输出:
undefined
解析:
将对象的方法赋值给变量后,this
不再指向原对象,而是指向全局对象。在浏览器中,全局对象的 name
属性通常为 undefined
。
15. call
和 apply
题目:
function greet() {
console.log(this.name);
}
const obj = { name: 'Alice' };
greet.call(obj);
greet.apply(obj);
输出:
Alice
Alice
解析:
call
和 apply
方法都可以改变函数的 this
指向,call
接受参数列表,apply
接受参数数组。
16. bind
方法
题目:
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name);
}
};
const greet = obj.greet.bind(obj);
greet();
输出:
Alice
解析:
bind
方法返回一个新的函数,this
被永久绑定到指定对象。
17. Object.is
与 ===
题目:
console.log(Object.is(NaN, NaN));
console.log(NaN === NaN);
输出:
true
false
解析:
Object.is
能正确判断 NaN
与 NaN
相等,而 ===
判断 NaN === NaN
为 false
。
18. 浮点数精度问题
题目:
console.log(0.1 + 0.2 === 0.3);
输出:
false
解析:
由于浮点数精度问题,0.1 + 0.2
的结果为 0.30000000000000004
,不等于 0.3
。
19. instanceof
操作符
题目:
function Foo() {}
const foo = new Foo();
console.log(foo instanceof Foo);
输出:
true
解析:
instanceof
检查对象的原型链中是否存在构造函数的 prototype
属性。这里 foo
的原型链包含 Foo.prototype
,所以返回 true
。
20. 函数声明与表达式的区别
题目:
console.log(foo);
var foo = function() {
return 'Hello';
};
输出:
undefined
解析:
变量 foo
声明被提升,但赋值不会提升。因此 console.log(foo)
输出 undefined
,而不是函数体。
21. 隐式类型转换
题目:
console.log([] == false);
console.log({} == false);
输出:
true
false
解析:
[]
转换为''
,'' == false
,再转换为0 == 0
,结果为true
。{}
转换为"[object Object]"
,"[object Object]" == false
结果为false
。
22. 解构赋值默认值
题目:
const [a = 10, b = a * 2] = [1];
console.log(a, b);
输出:
复制编辑
1 2
解析:
a
解构为 1
,b
默认值依赖 a
,所以 b = 1 * 2 = 2
。
23. 对象解构与重命名
题目:
const { x: y = 3 } = { x: undefined };
console.log(y);
输出:
3
解析:
对象中 x
是 undefined
,因此使用默认值 3
,同时变量被重命名为 y
。
24. async/await
的执行顺序
题目:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
async1();
console.log('script end');
输出:
script start
async1 start
async2
script end
async1 end
解析:
async/await
内部会在 await
后面把后续代码放到微任务队列,所以 'async1 end'
是在 'script end'
后执行的。
25. 事件循环题目
题目:
setTimeout(() => console.log(1));
Promise.resolve().then(() => console.log(2));
console.log(3);
输出:
3
2
1
解析:
同步任务优先,打印 3
。Promise.then
是微任务,setTimeout
是宏任务。微任务比宏任务先执行,所以输出顺序是 3 2 1
。
26. 构造函数返回对象
题目:
function A() {
this.name = 'A';
return { name: 'B' };
}
const a = new A();
console.log(a.name);
输出:
B
解析:
如果构造函数返回一个对象,那么 new
出来的实例就是这个对象,而不是 this
指向的实例。
27. 数组和对象的 toString
题目:
console.log([1,2,3].toString());
console.log({a:1}.toString());
输出:
"1,2,3"
"[object Object]"
解析:
- 数组的
toString
返回元素字符串以逗号连接。 - 对象的
toString
返回"[object Object]"
,除非自定义。
28. 字符串对象与原始字符串比较
题目:
console.log('abc' === new String('abc'));
输出:
false
解析:
'abc'
是原始值,new String('abc')
是对象,类型不同,所以严格相等比较返回 false
。
29. map
和 forEach
的区别
题目:
const arr = [1, 2, 3];
const res = arr.forEach(x => x * 2);
console.log(res);
输出:
undefined
解析:
forEach
不返回新数组,只是遍历元素。map
才返回新的数组。
30. Symbol
类型特性
题目:
const a = Symbol('desc');
const b = Symbol('desc');
console.log(a === b);
输出:
false
解析:
每个 Symbol
都是独一无二的,即使描述文字相同,两个 Symbol
也不相等。
🚀 JavaScript 面试进阶题精选(进阶 30 题)
31. Promise.all 中某个 Promise 失败
Promise.all([
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3)
])
.then(console.log)
.catch(console.error);
输出:
error
解析:
Promise.all
中任一 Promise 失败,整个结果直接进入 catch
,不会等其他成功结果。
32. for...in 与对象原型链
Object.prototype.extra = 'extra';
const obj = { a: 1 };
for (let key in obj) {
console.log(key);
}
输出:
a
extra
解析:
for...in
会枚举对象自身的可枚举属性以及原型链上的可枚举属性。
33. 函数默认参数的惰性求值
let a = 1;
function demo(x = a) {
console.log(x);
}
a = 2;
demo();
输出:
2
解析:
函数默认参数在调用时动态求值,因此是惰性求值。
34. 数组稀疏元素
const arr = [1,,3];
console.log(arr[1]);
console.log(arr.length);
输出:
undefined
3
解析:
数组中缺省位置不会赋值,元素为 undefined
,但 length
会计入该位置。
35. JSON.stringify 的行为
const obj = {
a: 1,
b: undefined,
c: function() {},
d: Symbol('d')
};
console.log(JSON.stringify(obj));
输出:
{"a":1}
解析:
undefined
、函数、Symbol
类型会被 JSON.stringify
忽略。
36. parseInt 的陷阱
console.log(['1', '2', '3'].map(parseInt));
输出:
[1, NaN, NaN]
解析:
map
会将元素和索引传给 parseInt(value, index)
,当 index = 1
时,相当于 parseInt('2', 1)
,返回 NaN
。
37. 数组去重技巧
console.log([...new Set([1,2,2,3,3])]);
输出:
[1,2,3]
解析:
Set
是集合结构,自动去重,配合展开运算符转回数组即可。
38. typeof null
console.log(typeof null);
输出:
object
解析:
历史遗留问题,typeof null
返回 object
,是一个 bug,被保留下来了。
39. Promise 的链式调用
Promise.resolve(1)
.then(x => x + 1)
.then(x => Promise.resolve(x + 1))
.then(console.log);
输出:
3
解析:
每个 then
返回值会自动被包装成 Promise,支持链式传递。
40. Object.freeze 与嵌套对象
const obj = Object.freeze({ a: 1, b: { c: 2 } });
obj.b.c = 3;
console.log(obj.b.c);
输出:
3
解析:
Object.freeze
是浅冻结,嵌套对象仍然可变。
以下是我根据面试经历整理的 30 道 JavaScript 读代码类面试题,涵盖变量作用域、闭包、原型链、异步机制、类型转换等核心知识点。每道题附有解析,助你在面试中脱颖而出。
41. NaN 与自身不相等
console.log(NaN === NaN);
输出:
false
解析:
NaN 是唯一一个不等于自身的值,用于表示“非数字”的结果。
42. Symbol 唯一性
console.log(Symbol('foo') === Symbol('foo'));
输出:
false
解析:
每次调用 Symbol()
都会生成唯一的 Symbol,即使描述相同也不相等。
43. instanceof 检测机制
console.log([] instanceof Array);
console.log([] instanceof Object);
输出:
true
true
解析:
[]
是 Array
的实例,Array
是 Object
的子类,因此两个都为 true
。
44. setTimeout 嵌套顺序
setTimeout(() => {
console.log('first');
}, 0);
Promise.resolve().then(() => {
console.log('second');
});
console.log('third');
输出:
third
second
first
解析:
Promise.then
是微任务,优先于宏任务 setTimeout
执行。
45. delete 删除变量
var x = 1;
delete x;
console.log(x);
输出:
1
解析:
delete
只能删除对象属性,不能删除通过 var
声明的变量。
46. typeof function
console.log(typeof function(){});
输出:
function
解析:
typeof
对函数返回 function
,是唯一不返回基本类型名的情况。
47. arguments 对象
function foo(a, b) {
arguments[0] = 10;
console.log(a);
}
foo(5);
输出:
10
解析:
非严格模式下 arguments
与形参共享内存,修改 arguments
会同步到形参。
48. 严格模式下 this
'use strict';
function show() {
console.log(this);
}
show();
输出:
undefined
解析:
严格模式下,普通函数中的 this
为 undefined
,非严格模式下为 window
。
49. 标签模板字符串
function tag(strings, value) {
console.log(strings, value);
}
const name = 'world';
tag`Hello ${name}`;
输出:
["Hello ", ""] "world"
解析:
标签模板字符串会将文本和插值拆分为多个参数传入函数。
50. Object.assign 浅拷贝
const obj1 = { a: { b: 1 } };
const obj2 = Object.assign({}, obj1);
obj2.a.b = 2;
console.log(obj1.a.b);
输出:
2
解析:
Object.assign
是浅拷贝,嵌套对象仍然引用相同的地址。
...
(题目 51~60 即将继续补充)
51. 模块导出默认值
// module.js
export default 42;
// index.js
import value from './module.js';
console.log(value);
输出:
42
解析:
使用 export default
导出的模块可以用任意名称导入,默认导出无需使用花括号。
52. 事件循环与 DOM 操作
document.body.innerHTML = '<button id="btn">Click</button>';
document.getElementById('btn').addEventListener('click', () => {
console.log('clicked');
});
document.getElementById('btn').click();
输出:
clicked
解析:
调用 click()
方法会模拟点击事件,触发绑定的事件处理函数。
53. Object.freeze 不可变对象
const obj = Object.freeze({ a: 1 });
obj.a = 2;
console.log(obj.a);
输出:
1
解析:
Object.freeze
冻结对象,使其属性无法被修改。
54. async/await 的返回值
async function foo() {
return 1;
}
foo().then(console.log);
输出:
1
解析:
async
函数总是返回一个 Promise,返回值会被包装在 Promise.resolve
中。
55. Map 与 Object 区别
const map = new Map();
map.set({}, 'val');
console.log(map.size);
输出:
1
解析:
Map
可使用对象作为键,每个对象键都是唯一引用,不会自动转为字符串。
56. typeof null
console.log(typeof null);
输出:
object
解析:
这是 JavaScript 的历史遗留 bug,typeof null
返回 'object'
。
57. 函数默认参数
function greet(name = 'Guest') {
console.log(name);
}
greet();
输出:
Guest
解析:
当未传参时,使用默认值 'Guest'
。
58. Array.from 特性
console.log(Array.from('abc'));
输出:
['a', 'b', 'c']
解析:
Array.from
可将类数组或可迭代对象(如字符串)转换为数组。
59. 对象属性访问顺序
const obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(obj));
输出:
["2", "7", "100"]
解析:
对象属性是数字时会按照数值升序排列,而非定义顺序。
60. JSON.stringify 忽略函数属性
const obj = {
a: 1,
b: () => {}
};
console.log(JSON.stringify(obj));
输出:
{"a":1}
解析:
JSON.stringify
会忽略对象中的函数、undefined
、symbol
类型的属性。
至此,JavaScript 面试中常见的 60 道读代码题汇总完毕,涵盖基础到进阶的多个方面。