一.Html + Css + Js篇
1.请说出LocalStorage、SessionStorage、Cookie、Session的区别
SessionStorage | Cookie | Session | ||
类型 | 客户端存储 | 客户端存储 | Http协议 | 服务端存储 |
生命周期 | 持久化存储 | 临时存储,关闭浏览器后清除 | 可以设置过期时间,也可以设置关闭浏览器后清除 | 依赖服务器配置,通常会话结束后消失 |
容量 | 约5M | 约5M | 约4kb | 取决于服务器 |
作用域 | 同源策略限制 | 同源策略限制 | 通过路径和域名设置作用域 | 服务器唯一ID |
交互 | 不主动携带 | 不主动携带 | 每次HTTP请求都会自动发送到服务器,服务器也可以设置或修改 | 通过session ID(通常存储在cookie中)关联客户端和服务器端的会话状态 |
用途 | 适合存储不敏感的用户偏好设置、主题选择等长期客户端数据 | 适用于存储会话相关的临时数据,如表单输入信息 | 主要用于认证、会话管理、用户跟踪等,以及一些小型的个性化设置 | 存储用户登录状态、购物车信息等需要在多个请求间保持的状态数据 |
存储格式 | 键值对 | 键值对 | 字符串 | 不固定 |
总之,LocalStorage和SessionStorage都属于客户端管理,Cookie和Session属于服务端和客户端之间的交互。
2.width:auto 和 width:100%的区别
- width: auto: 当设置为 auto,浏览器会自动计算元素的宽度。
对于非定位元素(static定位),宽度会根据元素的内容、内边距(padding)、边框(border)以及所在包含块的宽度进行自动调整,以适应内容的大小。这意味着如果内容增多,宽度会随之增大(在没有固定宽度约束的情况下),同时考虑其他影响宽度的样式,如 min-width, max-width 等。自动宽度允许元素适应其内容,而不是填满其父元素。 - width: 100%: 设置为 100% 意味着元素的宽度将占据其父元素内容区域的整个宽度。
这个值不包括父元素的内边距、边框或是外边距,纯粹基于父元素的宽度计算得出。如果父元素的宽度变化,子元素的宽度也会相应地调整。
如果子元素有额外的内边距、边框,这些不会被计算在 100% 内,可能导致总占用空间超过父元素宽度,这时候可能会出现滚动条或者溢出情况。
如果父元素没有明确的宽度(例如也是 auto 或未设定),100% 可能无法按预期工作,因为它是相对于父元素的实际宽度计算的。
可以根据这张图可以直观的感受区别:
3.请说明img 的 srcset 属性的作用
srcset
是 HTML <img>
标签中的一个属性,用于提供图片源集,使得浏览器可以根据设备的屏幕分辨率、视口尺寸或者像素密度等条件来选择最合适的图像资源进行加载。这一特性有助于实现响应式图像,提升网页性能和用户体验。如:
<img src="./imgs/banner_img1_03.png" srcset="./imgs/banner_img1_03.png 480w, ./imgs/react.webp 768w, ./imgs/vue.jpg 1024w"/>
在上述例子中,src
属性提供了默认的图片资源,用于不支持 srcset
的浏览器。第一张图片后面跟着的480w代表着表示小图适用于最大宽度480像素的设备,480w
指的是图片的宽度为480像素宽。
4.简要说明1rem、1em、1vh、1px各自代表的含义
- 1px:代表1个像素单位大小。
- 1em:代表当前元素字体fontSize的单位大小。默认情况下等于其父元素的字体大小。这意味着em值会随着父元素字体大小的变化而等比例缩放
- 1rem:rem是相对于根元素的fontSize的大小。不随嵌套层次变化,提供了一个统一的、基于文档的缩放基准。
- 1vh:视口高度的百分比,1vh等于视口高度的1%。随着浏览器窗口高度的变化,vh单位会相应调整,常用于实现响应式布局,尤其适合于依据可视窗口大小调整元素尺寸的情况。
5.style 标签写在 body 后与 body 前有什么区别?
按照 HTML 标准,<style> 标签应当放置在 head 中或者特定条件下而非 body 中。尽管现代浏览器对这种非标准用法较为宽容,但这仍违反了规范。
- 写在Body前面:浏览器在遇到 CSS 样式定义时会先加载并解析这些样式规则,然后在后续遇到 HTML 结构时应用相应的样式进行渲染。这样可以在元素首次渲染时就应用样式,避免了无样式内容的闪烁。
- 写在Body后面:浏览器会先解析 HTML 结构,当遇到位于
body
结尾的<style>
标签时,它必须停止当前的渲染过程,回头去应用新发现的样式规则。对于用户而言,这可能导致页面内容先以无样式或默认样式显示,之后突然改变外观,影响用户体验。特别是对于大型页面,这种做法会显著增加页面完全渲染完成的时间。
6.常见的响应式布局分别有哪些?在实际项目中如何选择?
- Flex布局:易于创建响应式界面,能够自动调整项目大小以填充容器,对齐和分配空间非常灵活。但对于某些特定的布局模式(如等分布局),在极端尺寸下可能需要额外的调整。
- Grid布局:提供了一种二维布局系统,非常适合创建复杂的网页布局,能够精确控制网格的行和列。但在老旧的浏览器中支持度有限,可能需要回退方案。
- 媒体查询:允许根据设备视口尺寸、屏幕定向等条件应用不同的CSS样式,非常灵活。但需要手动设定断点,维护成本较高,且可能产生大量重复或冗余的CSS代码。
- Bootstrap等响应式框架:提供预设的类和组件,快速实现响应式设计,减少开发时间。但可能引入不必要的CSS和JavaScript,影响页面加载速度,且定制化程度受限。
- 百分比布局:元素大小随容器变化,适用于简单的流式布局。但难以处理复杂的布局需求,尤其是需要精确控制元素位置和大小时。
- 子绝父相布局:结合相对定位的父元素和绝对定位的子元素,可以实现灵活的布局效果。但代码复杂,特别是在需要响应式调整时,可能需要大量媒体查询或JavaScript辅助。
在实际应用中,要根据项目特点、浏览器兼容性、开发效率、性能以及可维护性进行考虑。例如在复杂的网格布局项目中要考虑到Grid布局,但是还要综合考虑浏览器兼容性,进行评估。
7.响应式布局与自适应布局的区别是什么?
响应式布局 | 自适应布局 | |
核心特点 | 响应式布局侧重于使用 CSS 媒体查询 (@media 规则) 和灵活的网格系统来动态调整页面的布局结构和内容的展示方式。这意味着同一页面可以根据不同的屏幕尺寸呈现出完全不同的布局,比如列可能变成行,隐藏或显示某些内容,调整图片尺寸等 | 自适应布局主要是预先设计好几种固定的布局模板,每个模板针对特定的屏幕尺寸或设备类型。当页面加载时,会根据检测到的设备屏幕尺寸选择最合适的布局模板。这种布局方式不会像响应式那样流畅地改变布局结构,而是“跳跃”到最匹配的布局状态。 |
使用场景 | 适合内容丰富、结构复杂的网站,需要在多种设备上提供一致的用户体验,而不仅仅是调整内容的大小或简单堆叠 | 适用于内容相对固定、布局变化不那么频繁的网站,或对特定设备有专门优化需求的场景 |
技术实现 | 利用上述百分比单位、flexbox、grid布局以及媒体查询和Bootstrap来实现 | 通常依赖于服务器端检测、CSS媒体查询或JavaScript来判断设备类型或屏幕尺寸,然后加载相应的CSS样式或HTML结构 |
8. 同步和异步的区别
同步 | 异步 | |
执行顺序 | 同步代码按顺序执行,每个任务必须等待前一个任务完成后才能开始。这意味着程序的执行是线性的,后面的代码会阻塞等待前面的代码执行完毕。 | 异步使得多个任务可以并发执行,不必等待一个任务完成后才开始下一个,这对于提升性能和用户体验至关重要,尤其是在处理I/O密集型操作时。 |
阻塞性质 | 在执行同步操作时,整个线程会被阻塞,直到该操作完成。如果某个任务耗时较长(如I/O操作或网络请求),则会导致UI冻结,影响用户体验。 | 异步代码允许程序在等待某个操作(如文件读取、网络请求)完成的同时继续执行其他任务,提高了程序的响应性和效率。 |
结果获取 | 按顺序直接获取 | 异步操作通常通过回调函数、Promise对象或async/await语法来管理。一旦异步操作完成,会通过回调或Promise的resolve/reject通知调用者,或在async函数中使用await等待结果。 |
9.script 标签中 defer 和 async 的区别
defer | async | |
执行顺序 | 保证脚本按照它们出现在文档中的顺序执行 | 不保证脚本在文档中的顺序执行 |
页面加载 | 不阻塞页面加载 | 不阻塞页面加载,但是由于async不等待文档解析完毕,所以可能更快执行 |
使用场景 | 适用于那些不需要立即执行,且执行顺序重要的脚本,比如多个脚本之间有依赖关系。 | 适用于那些不依赖于文档解析的脚本,且相互之间也没有依赖关系,比如第三方统计脚本或分析脚本。 |
10.简述防抖和节流的作用和区别
- 防抖:防抖技术确保一个函数在频繁触发时,只有在最后一次触发后的指定延迟时间过后才执行一次。如果在延迟期间又有新的触发,则重新开始计时。这有助于减少不必要的函数调用,比如在网络请求频繁变动的输入框中,仅在用户停止输入一段时间后再发送请求。
- 节流:节流技术确保一个函数在频繁触发时,无论触发多么频繁,都保证在指定的时间间隔内只执行一次。这可以用来限制函数的执行频率,例如限制滚动事件的处理函数每秒只执行一次,保持动画流畅而不占用过多计算资源。
总之,防抖就是在一定时间后执行操作,而节流是在一定时间内只执行一次,以下是代码示例:
防抖:
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 使用防抖函数
const handleInput = debounce(function(value) {
console.log("查询输入:", value);
}, 300);
// 假设这是用户连续输入的模拟
inputElement.addEventListener('input', function(e) {
handleInput(e.target.value);
});
节流:
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// 使用节流函数
const handleScroll = throttle(function() {
console.log("滚动事件处理");
}, 200);
// 绑定滚动事件
window.addEventListener('scroll', handleScroll);
11.请简述call()、apply()、bind()之间的区别
- call():接受的第一个参数表示this要指向的对象,其余参数表示调用函数需要传入的参数,返回调用函数的返回结果,属于立即执行函数。
- apply():接受两个参数,第一个参数表示this要指向的对象,第二个参数表示调用函数所传入参数所组成的数组,返回调用函数的返回结果,属于立即执行函数。
- bind():接受一个及其以上的参数,和call()一致,但是返回结果是一个函数,并且不会立即执行。
bind()返回的函数使用new作为构造函数时,绑定的this值会失效,this指向的是实例对象,但是传入的参数会生效。这是因为当使用
bind()
方法绑定上下文后,再通过new
关键字调用这个函数,尽管bind()
尝试固定this
的值,但new
操作符会覆盖这一绑定,强制将this
指向新创建的实例。这是因为new
操作符的核心特性之一就是创建一个新的对象并将其作为this
上下文传递给构造函数。例如下面的代码:
function Person(name) {
this.name = name;
console.log(this);
}
Person.prototype.sayName = function() {
console.log(this.name);
};
const boundPerson = Person.bind({ name: "bound" });
const personInstance = new boundPerson("instance"); // 这里的this不再是bind的第一个参数,而是新创建的实例
console.log(personInstance.name); // 输出 "instance"
12.用css画三角形
普通三角形:
.triangle {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid red;
}
直角三角形:
.triangle {
width: 0;
height: 0;
border-left: 50px solid red;
border-right: 50px solid transparent;
border-bottom: 100px solid transparent;
}
二.Vue
1.为什么避免v-if与v-for一起使用?在Vue2与Vue3中的差异是什么?
原因:如果同时使用v-if和v-for的话,v-for的优先级高于v-if,导致v-for的循环元素会反复判断,例如循环10次数据,每一次循环都会做一次判断,做10次循环每一次循环都会判断10次,那么10次循环就会判断1000次,很浪费性能。
解决办法:
- 在外层嵌套<template>标签:在外层嵌套<template>标签(不会生成真实的dom结点)进行判断,在内部做循环。
- 利用computed属性:利用computed属性计算过滤需要渲染的数据后再循环渲染
<!--外层嵌套-->
<template v-if="isShow">
<p v-for="item in items">
</template>
区别:
在vue3中不需要考虑该问题,因为已经将v-if的优先级改为高于v-for
2.简述 pinia 与 vuex 的区别
pinia | vuex | |
架构设计 | 设计更为简洁和模块化,每个 store 都是一个独立的实例,这使得状态管理更加解耦。Pinia 没有 mutation 的概念,直接通过 actions(支持同步和异步)来修改 state。 | 采用集中式的架构,有一个全局的 store 对象来管理应用的所有状态。Vuex 强制使用 actions 进行异步操作,并通过 mutations 修改状态,以确保状态变更的可追踪性。 |
初始化和使用 | 更加直观,直接在 store 文件中定义 state、getters、actions,使用时可以直接从 setup 函数中导入并使用,支持 TypeScript 更友好。 | 需要在创建 store 时定义状态、mutations、actions 和 getters,使用时通过 mapState、mapGetters、mapActions 等辅助函数来绑定状态和方法。 |
状态持久化 | 虽然也能通过插件实现状态持久化,但默认情况下,如果需要将状态保存到本地存储,配置相对繁琐一些,不像 Vuex 有成熟的社区解决方案。 | 提供插件机制来实现状态持久化,如使用 vuex-persistedstate 插件将状态保存到 localStorage。 |
API和灵活性 | 提供了更现代和简洁的API,强调易用性和开发体验,如自动的类型推导,直接在setup中使用 store 等特性,使得状态管理更加直观。 | API 较为成熟和稳定,但较为复杂,对于初学者来说学习曲线可能较陡峭。 |
维护和支持 | 虽然不是Vue官方库,但由Vue的作者尤雨溪维护,得到了社区的广泛认可和积极发展,尤其在Vue 3的应用中推荐使用。 | 是 Vue 官方提供的状态管理库,长期得到维护和支持。 |
3.Vue中,watch和computed差异
watch | computed | |
目的和使用场景 | 主要用于监听数据变化并在变化时执行某些操作,可以处理异步操作,适用于需要在数据变化时执行副作用或复杂逻辑的场景,比如发起网络请求、更新DOM等。 | 主要用于计算属性,基于依赖的数据自动计算并缓存结果,当依赖的数据发生变化时,会自动重新计算。适合于不涉及异步操作且结果可被缓存的场景,比如过滤列表、计算总价等。 |
缓存机制 | 不提供缓存,每次依赖数据变化时都会触发回调函数执行,即使新旧值相同也不例外。 | 具有缓存机制,只有当依赖的数据发生变化时,计算属性才会重新计算,否则直接返回缓存的结果,这使得计算属性在多次访问时能提高性能。 |
执行时机 | 默认情况下,首次数据绑定时不会执行,仅在数据变化时执行。可以通过设置immediate: true 使其在初始绑定时立即执行一次。 | 在初始化时和依赖数据变化时计算,计算属性的值在读取时计算。 |
异步支持 | 支持异步操作,可以在回调函数中执行异步逻辑,如发送网络请求。 | 不直接支持异步操作,因为它的值是基于依赖计算得出的,并且需要能够被缓存。 |
返回值 | 接收一个函数作为回调,该函数有两个参数(新值和旧值),或者可以定义为一个对象,其中键是需要观察的表达式,值是对应回调函数。 | 通常返回一个值,可以是一个函数(getter),也可以是包含getter和setter的对象。 |
4.keep-alive 适用于什么应用场景?
-
列表与详情页面之间的切换:在从列表页面导航到详情页面,然后再返回到列表页面时,使用
keep-alive
可以保留列表页面的状态,包括滚动位置、筛选条件等,无需重新加载数据,提供流畅的用户体验。 -
tabs 切换:在一个含有多个 tab 的界面中,每个 tab 下可能是一个独立的组件。使用
keep-alive
可以让切换出去的 tab 组件保持活跃状态,当用户再次切换回来时,可以迅速恢复之前的状态,避免重新加载数据和渲染。 -
性能优化:对于一些数据量大、渲染成本高的组件,使用
keep-alive
可以显著减少因频繁切换导致的性能开销,尤其是在移动设备上。 -
状态保留:在需要保留用户输入或组件内部状态的场景下,使用
keep-alive
可以避免用户在返回时丢失已填写的信息或组件的临时状态。
5.Vue中,slot是什么?slot使用场景有哪些?
slot
是一个用于组件间内容插槽的概念,它允许你在一个组件内部插入外部的模板内容。简而言之,slot
充当了组件内部的一个占位符,使得父组件可以向子组件传递内容,这样可以让组件更加灵活和可复用。slot分为匿名slot、具名slot、作用域slot
应用场景:
-
页面布局组件:如头部、侧边栏、主要内容区域等,可以使用具名slot来区分不同部分的内容,使得布局组件高度可定制。
-
可复用组件:如对话框、卡片、列表项等,通过slot传递具体内容,使得组件能够在不同场景下复用,同时保持内容的多样性。
-
封装第三方组件:在封装第三方UI库的组件时,可以使用slot来允许用户自定义部分内容,如按钮的文字、图标等。
-
动态内容插入:当组件需要根据外部条件展示不同内容时,可以使用slot传递这些动态内容。
-
表单组件:构建表单时,可以使用slot来插入自定义的表单项,同时通过作用域插槽传递表单项的验证信息、错误提示等。
6.Vue 项目中,可以在哪些生命周期内调用异步请求?实际项目中如何选择?
可以在created()和mounted()两个生命周期函数内调用异步请求。在实际的项目中,要根据实际情况进行选择。一般情况下,优先选择created()进行调用,因为它可以更快地开始数据加载过程,提升用户体验。特别是当数据对页面初次渲染至关重要时,这能确保用户看到内容的速度最快。但如果异步请求的结果依赖于DOM元素(例如获取某个元素尺寸后发起请求),则应当在mounted中发起请求。
三.优化和其他
1.为确保前端展示数据的实时性,可以使用哪些技术?更推荐使用那种?
-
轮询:客户端定期向服务器发送请求,询问是否有新的数据。这种方法简单易实现,但可能会造成不必要的请求,增加服务器压力,且实时性受限于轮询间隔。
-
长轮询:客户端发送请求后,服务器不立即响应,直到有新数据产生或超时才返回响应。之后客户端立即发起新的请求,形成连续的连接。这种方式减少了无效请求,提高了实时性,但服务器仍需为每个连接保持资源,且在高并发下可能对服务器造成压力。
-
WebSocket:建立在TCP之上的全双工通信协议,允许服务器主动向客户端推送数据。一旦连接建立,双方都可以随时发送数据,实现实时双向通信。WebSocket是目前实现真正实时数据传输的最佳选择,特别适合需要低延迟、高频率交互的应用场景。
-
SSE:一种让服务器向浏览器发送实时更新的技术,只支持单向通信(服务器到客户端)。相对于WebSocket,实现更简单,对现有HTTP基础设施依赖较小,适合于只需要服务器推送数据到客户端的场景。
在实际应用中,一般对于大多数需要实时数据更新的现代Web应用,WebSocket 是最推荐的技术,因为它提供了真正的双向通信,低延迟,且连接持久,能够满足大部分实时交互的需求。WebSocket特别适合聊天应用、在线游戏、实时交易系统等场景。如果实时性要求不是极高,或者实现复杂度和资源消耗是主要考虑因素,可以考虑使用 Server-Sent Events (SSE),特别是在只需单向数据流的应用中,它比WebSocket更简单易用,且对服务器资源的消耗相对较低。而对于简单的实时更新需求,且对旧浏览器兼容有要求的场景,长轮询 也是一个可行的选择,虽然不如WebSocket高效,但在特定条件下可以作为一种折中方案。
2.若一个页面上有大量的图片,加载很慢,你有哪些方法优化这些图片的加载?
- 使用webp格式:可以把图片转换为webp格式的图片,webp格式图片比jpg、png图片体积小很多,加载更快。
- 懒加载(Vue Lazy Load): 使用Vue的懒加载插件,如
vue-lazyload
,来实现图片的按需加载。在Vue组件中,通过v-lazy
指令绑定图片URL,图片只有在接近可视区域时才会开始加载。 - 图片预加载(Vue Router 导航守卫): 利用Vue Router的导航守卫(如
beforeRouteEnter
或全局的守卫),在路由跳转前预加载即将访问页面的关键图片。 -
缓存策略: 在Vue项目的服务器配置中设置合理的缓存策略,确保浏览器能够有效缓存图片资源,减少重复请求。
-
SVG图标: 将图标和简单图形替换成SVG格式,并直接在Vue组件的模板中使用,避免额外的HTTP请求。
3.Vue项目中,你尝试过哪些针对性能方面的优化?
-
组件懒加载: 使用Vue的异步组件特性或第三方库(如Vue Router的懒加载功能)来实现路由组件的懒加载。这样,只有当用户访问到特定路由时,相关组件才会被加载,减少首次加载时的资源体积。
-
图片懒加载: 利用如
vue-lazyload
这样的库实现图片懒加载,确保图片只在即将进入视口时才开始加载,减少页面初次加载时间。 -
优化Vue模板性能:避免在
v-for
中使用复杂的计算属性,尽量将计算移至循环外。使用key属性来帮助Vue识别列表中元素的身份,提高列表更新的性能。减少不必要的DOM操作,使用v-once
指令缓存静态内容。 -
组件缓存: 利用
<keep-alive>
组件缓存不常变化的组件实例,减少不必要的渲染和初始化开销。 -
事件监听器的优化: 使用
.once
修饰符替代在每次组件更新时都添加新的事件监听器。 -
虚拟滚动: 对于长列表,使用虚拟滚动技术(如
vue-virtual-scroller
)只渲染可视区域内的列表项,大大减少DOM节点数量,提高滚动性能。