面试题手册

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

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

addEventListener 和 attachEvent 的区别是什么?

addEventListener 和 attachEvent 都是用于在 Web 开发中将事件监听器绑定到 DOM 元素上的方法,但它们之间存在一些关键差异:浏览器兼容性:addEventListener 是 DOM Level 2 Events 规范的一部分,它被所有现代浏览器(包括 IE9 及以上版本)支持。attachEvent 是微软为 IE8 及以下版本的浏览器提出的一个方法,不是标准的 DOM 事件处理方法。语法:addEventListener 采用以下语法: javascript element.addEventListener(event, function, useCapture); 其中 event 是事件名称(不带 on 前缀),function 是事件处理函数,而 useCapture 是一个布尔值,用于指定事件是在捕获阶段处理还是在冒泡阶段处理。attachEvent 采用不同的语法: javascript element.attachEvent(eventWithOn, function); 其中 eventWithOn 是带有 on 前缀的事件名称,function 是事件处理函数。事件流:addEventListener 允许你指定事件处理函数是在捕获阶段还是冒泡阶段被调用。attachEvent 只支持事件冒泡,不支持事件捕获。this 关键字:在 addEventListener 中绑定的事件处理函数里,this 指向绑定事件的元素。在 attachEvent 中绑定的事件处理函数里,this 指向全局对象 window,而不是绑定事件的元素。多个事件监听器:使用 addEventListener,你可以为同一个事件添加多个事件监听器,它们会按顺序执行。而 attachEvent 有可能会导致多个事件监听器的执行顺序不一致,而且同一个事件处理函数如果添加多次,也会被执行多次。移除事件监听器:addEventListener 配对的是 removeEventListener,可以用于移除事件监听器。attachEvent 配对的是 detachEvent,用于移除事件监听器,但需要确保传递给 detachEvent 的参数与 attachEvent 时的参数完全一致。由于 attachEvent 方法是一个非标准方法且仅在老版本的 IE 浏览器中可用,所以通常推荐使用 addEventListener,因为它是跨浏览器兼容的并遵循现代的 Web 标准。当需要支持老版本 IE 浏览器时,开发者可能需要编写额外的代码来兼容 attachEvent。
前端阅读 582024年6月24日 16:43

React setState 执行过程是同步的还是异步的?

React 的 setState 方法通常被视为异步的,这是因为 React 可以批量延迟更新来优化性能。当你调用 setState 时,React 会将传递的对象或函数排入更新队列,而不是立即更新组件状态。之后,React 将在其生命周期方法中以批处理的方式来决定何时实际更新状态和重新渲染组件。这种行为通常在事件处理、生命周期方法或任何由 React 控制的异步代码中表现得最为明显。例如:handleClick = () => { this.setState({ count: this.state.count + 1 }); console.log(this.state.count); // 这里可能不会立即反映更新后的状态 // ...其他逻辑}在上述代码中,console.log 执行时可能会打印出更新前的状态值,因为 setState 的调用并没有立即更新 this.state。然而,当 setState 被用在某些异步的上下文中,比如 setTimeout 或者原生事件处理时,它的表现就可能是“同步”的,因为 React 的批处理机制没有在这些场景中介入:setTimeout(() => { this.setState({ count: this.state.count + 1 }); console.log(this.state.count); // 这里将会立即反映更新后的状态}, 0);这里因为 setTimeout 跳出了 React 的控制,所以更新不再是批处理的,setState 将会同步地更新状态并重新渲染组件。为了以一种可预测的方式处理 setState 可能的异步行为,最佳实践是使用它的回调函数:this.setState((prevState) => { return { count: prevState.count + 1 };}, () => { console.log(this.state.count); // 这里将会在状态更新后执行});使用回调函数作为 setState 的第二个参数,可以确保在状态更新和组件重渲染之后执行特定的逻辑。这也解释了为什么在很多场合我们需要考虑到 setState 的异步特性。
前端阅读 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);
前端阅读 672024年6月24日 16:43

什么是BFC吗?BFC有哪些使用场景?

什么是BFCBFC (Block Formatting Context) 是 Web 页面的可视化 CSS 渲染的一部分,它是块级盒布局发生的区域,也是浮动元素与其他元素的交互限定区域。 如何创建 BFC在 CSS 中,以下几种方式可以创建 BFC:根元素或其它包含它的元素浮动元素 (元素的 float 不是 none)绝对定位元素(元素的 position 为 absolute 或 fixed)内联块(元素的 display 为 inline-block)表格单元格 (元素的 display 为 table-cell,HTML 表格单元格默认为 table-cell)表格标题(元素的 display 为 table-caption,HTML 表格标题默认为 table-caption)匿名表格单元格元素(元素的 display 为 table、table-row、table-row-group、table-header-group、table-footer-group 或 table-cell且是匿名的)overflow 值不为 visible 的块级元素弹性元素(display 为 flex 或 inline-flex 元素的直接子元素)网格元素(display 为 grid 或 inline-grid 元素的直接子元素)contain 值为 layout、paint 或 size 的元素多列容器(元素的 column-count 或 column-width 不为 auto,包括 column-count 为 1)column-span 为 all 的元素总是会创建一个新的 BFC,这样使得元素能跨越多列。14. BFC 有哪些规则BFC遵循一些特殊的规则,主要包括:内部的 Box 会在垂直方向一个接一个地放置。也就是说,在 BFC 中,块级盒子都是紧贴在一起的。垂直方向的距离由 margin 决定,两个相邻 Box 的 margin 会发生重叠。Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠。每个元素的左边,与包含块(Containing Block)的左边相接触。这句话的意思是:在 BFC 中,每一个 Box 的左边都贴紧包含块的左边。BFC的区域不会与 float box 重叠。BFC 是浮动元素和其它元素交互的界限,BFC 与浮动盒子不会重叠。计算 BFC 的高度时,浮动元素也参与计算。也就是说,如果 BFC 内部有浮动的元素,那么 BFC 的高度会扩大,以包含这个浮动元素,也就是说 BFC 是可以包含浮动元素的。6. BFC 的应用场景通过掌握 BFC,我们可以解决一些常见的 CSS 布局问题,如以下几种场景:避免外边距折叠在 BFC 中,处于同一 BFC 中的两个块元素的垂直外边距会发生折叠。如果你不想让这两个外边距折叠,你可以将元素放在不同的 BFC 中。清除浮动由于 BFC 可以包含浮动,我们可以用 BFC 来清除内部浮动,避免造成父元素高度塌陷。当父元素内部的子元素全部浮动后,它们脱离了正常的文档流,父元素就会失去高度,这个现象通常被称为高度塌陷。如果把父元素变为一个 BFC,那么它可以“感知”到浮动元素,从而包裹住浮动元素。这是因为 BFC 在生成时,会计算内部的浮动元素。为了将父元素变为一个 BFC,我们通常会使用 overflow 属性,如:overflow:auto 或者 overflow:hidden。.container { overflow: auto;}制作自适应两栏布局BFC 也可以用于构建布局。比如,常见的两栏布局,左栏固定宽度,右栏自适应:HTML 结构如下:<div class="container"> <div class="left"></div> <div class="right"></div></div>CSS 样式如下:.container { width: 100%;}.left { float: left; width: 200px; height: 200px; background-color: red;}.right { overflow: hidden; height: 200px; background-color: green;}在这个例子中,left 元素设置了浮动,然后 right 元素设置 overflow:hidden,这样 right 就基于 BFC,从而填充剩下的空间。综上,BFC 在处理一些布局问题和元素间的关系时非常有用,它是 CSS 中的一个重要概念,理解和掌握它可以帮助我们更好地应对布局难题。
前端阅读 332024年6月24日 16:43

webpack 的 loader 的作用是什么?如何自定义一个loader?

webpack 的 loader 的作用是什么?webpack 的 loader 有着非常关键的作用。在 webpack 打包过程中,loader 负责处理源代码文件,并且将其转换成 webpack 能够处理的模块。由于 webpack 本身只理解 JavaScript,所以当我们需要在 JavaScript 代码中导入 CSS、图片或者其他非 JavaScript 文件时,就需要用到 loader。例如,如果我们想在 JavaScript 中导入一个 CSS 文件,我们会使用 style-loader 和 css-loader。这两个 loader 分别做两件事情:css-loader 会处理 import 和 url() 等,就像 JavaScript 模块一样处理 CSS 文件。style-loader 会将计算后的 CSS 注入到页面的 <style> 标签中。其他常用的 loader 还包括:babel-loader:将 ES6+ 代码转换成兼容性更好的 JavaScript 代码。file-loader:处理文件导入,将文件输出到输出目录,并返回文件的 URL。url-loader:像 file-loader 但在文件大小低于指定的限制时会返回 Data URL。sass-loader:将 SASS/SCSS 文件编译成 CSS。等等。Loader 可以链式调用,每个 loader 将上一个处理的结果传递给下一个 loader,直到最后输出 webpack 能够处理的 JavaScript 模块。如何自定义一个 loader?自定义一个 loader 需要遵循一定的步骤:创建一个 JavaScript 函数:这个函数将接收源文件的内容作为参数。函数内部,可以使用任何 JavaScript 代码来处理输入的内容。函数最后需要返回处理后的内容,或者使用 this.callback 方法异步返回处理结果和可能的 map。遵守 loader 的规则:接收源文件的内容作为参数。不修改原始内容,返回一个新的字符串或者 Buffer。在需要时,可以通过 this.async 返回一个异步函数。使用 this.cacheable 标记 loader 的结果是否可缓存。通过 this.loaders 访问配置中的其他 loaders。导出这个函数:使用 module.exports 导出这个 loader 函数。在 webpack 配置中使用你的 loader:在 module.rules 中添加一个规则来使用你的 loader。例子:假设我们要创建一个简单的 markdown loader,将 markdown 转换为 HTML。const marked = require('marked');module.exports = function (markdown) { // 将 markdown 转换成 HTML const html = marked(markdown); // 返回 HTML 字符串 return `module.exports = ${JSON.stringify(html)}`;};然后在 webpack 配置文件中使用这个 loader:module.exports = { // ... module: { rules: [ { test: /\.md$/, use: [ { loader: 'html-loader' }, { loader: path.resolve('./path/to/your/custom-markdown-loader.js') } ] } ] } // ...};注意,这里我们使用了 html-loader 来处理我们自定义 loader 输出的 HTML 字符串,这样做可以确保 HTML 字符串也遵循 webpack 的模块系统。通过这种方式,开发者可以根据项目需求创建特定功能的 loader,以适应各种类型的文件处理场景。
前端阅读 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 的正确方式创建的,从而避免了某些类型的安全漏洞。
服务端阅读 1082024年6月24日 16:43

nodejs 如何使用 DllPlugin 动态链接库?

Node.js 中的 DllPlugin 及相关的 DllReferencePlugin 主要是用于改善构建时间和实现代码分离,在 Webpack 构建过程中使用。DllPlugin 用来打包出一个个独立的动态链接库文件,而 DllReferencePlugin 则用于在主应用程序中引用这些动态链接库。以下是使用 DllPlugin 的具体步骤:步骤 1: 创建 DLL 文件首先,你需要在项目中创建一个 webpack 配置文件专门用于构建 DLL。// webpack.dll.config.jsconst path = require('path');const webpack = require('webpack');module.exports = { entry: { vendor: ['lodash', 'react'] // 假设我们希望将 lodash 和 react 打包成一个 DLL }, output: { path: path.join(__dirname, 'dist'), filename: '[name].dll.js', // 输出的文件名 library: '[name]_library' // 全局变量名,其他模块会从此变量上获取到里面的模块 }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, 'dist', '[name]-manifest.json'), name: '[name]_library' }) ]};这个配置会将 lodash 和 react 打包成一个名为 vendor.dll.js 的文件,并生成一个 vendor-manifest.json 文件。步骤 2: 在主配置中引用 DLL然后,在你的主 webpack 配置文件中,你需要使用 DllReferencePlugin 来引用上一步中生成的 vendor-manifest.json 文件。// webpack.config.jsconst path = require('path');const webpack = require('webpack');module.exports = { // ...你的其他配置 plugins: [ // ...你的其他插件 new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./dist/vendor-manifest.json') // 引入 DLL 的 manifest 文件 }) ], // ...其余配置};步骤 3: 在 HTML 中引入 DLL 文件最后,你需要确保在应用程序加载前,先在 HTML 文件中引入这些 DLL 文件。例如:<!DOCTYPE html><html><head> <title>My App</title></head><body> <script src="./dist/vendor.dll.js"></script> <!-- 接下来是你应用程序的其它脚本 --></body></html>使用例子假设你有一个大型项目,每次构建都需要很长时间,因为第三方库例如 React、Vue 或 Lodash 并不经常更改,但它们每次都会被重新编译。通过使用 DLL,你可以将这些库预编译成静态资源,以便在开发过程中重复使用,从而减少了构建时需要处理的工作量,并加快了构建速度。使用 DLL 时需要注意的是,当你更新了 DLL 中的依赖项时,你需要重新构建 DLL 文件。同时,应当确保在生产环境构建中不包含 DLL 的引用,或者确保 DLL 是最新的,以避免因为版本不一致带来的问题。总的来说,DllPlugin 提高了开发效率,尤其是在大型项目和频繁构建的环境中,它可以显著减少构建时间并提升开发体验。
计算机基础阅读 1232024年6月24日 16:43

WebSocket和HTTP协议有什么区别?

WebSocket 和 HTTP 都是网络协议,它们在 Web 应用中承担着数据交换的角色,但是它们在设计上有着根本的差别,满足不同的应用场景。HTTP 协议HTTP(HyperText Transfer Protocol)是一种无状态的请求/响应协议,通常用于客户端和服务器之间的传统网页数据传输。它在 1991 年被发明,用来在互联网上传输超文本(HTML 文档),并随着时间进化到现在的 HTTP/1.1 和 HTTP/2 版本。特点:无状态: 每次请求之间相互独立,服务器不保存之前的请求信息。请求/响应模式: 客户端发送请求,服务器响应该请求,完成一次交互。短连接: 传统的 HTTP/1.1 协议在每次请求完成后都会关闭连接(虽然现在有持久连接的选项Connection: keep-alive)。不双向: 客户端发起请求,服务器响应,服务器不能主动向客户端发送消息。例子:一个典型的 HTTP 交互场景是,用户在浏览器中点击一个链接,浏览器发送一个 HTTP GET 请求到服务器,服务器处理请求并返回一个 HTML 页面,浏览器接收并显示给用户。WebSocket 协议WebSocket 是一个相对较新的协议,它在 2011 年成为国际标准。WebSocket 协议旨在通过单个长期连接提供全双工通信渠道,以支持实时和双向交互。特点:全双工: 客户端和服务器都可以随时向对方发送消息,无需等待响应。长连接: 一旦客户端和服务器之间的 WebSocket 连接打开,它将保持打开状态,直到任何一方显式关闭。低延迟: 数据包头部信息少,减少了发送消息的开销,适合需要快速通信的场景。例子:在一个实时聊天应用中,服务器可以在接收到一条新消息时立即将其推送给所有连接的客户端,而客户端也可以随时发送消息给服务器。所有这些通信都是在同一个 WebSocket 连接上完成的,并且可以非常迅速地进行。总结总得来说,WebSocket 通常用于需要服务器和客户端进行实时、双向和交互式通信的应用(如在线游戏、实时聊天室和协作工具),而 HTTP 更适合于传统的请求/响应模式的应用,比如网页浏览等。WebSocket 与 HTTP 的主要区别在于其持久的连接和低延迟的通信能力。虽然它们可以在相同的端口上运行(WebSocket 常常在 HTTP 的基础上握手建立连接,然后升级到 WebSocket 协议),但它们的设计目标和优化点大相径庭。
计算机基础阅读 932024年6月24日 16:43

谈一谈 HTTP 是如何数据传输

HTTP (HyperText Transfer Protocol) 是用于分布式、协作式、超媒体信息系统的应用层协议。它是互联网上数据通信的基础。其数据传输流程大致如下:建立连接:客户端到服务器的连接:当用户通过浏览器或应用程序请求一个网页时,浏览器首先需要与服务器建立连接。在HTTP/1.1协议中,每次请求通常都会创建一个新的TCP连接,而在HTTP/2中,多个请求可以在同一个连接上复用。发送请求:组建HTTP请求:客户端(如浏览器)会创建一个HTTP请求消息,这个消息包括请求行(包括请求方法如GET或POST,请求的资源路径,以及HTTP版本),请求头部(包括各种元数据如Accept, User-Agent, Host等),以及请求正文(通常在POST请求中携带数据)。发送请求到服务器:客户端将这个请求通过TCP连接发送给服务器。服务器处理请求:服务器解析请求:服务器接收请求消息,并解析请求行和头部,确定请求的资源和操作。处理请求:服务器根据请求类型,调用相关的服务或脚本,如数据库查询、文件读取等,来处理这个请求。发送响应:组建HTTP响应:服务器处理完请求后,会创建一个HTTP响应消息,包括状态行(HTTP版本,状态码,状态文本),响应头部(包括内容类型、内容长度、缓存控制等元数据)和响应正文(请求的资源内容)。发送响应到客户端:服务器通过TCP连接将响应消息发送回客户端。客户端接收响应:解析响应:客户端收到响应后,会解析状态码以了解请求是否成功,以及如何处理返回的数据。显示内容:如果是网页请求,浏览器会解析响应正文中的HTML、CSS和JavaScript内容,并在屏幕上渲染显示网页。关闭连接:断开连接:在HTTP/1.0中,通常每个请求/响应之后连接即关闭。而在HTTP/1.1中,引入了持久连接(keep-alive),允许在一个连接上发送、接收多个请求/响应。不过,最终这个连接也会在一定时间后或按照客户端或服务器的需求被关闭。例子:假设您在浏览器的地址栏输入了一个URL http://example.com 并按下了回车键:建立连接:浏览器通过DNS解析获得 example.com的IP地址,然后向这个地址的80端口发起TCP连接(HTTP默认端口)。发送请求:浏览器构建一个GET请求消息,请求行为 GET / HTTP/1.1,请求头部包含了浏览器类型、接受的内容类型等。服务器处理请求:example.com的服务器接收请求,解析路径 /,找到首页的内容。发送响应:服务器构建响应消息,状态行可能为 HTTP/1.1 200 OK,响应头部包含内容类型为 text/html,然后是响应正文,即HTML内容。客户端接收响应:浏览器接收到响应,解析HTML内容,并在屏幕上渲染出来。关闭连接:如果没有更多的请求,或服务器/客户端决定关闭连接,TCP连接将被关闭。
计算机基础阅读 932024年6月24日 16:43

基于 HTTP 网络层,前端能可以做哪些性能优化?

基于HTTP网络层的前端性能优化主要关注的是资源的加载和传输效率。以下是一些前端可以采取的性能优化措施:1. 减少HTTP请求次数合并文件:将多个CSS或JavaScript文件合并成单个文件以减少请求数量。精灵图(Sprite Maps):将多个小图标合并到一张图片中,通过CSS背景定位显示所需图标。内联图片(Data URIs):将小图像直接内嵌到HTML或CSS中,减少图片请求。2. 使用CDN(内容分发网络)分布式节点:CDN将静态资源缓存在全球的多个节点,使用户能从就近的服务器下载,降低延迟。缓存效率:CDN通常会对资源进行优化缓存,提高再次访问的速度。3. 缓存优化强缓存:通过设置HTTP头中的Cache-Control和Expires标识,使资源在客户端本地缓存,直到缓存过期。协商缓存:利用ETag和Last-Modified标识,仅当资源更新时才重新下载。4. 延迟加载和按需加载懒加载:对于非首屏图片、视频等资源进行懒加载,即滚动到可视区域后才加载。代码分割(Code Splitting):通过工具如Webpack实现模块的按需加载,避免单页应用加载过多不必要的JavaScript代码。5. 优化请求头和响应头压缩请求头:减少Cookie的大小和使用的数量,以及其他不必要的HTTP头部信息。Gzip/Brotli压缩:对文本资源进行Gzip或Brotli压缩,减少传输大小。6. HTTP/2和HTTP/3的使用多路复用:HTTP/2允许在同一个连接上并行传输多个请求响应,消除了队头阻塞问题。服务器推送:HTTP/2的Server Push可以提前发送资源,减少等待时间。QUIC协议:HTTP/3使用QUIC协议,减少连接建立时间,提升传输效率。7. 优化TLS/SSL握手TLS 1.3:使用更新的TLS版本可以减少握手过程中的往返次数。OCSP Stapling:通过服务器代替客户端查询证书状态,减少TLS握手的延迟。8. 优化资源的加载顺序关键请求优先:确保关键资源(如HTML、CSS、关键JavaScript函数)最先加载。异步加载:使用async和defer属性对非关键JavaScript脚本进行异步加载。9. WebP格式使用WebP:相比传统的JPEG或PNG格式,WebP格式在同等质量下具有更小的文件大小。10. Service Workers离线体验:Service Workers可以帮助缓存资源,并在无网络状态下提供访问。背景同步:Service Workers可以在后台进行数据同步或推送通知。举例来说,对于一个电商网站,我之前优化过的一个项目中,通过使用图片懒加载和WebP格式,网站首页的加载时间减少了约30%。利用HTTP/2的多路复用特性,也显著提高了资源加载的并行度,进一步减少了页面的完