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

前端面试题手册

什么是 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

JavaScript 如何使用 setTimeout 模拟实现 setInterval?

使用 setTimeout 模拟实现 setInterval 的基本思路是:在 setTimeout 的回调函数中再次调用 setTimeout,这样可以不断地延迟执行相同的操作,形成类似 setInterval 的效果。不过,值得注意的是,使用这种方法可以更精确地控制下一次执行的时间,因为你可以在当前任务结束后再设置下一次执行,这样就不会受到之前任务执行时间的影响。下面是一个模拟 setInterval 的示例函数 simulateSetInterval:function simulateSetInterval(callback, interval) { // 用来清除定时器的函数,在simulateSetInterval返回的对象上调用clear方法即可停止。 let timer = { clear: function() { clearTimeout(this.timeoutId); } }; // 这是一个递归函数,用于模拟重复间隔执行 const repeat = function() { callback(); timer.timeoutId = setTimeout(repeat, interval); }; // 开始执行 timer.timeoutId = setTimeout(repeat, interval); // 返回一个控制对象,可以用来停止间隔执行 return timer;}// 使用 simulateSetInterval 的例子let counter = 0;const exampleTimer = simulateSetInterval(() => { console.log('Hello World!'); counter++; if (counter >= 5) { exampleTimer.clear(); console.log('Timer stopped after 5 iterations.'); }}, 1000);在这个例子中,simulateSetInterval 函数接受一个回调函数 callback 和一个间隔时间 interval。函数内部定义了一个递归的 repeat 函数,它首先执行传入的 callback,然后使用 setTimeout 来延迟下一次执行 repeat 函数本身,从而达到周期执行的效果。返回的 timer 对象包含一个 clear 方法,可以用来清除定时器,停止进一步的执行。这种模拟实现方式的好处是,它更加灵活,可以根据任务的实际执行时间动态调整间隔,而 setInterval 在一些情况下可能会导致任务之间的间隔不准确,尤其是在某些任务执行时间较长时。使用 simulateSetInterval,下一次任务的开始时刻总是在当前任务完成后按照设定的间隔进行计时。
阅读 42·2024年6月24日 16:43

如何删除一个cookie值?

删除一个cookie的值可以通过多种方式实现,具体取决于您使用的编程语言和环境。以下是一些通用的方法和例子:在JavaScript中删除一个Cookie:要在客户端JavaScript中删除一个cookie,您可以将cookie的过期时间设置为过去的一个时间点。这样做会告诉浏览器此cookie已经过期,于是浏览器会删除它。function deleteCookie(name) { document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';}使用这个函数,您只需要传入您希望删除的cookie的名称即可。在HTTP响应中删除一个Cookie:如果您是在服务器端工作,比如使用Node.js的Express框架,您可以通过设置响应头来告诉浏览器删除一个cookie。res.clearCookie('cookieName');这行代码会设置一个响应头来清除名称为 cookieName的cookie。在PHP中删除一个Cookie:在PHP中,您可以通过设置一个负的过期时间来删除一个cookie。setcookie("cookieName", "", time() - 3600);上面的代码将 cookieName的过期时间设置为一个小时前,这会导致它被删除。在Python的Flask框架中删除一个Cookie:如果您在使用Flask框架,可以利用响应对象来删除cookie。from flask import make_response@app.route('/delete-cookie')def delete_cookie(): response = make_response('Cookie has been deleted') response.set_cookie('cookieName', '', expires=0) return response这段代码创建了一个响应对象,并使用 set_cookie方法将 cookieName的值设置为空字符串,并且将过期时间设置为0,这样浏览器会删除这个cookie。总结:通常删除cookie的方法是将其过期时间设置为过去的某个时间点,这样浏览器就会认为cookie已经过期,自动将其删除。不同的编程语言和框架有各自的函数或方法来实现这一点。重要的是要确保您发送正确的HTTP头信息,以便浏览器知道要删除的cookie。
阅读 41·2024年6月24日 16:43

React 项目中有哪些内存泄露的场景?

在React项目中,内存泄露主要是指应用程序持续占用不再需要使用的内存。这会导致应用性能下降,甚至崩溃。以下是一些典型的内存泄露场景:1. 组件卸载后的挂载状态更新例子:class MyComponent extends React.Component { state = { data: null }; componentDidMount() { someAsyncCall().then(data => { if (!this.isUnmounted) { this.setState({ data }); } }); } componentWillUnmount() { this.isUnmounted = true; }}在上面的例子中,someAsyncCall是一个异步操作,如果在异步操作完成前组件被卸载,而异步操作的回调仍试图执行this.setState,这可能会导致内存泄露。通过设置一个标志isUnmounted并在componentWillUnmount中标记组件已卸载,可以避免这种情况。2. 没有清理的定时器和数据订阅例子:class MyComponent extends React.Component { componentDidMount() { this.intervalId = setInterval(this.fetchData, 1000); } componentWillUnmount() { clearInterval(this.intervalId); } fetchData = () => { // Fetch data from API }}如果在组件卸载后,没有清理定时器,定时器依然会运行,这会阻止JavaScript引擎回收相关内存,导致内存泄露。在componentWillUnmount中清除定时器可以解决这个问题。3. 外部库的事件监听器未正确移除例子:class MyComponent extends React.Component { componentDidMount() { ExternalLibrary.on('data', this.handleData); } componentWillUnmount() { ExternalLibrary.off('data', this.handleData); } handleData = (data) => { // Handle the event }}如果忘记移除事件监听器,那么即使组件卸载了,事件监听器仍然存在,导致内存泄露。应该在componentWillUnmount中移除监听器。4. 闭包引用导致的内存泄露例子:function setup() { let largeData = new Array(1000).fill(new Array(1000).fill('data')); document.getElementById('button').addEventListener('click', function handleClick() { console.log('Button clicked'); });}在上面的例子中,即使按钮点击事件的处理函数handleClick并不需要使用largeData,但由于闭包的作用,largeData会被保留在内存中,除非handleClick被移除或者largeData变量被显式设置为null。5. 长时间运行的WebSocket连接如果组件与一个WebSocket服务建立了连接,并且在组件卸载时没有关闭连接,那么这个连接可能会持续存在并保持对组件的引用,从而导致内存泄露。防止内存泄露的一般建议:当组件卸载时,确保取消所有订阅和异步操作。使用useEffect的清理函数来处理功能性组件的副作用和相关的清理操作。对于外部事件监听器和定时器,确保在组件卸载时移除或清理它们。考虑使用WeakMap或WeakSet来缓存大对象,它们不会阻止垃圾回收。避免在组件状态中保存大量的数据,特别是如果这些数据在组件的生命周期内不再需要。使用工具(如Chrome开发者工具的Memory选项卡)定期对应用的内存使用进行分析和调试。
阅读 45·2024年6月24日 16:43

浏览器为什么要有同源限制?

同源政策(Same-origin policy)是一种重要的安全措施,它被设计为Web浏览器中的一个关键安全特性。它的目的是为了保护用户的在线安全,防止恶意网站访问或操纵另一个网站的敏感数据。同源限制确保了一个源(origin)中加载的文档或脚本只能与同一源的资源进行交互,而不能与其他源的资源进行交互。这里的源是指协议,端口和主机名的组合。以下是同源政策的必要性以及它如何提高网络安全的几个方面:防止跨站点请求伪造(CSRF):跨站点请求伪造是一种网络攻击,攻击者通过伪装成受信任用户的请求来利用用户已经认证的身份。如果没有同源限制,攻击者可以轻易地从自己的网站发起请求到另一个网站,并潜在地进行未经授权的操作。例如,用户登录到他们的银行网站,并在另一个标签页中访问了一个恶意网站。如果没有同源政策,恶意网站可以发起请求到银行网站,并可能执行一些不良操作,例如转账,而这些操作是在用户的权限下进行的。保护用户隐私:同源政策防止了一个网站的脚本访问另一个网站的数据,这样可以保护用户个人信息不被未授权的第三方获取。例如,如果你访问了一个电子邮件提供商的网页,同源政策将防止其他网站的脚本读取你的邮件内容。阻止DOM操作:如果没有同源限制,恶意脚本可以操纵其他网站的DOM(文档对象模型),从而可能篡改页面内容、捕获敏感信息或者在不知情的情况下在用户界面上执行操作。限制网络攻击范围:通过限制脚本只能与同一源的资源交互,同源政策有助于减少网络攻击面,因为攻击者不能简单地通过自己控制的网站操纵其他网站的资源。隔离潜在恶意文件:当网站加载第三方插件或广告时,同源政策防止这些第三方资源访问宿主网站的数据,因此即使这些资源被恶意利用,它们也不能直接访问宿主网站的敏感信息。控制Web API和资源的访问:一些现代Web API,如Web Storage、IndexedDB等,受同源政策的保护,只有在同源环境下才能被访问。这样可以确保只有在合适的安全上下文中才能访问和操作用户数据。促进安全的跨源通信:同源政策实施了严格的限制,但同时,Web也提供了如CORS(跨源资源共享)这样的机制来安全地允许跨源访问,其中需要明确服务器端的许可和合适的客户端处理。
阅读 27·2024年6月24日 16:43

Chrome 打开一个页面需要启动多少进程?分别有哪些进程?

在谷歌Chrome浏览器中,当您打开一个新的网页时,Chrome通常会启动几个进程。这是因为Chrome采用了多进程架构来提升性能和安全性。具体需要启动的进程数量可能会根据您的浏览器设置、扩展插件、打开的标签页数量以及Chrome的版本不同而有所变化。以下是Chrome打开一个新页面时可能会启动的主要进程类型:浏览器进程(Browser Process):这是主要的控制进程,管理Chrome用户界面的所有方面,包括地址栏、书签、前进和后退按钮等。它也负责文件的下载和安全策略。渲染进程(Renderer Process):每个标签页通常有自己的渲染进程(在默认情况下)。渲染进程负责一个网页的渲染,包括HTML、CSS、JavaScript的执行等。这种隔离机制可以确保如果一个网页崩溃,它不会影响到其他标签页。插件进程(Plugin Process):如果页面使用了插件(如Adobe Flash Player,虽然现在已经逐渐弃用),每个插件可能有其自己的进程。GPU进程(GPU Process):Chrome有一个专门的GPU进程,用来处理GPU加速的任务,例如3D CSS效果、WebGL内容等。扩展进程(Extension Process):如果您安装了Chrome扩展,每一个活动的扩展通常都会有自己的进程。例如,假设您打开了一个新的Chrome窗口,并在其中打开了一个包含多媒体内容且有多个扩展活动的复杂网页,那么您可能至少会看到如下的几个进程:一个浏览器进程一个渲染进程多个扩展进程(根据活动的扩展数量)一个GPU进程所以在这个假设中,打开这个页面至少需要4种类型的进程,具体进程数取决于活动扩展的数量。此外,从Chrome 67版本开始,Chrome推出了一种名为“Site Isolation”的安全特性,这会导致更多的进程生成,因为它会为来自不同站点的渲染器分别创建不同的进程,以加强站点之间的隔离。这意味着即使是单个标签页也可能因为加载了来自不同域的iframe而产生多个渲染进程。
阅读 34·2024年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值,可以在任何时候访问前一个状态的值,而不会触发组件的重新渲染。
阅读 66·2024年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> );}
阅读 209·2024年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请求时常用的串行和并行模式。根据实际需求选择合适的方式。在实际面试中,可以根据面试官的要求提供更详细的代码实例或解释。
阅读 52·2024年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”。
阅读 37·2024年6月24日 16:43