乐闻世界logo
搜索文章和话题

前端面试题手册

前端模块规范有哪些?模块如何异步加载?

前端模块规范在JavaScript中,模块规范主要有以下几种:CommonJS:CommonJS是Node.js采用的模块规范,它通过 require函数来同步加载依赖的其他模块,通过 module.exports或 exports对象来导出模块。例子: // math.js function add(a, b) { return a + b; } module.exports = { add }; // main.js const math = require('./math'); console.log(math.add(1, 2));AMD (Asynchronous Module Definition):AMD是RequireJS实现的规范。它支持在浏览器环境中异步加载模块,使用 define函数定义模块,使用 require函数加载模块。例子: // 定义模块math.js define(function() { return { add: function(a, b) { return a + b; } }; }); // 加载模块 require(['math'], function(math) { console.log(math.add(1, 2)); });ES Modules (ESM):ECMAScript 2015(也称为ES6)引入了官方的JavaScript模块标准,这是现代前端开发中推荐使用的模块化解决方案。它支持静态导入和导出,也可以配合特定的语法进行动态导入。例子: // math.js export function add(a, b) { return a + b; } // main.js import { add } from './math.js'; console.log(add(1, 2));UMD (Universal Module Definition):UMD旨在提供一个跨平台的模块定义方式,使得一个模块可以同时在AMD和CommonJS环境中运行。例子: (function(root, factory) { if (typeof define === 'function' && define.amd) { // AMD环境 define([], factory); } else if (typeof exports === 'object') { // CommonJS环境 module.exports = factory(); } else { // 浏览器全局变量 root.myModule = factory(); } }(this, function() { return { add: function(a, b) { return a + b; } }; }));模块的异步加载使用AMD规范(如RequireJS):AMD规范设计之初就考虑了模块的异步加载。通过 require函数,我们可以异步加载所需的模块。使用ES Modules的动态 import():ES6模块支持使用 import()函数来实现模块的动态导入,这使得我们可以在代码执行时按需加载模块。例子: // 假设我们有一个math.js模块 // 动态导入ES模块 import('./math.js').then(math => { console.log(math.add(1, 2)); }).catch(error => { console.log('Error loading the module:', error); });Webpack等构建工具的代码分割(Code Splitting)功能:现代前端构建工具像Webpack提供了代码分割功能,可以将应用程序分割成多个chunks,并在需要时异步加载。例子:在Webpack中,可以通过动态 import()语法来创建所谓的"分割点"(split point)。 // Webpack代码分割示例 import(/* webpackChunkName: "math" */ './math').then(math => { console.log(math.add(1, 2)); });
阅读 31·2024年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。
阅读 32·2024年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 的异步特性。
阅读 58·2024年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 网格系统,你可以创建出既灵活又响应式的布局,以适应不同设备和屏幕尺寸。
阅读 94·2024年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 链式调用的本质。
阅读 54·2024年6月24日 16:43

什么是混合应用 hybrid app?

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

什么是 MVVM 模式?是为了解决什么问题?

MVVM 模式介绍MVVM 是 Model-View-ViewModel 的缩写,是一种设计模式,专门用于简化用户界面的事件驱动编程。它将用户界面(UI)的表示和业务逻辑分离开来,以达到更好的关注点分离(Separation of Concerns),从而使得开发和维护变得更加容易。MVVM 的组成部分Model(模型):代表的是数据和业务逻辑层。这是应用程序的核心,包含了数据的状态以及对数据的处理方法。View(视图):是用户界面层,显示数据并捕获用户行为。视图的任务是向用户展示信息,并接收用户的输入。ViewModel(视图模型):是视图的抽象,它负责处理视图的逻辑。它会监听模型的变化并更新视图,反之亦然,它也会处理视图的用户输入并可能影响模型。MVVM 解决的问题UI与业务逻辑分离:MVVM 通过引入 ViewModel,实现了界面逻辑与业务逻辑的分离。开发人员可以专注于业务逻辑,而设计师可以专注于界面设计,两者可以独立进行。双向数据绑定:ViewModel 通常实现了双向数据绑定,即当数据发生变化时,UI自动更新;用户界面变化(如用户输入),数据也会同步更新。这极大地简化了状态同步的复杂性。更易于测试:由于 ViewModel 不依赖于视图层的具体实现,因此可以在不涉及用户界面的情况下进行测试。提高代码的可维护性:将视图逻辑(如状态的显示和转换)移动到 ViewModel 可以减少视图代码的复杂性,使其变得更加整洁和可维护。提高可复用性:ViewModel 可以从视图中抽象出来,因此可以在不同的视图中复用。实例应用假设我们的应用中有一个用户表单界面,用户需要输入他们的信息。在不使用 MVVM 的情况下,视图代码可能会变得非常复杂,因为它需要处理数据的加载、显示、编辑、验证和保存等逻辑。在 MVVM 模式下,这些逻辑将会从视图中分离出来:Model:包含用户信息的数据结构。它可能还包含与数据存储和业务规则相关的逻辑。View:显示一个表单,用户可以在其中输入他们的信息。它不包含逻辑,只是简单的显示和收集用户输入。ViewModel:处理表单的显示逻辑,例如当用户点击保存时验证输入并更新模型。通过这种方式,视图不需要知道数据是如何被处理和验证的,而 ViewModel 中的逻辑可以被独立测试,不需要考虑用户界面的具体实现。
阅读 88·2024年6月24日 16:43