2025面试前必看web前端八股文题库!25年准备换工作或者想要跳槽的小伙伴快来看啊!
需要完整的可以看文末
JavaScript
1.不会冒泡的事件有哪些?
参考答案:
在JavaScript和浏览器中,绝大多数事件都会按照DOM事件流模型
冒泡,即事件会从目标元素开始向上冒泡到它的父元素,并最终到达document元素。然而,也有一些事件是不会冒泡的。这些事件通常直接在目标元素上触发,并不会向上传播。
以下是一些不会冒泡的事件的示例:
-
focus:当元素获得焦点时触发,例如通过键盘或鼠标点击。这是一个不会冒泡的事件。
-
b1ur:当元素失去焦点时触发。这也是一个不会冒泡的事件。
-
focusin:与focus类似,但会在元素或其父元素上触发(冒泡),因此这个事件是特例。
-
focusout:与blur类似,但会在元素或其父元素上触发(冒泡),因此这个事件是特例。
-
1oad:当图像、音频、视频或其他资源加载完成时触发。例如,在img元素上触发的1oad事件不会冒泡。
-
un1oad:当页面即将被导航离开时触发。这通常用于执行清理工作,也不会冒泡。
-
stop:通常与media元素相关,例如audio或video元素。这是在媒体播放停止时触发的事件。
-
readystatechange:当document的readyState改变时触发。这通常在页面加载时使用。
-
scro11:当元素滚动时触发。这个事件在某些浏览器中可能会冒泡。
这些事件通常直接在目标元素上触发,并且不会传播到父元素上。
2. mouseEnter和mouseOver有什么区别?
参考答案:
mouseenter和mouseover是两个用于处理鼠标进入元素时的事件,但它们在一些关键点上有所不同:
- 事件冒泡:
- mouseenter:这个事件在鼠标指针首次进入特定元素(或其子元素)时触发。当鼠标进入元素时,会触发该元素的mouseenter事件,但不会在元素的子元素上冒泡。因此,该事件通常用于检测鼠标首次进入元素时的动作。
- mouseover:这个事件在鼠标指针移动到某个元素上时触发,不论它是直接在这个元素上触发还是在其子元素上触发。当鼠标进入一个元素时,它会在该元素上触发mouseover事件,然后冒泡到父元素。
- 事件触发范围:
- mouseenter:当鼠标进入元素自身时触发,只在目标元素上触发,不会因为鼠标移动到其子元素上而再次触发。
- mouseover:不仅在目标元素上触发,也在其子元素上触发。所以,如果鼠标从一个子元素移动到另一个子元素,这些元素的父元素会触发多个mouseover事件。
- 事件对象的属性:
- mouseenter:事件对象通常会有relatedTarget属性,它指向鼠标移动前的那个元素。如果relatedTarget指向目标元素或为nul1,那么事件就不会触发。
- mouseover:事件对象也会有relatedTarget属性,通常指向从中离开的那个元素。
使用场景
- mouseenter更适合用来检测鼠标首次进入某个元素时的行为。
- mouseover更适合用来检测鼠标在元素或其子元素之间移动时的行为,因为它冒泡。
在实际使用时,如果你只想在鼠标首次进入元素时触发某些行为(比如显示一个提示),可以使用mouseenter:如果你希望在鼠标移动到某个元素或其子元素上时都触发某些行为(比如动态改变样式),可以使用mouseover。
3.MessageChannel是什么,有什么使用场景?
叁考答案:
MessageChanne1是一个JavaScript API,用于在两个独立的执行环境(如 Web Workers 或者不同的 browsingcontexts)之间建立双向通信的通道。MessageChannel提供了两个通信端点(port1和port2),可以在两个不同的执行环境之间传递消息,并通过事件监听的方式来处理这些消息。
使用场景包括但不限于:
-
Web Workers 通信:在 Web开发中,MessageChannel通常用于在主线程和 Web Worker 之间建立通信通道,以便主线程与Worker之间传递消息和数据。
-
不同浏览上下文(browsing context)之间的通信:在现代浏览器中,多个标签页、iframe或者其他类型的browsing context 可以通过MessageChannel实现通信。
-
SharedWorker 通信:MessageChanne1可以用于在主线程和 Shared Worker 之间建立通信通道。
-
服务端和客户端之间的通信:MessageChannel可以用于客户端(如浏览器)与服务端(如WebSocket服务器)之间的通信,特别是在与WebSocket或其他类似技术结合使用时。
-
异步任务处理:在某些场景中,使用MessageChannel可以更方便地处理异步任务,因为它提供了独立于主线程的通信通道。
使用示例
下面是一个简单的示例,展示如何使用MessageChanne1在主线程和WebWorker之间建立通信通道:
// 创建 MessagechanneL
const channel = new Messagechannel();
const port1 = channel.port1;
const port2 = channel.port2;
// 在主线程中
const worker = new worker('worker.js');
worker.postMessage({ port: port2 },[port2]);
port1.onmessage = function(event) {
console.log('Received message from worker:', event.data);
};
// 发送消息给worker
port1.postMessage('Hello, Worker!');
在上面的示例中,我们创建了一个MessageChannel,并通过port1和port2进行通信。我们将port2发送给Web Worker,port1留在主线程。然后,主线程可以通过监听port1的onmessage事件来接收来自Web Worker 的消息,并通过port1.postMessage()向 Web Worker 发送消息。
React
1.下面代码中,点击"+3”按钮后,age的值是什么?
import {usestate } from 'react';
export default function Counter() {
const [age, setAge] = usestate(42);
function increment( {
setAge(age + 1);
}
return (
<>
<h1>Your age: {age}</h1>
<button onclick={() =>{
incrementO;
incrementO;
increment();
}}>+3</button>
</>
);
}
参考答案:
点击+3 时,可能只更新为43。
这是因为setAge(age+1)即使多次调用,也不会立即更新组件状态,而是会进行合并,最终只触发一次重新渲染。
如果要实现调用三次就增加3,可以将increment改为函数式更新:
function increment( {
setAge(a => a + 1); // 西数式更新
}
2. React Portals有什么用?
参考答案:
React Portals是React提供的一种机制,用于将子组件渲染到父组件DOM层次结构之外的位置。它在处理一些特
殊情况下的UI布局或交互时非常有用。以下是一些使用ReactPortals的常见情况:
-
在模态框中使用:当你需要在应用的根DOM结构之外显示模态框(对话框)时,React Portals可以帮助你将
模态框的内容渲染到根DOM之外的地方,而不影响布局。 -
处理z-index问题:在一些复杂的布局中,可能存在z-index的层级关系导致组件无法按照预期的方式叠加显
示。使用ReactPortals可以将组件渲染到具有更高z-index的容器中,以解决这些问题。 -
在全局位置显示组件:如果你希望某个组件在页面的固定位置显示,而不受父组件的定位影响,ReactPortals
可以将该组件渲染到body或其他容器中。 -
在动画中使用:当你需要在页面中的某个位置执行动圆时,ReactPortals可以帮助你将动画的内容渲染到离该
位置更近的DOM结构中,以提高动画性能。
使用 React Portals 的基本步骤如下:
import React from 'react';
import ReactDom from 'react-dom';
function MyPortalComponent(){
return ReactDoM.createPortal(
// 子组件的内容
<div>
This is rendered using a portal!
</div>,
// 渲染目标的 DOM 元素
document.getElementById('portal-root')
);
}
//在应用的根组件中渲染MyPortaLComponent
function AppO{
return (
<div>
{/*此的内容在正常的DOM结构中*/}
<p>This is a normal component.</p>
{/> 使用React Portals 渲染到'portal-root'元素外 */}
<MyPortalComponent />
</div>
);
}
export default App;
在上面的例子中,MyPortalComponent中的内容会被渲染到具有 id 为'portal-root'’的DOM 元素外。
Vue
1.Vue有了数据响应式,为何还要diff?
参考答案:
Vue中的数据响应式和虚拟DOM
的dif算法是两个不同的概念,它们分别解决了不同的问题,相互协作以提高页面渲染的效率和性能。
数据响应式
Vue 的数据响应式系统通过Object.defineProperty或者ES6的Proxy来实现,主要解决了以下问题:
1.数据绑定:保证了视图与数据的同步更新,当数据发生变化时,视图会自动更新,避免了手动操作DOM的繁琐和易出错性。
2,依赖追踪: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为什么不需要时间分片?
参考答案:
Vue3不需要时间分片(timeslicing)主要是因为它的核心渲染机制和性能优化策略已经足够高效,能够在大多数情况下提供流畅的用户体验。以下是详细的原因:
- 编译器优化
Vue3引入了一个全新的编译器,能够生成更高效的渲染数。这个编译器在编译过程中进行了一系列优化,例如:
- 静态提升:将不变的节点提升为常量,只在初次渲染时计算一次。
- 预字符串化:将静态内容直接转化为字符串,减少了运行时的开销。
- 缓存事件处理程序:避免了不必要的重新绑定。
这些优化措施大大减少了Vue3在更新DOM时的计算量,使得渲染过程更加高效。
- 响应式系统的改进
Vue 3使用了基于代理的响应式系统,替代了Vue2中基于Object.defineProperty的实现。新的响应式系统更加高效,具备以下优点:
- 精细的依赖追踪:只追踪实际使用的属性,避免了不必要的依赖收集。
- 懒惰计算:仅在需要时才计算依赖,减少了计算量。
这些改进使得Vue3能够更快速地响应数据变化,从而减少了渲染开销。
- 虚拟 DOM 和 Diff 算法的优化
Vue 3对虚拟DOM及其diff算法进行了优化,使得差异计算更加高效:
- 静态标记:编译期间标记静态节点,跳过不变的部分。
- 块级优化:将动态节点分块,只对发生变化的块进行更新。
这些优化措施减少了DOM更新的频率和范围,提高了整体渲染性能。
- 单次异步队列
Vue3的更新机制基于单次异步队列(singleasynchronousqueue),它确保在同一事件循环中只进行一次批量更新。这种方式减少了不必要的重复计算和DOM操作,使得更新过程更加高效。
5.自动批处理
Vue3实现了自动批处理机制,在同一个事件循环中对多次数据更新进行合并,从而减少了渲染次数。这种机制在避免频繁重绘的同时,保证了界面的流畅性。
- 现代浏览器的性能
现代浏览器的性能已经得到了极大的提升,尤其是在处理JavaScript和DOM操作方面。Vue3的优化能够充分利用这些性能改进,从而在绝大多数情况下不需要时间分片。
总结
Vue3通过编译器优化、响应式系统改进、虚拟DOM和Diff算法优化、单次异步队列、自动批处理等技术手段,大幅提升了渲染效率和性能。再加上现代浏览器的性能提升,使得Vue3能够在大多数情况下提供流畅的用户体验,而无需借助时间分片等复杂的技术。
工程化
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. webpack 5的主要升级点有哪些?
叁考答案:
- 持久缓存(Persistent Caching):Webpack 5
-
引I入了更好的持久缓存机制,利用了更稳定的HashedModuleIdsPlugin和NamedChunksPlugin,以改善构建性能。
-
Tree-shaking改进:Webpack 5对Tree-shaking进行了改进,提供了更好的代码优化,以便删除未使用的代码。
-
支持WebAssembly(WASM):Webpack 5对WebAssembly提供了原生的支持,使得在项目中使用WebAssembly 更加方便。
-
·支持ES6模块导入(Dynamic Import):Webpack 5对动态导入语法(importO)提供了更好的支持,可以更轻松地进行代码分割。
-
模块联邦(ModuleFederation):这是Webpack5中的一项重大功能,允许将多个独立的Webpack构建连接在一起,实现模块共享,从而更好地支持微服务架构。
-
缓存组(CachingGroups):新的缓存组概念被引入,可以更细粒度地控制模块的缓存策略。
-
内置代码分割优化(optimization.splitChunks):Webpack 5通过optimization.splitChunks进行了重新设计,提供了更灵活的配置选项,使得代码分割更为强大和易用。
-
默认配置优化:Webpack5默认配置中的一些优化,使得开箱即用的性能更好。
-
提高构建性能:Webpack5引入了一些性能优化,包括更快的持久化缓存、更快的构建速度等。
-
移除废弃特性:作为更新,Webpack5移除了一些过时的特性和API,因此在升级时需要注意潜在的破坏性变化。
3. 说下Vite的原理
这里的背景介绍会从与Vite紧密相关的两个概念的发展史说起,一个是JavaScript的模块化标准,另一个是前端构建工具。
共存的模块化标准
为什么JavaScript会有多种共存的模块化标准?因为js在设计之初并没有模块化的概念,随着前端业务复杂度不断提高,模块化越来越受到开发者的重视,社区开始涌现多种模块化解决方案,它们相互借鉴,也争议不断,形成多个派系,从CommonJs开始,到ES6正式推出ES Modules规范结束,所有争论,终成历史,ES Modules也成为前端重要的基础设施。
- CommonJS:现主要用于Node.js(Node@13.2.0开始支持直接使用ESModule)
- AMD:require.js依赖前置,市场存量不建议使用
- CMD:sea.js就近执行,市场存量不建议使用
- ES Module:ES语言规范,标准,趋势,未来
而Vite的核心正是依靠浏览器对ES Module规范的实现。
发展中的构建工具
近些年前端工程化发展迅速,各种构建工具层出不穷,目前Webpack仍然占据统治地位,npm每周下载量达到两千多万次。下面是我按npm发版时间线列出的开发者比较熟知的一些构建工具。
当前工程化痛点
现在常用的构建工具如Webpack,主要是通过抓取-编译-构建整个应用的代码(也就是常说的打包过程),生成一份编译、优化后能良好兼容各个浏览器的的生产环境代码。在开发环境流程也基本相同,需要先将整个应用构建打包后,再把打包后的代码交给dev server(开发服务器)。
Webpack等构建工具的诞生给前端开发带来了极大的便利,但随着前端业务的复杂化,js代码量呈指数增长,打包构建时间越来越久,devserver(开发服务器)性能遇到瓶颈:
- 缓慢的服务启动:大型项目中devserver启动时间达到几十秒甚至几分钟。
- 缓慢的HMR热更新:即使采用了HMR模式,其热更新速度也会随着应用规模的增长而显著下降,已达到性能瓶颈,无多少优化空间。
缓慢的开发环境,大大降低了开发者的幸福感,在以上背景下Vite应运而生。