今天面试了一个中高级前端开发候选人,说出来有点颠覆认知——她居然连闭包+事件循环的输出顺序题都答不上,这可是前端面试的入门必修题。
她的简历堪称完美:计算机专业、3年大厂经验、熟练使用Vue和React,还写着“主导过复杂后台管理系统重构”。可当我抛出第一个基础代码题,她的反应就让我瞬间清醒。
👉 Q1:这段代码的输出结果是什么?为什么?
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
console.log('end')
她:“输出 0 1 2 3 4 吧?setTimeout 延迟执行,循环完了输出。”
✅ 前端必懂:输出结果是 end 然后 5 5 5 5 5。var 没有块级作用域,循环结束时 i 已经变成 5,setTimeout 回调执行时访问的是同一个 i。考察点:作用域、事件循环、闭包。如果要输出 0-4,需要用 let 或者闭包(IIFE)保存每次循环的值。
👉 Q2:用 let 改成上面的代码,输出是什么?如果 setTimeout 延迟改成 0 呢?
她:“let 有块级作用域,应该输出 0 1 2 3 4。延迟 0 也是 0 1 2 3 4 吧?”
✅ 理想回答:用 let 确实输出 0 1 2 3 4。但如果延迟改成 0,输出顺序是 end 然后 0 1 2 3 4。因为 setTimeout 即使是 0 也会被放入任务队列,等主线程同步代码执行完才执行。考察点:宏任务/微任务、事件循环优先级。
👉 Q3:不用 let,怎么用闭包实现输出 0 1 2 3 4?
她:(沉默)...用闭包?不知道怎么改。
✅ 前端必会:
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(() => {
console.log(j)
}, 1000)
})(i)
}
考察点:闭包的定义——函数能够记住并访问它的词法作用域,即使这个函数在作用域之外执行。IIFE 每次循环创建一个新的作用域,把 i 的值保存在参数 j 中。
👉 Q4:如果我想实现每隔 1 秒输出一个数字,0 1 2 3 4,应该怎么改造?
她:...可以用 async/await 加 sleep 吧?具体怎么写不太确定。
✅ 进阶方案:
// 方案一:递归 + setTimeout
let i = 0
function print() {
if (i < 5) {
console.log(i++)
setTimeout(print, 1000)
}
}
print()
// 方案二:Promise + reduce 串行
[0,1,2,3,4].reduce((promise, num) => {
return promise.then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(num)
resolve()
}, 1000)
})
})
}, Promise.resolve())
考察点:异步编程能力、Promise 串行控制、递归思想。
面试到这里,我已经知道结果了。不是我苛刻,而是中高级前端岗,“JavaScript 基础是底线”。
很多人觉得,做前端只要会用框架就行,没必要深究闭包、原型链、事件循环。但事实是,不懂这些底层原理,遇到复杂交互的性能问题根本找不到优化方向;更别说做组件库设计、写 BFF 层、做工程化基建,只能停留在“调 API 画页面”的初级阶段。
前端赛道很卷,想拿高薪的人很多,但技术岗拼的从来不是简历包装,而是硬实力。
提醒所有想冲击中高级前端的朋友:先吃透 JavaScript 核心概念,搞懂执行上下文、闭包、原型链、事件循环这些底层逻辑,再去学框架源码、工程化工具,才能真正站稳脚跟。别让“连闭包输出题都答不上”,断送自己的面试和职业路。
以下面试题:
JavaScript
1. 不会冒泡的事件有哪些
在 JavaScript 和浏览器中,绝⼤多数事件都会按照 DOM 事件流模型冒泡,即事件会从⽬标元素开始向上冒泡到它的⽗元素,并最终到达 document 元素。然⽽,也有⼀些事件是不会冒泡的。这些事件通常直接在⽬标元素上触发,并不会向上传播。
以下是⼀些不会冒泡的事件的⽰例:
- focus :当元素获得焦点时触发,例如通过键盘或⿏标点击。这是⼀个不会冒泡的事件。
- blur :当元素失去焦点时触发。这也是⼀个不会冒泡的事件。
- focusin :与 focus 类似,但会在元素或其⽗元素上触发(冒泡),因此这个事件是特例。
- focusout :与 blur 类似,但会在元素或其⽗元素上触发(冒泡),因此这个事件是特例。
- load :当图像、⾳频、视频或其他资源加载完成时触发。例如,在 img 元素上触发的 load 事件不会冒泡。
- unload :当⻚⾯即将被导航离开时触发。这通常⽤于执⾏清理⼯作,也不会冒泡。
- stop :通常与 media 元素相关,例如 audio 或 video 元素。这是在媒体播放停⽌时触发的事件。
- readystatechange :当 document 的 readyState 改变时触发。这通常在⻚⾯加载时使⽤。
- scroll :当元素滚动时触发。这个事件在某些浏览器中可能会冒泡。
这些事件通常直接在⽬标元素上触发,并且不会传播到⽗元素上。
- 2.mouseEnter 和 mouseOver 有什么区别?
- 3.MessageChannel 是什么,有什么使用场景?
- 4. async、await 实现原理
- 5.Proxy 能够监听到对象中的对象的引用吗?
- 6.如何让 var [a, b]= {a: 1,b:2}解构赋值成功?
- 7.下面代码会输出什么?
- 8.描述下列代码的执行结果
- 9.什么是作用域链?
- 10.bind、call、apply 有什么区别?如何实现-个bind?
- 11.common.js和es6中模块引入的区别?
- 12.说说 vue3 中的响应式设计原理
- 13.saript标签放在header里和放在body底部里有什么区别?
- 14.下面代码中,点击“+3”按钮后,age 的值是什么?
- 15.Vue中,created和mounted两个钩子之间调用时间差值受什么影响?
- 16.vue中,推荐在哪个生命周期发起请求?
- ......
性能优化
1. script标签放在header⾥和放在body底部⾥有什么区别?
将 script 标签放在 head 和 body 底部,会对⻚⾯的加载和性能产⽣不同的影响:script 标签放在 head 部分
优点:
- 预加载: 浏览器在渲染⻚⾯之前,会先下载和解析所有在 head 部分的脚本⽂件。这样可以确保脚本在⻚⾯加载过程中随时可以被调⽤。
- 全局可⽤性: ⼀些脚本,特别是需要在⻚⾯⼀加载就运⾏的脚本,适合放在 head 中。
缺点:
- 阻塞渲染: 浏览器在遇到 script 标签时会暂停 HTML 的解析和渲染,直到脚本下载并执⾏完毕。这可能会导致⻚⾯加载变慢,尤其是当脚本⽂件较⼤或者需要从远程服务器下载时。
- ⻚⾯⽩屏时间延⻓: ⽤⼾可能会看到⻚⾯在加载过程中有⼀段时间的⽩屏,直到脚本加载完成。script 标签放在 body 底部
优点:
- ⾮阻塞渲染: 将 script 标签放在 body 底部意味着浏览器可以优先下载和渲染 HTML 内容,这样⽤⼾可以更快地看到⻚⾯内容。
- 更好的⽤⼾体验: ⽤⼾不会因为等待脚本加载⽽⻓时间看到空⽩⻚⾯。⻚⾯内容会先显⽰出来,然后再执⾏脚本,这提⾼了⻚⾯的响应速度和⽤⼾体验。
缺点:
- 延迟脚本执⾏: 如果某些脚本需要在⻚⾯加载之前运⾏(如某些初始化脚本),放在 body 底部可能会导致这些脚本运⾏延迟,影响功能。
现代优化技术
- defer 属性: 在 head 部分使⽤ script 标签时,可以添加 defer 属性。这个属性会告诉浏览器异步下载脚本,但在⻚⾯解析完成后再执⾏脚本。这样既可以保持脚本全局可⽤,⼜不会阻塞⻚⾯渲染。
1 <script src="script.js" defer></script>
2. async 属性: async 属性也⽤于异步加载脚本,但它会在脚本下载完成后⽴即执⾏,不考虑⻚⾯的解析进度。这对某些独⽴的、不会依赖于其他脚本或 DOM 结构的脚本很有⽤。
1 <script src="script.js" async></script>
总结
• 部分: 适合需要⽴即执⾏的脚本,但可能阻塞⻚⾯渲染。
• 底部: 适合⼀般脚本,能提⾼⻚⾯加载性能和⽤⼾体验。
• 使⽤ defer 或 async : 现代浏览器⽀持这些属性,可以同时兼顾性能和功能需求。
- 2.前端性能优化指标有哪些?怎么进行性能检测?
- 3.SPA(单页应用)首屏加载速度慢怎么解决?
- 4.如果使用CSS提高贞面性能?
- 5.怎么进行站点内的图片性能优化?
- 6.虚拟DOM一定更快吗?
- 7.有些框架不用虚拟dom,但是他们的性能也不错是为什么?
- 8.如果某个页面有几百个函数需要执行,可以怎么优化页面的性能?
- 9.讲一下png8、png16、png32的区别,并简单讲讲 png 的压缩原理
- 11.React.memo()和 useMemo()的用法是什么,有哪些区别?
- 12.导致页面加载白屏时间长的原因有哪些,怎么进行优化?
- 13.如果一个列表有 100000 个数据,这个该怎么进行展示?
- 14.DNS 预解析是什么?怎么实现?
- 15.在 React 中可以做哪些性能优化?
- 16.浏览器为什么要请求并发数限制?
- ......
React
1. 下⾯代码中,点击 “+3” 按钮后,age 的值是什么?
1 import { useState } from 'react';
2
3 export default function Counter() {
4 const [age, setAge] = useState(42);
5 function increment() {
6 setAge(age + 1);
7 }
8 return (
9 <>
10 <h1>Your age: {age}</h1>
11 <button onClick={() => {
12 increment();
13 increment();
14 increment();
15 }}>+3</button>
16 </>
17 );
18 }
点击 +3 时,可能只更新为 43。
这是因为 setAge(age + 1) 即使多次调⽤,也不会⽴即更新组件状态,⽽是会进⾏合并,最终只触发⼀次重新渲染。
如果要实现调⽤三次就增加 3 ,可以将 increment 改为函数式更新:
1 function increment() {
2 setAge(a => a + 1); // 函数式更新
3 }
- 2.React Portals 有什么用?
- 3.react 和 react-dom 是什么关系?
- 4. React 中为什么不直接使用 requestIdleCallback?
- 5.为什么 react 需要 fiber 架构,而 Vue 却不需要?
- 6.子组件是一个 Portal,发生点击事件能冒泡到父组件吗?
- 8.说说React render方法的原理?在什么时候会被触发?
- 9.说说React事件和原生事件的执行顺序
- 10.说说对受控组件和非受控组件的理解,以及应用场景?
- 11.你在React项目中是如何使用Redux的?项目结构是如何划分的?
- 12.说说对Redux中间件的理解?常用的中间件有哪些?实现原理?
- 13.说说你对Redux的理解?其工作原理?
- 14.说说你对immutable的理解?如何应用在react项目中?
- 15.说说React ]sx转换成真实DOM过程?
- 16.说说你在React项目是如何捕获错误的?
- ......
Vue
1. Vue 有了数据响应式,为何还要 diff ?
Vue 中的数据响应式和虚拟 DOM 的 diff 算法是两个不同的概念,它们分别解决了不同的问题,相互协作以提⾼⻚⾯渲染的效率和性能。
数据响应式
Vue 的数据响应式系统通过 Object.defineProperty 或者 ES6 的 Proxy 来实现,主要解决了以下问题:
- 数据绑定:保证了视图与数据的同步更新,当数据发⽣变化时,视图会⾃动更新,避免了⼿动操作DOM 的繁琐和易出错性。
- 依赖追踪:Vue 能够追踪每个数据的依赖关系,即哪些组件或者计算属性依赖于某个数据。当数据变化时,⾃动更新依赖的组件或者计算属性。
虚拟 DOM 和 Diff 算法
虚拟 DOM 是⼀种内存中的表⽰结构,它是对真实 DOM 的抽象。Diff 算法是⼀种⾼效更新 DOM 的策略,它通过⽐较新旧虚拟 DOM 树的差异,最⼩化了更新操作,提⾼了⻚⾯的渲染效率。
为什么还需要 Diff 算法?
- 性能优化:直接操作真实 DOM 是⾮常昂贵的,⽽虚拟 DOM 可以在内存中快速进⾏⽐较和计算差异。Diff 算法帮助减少了更新操作的次数和范围,从⽽提升了⻚⾯渲染的性能。
- 批量更新:Diff 算法能够将多次 DOM 更新操作合并为⼀次,避免了频繁的 DOM 操作,减少了浏览器的重排和重绘。
- 跨平台兼容:虚拟 DOM 和 Diff 算法使得 Vue 可以运⾏在不同的平台上,例如浏览器、Weex 等,统⼀了渲染逻辑和数据响应式的实现。
- 更新效率:即使是响应式系统可以⾃动更新视图,但是如果每次数据变化都直接操作真实 DOM,可能会带来性能问题。Diff 算法可以智能地⽐较新旧 DOM 树的变化,只更新必要的部分,从⽽提⾼了更新效率。
综合作⽤
Vue 的数据响应式系统和虚拟 DOM + Diff 算法是紧密协作的:
• 数据响应式:保证了数据和视图的同步更新,提供了便捷的开发⽅式。
• 虚拟 DOM + Diff 算法:提⾼了⻚⾯渲染的效率和性能,减少了不必要的 DOM 操作,确保了⻚⾯的流畅性和响应性。
总体来说,数据响应式和 Diff 算法是为了解决不同层⾯的问题,结合起来使得 Vue 能够提供⾼效、流畅的⽤⼾体验。
- 2.vue3 为什么不需要时间分片?
- 3.vue3 为什么要引入 Composition API?
- 4.谈谈 Vue 事件机制,并手写off、once
- 5.computed 计算值为什么还可以依赖另外-个 computed 计算值?
- 6.说-下 vm.$set 原理
- 7. 怎么在 Wue 中定义全局方法?
- 8.Vue 中父组件怎么监听到子组件的生命周期?
- 10.说说 vue3 中的响应式设计原理
- 11.Vue中,created和mounted两个钩子之间调用时间差值受什么影响?
- 12.vue中,推荐在哪个生命周期发起请求?
- 13.为什么 react 需要 fiber 架构,而 Vue 却不需要?
- 14.SPA(单页应用)首屏加载速度慢怎么解决?
- 15.说下Vite的原理
- 16.Vue2.0为什么不能检查数组的变化,该怎么解决?
- ......
工程化
1. package.json ⽂件中的 devDependencies 和 dependencies 对象有什么区别?
前端项⽬的 package.json ⽂件中, dependencies 和 devDependencies 对象都⽤于指定项⽬所依赖的软件包,但它们在项⽬的开发和⽣产环境中的使⽤有所不同。
- dependencies:
◦ dependencies 是指定项⽬在⽣产环境中运⾏所需要的依赖项。
◦ 这些依赖项通常包括运⾏时需要的库、框架、⼯具等。
◦ 当你通过 npm install 或 npm ci 安装依赖时,默认会安装 dependencies 中的包。
◦ 这些依赖项会被打包和部署到⽣产环境中,因此它们对于项⽬的运⾏是必需的。
- devDependencies:
◦ devDependencies 是指定在开发过程中所需要的依赖项。
◦ 这些依赖项通常包括开发、测试、构建、部署等过程中所需的⼯具、库等。
◦ 例如,测试框架、构建⼯具、代码检查⼯具等通常属于 devDependencies 。
◦ 当你在开发环境中使⽤ npm install 安装依赖时,只会安装 dependencies 中的包。要安装 devDependencies 中的包,你需要额外使⽤ npm install --dev 或 npm
install --only=dev 等命令。
◦ 这些依赖项不会被打包到⽣产环境中,因为它们只在开发过程中需要,对于实际部署和运⾏项⽬并不需要。
总的来说, dependencies 中的依赖项是项⽬运⾏所必需的,⽽ devDependencies 中的依赖项则是在开发过程中需要的辅助⼯具和库。
- 2.webpack5 的主要升级点有哪些?
- 3.说下vite的原理
- 4.与webpack类似的工具还有哪些?区别?
- 5.说说如何借助webpack来优化前端性能?
- 6.说说webpack proxy工作原理?为什么能解决跨域?
- 7.说说webpack的热更新是如何做到的?原理是什么?
- 8.面试官:说说Loader和Plugin的区别?编写Loader,Plugin的思路?
- 9.说说webpack中常见的Plugin?解决了什么问题?
- 10.说说webpack中常见的Loader?解决了什么问题?
- 11.说说webpack的构建流程?
- 12.说说你对webpack的理解?解决了什么问题?
- 13.webpack loader 和 plugin 实现原理
- 14.如何提高webpack的构建速度?
- 15.说说 webpack-dev-server 的原理
- 16.你对 babel 了解吗,能不能说说几个 stage 代表什么意思?
- ......