这是前端JS核心面试必考6大知识点 的代码分析题+进阶题 全集,严格遵循JS执行逻辑,覆盖基础必考题+进阶拔高题,每题附代码、输出结果、逐行详解,完全适配中大厂前端面试要求。
前端JS核心面试题详解(作用域/this/对象/闭包/原型/异步)
核心考点优先级
- this指向、异步(事件循环/Promise)⭐⭐⭐⭐⭐
- 闭包、原型链 ⭐⭐⭐⭐
- 作用域、对象 ⭐⭐⭐
一、作用域 & 作用域链 & 变量提升
核心原理
- 三种作用域:全局作用域、函数作用域、块级作用域(
let/const) - 变量提升:
var声明提升、let/const存在暂时性死区 - 作用域链:内层函数访问变量时,逐级向上查找外层作用域
基础题1:var 变量提升
js
console.log(a);
var a = 10;
console.log(a);
输出 :undefined → 10
详解:
-
JS预编译:
var a声明提升到顶部,赋值保留原地 → 代码等价于:jsvar a; console.log(a); // 仅声明未赋值 → undefined a = 10; console.log(a); // 赋值完成 → 10
基础题2:let 暂时性死区
js
console.log(b);
let b = 20;
输出 :Uncaught ReferenceError: Cannot access 'b' before initialization
详解 :let/const 存在块级作用域和暂时性死区,声明前无法访问,无变量提升。
进阶题1:嵌套作用域 + 作用域链
js
var num = 1;
function fn1() {
var num = 2;
function fn2() {
num = 3;
function fn3() {
console.log(num);
}
fn3();
}
fn2();
}
fn1();
console.log(num);
输出 :3 → 1
详解:
fn3无自身num,沿作用域链找到fn2修改的num=3,打印3;- 所有修改都在函数作用域 内,全局
num不受影响,最终打印1。
二、this指向(面试第一考点)
核心绑定规则(优先级从高到低)
- new 绑定 → 2. 显式绑定 (call/apply/bind)→ 3. 隐式绑定 → 4. 默认绑定
- 箭头函数:无自己的this,继承外层作用域的this
基础题1:默认绑定(独立函数调用)
js
var name = "全局";
function fn() {
console.log(this.name);
}
fn();
输出 :全局
详解 :全局独立调用函数,this默认指向全局对象(浏览器=window)。
基础题2:隐式绑定(对象方法调用)
js
const obj = {
name: "对象",
fn: function() {
console.log(this.name);
}
}
obj.fn();
输出 :对象
详解 :谁调用方法,this就指向谁 → obj调用,this=obj。
基础题3:箭头函数this
js
const obj = {
name: "箭头",
fn: () => {
console.log(this.name);
}
}
obj.fn();
输出 :undefined(浏览器严格模式)/ 全局
详解 :箭头函数无this,继承外层全局作用域的this,而非obj。
进阶题1:混合绑定优先级(必考)
js
function Person(name) {
this.name = name;
}
const obj = {};
const fn = Person.bind(obj);
fn("张三");
console.log(obj.name);
const p = new fn("李四");
console.log(p.name);
输出 :张三 → 李四
详解:
bind显式绑定obj,fn("张三")→this=obj,obj.name=张三;- new绑定优先级高于bind ,
new fn时this指向新实例,覆盖bind绑定。
进阶题2:定时器 + this + 箭头函数
js
const obj = {
name: "定时器",
fn1: function() {
setTimeout(function() {
console.log(this.name);
}, 0);
},
fn2: function() {
setTimeout(() => {
console.log(this.name);
}, 0);
}
}
obj.fn1();
obj.fn2();
输出 :undefined → 定时器
详解:
- 普通函数定时器:回调是独立调用 → 默认绑定
window,无name; - 箭头函数定时器:继承
fn2的this(指向obj),正常打印。
三、对象 & 属性 & 深浅拷贝
核心原理
- 对象属性:自有属性/原型属性,
hasOwnProperty判断自有属性 - 浅拷贝:只拷贝一层,引用类型共享内存
- 深拷贝:递归拷贝所有层级,完全独立
基础题1:in / hasOwnProperty
js
const obj = { a: 1 };
console.log('a' in obj);
console.log('toString' in obj);
console.log(obj.hasOwnProperty('toString'));
输出 :true → true → false
详解:
in:检查自有+原型属性;hasOwnProperty:仅检查自有属性 ,toString是Object原型属性。
进阶题1:浅拷贝的坑(高频)
js
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { ...obj1 };
obj2.a = 10;
obj2.b.c = 20;
console.log(obj1.a, obj1.b.c);
输出 :1 → 20
详解:
- 扩展运算符是浅拷贝 ,基础类型
a独立,引用类型b共享内存; - 修改
obj2.b.c会同步修改原对象。
四、闭包(高频必考)
核心定义
函数能够访问并保留其词法作用域,即使在词法作用域外执行,就形成闭包。
作用:私有化变量、模块化、保存数据
基础题1:闭包基础
js
function outer() {
let num = 10;
function inner() {
console.log(num);
}
return inner;
}
const fn = outer();
fn();
输出 :10
详解 :inner在outer外部执行,仍能访问outer的num → 闭包。
基础题2:经典循环闭包BUG(var)
js
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
输出 :3、3、3
详解:
var是函数作用域,循环共用一个i;- 定时器是异步,执行时循环已结束,
i=3。
进阶题1:闭包修复循环(两种方案)
js
// 方案1:let 块级作用域
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 方案2:闭包包裹
for (var i = 0; i < 3; i++) {
(function(j){
setTimeout(() => console.log(j), 0);
})(i)
}
输出 :0、1、2
详解:
let每次循环生成独立块级作用域;- 立即执行函数创建独立作用域,保存每次的
i值。
五、原型 & 原型链 & 继承
核心原理
- 实例.proto = 构造函数.prototype
- 原型链终点:
Object.prototype.__proto__ = null instanceof:判断构造函数的prototype是否在实例的原型链上
基础题1:原型链属性查找
js
function Person() {}
Person.prototype.name = "原型";
const p = new Person();
p.name = "实例";
console.log(p.name);
delete p.name;
console.log(p.name);
输出 :实例 → 原型
详解:
- 先找实例自有属性,有则直接用;
- 删除实例属性后,沿原型链找到
Person.prototype的name。
进阶题1:组合继承(面试必考)
js
function Parent(name) {
this.name = name;
}
Parent.prototype.say = function() {
console.log(this.name);
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
const c = new Child("小明", 18);
c.say();
console.log(c.age);
输出 :小明 → 18
详解:
Parent.call:继承实例属性;Object.create:继承原型方法,完美实现JS组合继承。
六、异步编程(Promise/async/await/事件循环)
核心原理(浏览器)
- 执行顺序:同步代码 → 微任务 → 宏任务
- 微任务:
Promise.then/catch/finally、process.nextTick - 宏任务:
setTimeout、setInterval、AJAX、DOM事件
基础题1:Promise链式调用
js
Promise.resolve()
.then(() => console.log(1))
.then(() => console.log(2));
Promise.resolve()
.then(() => console.log(3));
输出 :1 → 3 → 2
详解 :微任务队列按顺序执行,第一个链的then1执行完,执行第二个then3,最后执行第一个then2。
进阶题1:async/await 执行顺序(终极面试题)
js
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => console.log('setTimeout'), 0);
async1();
new Promise(resolve => {
console.log('Promise');
resolve();
}).then(() => console.log('Promise then'));
console.log('script end');
输出 :
script start → async1 start → async2 → Promise → script end → async1 end → Promise then → setTimeout
详解:
- 同步代码优先执行:
script start→async1 start→async2→Promise→script end; await后面的代码 = 微任务,Promise.then也是微任务,按顺序执行;- 最后执行宏任务
setTimeout。
进阶题2:Promise.all 异常处理
js
const p1 = Promise.resolve(1);
const p2 = Promise.reject("错误");
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err));
输出 :错误
详解 :Promise.all 只要有一个失败,整体直接失败,返回第一个错误。
总结(面试必背核心结论)
- this:new > 显式 > 隐式 > 默认,箭头函数继承外层this;
- 闭包 :函数访问外层作用域变量,解决循环问题用
let/立即执行函数; - 原型链 :实例通过
__proto__找原型,自有属性优先级高于原型; - 异步:同步 → 微任务(Promise)→ 宏任务(定时器);
- 作用域 :
var函数作用域+提升,let块级作用域+暂时性死区。