前端面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

前端阅读 352024年6月24日 16:43

在 Web 应用中,从服务器主动推送Data到客户端有那些方式?

在Web应用中,服务器向客户端主动推送数据是一个常见需求,可以实现如实时通知、即时聊天等功能。以下是一些实现服务器到客户端推送的技术:轮询(Polling)轮询是最简单的一种方式,客户端通过定时发送HTTP请求到服务器查询是否有新数据。这种方式的缺点是效率较低,并且会产生很多无用的网络流量,因为即使没有数据更新,客户端也会定时发起请求。长轮询(Long Polling)长轮询是对传统轮询的改进。客户端发送请求给服务器后,服务器会持有这个请求,直到有新数据可以发送或者达到某个时间限制。这种方式比传统轮询效率更高,但仍有延迟,并且会占用服务器资源。服务器发送事件(Server-Sent Events, SSE)SSE允许服务器通过HTTP连接向客户端推送事件。与轮询不同,这里的连接是单向的:服务器到客户端。SSE支持自动重连,并且可以只发送更新的数据。但SSE只支持文本数据,并且不是所有浏览器都支持。WebsocketWebsocket提供了一个全双工的通信通道,允许服务器和客户端之间进行双向通信。连接一旦建立,服务器就可以在任何时候发送数据给客户端,同样客户端也可以随时发送数据给服务器。Websocket适合需要高频实时交互的应用,如在线游戏、交易平台等。例子:一个即时消息应用可能会使用Websocket来推送消息。当一个用户发送消息时,服务器接收这个消息并通过已经打开的Websocket连接将它推送给其他在线用户。由于Websocket允许低延迟的双向通信,用户体验接近即时通信。Web Push Notifications这种技术允许服务器向注册了推送服务的客户端发送通知,即使Web应用没有在前台运行,用户也能够收到通知。Web推送通知通常用于向用户发送即时信息,比如电子邮件到达、社交媒体的新动态等。Message Queues(如RabbitMQ)和Push Services(如Google Firebase)一些服务器并不直接发送数据给客户端,而是利用消息队列和第三方推送服务。消息队列可以缓存消息,然后基于某些条件(如客户端在线)进行分发。第三方推送服务则提供了一套完整的解决方案来管理消息的推送。在设计一个系统时,选择哪种技术取决于应用的具体需求,比如是否需要低延迟、高吞吐量,以及客户端支持的技术等。通常,实时性要求较高的应用会选择Websocket,而对实时性要求较低的应用可以选择SSE或长轮询。对于移动应用,Web Push Notifications是一个不错的选择,因为它们可以推动用户重新参与使用应用。
前端阅读 332024年6月24日 16:43

React Fiber 架构是什么?有什么优势?

React Fiber 是 React 框架的一个核心算法重写版本,它是 React 16 版本中引入的。Fiber 架构的主要目标是增强 React 在处理动画、布局、手势等方面的能力,并且让这些任务的执行变得更加平滑,不会引起应用程序的卡顿。这种架构的引入是为了优化渲染过程,使之能够利用浏览器的空闲时间执行,从而提高应用程序的性能并使用户界面更加流畅。React Fiber 架构的主要优势有:增量渲染:Fiber 架构的主要功能之一是能够将渲染工作拆分成多个小任务,并将这些任务分散到多个帧中。这个特点允许 React 暂停和恢复渲染任务,这种“可中断”的渲染过程意味着主线程可以更响应用户操作,从而提高了应用的性能。任务优先级:Fiber 架构可以为更新分配优先级。一些任务(如动画)比其他任务(如数据的后台同步)更为紧急。React Fiber 可以区分这些任务,并且先执行更高优先级的任务,再在空闲时处理低优先级的任务。更好的错误处理:Fiber 引入了新的错误边界概念,使得组件能够更好地捕获子组件的错误,并且定义备用 UI,从而提供更好的用户体验。更平滑的动画和过渡:由于 React Fiber 可以利用浏览器的空闲时间执行渲染任务,因此可以更平滑地执行动画和过渡,降低了卡顿的可能性。更好的适配未来的变化:Fiber 架构为将来 React 框架的可能更新和改变打下了基础,比如并发模式(Concurrent Mode)和 Suspense 等新特性。示例:优先级调度:想象一下一个用 React Fiber 构建的聊天应用。用户正在输入消息,同时应用正在后台同步接收新消息。使用 Fiber 架构,React 可以给用户输入的响应分配更高的优先级,从而保证输入的流畅,而消息同步的任务可以在浏览器空闲时进行,用户体验因此得到提升。通过这些改进,React Fiber 架构使得开发者可以构建出更加响应快速、用户体验更好的应用程序。
前端阅读 1902024年6月24日 16:43

什么是 webp 图片?它的优势和缺点是有哪些?

WebP 图片简介:WebP 是一种现代图像格式,由Google在2010年发布。它是一种旨在为网页图像压缩提供优异的压缩比的格式,其目标是减小图像的文件大小,而在视觉上保持高质量。WebP 支持无损压缩和有损压缩,同时还支持透明度(Alpha通道)和动画。WebP 的优势:更小的文件大小: 相较于传统的JPEG和PNG格式,WebP图像通常可以在不牺牲视觉质量的情况下提供更小的文件大小。这意味着更快的加载时间和更低的带宽消耗,这对于移动设备用户和带宽有限的环境尤其重要。支持有损和无损压缩: WebP既可以提供有损压缩以减少文件大小,也可以提供无损压缩来保持图像质量。这使得它比只能提供一种压缩方式的格式更为灵活。支持动画: WebP可以替代传统的GIF格式,提供更优的动画压缩效果,同时文件尺寸比GIF更小。支持透明度: 除了对颜色的压缩外,WebP还支持8位的透明通道,即使是在有损压缩的情况下也能处理透明度,这对于网页设计中的图标和图形元素非常有用。色彩表现: WebP支持广色域,这意味着它能够展现更丰富的颜色和更平滑的色彩过渡。WebP 的缺点:兼容性问题: 尽管WebP的支持正在增加,但一些旧的浏览器和图像编辑软件还不支持WebP格式。这可能导致必须为不同的用户提供不同格式的图片。编辑软件支持有限: 相较于JPEG和PNG,WebP格式在图像编辑软件中的支持较少,可能需要使用特定的工具来编辑或转换这些图像。用户认知: 普通用户可能不熟悉WebP格式,对于需要用户上传图片的网站来说,可能需要额外的工作来确保用户上传的图片是支持的格式。转换成本: 对于已经有大量存量图片的网站,将图片转换为WebP格式可能需要消耗一定的时间和资源。例子:为了说明WebP格式的优势,我们可以考虑一个在线商城。该商城的页面包含了大量的产品图片,如果这些图片使用WebP格式而不是JPEG或PNG,那么页面加载速度可能会显著提高,从而改善用户体验和提升转化率。在一个实际测试中,转换JPEG图片到WebP格式,可能会看到文件大小减少25%以上,而视觉上的差异几乎察觉不到。这对于用户来说意味着更快的加载时间,对于商城运营者来说,则意味着更低的数据传输费用。然而,为了确保所有用户都能正常浏览图片,商城可能需要保留JPEG或PNG格式的备份,以便不支持WebP的浏览器也能显示图片。
前端阅读 572024年6月24日 16:43

移动设备安卓与-ios-的软键盘弹出的处理方式有什么不同

移动设备的Android和iOS平台在软键盘弹出的处理方式上确实存在一些差异,主要反映在以下几个方面:1. 视图调整Android:Android平台上,当软键盘弹出时,默认情况下会重新调整活动(Activity)的大小,以确保当前获得焦点的输入字段可见。开发者可以通过设置AndroidManifest.xml中Activity的windowSoftInputMode属性来调整这一行为。例如,adjustResize会调整Activity的大小,而adjustPan会上移整个视图。例子:<activity android:name=".MainActivity" android:windowSoftInputMode="adjustResize"></activity>iOS:在iOS上,软键盘的弹出默认不会引起视图的调整,除非开发者主动编写代码来处理键盘事件。开发者通常需要监听键盘的UIKeyboardWillShow和UIKeyboardWillHide通知,并据此调整视图,比如通过更改底部视图的约束或者滚动视图的contentInset。例子:NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)@objc func keyboardWillShow(notification: NSNotification) { // 调整视图以适应键盘}@objc func keyboardWillHide(notification: NSNotification) { // 恢复视图}2. 第三方键盘支持Android:Android系统在较早时候就开放了对第三方键盘应用的支持,因此Android上的应用需要能够适应多样的键盘布局和行为。iOS:iOS开始对第三方键盘支持是从iOS 8开始的,iOS平台上的应用也需要适应不同的键盘行为,虽然Apple提供了一套相对统一的界面风格。3. 输入法切换Android:Android设备通常会在软键盘上提供一个方便的输入法切换按钮,允许用户快速切换不同的键盘。iOS:iOS设备通常需要用户通过点击全局的键盘切换键或者长按地球标志键来切换输入法。4. API和框架支持Android:Android提供了InputMethodManager等API来管理软键盘的显示和隐藏。iOS:iOS通过UIKit框架中的UITextField、UITextView等组件与键盘进行交互,同时提供了相关的API来控制键盘的行为。5. 软键盘定制Android:Android允许开发者相对更深层次地定制软键盘,例如通过创建自定义输入法编辑器(IME)。iOS:iOS对于键盘的定制性较低,对于标准的键盘行为有着较严格的界面指南。总结:Android和iOS在软键盘弹出的处理方式上体现出的差异主要是由于两个平台的设计哲学和开发者工具的不同。Android提供了更多的自动调整视图的选项和对第三方键盘的支持,而iOS则要求开发者更加主动地管理键盘事件和视图调整。开发者在开发跨平台应用时,需要了解这些差异,并确保提供适合各自平台的用户体验。
前端阅读 192024年6月24日 16:43

介绍防抖节流原理区别以及应用

防抖(Debouncing)原理:防抖是一种控制方法,用于减少函数调用的频次。具体而言,当一个动作频繁触发时,防抖会确保该函数在指定的时间内只被执行一次。如果在这个延迟时间内再次触发了该动作,计时器会被重置,直到延迟时间结束后,才会执行函数。应用:举个例子,假设你在一个搜索框中键入文本,为了减少请求服务器的次数,你可以利用防抖技术。这意味着用户停止键入一段时间(比如500毫秒)之后,才会发送搜索请求。如果用户在500毫秒内继续输入,计时器会重置,直到用户停止输入后过了500毫秒,才会执行搜索。节流(Throttling)原理:节流是另一种控制方法,它同样用于减少函数调用的频次。与防抖不同的是,节流会强制函数以固定的时间间隔执行。即便动作频繁触发,函数也只会按照这个间隔执行,不会像防抖那样完全等待动作停止。应用:以滚动事件为例,如果我们需要在用户滚动页面时进行一些计算或更新界面元素,不加控制的话可能会导致性能问题。此时我们可以应用节流技术,比如设置每100毫秒执行一次更新操作,无论在这100毫秒内滚动事件被触发了多少次,更新函数都只会执行一次。区别触发频率:防抖函数会在动作结束后才执行,而节流函数会按照预定的频率执行,不管动作是否结束。目的:防抖通常用于确保某些计算密集型或高延迟的任务不会因为用户的高频操作而频繁执行,节流则用于控制函数的执行频率,保持性能的平稳。使用场景:防抖适用于诸如输入框内容自动完成、窗口大小调整(resize)、提交按钮(防止多次提交)等场景。节流适用于滚动加载、用户滚动监听、动画的持续触发等场景。总结来说,防抖和节流都是优化高频事件触发后的回调执行频率的技术。防抖是通过延迟执行来合并频繁的触发,而节流是通过减少执行的频率来降低触发的次数。根据具体的应用场景和需求选择合适的优化策略,可以显著提升应用的性能和用户体验。
前端阅读 942024年6月24日 16:43

Bootstrap 网格系统的工作原理是什么

Bootstrap 网格系统基于一个响应式的12列布局,它允许开发者快速地创建复杂的布局。这个系统使用一系列容器(containers)、行(rows)和列(columns)来布局和对齐内容。以下是它的工作原理的具体步骤:1. 容器(Containers)Bootstrap 网格系统首先需要一个容器(.container 或者 .container-fluid)来包裹网站内容。.container 类提供一个固定宽度且居中的容器,宽度取决于浏览器窗口的大小。.container-fluid 类提供一个全宽的容器,占据100%的视口(viewport)宽度。2. 行(Rows)在容器内,你需要使用行(.row)来创建一组横向的列。行作为列的直接父元素,用于创建列之间的水平组。行通过负边距来抵消列的内边距(padding),这样就可以保证内容贴近容器的边缘。3. 列(Columns)行内部,你可以添加多个列(.col-大小)来创建你的布局。列通过内边距(padding)来创建列内容之间的间隔。列的大小可以通过添加不同的类来指定,例如 .col-1 到 .col-12,表示占据1/12到全部(12/12)的容器宽度。Bootstrap 也支持响应式布局,可以通过添加如 .col-md-大小 的类来指定在不同尺寸的屏幕下列的表现。例如.col-md-6会在中等尺寸的屏幕(如平板电脑)上占据半个容器的宽度。4. 响应式断点(Responsive Breakpoints)Bootstrap 网格系统使用一系列的响应式断点,来适配不同尺寸的屏幕,这些断点包括以下几种:Extra small (xs) - <576pxSmall (sm) - ≥576pxMedium (md) - ≥768pxLarge (lg) - ≥992pxExtra large (xl) - ≥1200pxXXL (xxl) - ≥1400px开发者可以根据需要添加特定的类来定义元素在不同断点下的表现。例子:假设你想创建一个三列的布局,在中等尺寸屏幕以上都是三列并排显示,在手机屏幕上则堆叠显示,你可以这样做:<div class="container"> <div class="row"> <div class="col-md-4">Column 1</div> <div class="col-md-4">Column 2</div> <div class="col-md-4">Column 3</div> </div></div>在这个例子中,每个 .col-md-4 类的列占据4个网格单位,因此在中等尺寸的屏幕或更大尺寸上,三列将平分容器宽度。在小于768px宽的屏幕上,由于没有指定sm或xs类,列会自动堆叠,每列占据整行宽度。通过合理使用 Bootstrap 网格系统,你可以创建出既灵活又响应式的布局,以适应不同设备和屏幕尺寸。
前端阅读 352024年6月24日 16:43

React 中 JSX 是什么?

JSX是React框架中使用的一种语法扩展,它允许我们在JavaScript代码中编写看起来像HTML的结构。JSX提供了一种更为直观和声明式的方式来创建React元素树,并且让代码的结构清晰易懂。这种语法对于开发者来说非常直观,因为它使得编写UI组件时可以像编写HTML标签一样自然。以下是一个简单的JSX示例:const myElement = <h1>Hello, world!</h1>;这行代码定义了一个React元素,它将会被渲染为一个<h1>标签,包含文本"Hello, world!"。使用JSX的好处包括:可读性: JSX由于类似于HTML结构,使得组件的结构更加直观和易于理解。表现力: 它能够很好地表达UI组件的层次结构和属性。工具支持: 现代前端工具链(如Babel)支持JSX,并且可以将其编译为浏览器可以理解的JavaScript代码。值得注意的是,JSX并不是必须的;React也可以不使用JSX来创建元素。但是,大多数React开发者都倾向于使用JSX,因为它提供了一种更加方便快捷的开发方式。下面是一个不使用JSX的React元素创建示例,与上面的JSX示例作用相同:const myElement = React.createElement('h1', null, 'Hello, world!');可以看到,不使用JSX时,代码会更加冗长和不易读,这也是为什么JSX在React开发中变得如此流行。
前端阅读 452024年6月24日 16:43

说说em/px/rem/vh/vw区别

pxpx 是一个固定的像素单位,不依赖于父级元素的字体大小。例如,如果你设置一个元素的字体大小为14px,不论其父级元素的字体大小是多少,这个元素的字体大小都将是14px。emem 是一个相对单位,代表其父级元素的字体大小。例如,如果父级元素的字体大小是16px,那么1em = 16px,2em=32px,以此类推。remrem 也是一个相对单位,但与em不同的是,它是相对于根元素(html)的字体大小,而不是父元素。这意味着如果你的HTML元素的字体大小是20px,那么1rem将等于20px,不论这个元素在DOM树中的位置。vwvw 与vh类似,但它是相对于视窗宽度的单位。1vw等于视窗宽度的1%。总的来说,em和rem都是相对单位,可以提供更好的可伸缩性和响应能力。px则是固定单位,简单直接。vh和vw是依赖于视窗大小的相对单位,非常适合于创建响应式设计。vhvh 是一个相对于视窗高度的单位。1vh等于视窗高度的1%。例如,如果视窗高度是800px,那么1vh就等于8px。这种单位在创建全占满视窗的块级元素时非常方便,无论设备或浏览器窗口大小如何改变,元素总能占满整个视窗。
前端阅读 542024年6月24日 16:43

Promise 是如何实现链式调用的?

Promise 实现链式调用主要依赖于其返回一个新的 Promise 对象的特性。在 JavaScript 中,Promise 是一个处理异步操作的对象,可以在原调用位置以同步方式处理异步操作结果。下面是 Promise 的链式调用的基本实现:Promise 构造函数接收一个执行函数,执行函数接收两个参数:resolve 和 reject,分别用于异步操作成功与失败的情况。调用 Promise 对象的 .then 方法提供链式调用。.then 方法接收两个参数(都是可选的):onFulfilled 和 onRejected,分别在 Promise 成功或失败时调用。.then 方法也返回一个 Promise 对象,以便进行链式调用。如果 onFulfilled 或 onRejected 返回一个值 x,运行 Promise 解决过程:[Promise Resolution Procedure]。如果 onFulfilled 或 onRejected 抛出一个异常 e,Promise.then 的返回的 Promise 对象会被 reject 掉。如果 onFulfilled 不是函数且 promise1(前一个 promise) 成功执行,promise2(下一个 promise)成功处理 promise1 的 final state。如果 onRejected 不是函数且 promise1 失败,promise2 会拒绝 promise1 的原因。以下是一个示例:new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); // 第一步:创建一个 Promise 并执行一个异步操作}).then(function(result) { // 第二步:注册一个 onFulfilled 回调 console.log(result); // 打印:1 return result + 2;}).then(function(result) { // 第三步:链式调用 console.log(result); // 打印:3 return result + 2;}).then(function(result) { console.log(result); // 打印:5 return result + 2;});在这个例子中,每个 .then 调用后都返回一个新的 Promise 对象,这个新的 Promise 对象会立即执行,并在执行完毕后调用下一个 .then 注册的回调。通过这种方式,我们可以以同步的方式处理异步的结果,而这就是 Promise 链式调用的本质。
前端阅读 322024年6月24日 16:43

什么是混合应用 hybrid app?

混合应用(Hybrid App)是一种移动应用程序,它结合了原生应用以及网页应用的特点。它们通常是通过Web技术(如HTML5, CSS和JavaScript)来开发,并通过一个原生容器在移动设备上运行。下面是混合应用的几个关键特点:跨平台兼容性:混合应用的一个主要优势是能够使用一套代码基础适用于不同的操作系统,例如iOS和Android。这意味着开发者可以编写一次代码,然后通过桥接技术使其在不同的移动平台上运行。开发成本和时间:与分别为各个平台开发原生应用相比,混合应用可以显著降低开发成本和时间,因为它们共享一套代码库。易于更新:由于混合应用的内容可以像网页一样从服务器端获取,因此它们可以更频繁地更新而无需经过应用商店的审核。性能问题:混合应用通常比原生应用性能稍逊,因为它们需要通过Web视图(如WebView)来运行,这可能会导致比直接在原生平台上开发的应用更慢的性能。设备特性访问:虽然混合应用可以通过插件访问设备的原生特性,如摄像头、GPS等,但通常访问这些特性的效率比原生应用要低。举一个例子,假设我们开发了一款健身应用,该应用需要能够在iOS和Android设备上运行。通过选择混合应用开发模式,我们可以使用例如Cordova或Ionic这样的框架来开发应用,这样我们就能够编写一次代码,并将其部署到不同平台的应用商店。这样做省去了为每个平台单独开发应用的需要,节约了资源和时间。总之,混合应用是一种平衡方案,它结合了原生应用的高性能和设备特性访问能力以及Web应用的跨平台兼容性和开发效率。根据项目的需求和资源,混合应用可以是一个非常有吸引力的选择。
前端阅读 472024年6月24日 16:43

页面意外崩溃,这时候 JS 线程都已经崩溃了,如何传递通知呢?

在Web应用中,当JavaScript线程崩溃导致页面无法正常工作时,确实需要一种机制来通知用户或者开发者。在这种情况下,由于主JavaScript线程已经崩溃,传统的错误捕获如 try-catch或者 window.onerror事件监听可能都不起作用。但是,我们仍然可以采用以下几种策略:1. 使用Web WorkersWeb Workers运行在与主JavaScript线程分离的后台线程中。即使主线程崩溃,Web Workers可能仍然保持运行状态。因此,可以在页面加载时,启动一个监控Worker,用来检测主线程的心跳。如果主线程心跳停止(例如,可以通过设置定时消息来实现心跳),Worker可以尝试通知服务器或者更新UI来告诉用户发生了错误。2. 利用 window.onunload和 window.onbeforeunload事件可以在 window.onunload或 window.onbeforeunload事件中进行错误上报。如果浏览器支持 navigator.sendBeacon方法,即使在页面卸载的情况下也可以向服务器发送数据。虽然这两个事件不是为错误处理设计的,但它们可以用于在页面关闭时发送一些信息,可能包括崩溃通知。3. 使用Service WorkersService Workers作为一种在浏览器后台运行的脚本,可以用来拦截和缓存网络请求,推送通知等。如果设置了Service Worker,并且页面发生崩溃的情况下,它仍然能够接收到fetch请求或者推送事件,从而可以实现一定程度上的错误处理或状态报告。4. 外部心跳系统通过外部系统定时检测Web应用的状态,例如可以通过服务器定时发送请求到客户端,检测页面的响应。如果在预定时间内没有收到响应,或者收到错误响应,服务器则可以记录这类事件并采取相应的措施。5. 自动化监控和错误上报工具使用像Sentry、LogRocket这样的第三方错误监控服务,它们可以帮助在JavaScript出现未捕获异常时自动上报错误。虽然在某些崩溃情况下这些工具也可能失效,但它们仍然是一种有效的自动监控手段。6. 客户端存储在客户端使用如localStorage或sessionStorage,可以在检测到问题时写入状态标志。然后在页面重载或重新打开时检查这些标志,如果发现有异常状态,可以采取相应的通知措施。7. 使用全局异常处理器在可能的情况下,可以注册全局异常处理器 window.addEventListener('error', function() {...}),尽管在某些崩溃情况下可能不会被调用,但它提供了一个捕获异常并尝试通知的机会。示例举一个使用Web Workers进行心跳检测的简单示例:假设我们在主线程中有一个定期运行的函数,模拟心跳:function sendHeartbeat() { if (worker) { worker.postMessage('heartbeat'); }}// 定期发送心跳到WorkersetInterval(sendHeartbeat, 5000);
前端阅读 662024年6月24日 16:43

React 中 useRef 的使用方式和使用场景有哪些?

在React中,useRef是一个非常有用的Hook,它主要被用来访问DOM元素和保存跨渲染周期持久的可变数据。useRef返回一个可变的ref对象,其 .current属性被初始化为传递给 useRef的参数(initialValue)。返回的对象在组件的整个生命周期内保持不变。 使用方式首先,我们需要在组件内部导入 useRef:import React, { useRef } from 'react';然后可以使用 useRef来创建一个ref对象:const myRef = useRef(initialValue);其中 initialValue是ref对象 .current属性的初始值。使用 myRef.current可以访问或者修改这个值。使用场景有几种常见的场景适合使用 useRef:1. 访问DOM元素当你需要直接操作DOM节点时,比如获取输入框的值或者设置焦点,可以使用 useRef。function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // 直接访问DOM元素来设置焦点 inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Set focus</button> </> );}2. 保存任意可变值另一个使用场景是当你想要保存一个在组件的整个生命周期中都保持不变的值,这个值不会触发组件的重新渲染。function Timer() { const intervalRef = useRef(); useEffect(() => { const id = setInterval(() => { // 执行一些操作 }, 1000); intervalRef.current = id; return () => { clearInterval(intervalRef.current); }; }, []); // ...}在这个例子中,intervalRef被用来保存一个间隔定时器的ID,虽然这个ID会在不同的渲染周期间发生变化,但我们不希望这种变化触发组件的重新渲染。3. 跟踪上一个值useRef也可以被用来跟踪组件中的变量或状态的旧值。function Counter() { const [count, setCount] = useState(0); const prevCountRef = useRef(); useEffect(() => { prevCountRef.current = count; }); const prevCount = prevCountRef.current; // 在渲染时获取旧的值 // ...}这里 prevCountRef用于存储每次渲染后的 count值,可以在任何时候访问前一个状态的值,而不会触发组件的重新渲染。
前端阅读 2092024年6月24日 16:43

React 中 useContext 的使用方式和使用场景有哪些?

React 中的 useContext 钩子是一个用于让组件能够访问 React 上下文(Context)的工具。这个上下文设计用于共享那些对于一个组件树而言是“全局”的数据,如当前认证的用户、主题或首选语言等。 使用方式:首先,你需要创建一个 Context 对象。这可以通过 React.createContext() 完成,并且通常会在组件树的较高层级上完成。import React from 'react';// 创建 Context 对象const MyContext = React.createContext(defaultValue);一旦你有了一个 Context 对象,就可以使用 Context.Provider 组件包裹你的组件树的一部分,以在其下所有的组件中提供上下文数据。<MyContext.Provider value={/* 某些值 */}> {/* 组件树 */}</MyContext.Provider>然后,在组件树中的任何层级上,你都可以使用 useContext 钩子来访问该上下文。import React, { useContext } from 'react';function MyComponent() { // 使用 useContext 钩子获取上下文值 const contextValue = useContext(MyContext); return <div>{/* 使用 contextValue 做些什么 */}</div>;}通过使用 useContext 钩子,你不需要通过组件的 props 手动传递数据,可以直接访问上层组件通过 Context.Provider 提供的数据。使用场景:主题切换:当你想要在应用程序中切换主题时,你可以使用上下文来保持当前主题的状态,并在整个应用程序中轻松访问它。用户认证:在需要知道当前用户是否已经登录的多个组件中,你可以使用上下文来共享用户的登录状态和用户信息。国际化:你可以使用上下文来存储当前的语言设置,并在组件树中的任何地方访问它,以便于国际化。状态管理:在某些简单的情况下,你可以使用上下文来代替其他状态管理库(如 Redux),来存储和管理全局状态。示例:假设我们有一个需要在多个组件之间共享的用户认证状态,可以这样使用 useContext:// AuthContext.jsimport React, { createContext, useState } from 'react';// 创建上下文对象,初始值为 nullexport const AuthContext = createContext(null);// 创建一个提供者组件export const AuthProvider = ({ children }) => { const [authUser, setAuthUser] = useState(null); // 登录逻辑 const signIn = (username, password) => { // 假设验证逻辑在这里 const user = { name: 'Mock User', username }; setAuthUser(user); }; // 登出逻辑 const signOut = () => { setAuthUser(null); }; return ( <AuthContext.Provider value={{ authUser, signIn, signOut }}> {children} </AuthContext.Provider> );};// App.jsimport React from 'react';import { AuthProvider } from './AuthContext';import MyComponent from './MyComponent';function App() { return ( <AuthProvider> <MyComponent /> </AuthProvider> );}// MyComponent.jsimport React, { useContext } from 'react';import { AuthContext } from './AuthContext';function MyComponent() { const { authUser, signIn, signOut } = useContext(AuthContext); return ( <div> {authUser ? ( <div> <p>Welcome, {authUser.name}!</p> <button onClick={signOut}>Sign out</button> </div> ) : ( <button onClick={() => signIn('user', 'password')}> Sign in </button> )} </div> );}
前端阅读 522024年6月24日 16:43

如何基于 Promise.all 实现Ajax请求的串行和并行?

Ajax请求的串行实现对于串行执行多个Ajax请求,我们通常需要确保一个请求完全完成后,再执行下一个请求。这可以通过链式调用then方法来实现,也就是在每个Promise对象的then方法中启动下一个Ajax请求。function ajaxRequest(url) { return new Promise((resolve, reject) => { // 这里是Ajax请求的代码,成功时调用resolve,失败时调用reject const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onload = () => resolve(xhr.responseText); xhr.onerror = () => reject(xhr.statusText); xhr.send(); });}const urls = ['/url1', '/url2', '/url3']; // 假设我们有多个请求需要串行处理let promiseChain = Promise.resolve(); // 初始化一个已完成的Promiseurls.forEach(url => { promiseChain = promiseChain.then(() => ajaxRequest(url)).then(response => { console.log('请求完成:', response); // 这里可以处理每个请求的响应 });});// 最后可以在所有请求都完成后执行一些操作promiseChain.then(() => { console.log('所有请求都已串行完成。');});在这个例子中,每个请求仅在前一个请求的then方法中被调用,这确保了请求的串行执行。Ajax请求的并行实现要并行执行多个Ajax请求,可以使用Promise.all方法。Promise.all接收一个Promise对象数组,等待所有的Promise对象都成功完成后,它将返回一个新的Promise,这个新Promise将解析为一个结果数组,数组中的每个结果对应于原Promise数组中的每个请求。function ajaxRequest(url) { return new Promise((resolve, reject) => { // 这里是Ajax请求的代码,成功时调用resolve,失败时调用reject const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onload = () => resolve(xhr.responseText); xhr.onerror = () => reject(xhr.statusText); xhr.send(); });}const urls = ['/url1', '/url2', '/url3']; // 假设我们有多个请求需要并行处理const promises = urls.map(ajaxRequest); // 创建一个包含所有请求的Promise数组Promise.all(promises).then(responses => { console.log('所有请求都已并行完成。'); responses.forEach(response => { console.log('请求完成:', response); // 这里可以处理每个请求的响应 });}).catch(error => { // 如果任何一个请求失败,这里会捕获到错误 console.error('请求失败:', error);});在这个例子中,Promise.all并行地处理所有的Ajax请求,并在所有请求成功完成后,按照请求的顺序输出响应结果。如果任何一个请求失败,Promise.all会立即拒绝,并返回第一个遇到的错误。这两种方法是处理多个Ajax请求时常用的串行和并行模式。根据实际需求选择合适的方式。在实际面试中,可以根据面试官的要求提供更详细的代码实例或解释。
前端阅读 372024年6月24日 16:43

attribute和property的区别是什么

HTML文档中,attribute(属性)和property(属性)这两个术语经常使用,它们在不同的上下文中有不同的含义。 对象导向编程中的Attribute和Property在对象导向编程(OOP)的上下文中,attribute和property通常指代与对象关联的数据,但它们的概念和用途有所不同。Attribute (属性):在OOP中,attribute通常指的是对象的内部状态,它们是类定义中的变量。这些是对象的数据成员,用于存储对象的信息。例如,假设我们有一个 Car类,那么 color和 model可能是 Car对象的attributes。Property (属性):Property在OOP中通常指的是提供对attribute的访问的一种特殊方法,这些方法通常是通过getter和setter方法暴露的。Property允许封装attribute,从而可以在读取或修改attribute时添加附加的逻辑,如验证或事件触发。例如,Car类可能有一个 mileage属性,它通过getter方法 get_mileage()和setter方法 set_mileage(value)来访问和修改里程信息,而不是直接公开一个 mileage attribute。HTML文档处理中的Attribute和Property在HTML和Web开发的上下文中,attribute和property也有不同的含义。Attribute (属性):HTML attribute是HTML标签的一部分,用于在HTML文档中为元素定义特定的配置或行为。Attributes在HTML源代码中明确定义,例如 <input type="text" value="Hello">中的 type和 value。Attributes的值通常是在页面加载时定义的静态值。Property (属性):HTML元素在浏览器中表示为JavaScript对象,这些对象具有properties。这些properties与JavaScript运行时相连,表示DOM元素的当前状态。Properties可以在运行时动态改变,例如,通过JavaScript改变input元素的 value property,document.getElementById('myInput').value = 'New Value';。示例:考虑HTML中的一个 <input>元素,其初始HTML可能如下所示:<input id="myInput" type="text" value="Initial">这里,id、type和 value都是HTML attributes。当页面加载后,我们可以通过JavaScript访问 <input>元素的property,比如:var inputElement = document.getElementById('myInput');console.log(inputElement.value); // 输出: Initial在这个时候,value attribute和 value property都是“Initial”。然而,如果用户在input框中输入新的文本,比如“Hello”,那么 value property将改变,而 value attribute仍然为“Initial”。
前端阅读 312024年6月24日 16:43

前端如何解决移动端H5点击有300ms延迟?

在移动端H5开发中,300ms点击延迟是一个历史问题,它最初是由于早期智能手机的浏览器为了区分单击与双击(放大操作)而故意设置的。用户在触摸屏幕的时候,浏览器会等待大约300毫秒来判断用户是否要进行双击操作。这在现代的Web开发中通常是不必要的,并且会导致用户体验下降。以下是几种常见的解决方法:使用viewport meta标签:通过在HTML中添加viewport meta标签,可以禁用用户缩放,从而消除延迟。例如: <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">这段代码能禁止用户缩放页面,因此浏览器不需要等待判断用户是否要双击缩放,从而消除了300ms延迟。CSS touch-action属性:CSS的touch-action属性可以用来指定某个元素的一些触摸行为,例如: button { touch-action: manipulation; }这个属性设置后可以关闭某些默认的触摸操作,比如缩放和双击滚动,从而消除延迟。快速点击库(如FastClick):FastClick是一个流行的JavaScript库,用于在不支持Pointer Events的浏览器上消除点击延迟。它通过监听触摸事件来避开300ms延迟。直接在touchend事件触发后立即发出click事件,从而绕过浏览器自身的300ms延迟。使用方法很简单,只需要在页面加载完毕后实例化FastClick对象即可: if ('addEventListener' in document) { document.addEventListener('DOMContentLoaded', function() { FastClick.attach(document.body); }, false); }利用Pointer Events:Pointer Events是一个浏览器的新标准,它可以合并触摸、鼠标、笔和其他类型的输入事件。通过使用pointerevent,你可以避免300ms延迟。例如: element.addEventListener('pointerdown', function(event) { // 处理点击事件 });然而,并非所有浏览器都支持Pointer Events,因此需要检查兼容性或使用polyfill。使用touchstart或touchend代替click事件:你也可以直接监听touchstart或者touchend事件来代替click事件,这样可以避免等待300ms。但这种方法需要注意的是,touchstart和touchend可能会在用户没有意图点击时触发(例如滚动屏幕时),所以需要额外的逻辑来判断实际的用户意图。以上就是几种常用的解决移动端H5点击300ms延迟的方法。在实际开发中,可能需要根据具体情况和浏览器的兼容性选择最适合的方案。
前端阅读 592024年6月24日 16:43

为什么 React 元素有一个 $$typeof 属性

React 元素有一个 $$typeof 属性的原因主要是出于安全考虑,它是用来防止一种名为 XSSI(跨站脚本包含)的攻击。$$typeof 属性可以帮助确保在 React 应用中引入的数据是有效的 React 元素,而非恶意对象。在过去,攻击者可能会尝试利用 JSONP 等技术将恶意脚本插入到网站中。JSONP 是一种通过 <script> 标签载入跨域的 JSON 数据的方法。在 JSONP 中,由于 <script> 标签会执行载入的内容,如果返回的数据不是纯粹的 JSON 而是可执行的 JavaScript 代码,那么它就可能被用于执行恶意脚本。为了防止这种攻击,React 开发团队引入了 $$typeof 属性。每个通过 React.createElement 创建的元素都会自动添加这个属性。这个属性的值是一个特殊的符号(在支持 Symbol 的 JavaScript 环境中)或者一个特定的数字(在不支持 Symbol 的环境中)。这样,在 React 对元素进行处理之前,它可以检查元素是否具有正确的 $$typeof 值,确保该元素是由 React 创建的,从而防止可能的 XSSI 攻击。例如,如果我们通过网络请求获取了一个对象,并且直接将其作为子元素传递给 React 来渲染,React 会检查这个对象是否具有正确的 $$typeof 属性。如果没有,React 将不会将其作为 React 元素进行处理,这样就能防止恶意对象被当作 React 元素来渲染,从而避免潜在的安全风险。总之,$$typeof 属性是 React 为了增强应用安全而引入的一个机制,用于验证被处理的元素确实是通过 React 的正确方式创建的,从而避免了某些类型的安全漏洞。
前端阅读 332024年6月24日 16:43

JS内存泄露如何检测?场景有哪些?

JavaScript内存泄露是指在应用程序中不再需要使用的内存由于某些原因没有被释放或回收,导致可用内存逐渐减少,最终可能会导致应用程序或系统性能下降,甚至崩溃。 如何检测JS内存泄露?检测JavaScript内存泄露通常可以通过以下途径进行:浏览器开发者工具:大多数现代浏览器都提供了内置的开发者工具,可以用来监视内存使用情况。例如,Google Chrome的开发者工具中有"Performance"和"Memory"面板,允许开发者记录和分析网站的运行时性能和内存使用情况。堆快照(Heap Snapshots):通过浏览器的开发者工具,可以捕获堆快照,它会展示内存分配的静态视图。通过比较连续的堆快照,可以观察到哪些对象被分配内存后没有被释放。时间线记录(Timeline Profiling):这个工具可以帮助我们理解内存是如何随着时间的推移而增加的。我们可以使用浏览器工具的时间线功能记录一个时间段内的内存使用情况,寻找内存使用上升的趋势。代码审查(Code Review):定期进行代码审查以查找常见的内存泄露模式,如未取消的事件监听器、闭包的滥用、未清除的定时器等。内存泄露场景内存泄露可能出现在多种不同的场景中,以下是一些常见的场景:全局变量:意外地创建全局变量会导致这些变量不被回收,例如,忘记使用 var、let或 const关键字。事件监听器未移除:如果在DOM元素上添加了事件监听器,但在不需要时没有正确移除,它们会持续占用内存。闭包:不当使用闭包可能会导致父作用域中的变量无法被释放。DOM引用:JavaScript中的变量如果引用了已经从DOM中移除的元素,如果引用一直保持,那么这部分内存也不会被回收。定时器:设置了定时器(如 setInterval)而没有清除(clearInterval),可能会导致内部回调函数和相关变量长期占用内存。第三方库:使用的第三方库如果存在内存泄露,同样也会影响到使用它的应用程序。举个具体的例子:在开发一个单页应用时,我注意到随着页面的使用时间增加,页面的响应速度逐渐变慢。我使用Chrome开发者工具中的Performance面板进行了记录,发现内存使用量呈现持续上升的趋势。通过分析和比较不同时间点的堆快照,我发现存在一个大量DOM元素对应的监听器没有在元素被移除时一并清理。修复这个问题后,应用的性能得到了显著的提升。
前端阅读 412024年6月24日 16:43

React 的调和阶段, setState内部做了哪些动作?

在React中,setState 函数用于更新组件的状态,并触发重新渲染流程。调和(Reconciliation)阶段是React用来对比新旧虚拟DOM树差异,并决定如何高效更新真实DOM的过程。当你调用 setState 时,内部会触发以下动作:排队状态更新(Enqueuing State Update):setState 调用并不会立即更新组件的状态,而是将状态更新排队。这意味着React可能会累积多个 setState 调用,然后批量更新状态以优化性能。标记组件需要更新(Marking Component for Update):一旦状态被置入队列,React会将当前组件标记为“脏”(dirty),意味着组件的状态与显示的输出不同步,需要进行更新。批处理和合并状态(Batching and Merging State):React会将所有排队的 setState 调用进行批处理。如果有多个状态更新,React会将它们合并以减少不必要的渲染和调和操作。调用生命周期方法(Lifecycle Methods Invoking):在实际更新之前,React会调用 componentWillUpdate(在旧版本的React中)或 getDerivedStateFromProps 和 shouldComponentUpdate(在新版本中),这些生命周期方法允许开发者在渲染发生前执行额外的操作。创建新的虚拟DOM树(Virtual DOM Tree Creation):有了新的状态,React会创建新的虚拟DOM树,这个树反映了状态更新后的组件结构。对比新旧虚拟DOM(Diffing Virtual DOM Trees):接下来,React会使用调和算法对比新旧虚拟DOM树,确定哪些部分需要更新。这个过程产生了所谓的“差异”(diffs)。生成更新操作(Generating Update Operations):根据差异,React会生成一系列更新操作,这些操作将被应用到真实的DOM上以实现UI的最终变化。执行更新操作(Executing Update Operations):React会按照效率最高的方式批量执行这些更新操作,这可能包括添加、移动、更新或删除DOM节点。调用生命周期方法(Lifecycle Methods Invoking):在更新操作完成之后,React会调用 componentDidUpdate 生命周期方法,使开发者有机会执行需要DOM更新后才能进行的操作。例如,假设我们有一个计数器组件,其中包含一个按钮,当点击按钮时,它会通过 setState 增加计数值。React将按照上述步骤进行操作,确保界面反映了最新的计数状态,并以最高效的方式更新DOM。请注意,从React 16开始,引入了Fiber架构,它改变了内部工作原理,特别是更新过程可以被中断和恢复,以便更好地管理UI渲染的性能。但是,以上所述的基本步骤仍然适用。
前端阅读 442024年6月24日 16:43

如何实现Promise的resolve?

在JavaScript中,Promise 对象是异步编程的一种解决方案。一个 Promise 在创建时处于 pending(等待)状态,可以通过其 resolve 方法转变为 fulfilled(成功)状态,或通过其 reject 方法转变为 rejected(失败)状态。要实现 Promise 的 resolve,通常是在异步操作成功完成时调用。下面是一个简单的例子说明如何使用 Promise 的 resolve 方法:function asyncOperation() { // 创建一个新的Promise对象 return new Promise((resolve, reject) => { // 执行异步操作 setTimeout(() => { const operationWasSuccessful = true; // 假设这是基于异步操作结果的条件 if (operationWasSuccessful) { resolve('Operation successful'); // 如果操作成功,调用resolve并传递结果 } else { reject('Operation failed'); // 如果操作失败,调用reject并传递错误信息 } }, 1000); // 假设这个异步操作需要1秒钟 });}asyncOperation() .then(result => { console.log(result); // 打印成功结果 }) .catch(error => { console.error(error); // 打印错误信息 });在上述代码中,asyncOperation 函数返回一个新的 Promise 对象。在这个 Promise 的构造函数中,有两个参数:resolve 和 reject。这两个参数也是函数,它们被用来分别处理异步操作的成功和失败情况。在异步操作(这里使用 setTimeout 模拟)完成后,根据操作的结果调用 resolve 或 reject。如果异步操作成功(在这个例子中,我们假设 operationWasSuccessful 为 true),则调用 resolve 函数并传递结果消息 'Operation successful'。这将使得 Promise 对象的状态变为 fulfilled,并将结果传递给随后的 .then 方法的回调函数。如果异步操作失败,就调用 reject 函数并传递错误消息 'Operation failed'。这将使得 Promise 对象状态变为 rejected,并将错误信息传递给随后的 .catch 方法的回调函数。