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

前端面试题手册

浏览器有哪些缓存策略?

浏览器缓存策略主要是用于提高网页加载速度,减少服务器压力以及节省带宽。以下是几种主要的浏览器缓存策略:强缓存(Strong Cache)Expires:这是HTTP/1.0中使用的头信息,用来指定资源到期的时间。如果请求的时间小于Expires的时间,浏览器会直接使用缓存中的资源,而不会向服务器发起请求。Cache-Control:在HTTP/1.1中引入,比Expires更灵活。常用的指令包括max-age(资源最大有效时间)、no-cache(每次都要向服务器确认)、no-store(完全不缓存),等等。若设置了max-age,且缓存时间未过期,则浏览器会直接使用本地缓存。协商缓存(Negotiation Cache)Last-Modified和If-Modified-Since:服务器在响应中加入Last-Modified标头指明资源最后修改时间,浏览器再请求时通过If-Modified-Since将这个值发送给服务器,由服务器判断资源是否有更新。ETag和If-None-Match:ETag是资源的唯一标识符,当资源有变动时ETag也会变。浏览器存储资源的ETag,并在下次请求时通过If-None-Match发送给服务器,以检查资源是否有更新。若在协商缓存中服务器确认内容没有更新,则服务器会返回304状态码,浏览器就会使用本地缓存;如果内容更新了,则会返回200状态码和新的资源内容。预缓存(Pre-Caching)Service Workers:通过Service Workers可以拦截网络请求,并动态地缓存或者恢复资源。这允许创建有效的离线体验,并且可以精细控制缓存策略。内存和硬盘缓存浏览器通常将资源缓存在内存或硬盘中:内存缓存:缓存存储在内存中,访问速度快,但只在浏览器会话期间有效。硬盘缓存:缓存存储在硬盘上,访问速度慢一些,但即使关闭浏览器后依然可以使用。举个例子,假设您访问了一个前端的网站,网站的CSS文件设置了强缓存,Cache-Control设置为max-age=3600,这意味着在接下来的一个小时内,如果您再次访问该网站,浏览器就会直接使用本地缓存的CSS文件,而不需要再次请求服务器,这样就能加快页面的加载速度。而对于网站的新闻部分,可能会使用协商缓存,每次访问时通过ETag或者Last-Modified信息检查内容是否有更新,以确保用户总是看到最新的内容,同时在内容没有更新的情况下减少不必要的资源传输。
阅读 143·2024年8月5日 12:50

Web 端应用如何做移动的适配

为了确保Web应用能够在移动设备上良好运行,我们需要关注几个关键点:1. 响应式设计(Responsive Design)响应式设计是适配移动端的核心。通过使用媒体查询(Media Queries)和相对单位(如百分比,em,rem等),我们可以确保网页布局和元素能够根据不同设备屏幕尺寸和分辨率自动调整。例如,Bootstrap框架提供了一系列预定义的响应式类,可以帮助开发者更快地实现响应式设计。例子:@media screen and (max-width: 768px) { .container { width: 100%; }}上面的媒体查询表明,当屏幕宽度小于或等于768像素时,.container 类的宽度将被设置为100%。2. 触控优化(Touch Optimization)移动设备通常是通过触控进行操作的,因此需要确保所有的交互元素(如按钮、链接、表单控件等)都足够大,以便手指点击,并有足够的间距,以防误触。HTML5为表单元素提供了适用于移动端的类型,如 <input type="email">,这会调用适合输入电子邮件的虚拟键盘。例子:<button class="touch-optimized-button">Submit</button>.touch-optimized-button { padding: 15px; margin: 10px; font-size: 18px;}3. 视口配置(Viewport Configuration)通过设置 <meta>标签中的 viewport属性,我们可以控制视口的尺寸和比例。这告诉浏览器如何控制页面的尺寸和比例,以匹配不同设备的屏幕。例子:<meta name="viewport" content="width=device-width, initial-scale=1.0">4. 性能优化(Performance Optimization)移动设备相比于桌面设备,其网络连接可能更不稳定,处理能力可能更弱。因此,要特别注意减少代码体积、图片大小,以及优化加载时间。使用压缩工具、图片懒加载、缓存策略等,都是性能优化的常见做法。例子:使用WebP格式的图片,它比传统的JPEG或PNG格式具有更好的压缩率,可以更快地加载。5. 使用框架和工具(Frameworks and Tools)现代前端框架和库,如React、Vue、Angular等,通常都有考虑到移动设备的适配。使用这些框架和它们的UI组件库(如Material-UI, Vuetify等),可以更快地开发出适合移动设备的界面。例子:import { Button } from '@material-ui/core';<Button variant="contained" color="primary"> Click me</Button>综上所述,要做好移动端的Web应用适配,需要综合考虑布局、交互、视口设置、性能优化以及合适的开发工具。通过以上这些方法,我们可以提供更好的移动用户体验,并确保应用在不同设备上都能够良好地运行。
阅读 171·2024年8月5日 12:50

React hook 使用需要注意哪些?

在使用 React Hooks 时需要遵循一些最佳实践和注意事项,以确保代码的可维护性与功能的正确性。以下是一些关键点:1. 遵守Hooks规则不要在循环、条件或嵌套函数中调用HooksHooks 应该始终在组件的顶层被调用,这样可以保证 Hooks 在每次渲染时都以相同的顺序被调用,这对于 React 的内部状态追踪机制非常重要。只在React函数中调用Hooks应该仅在React的函数组件或自定义 Hooks 中调用 Hooks。不要在普通的 JavaScript 函数中调用。2. 使用 useState时的注意事项初始化状态对于复杂的状态逻辑,可以通过传递一个函数给 useState 来惰性初始化,这样可以避免在每次渲染时重新创建初始状态。const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState;});状态更新函数的身份稳定setState 函数是身份稳定的,这意味着你可以在其他 Hooks 中安全地引用它,而不用担心它会在重新渲染时改变。3. 使用 useEffect时的注意事项清理副作用在 useEffect 中创建的订阅、定时器、监听事件等副作用,应该在返回的清理函数中进行清除,以避免内存泄漏。useEffect(() => { const subscription = props.source.subscribe(); return () => { // 清理订阅 subscription.unsubscribe(); };}, [props.source]);依赖列表的完整性确保依赖列表包含了所有外部作用域中被 useEffect 使用到的值,这样才能正确响应这些值的变化。如果忽略了依赖,可能会导致旧的闭包中的值被捕获,从而引发错误。useEffect(() => { function doSomething() { console.log(someProp); } doSomething();}, [someProp]); // 确保所有使用到的变量都被包含在依赖列表中4. 避免在 useEffect中进行不必要的操作节流和防抖如果 useEffect 中的操作非常昂贵,考虑使用节流(throttling)或防抖(debouncing)技术来减少操作的频率。5. 自定义Hooks代码复用当你发现需要在不同组件之间复用状态逻辑时,可以将其抽离成自定义 Hooks。这有助于减少代码冗余并增强逻辑的可维护性。例如,使用自定义 useForm Hook 来处理表单:function useForm(initialValues) { const [values, setValues] = useState(initialValues); const handleChange = (event) => { setValues({ ...values, [event.target.name]: event.target.value, }); }; return [values, handleChange];}6. 性能优化useMemo 和 useCallback在有必要的情况下,使用 useMemo 和 useCallback 来避免不必要的渲染或计算。useMemo 可以用来缓存复杂计算的结果,useCallback 可以用来缓存函数,这在将函数传递给子组件时特别有用,可以避免不必要的子组件重渲染。
阅读 154·2024年8月5日 12:50

封装一个可以设置过期时间的localStorage存储函数

实现一个具有过期时间功能的localStorage存储函数,需要定义一个函数,它会将数据和过期时间一起存储在localStorage中。 下面是一个简单的实现示例:/** * 设置带过期时间的localStorage * @param {string} key - 存储的键名 * @param {*} value - 要存储的值,可以是任何可序列化的数据 * @param {number} ttl - 过期时间(毫秒) */function setLocalStorageWithExpiry(key, value, ttl) { const now = new Date(); // 创建一个包含数据和过期时间的对象 const item = { value: value, expiry: now.getTime() + ttl, }; // 将对象序列化之后存储到localStorage中 localStorage.setItem(key, JSON.stringify(item));}/** * 获取localStorage存储的值 * @param {string} key - 存储的键名 * @returns {*} 存储的值或者当值不存在或过期时返回null */function getLocalStorageWithExpiry(key) { const itemStr = localStorage.getItem(key); // 如果没有找到对应的存储项 if (!itemStr) { return null; } const item = JSON.parse(itemStr); const now = new Date(); // 检查过期时间 if (now.getTime() > item.expiry) { // 如果已过期,删除存储并返回null localStorage.removeItem(key); return null; } // 如果未过期,返回存储的值 return item.value;}// 示例使用// 存储一个名为 'myData' 的数据,过期时间为1小时(3600000毫秒)setLocalStorageWithExpiry('myData', { a: 1, b: 2 }, 3600000);// 获取存储的数据const myData = getLocalStorageWithExpiry('myData');console.log(myData); // 如果还未过期,则会打印出存储的对象 { a: 1, b: 2 }在这个封装的函数中,我们通过 setLocalStorageWithExpiry函数存储数据的时候,会额外添加一个过期时间戳到对象中,并将该对象序列化后保存在localStorage里。当通过 getLocalStorageWithExpiry函数获取数据的时候,我们会先检查当前时间是否已经超过了存储时设置的过期时间戳,如果已经过期,则从localStorage中删除该项,并返回 null;如果未过期,则返回保存的值。
阅读 139·2024年8月5日 12:50

Webpack 的详细工作流程

Webpack是一个现代JavaScript应用程序的静态模块打包器,它主要的工作就是分析项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(如TypeScript等),并将其转换和打包为合适的格式供浏览器使用。 Webpack的工作流程主要分为以下几个阶段:初始化在Webpack启动后,它会从配置文件(默认是 webpack.config.js)中读取配置的参数,合并命令行传递过来的参数,形成最终的配置对象。编译(Compiling)Webpack开始编译整个项目。在这个阶段,Webpack会根据配置中的入口(Entry)开始递归解析所有依赖项。配置文件中的 entry属性用于定义入口文件,可能是一个或多个。构建(Building)针对每个依赖项,Webpack会使用对应的loader去处理文件,例如使用 babel-loader来处理JavaScript文件,css-loader处理CSS文件,file-loader处理图片等资源。Loaders的定义让Webpack能够去处理非JavaScript文件(Webpack本身只理解JavaScript)。生成(Output)经过加载和转换,Webpack会根据配置中的 output部分,把处理过的文件生成到文件系统中。通常是在项目的 dist目录下生成 bundle.js或者其他自定义名称的文件。优化(Optimizing)在生成出来的文件中,Webpack可以进行代码压缩、分割代码以实现按需加载等优化操作。这通过配置 plugins来实现,比如 UglifyJsPlugin、SplitChunksPlugin等。输出(Emitting)将所有的资源文件输出到指定目录下,此时,Webpack的工作就算是完成了。示例例如,您有一个项目,其入口文件是 src/index.js。Webpack会解析这个文件,并解析出这个文件依赖的模块。假如 index.js中依赖了 src/print.js,Webpack会继续解析 print.js的依赖。假设 index.js中还使用了ES6的语法和 .scss样式文件,那么在构建阶段,Webpack会使用 babel-loader将ES6代码转换为ES5代码,使用 sass-loader将SCSS文件转换为CSS文件,并且结合 css-loader和 style-loader将CSS代码注入到JavaScript中,这样就可以通过JavaScript将样式添加到DOM上。在优化阶段,可能会有插件去检查代码,去除未引用的代码(dead code),压缩混淆输出的文件,以减少文件大小和提高加载速度。最终,在输出阶段,Webpack会在 dist目录下输出 bundle.js,其中包含了所有的应用程序代码,以及所有的样式打包成的CSS代码。这就是Webpack的一个基本工作流程。它的强大之处在于可扩展性,通过配置文件和插件系统,可以适应各种复杂的项目需求。
阅读 82·2024年8月5日 12:48

如何做 CSS 的性能优化

CSS 性能优化是 web 项目性能优化中的重要部分。以下是一些策略来帮助优化 CSS 的性能:减少冗余代码为类或元素重复写入相同的 CSS 规则会浪费带宽和浏览解析时间。实用工具如 PurgeCSS 可帮助删除无用的 CSS。CSS 压缩CSS 压缩可以移除所有多余的字符,包括空格、换行符和注释。使用CSS 压缩工具如 CSSO 或 clean-css。使用 CSS 雪碧图CSS 雪碧图合并了一系列的小图片到一张大的图片中。这可以减少HTTP请求的数量,提高加载速度。CSS 对象模型(CSSOM) 和 渲染树浏览器通过解析 HTML 和 CSS 成 CSSOM 和 DOM ,然后结合他们形成渲染树。因此,应该尽量把 CSS 放在 HTML 文档的顶部,以加快渲染速度。避免使用过于复杂的选择器复杂的选择器可能会导致浏览器使用更多的资源来解析它们,优先使用类和 ID 选择器。使用 CSS 预处理器CSS 预处理器如 Sass 或 Less 可以使 CSS 更易于维护,同时可以使用变量,嵌套,混入 (Mixins) 等高级特性。使用硬件加速利用 GPU 来提供高效渲染,例如 transform 或 opacity。避免使用 @import@import 可能会导致更多的 HTTP 请求,使页面加载速度变慢。应该尽量使用命令行工具或构建系统的导入功能,以便在构建过程中进行文件合并。按需加载 CSS只加载需要立即使用的 CSS。缩小 CSS 的范围例如, instead of using * {margin: 0; padding: 0;}, 用类似 .myClass {margin: 0; padding: 0;} 更好。
阅读 136·2024年8月5日 12:48

Composition API 如何实现逻辑复用

在Vue.js的Composition API中,逻辑复用是通过使用可组合函数(composables)来实现的。可组合函数是可以封装和重用Vue组件逻辑的函数。Composition API引入了一种新的组织和重用组件逻辑的方式,它提供了更灵活的代码组织结构,使得函数的复用变得更加简单和清晰。要实现逻辑复用,你可以按照以下步骤操作:创建可组合函数(composables):你可以创建一个独立的JavaScript函数,这个函数利用Composition API中的ref, reactive, computed, watch, watchEffect等响应性API来创建和管理状态或逻辑。在组件中使用可组合函数:在Vue组件的setup函数中,你可以引入和使用这些可组合函数。这样,你就可以在多个组件之间共享和重用相同的逻辑,而无需复制代码。传递参数和返回值:可组合函数可以接受参数并返回一些响应式引用、方法或其他值,这使得它们可以与组件进行交互并根据组件的需要进行调整。下面我将通过一个简单的例子来说明这一过程:假设我们有一个用于处理用户信息的逻辑,这部分逻辑需要在多个组件中复用。我们可以创建一个名为useUser的可组合函数来封装这部分逻辑。// useUser.jsimport { ref } from 'vue';export function useUser() { const user = ref(null); const isLoading = ref(false); async function loadUser(userId) { isLoading.value = true; try { const response = await fetch(`/api/users/${userId}`); user.value = await response.json(); } catch (error) { console.error('Failed to load user', error); } finally { isLoading.value = false; } } return { user, isLoading, loadUser };}在上面的例子中,useUser函数创建了一个用户信息的响应式引用user和一个加载状态的响应式引用isLoading。它还提供了一个异步函数loadUser来加载用户数据。现在,我们可以在组件中使用这个可组合函数了:// UserProfile.vue<template> <!-- 使用user和isLoading渲染UI --></template><script>import { onMounted } from 'vue';import { useUser } from './useUser';export default { setup() { const { user, isLoading, loadUser } = useUser(); onMounted(() => { loadUser('123'); // 假设'123'是用户ID }); return { user, isLoading }; }};</script>在UserProfile.vue组件的setup函数中,我们引入并调用useUser可组合函数,并在组件被挂载时调用loadUser函数来加载用户数据。这样,user和isLoading就可以在组件的模板中直接使用了。这种方法不仅使得代码更加清晰和易于维护,而且还提高了代码的复用性。通过这种方式,我们可以将逻辑抽离出来,并在多个组件之间共享。
阅读 121·2024年8月5日 12:48

Composition API和Options API 之间的区别是什么

Composition API 和 Options API 是 Vue.js 框架中用于创建和组织组件的两种不同的API。Vue.js 是一个流行的前端JavaScript框架,用于构建用户界面和单页应用程序。下面我将详细说明它们之间的区别:Options APIOptions API 是 Vue.js 最初提供的接口,它是基于一个包含描述组件选项的对象的概念。这些选项包括了data、methods、props、computed、watch、lifecycle hooks等属性。这种API的特点是将组件的不同方面划分到这些选项中,代码按功能组织。例子:export default { data() { return { message: 'Hello Vue!', }; }, props: { user: String, }, computed: { normalizedUser() { return this.user.trim().toLowerCase(); }, }, methods: { sayHello() { alert(this.message); }, },};在这个例子中,data是组件的状态,props是外部传入的属性,computed是计算属性,methods是组件的方法。优点:易于理解和上手,特别是对于初学者。由于选项类型的组织方式,IDEs 和静态类型检查工具通常可以提供更好的支持。缺点:在大型和复杂的组件中,相互关联的逻辑会被拆分到不同的选项中,导致代码维护和理解上的困难。当组件变得庞大时,相同功能的代码可能散布在不同的选项中,难以追踪和组织。Composition APIComposition API 是在 Vue.js 3 中引入的,旨在解决 Options API 在构建大型应用时遇到的问题。它提供了更加灵活的方式来组织和重用代码。使用Composition API,开发者可以更容易地将组件逻辑基于功能划分和抽象成可复用的函数。例子:import { ref, computed } from 'vue';export default { setup(props) { const message = ref('Hello Vue!'); const normalizedUser = computed(() => props.user.trim().toLowerCase()); function sayHello() { alert(message.value); } return { message, normalizedUser, sayHello, }; }, props: { user: String, },};在这个例子中,setup函数是组件中所有Composition API逻辑的起点。通过导入ref和computed,我们可以定义响应式状态和计算属性。setup 函数返回的对象将定义组件的响应式状态和方法。优点:更好的逻辑复用和抽象,便于开发者根据功能组织代码,使得代码更加模块化。更容易控制变量的作用域和生命周期。更好地与TypeScript集成,提升类型推断的能力和开发体验。缺点:学习曲线相对较陡峭,特别是对于那些习惯于 Options API 的开发者。尽管它提供了更大的灵活性,但在小型项目或简单组件中可能会引入不必要的复杂性。结论Options API 和 Composition API 都是 Vue.js 提供的强大工具,它们各有优势。选择哪种API取决于项目的需求、组件的复杂性以及开发团队的偏好。Composition API 在处理大型项目和复杂组件时优势明显,而Options API 在小型项目或对于新手更
阅读 108·2024年8月5日 12:48

什么是 React 的受控组件和非受控组件?

在React中,受控组件(Controlled Components)和非受控组件(Uncontrolled Components)都是处理表单输入的方式,但它们处理数据的方式不同。受控组件(Controlled Components)受控组件是React的一种模式,在这种模式下,表单数据是由React组件的状态管理的。这意味着每次字段的值发生变化时,我们都会通过一个事件处理函数(通常是 onChange)来更新组件的状态。然后,组件的状态被用作输入字段的值,确保组件的状态是数据的唯一来源。示例:假设我们有一个受控的 <input>元素:class ControlledComponent extends React.Component { constructor(props) { super(props); this.state = {value: ''}; } handleChange = (event) => { this.setState({value: event.target.value}); } render() { return ( <form> <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> </form> ); }}在上面的例子中,<input>的值始终由 this.state.value决定,而且每当用户输入时,handleChange函数都会被调用,更新状态,因此界面显示的内容总是和状态同步。非受控组件(Uncontrolled Components)非受控组件是另一种模式,在这种模式下,表单数据是由DOM本身处理的,而不是由React状态管理。这就像传统的HTML表单工作方式。在非受控组件中,我们通常使用 ref来从DOM节点获取表单数据,而不是为每个状态变化编写事件处理函数。示例:下面是一个非受控组件的例子:class UncontrolledComponent extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } handleSubmit = (event) => { alert('A name was submitted: ' + this.inputRef.current.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" ref={this.inputRef} /> </label> <button type="submit">Submit</button> </form> ); }}在上面的例子中,<input>不是通过状态来控制其值,而是通过 ref来访问DOM节点获取其值。 总结受控组件允许你更好地控制表单的行为,因为组件的状态充当了数据的真实来源。非受控组件舍弃了对表单状态的即时控制,使得组件的代码更简洁,但可能会更难管理表单的状态,尤其是在复杂的表单交互时。在实际的开发实践中,受控组件通常是首选方法,因为它们更加符合React的数据流概念,使得状态的管理更加清晰和可预测。然而,对于一些简单的表单或者集成第三方DOM库时,非受控组件也可能是一个不错的选择。
阅读 147·2024年8月5日 12:48