MJ
八股
Vue常考八股
好的,下面是一些常见的 Vue 面试问题及答案,涵盖了 Vue 的基础知识、核心概念、高级特性以及生态系统:
基础知识
Vue.js 是什么?它有什么特点?
- Vue.js 是一套用于构建用户界面的渐进式框架。
- 特点:
- 易用性:易于学习和使用,只需要基本的 HTML、CSS 和 JavaScript 知识。
- 灵活性:可以作为库引入,也可以构建复杂的单页应用。
- 高效性:虚拟 DOM 和响应式系统使得性能优化更容易。
- 组件化:将 UI 划分为可复用的组件,提高代码的可维护性和可重用性。
- 生态系统:拥有丰富的官方和社区插件,如 Vue Router、Vuex、Vue CLI 等。
Vue 的双向数据绑定是如何实现的?
- Vue 2 使用 Object.defineProperty() 的 getter 和 setter 来劫持数据的变化,结合发布-订阅模式实现双向数据绑定。
- Vue 3 使用 Proxy 对象来代理数据的变化,更加高效和灵活。
- 原理:
- 数据劫持(Observer):监听数据的变化。
- 依赖收集(Dep):收集依赖(Watcher)。
- 发布-订阅模式(Watcher):当数据变化时,通知相应的 Watcher 更新视图。
v-if 和 v-show 的区别?
v-if
:- 条件渲染,当条件为 false 时,元素不会被渲染到 DOM 中(销毁/创建)。
- 适用于切换频率较低的场景。
v-show
:- 通过 CSS 的
display
属性控制元素的显示和隐藏,元素始终存在于 DOM 中。 - 适用于切换频率较高的场景。
- 通过 CSS 的
Vue 中 key 的作用是什么?
key
是 Vue 中用于标识列表中的元素的唯一属性。- 作用:
- 高效更新虚拟 DOM:当列表数据变化时,Vue 可以根据
key
来判断哪些元素需要更新、删除或创建,提高渲染效率。 - 避免复用错误:如果没有
key
,Vue 可能会复用错误的元素,导致显示异常。
- 高效更新虚拟 DOM:当列表数据变化时,Vue 可以根据
- 建议:
key
应该是唯一的、稳定的值。- 尽量不要使用数组的索引作为
key
,除非列表是静态的。
computed 和 watch 的区别?
- computed:
- 计算属性,基于响应式依赖进行缓存,只有当依赖变化时才会重新计算。
- 适用于需要根据其他数据计算得出新数据的场景。
- 具有getter和setter。
- watch:
- 侦听器,监听一个数据的变化,并在数据变化时执行回调函数。
- 适用于需要在数据变化时执行异步操作或复杂逻辑的场景。
- computed:
核心概念
Vue 的生命周期钩子有哪些?它们分别在什么时候执行?
- beforeCreate:实例初始化之后,数据观测和事件配置之前。
- created:实例创建完成,数据观测和事件配置已完成,但 DOM 尚未渲染。
- beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用。
- mounted:实例挂载到 DOM 后调用,可以访问到 DOM。
- beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
- updated:数据更新后调用,发生在虚拟 DOM 重新渲染和打补丁之后。
- beforeDestroy:实例销毁之前调用,可以执行一些清理操作。
- destroyed:实例销毁后调用,所有的数据绑定和事件监听器都被移除。
- activated (keep-alive): 被 keep-alive 包裹的组件激活时调用。
- deactivated (keep-alive): 被 keep-alive 包裹的组件失活时调用。
- errorCaptured:(2.5.0+)当捕获一个来自子孙组件的错误时被调用
Vue 组件之间的通信方式有哪些?
父子组件:
props
:父组件向子组件传递数据。$emit
:子组件向父组件触发事件。$refs
:父组件直接访问子组件的实例。$parent
/$children
: 访问父组件/子组件的实例(不推荐)。
兄弟组件:
- 事件总线(Event Bus):创建一个空的 Vue 实例作为中央事件总线,通过
$emit
触发事件,$on
监听事件。 - Vuex:状态管理模式。
- 事件总线(Event Bus):创建一个空的 Vue 实例作为中央事件总线,通过
跨级组件:
provide
/inject
:祖先组件提供数据,后代组件注入数据。- Vuex。
Vuex 是什么?它的核心概念有哪些?
- Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
- 核心概念:
- State:单一状态树,存储应用的所有状态。
- Getters:从 State 中派生出一些状态,类似于计算属性。
- Mutations:同步修改 State 的方法,必须是同步函数。
- Actions:提交 Mutations,可以包含异步操作。
- Modules:将 Store 分割成模块,每个模块拥有自己的 State、Getters、Mutations、Actions。
Vue Router是什么, 模式有几种,区别是什么?
- Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让构建单页面应用变得非常简单。
- 模式:
- hash: 使用 URL hash 值来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
- history: 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法,需要后台配置支持。
- abstract:支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
高级特性
Vue 的自定义指令如何使用?
- 通过
Vue.directive
或在组件中使用directives
选项定义自定义指令。 - 自定义指令可以操作 DOM,实现一些特殊的功能。
- 常用钩子函数:
bind
:只调用一次,指令第一次绑定到元素时调用。inserted
:被绑定元素插入父节点时调用。update
:所在组件的 VNode 更新时调用。componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
- 通过
Vue 的 mixin 如何使用?
- Mixin 是一种分发 Vue 组件中可复用功能的灵活方式。
- Mixin 对象可以包含任何组件选项。
- 当组件使用 Mixin 时,Mixin 对象中的所有选项将被“混合”到组件自身的选项中。
- 注意:
- 如果有同名选项,组件选项会覆盖 Mixin 选项。
- 生命周期钩子会合并成一个数组,先执行 Mixin 中的钩子,再执行组件中的钩子。
Vue 的 keep-alive 组件有什么作用?
<keep-alive>
可以缓存不活动的组件实例,而不是销毁它们。- 作用:
- 性能优化:避免重复渲染相同的组件,提高性能。
- 状态保持:保留组件的状态,避免用户操作丢失。
- 常用于标签页的切换。
Vue 3 相关
Vue 3 有哪些新特性?
- Composition API:一种新的组织代码的方式,可以更好地处理大型组件的逻辑复用和可读性。
- Proxy 响应式系统:性能更好,支持更多的数据类型(如 Map、Set)。
- 更小的包体积:通过 tree-shaking 移除未使用的代码。
- 更好的 TypeScript 支持。
- Teleport:可以将组件的内容渲染到 DOM 树的任何位置。
- Fragments:组件可以有多个根节点。
- Suspense: 允许我们的组件在等待异步操作时渲染一些备用内容
Composition API 和 Options API 的区别?
Options API:
- 基于选项(data、methods、computed、watch 等)来组织代码。
- 对于小型组件来说比较清晰,但对于大型组件来说可能会变得难以维护。
Composition API:
- 基于函数来组织代码,将相关的逻辑放在一起。
- 可以更好地处理大型组件的逻辑复用和可读性。
- 更灵活,可以更好地利用 JavaScript 的特性。
其他
如何优化 Vue 应用的性能?
- 代码层面:
- 合理使用
v-if
和v-show
。 - 为列表项添加唯一的
key
。 - 使用 computed 缓存计算结果。
- 使用 keep-alive 缓存组件。
- 使用异步组件。
- 使用函数式组件。
- 事件销毁
- 图片懒加载
- 第三方插件按需引入
- 适当采用长列表技术,如只渲染可是区域
- 合理使用
- Webpack 层面:
- 代码分割(Code Splitting)。
- Tree Shaking。
- 使用 CDN 加速。
- 代码层面:
这只是一些常见的 Vue 面试问题,实际面试中可能会根据你的经验和项目经历进行更深入的提问。建议你在准备面试时,结合自己的实际情况,深入理解 Vue 的原理和最佳实践。
一、块级元素和行内元素
1. 定义与区别
- 块级元素:独占一行,默认宽度撑满父容器,可以设置宽高、内外边距。常见标签:
<div>
、<p>
、<h1>~<h6>
、<ul>
、<li>
、<section>
、<header>
、<footer>
、<article>
、<aside>
、<figure>
、nav
、form
。 - 行内元素:不换行,默认宽高由内容决定,无法直接设置宽高。
常见标签:<span>
、<a>
、<strong>
、<em>
、<img>
、<input>
、<button>
。其中<img>
、<input>
、<button>
、textarea
、select
、Video
作为可替换元素比较特殊,可以设置宽高。
2. 行内元素如何定义宽高?
- 方法:
- 转换为块级元素:
display: block
或display: inline-block
。- 它们的区别:
display: block
:独占一行,默认宽度撑满父容器,可以设置宽高、内外边距。display: inline-block
:不换行,默认宽高由内容决定,可以设置宽高、内外边距。
- 它们的区别:
- 使用 CSS 属性:如
transform: scale()
(不改变布局流)。 - 某些行内替换元素(如
<img>
、<input>
)天然支持宽高设置。
- 转换为块级元素:
二、语义化标签
1. 定义
通过 HTML 标签的命名(如 <header>
、<nav>
、<article>
)直接表达内容的结构和意义,而非仅用 <div>
和 <span>
。
2. 优点
- SEO 友好:搜索引擎更容易理解页面结构。
- 可访问性:屏幕阅读器能更准确地解析内容。
- 代码可维护性:结构清晰,便于团队协作。
3. 常见标签
<header>
、<footer>
、<main>
、<section>
、<article>
、<aside>
、<figure>
。
三、iframe
1. 定义
用于在当前页面中嵌入另一个独立的 HTML 文档(如地图、广告等)。
2. 特点
- 独立上下文:内部样式和脚本与父页面隔离。
- 性能问题:加载子页面可能阻塞父页面,增加内存消耗。
- 现代替代:微前端或组件化开发(如 Web Components)。
事件绑定和阻止冒泡:事件在 DOM 中有三个阶段:捕获阶段(自上而下)、目标阶段、冒泡阶段(自下而上)。
- 事件绑定:
- 选中元素:document.getElementById(‘element’)或者document.querySelector(‘#element’)
- 绑定事件:element.addEventListener(‘click’, handler);
- 阻止冒泡:stopPropagation() 阻止事件继续传播,preventDefault() 阻止默认行为(比如点击链接跳转)。
1
2
3
4
5
6
7
8
9
10// 绑定事件
element.addEventListener('click', handler);
// 取消绑定
element.removeEventListener('click', handler);
// 阻止冒泡
function handler(event) {
event.stopPropagation(); // 阻止冒泡
event.preventDefault(); // 阻止默认行为
}
四、CSS 选择器
1. 常见类型
- 基础选择器:
#id
(id选择器)、.class
(类选择器)、tag
(标签选择器)、*
(通配符选择器)。 - 组合选择器:
div p
(后代选择器)、div > p
(直接子元素选择器)、div + p
(相邻兄弟选择器)。 - 属性选择器:
[type="text"]
(属性选择器)。1
2
3[class^="prefix"] { } /* 以prefix开头 */
[class$="suffix"] { } /* 以suffix结尾 */
[class*="contain"] { } /* 包含contain */ - 伪类
1
2
3
4
5
6
7
8
9
10
11
12/* 状态伪类 */
:hover { }
:active { }
:focus { }
:visited { }
/* 结构伪类 */
:first-child { }
:last-child { }
:nth-child(n) { }
:nth-of-type(n) { }
:not() { } - 伪元素:
::before
(伪元素)。1
2
3
4
5
6/* 使用双冒号 */
::before { }用于在选定元素的内容之前插入内容。它创建一个虚拟的元素,这个元素不会出现在DOM树中,但可以通过CSS进行样式设置。比如li前面的点
::after { }用于在选定元素的内容之后插入内容。它创建一个虚拟的元素,这个元素不会出现在DOM树中,但可以通过CSS进行样式设置。比如li后面的点
::first-line { }用于选择元素的第一行文本。
::first-letter { }用于选择元素的第一行文本。
::selection { }用于选择用户选择的文本。
2. &gt;
就是 >
子元素选择器,仅匹配直接子元素。
示例:.parent > .child
仅选择 .parent
下一级的 .child
。
五、水平垂直居中
1. Flex 布局
1 |
|
4. 行内元素居中
1 |
|
3. 绝对定位 + Transform
1 |
|
2. Grid 布局
1 |
|
CSS 选择器权重
让我以面试的方式回答 CSS 选择器权重的问题:
CSS 选择器权重从高到低排序如下:
!important (权重值:∞)
1
2
3.box {
color: red !important; /* 最高优先级 */
}行内样式 (权重值:1000)
1
<div style="color: red;"> <!-- 直接写在标签上的样式 -->
ID选择器 (权重值:100)
1
2
3#header {
color: red;
}类选择器、属性选择器、伪类 (权重值:10)
1
2
3.container { } /* 类选择器 */
[type="text"] { } /* 属性选择器 */
:hover { } /* 伪类 */标签选择器、伪元素 (权重值:1)
1
2div { } /* 标签选择器 */
::before { } /* 伪元素 */通配符、关系选择器 (权重值:0)
1
2
3* { } /* 通配符 */
> { } /* 子选择器 */
+ { } /* 相邻选择器 */
面试要点:
权重值不进位:
- 11个类选择器 (110) 也不会超过 1个ID选择器 (100)
- 这不是一个十进制的计数系统
继承的权重最低:
- 继承的样式优先级最低,即使是从 ID 选择器继承来的样式
相同权重的情况:
- 后面的样式会覆盖前面的样式(就近原则)
- 外部样式表和内部样式表权重相同时,后加载的优先
通配符的特殊性:
*
的权重为 0,但比继承的样式优先级高
权重叠加:
1
2
3
4/* 两个类选择器叠加 (20) 会大于一个类选择器 (10) */
.header.active {
color: red;
}
在实际开发中的建议:
- 避免过度使用
!important
- 避免过长的选择器链
- 合理使用类选择器,降低选择器的耦合度
- 遵循 BEM 等命名规范,减少选择器权重混乱
- 使用 CSS Modules 或 CSS-in-JS 等方案来避免全局样式冲突
六、回流(Reflow)与重绘(Repaint)
1. 回流
- 触发条件:修改布局属性(如宽高、位置、DOM 结构)。
- 性能消耗高:需重新计算元素几何信息。
2. 重绘
- 触发条件:修改样式但不影响布局(如颜色、背景)。
- 性能消耗低:无需重新计算布局。
3. 优化建议
- 避免频繁操作 DOM(使用
DocumentFragment
或虚拟 DOM)。 - 使用 CSS3 动画(
transform
、opacity
触发 GPU 加速)。 - 批量修改样式(通过
class
或requestAnimationFrame
)。
Cookie、LocalStorage、SessionStorage
Cookie
Cookie是存储在浏览器中的小型文本文件。
特点:
- 容量小:约4KB
- 每次请求都会自动携带
- 可设置过期时间
- 可设置域名和路径
1 |
|
LocalStorage
永久性的本地存储,除非手动删除,否则数据永远不会过期。
特点:
- 容量大:约5MB
- 永久存储
- 不会随请求发送到服务器
- 同源策略限制
1 |
|
SessionStorage
会话级别的存储,关闭标签页或浏览器后数据就会清除。
特点:
- 容量大:约5MB
- 会话期间有效
- 不会随请求发送到服务器
- 同源策略限制
- 标签页隔离
1 |
|
使用场景
- Cookie适用于:
1 |
|
- LocalStorage适用于:
1 |
|
- SessionStorage适用于:
1 |
|
安全性考虑
- 敏感数据处理:
1 |
|
- 数据验证:
1 |
|
- 跨站脚本攻击(XSS)防护:
1 |
|
区别
特性 | Cookie | LocalStorage | SessionStorage |
---|---|---|---|
存储大小 | 4KB 左右 | 5~10MB | 5~10MB |
生命周期 | 可设置过期时间(默认会话级) | 永久存储,需手动删除 | 标签页关闭后自动删除 |
与请求的关系 | 每次请求自动携带(影响性能) | 不参与请求 | 不参与请求 |
访问权限 | 同源窗口共享 | 同源窗口共享 | 仅当前标签页 |
API 易用性 | 需手动封装 | localStorage.setItem() |
sessionStorage.setItem() |
什么是异步和同步
- 同步(Synchronous)
同步操作是按顺序执行的,每个操作必须等待前一个操作完成才能执行。
1 |
|
- 异步(Asynchronous)
异步操作允许多个操作同时进行,不需要等待前一个操作完成。
常见的异步操作:
- 网络请求
- 文件读写
- 定时器
- 事件监听
异步实现方式
- 回调函数(Callbacks):
1 |
|
- Promise:
1 |
|
- async/await:
1 |
|
实际应用示例
- API请求:
1 |
|
- 文件操作:
1 |
|
- 多个异步操作:
1 |
|
异步的优势
- 不阻塞主线程:
- 提高应用性能:
- 更好的用户体验:
Promise 的用法
1. 基本语法
1 |
|
2. 链式调用
1 |
|
3. Async/Await(更简洁的写法)
1 |
|
let
、var
和 const
的区别
特性 | let |
var |
const |
---|---|---|---|
重新赋值 | ✅(允许) | ✅(允许) | ❌(不可重新赋值) |
块级作用域 | ✅ | ❌ | ✅ |
声明必须初始化 | ❌(可稍后赋值) | ❌(可稍后赋值) | ✅(必须声明时初始化) |
适用场景 | 变量需要修改时(如循环计数器) | 不推荐使用 | 常量(如配置项、固定引用) |
定义计时器
1. setTimeout
(单次延迟执行)
1 |
|
2. setInterval
(重复执行)
1 |
|
Vue 组件通信方式
1. 父子组件
- 父传子:
props
- 子传父:
$emit
触发事件
2. 跨层级组件
- 事件总线:
EventBus
(小型项目) - Vuex/Pinia:状态管理库(中大型项目)
- 依赖注入:
provide/inject
3. 兄弟组件
- 通过共同父组件中转
- Vuex 或全局事件总线
Vue 中 v-for
的 key
作用
- 核心作用:帮助 Vue 识别虚拟 DOM 节点的唯一性,优化 Diff 算法效率。
- 错误用法:使用索引
index
作为 key(可能导致渲染错误或性能问题)。 - 正确实践:使用唯一标识(如
id
)。
为什么 Vue 不建议 v-for
和 v-if
一起用?
- 优先级问题:
v-for
优先级高于v-if
,导致每次循环都会执行条件判断,浪费性能。 - 解决方案:
- 改用计算属性过滤数据后再遍历。
- 在外层包裹
<template>
单独使用v-if
。
computed
和 watch
的区别
特性 | computed |
watch |
---|---|---|
用途 | 基于依赖的派生数据(如过滤、计算) | 监听数据变化执行异步或复杂操作 |
缓存 | 有缓存(依赖不变时直接返回结果) | 无缓存(每次触发都执行) |
同步/异步 | 仅支持同步 | 支持异步操作 |
示例场景 | 计算总价、格式化日期 | 搜索框输入防抖、路由跳转 |
Vue 如何实现双向绑定?
双向绑定语法糖:v-model
本质是 :value
+ @input
的简写,常用于表单控件。
介绍主流浏览器的特性,解决常见兼容性问题
1. 主流浏览器特性
浏览器 | 渲染引擎 | JavaScript引擎 | 特点 |
---|---|---|---|
Chrome | Blink | V8 | 性能好,对新特性支持度高 |
Firefox | Gecko | SpiderMonkey | 开源,扩展性强 |
Safari | WebKit | JavaScriptCore | 对苹果生态优化好 |
- 其实使用Vue3框架以及Element plus等UI框架+vite等打包工具,解决了大部分的兼容性问题;以下是从原理上讲;
2. 常见兼容性问题及解决方案
CSS兼容性
浏览器前缀
1
2
3
4
5
6
7.box {
-webkit-transform: rotate(45deg); /* Chrome, Safari */
-moz-transform: rotate(45deg); /* Firefox */
-ms-transform: rotate(45deg); /* IE */
-o-transform: rotate(45deg); /* Opera */
transform: rotate(45deg);
}Flex布局兼容
1
2
3
4
5
6
7.flex-container {
display: -webkit-box; /* iOS 6-, Safari 3.1-6 */
display: -webkit-flex; /* Chrome */
display: -moz-box; /* Firefox */
display: -ms-flexbox; /* IE 10 */
display: flex; /* 现代浏览器 */
}自动添加前缀
1
2
3
4
5
6
7
8// 使用 Autoprefixer (PostCSS 插件)
module.exports = {
plugins: [
require('autoprefixer')({
browsers: ['last 2 versions', '> 1%']
})
]
}
JavaScript兼容性
特性检测
1
2
3
4
5
6// 检测是否支持某个API
if (typeof window.localStorage !== 'undefined') {
// 使用 localStorage
} else {
// 使用替代方案
}Polyfill使用
1
2
3
4
5
6// 使用 core-js 添加 ES6+ 特性支持
import 'core-js/stable';
import 'regenerator-runtime/runtime';
// 或使用 @babel/polyfill
import '@babel/polyfill';Babel配置
1
2
3
4
5
6
7
8
9
10// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
browsers: ['last 2 versions', '> 1%']
}
}]
]
}
响应式
viewport设置
1
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
媒体查询
1
2
3
4
5
6/* 响应式设计 */
@media screen and (max-width: 768px) {
.container {
width: 100%;
}
}rem适配方案
1
2
3
4
5
6
7
8
9// 动态设置根元素字体大小
function setRem() {
const html = document.documentElement;
const width = html.clientWidth;
html.style.fontSize = width / 10 + 'px';
}
window.addEventListener('resize', setRem);
setRem();
Webpack/gulp等打包工具的使用
5. 打包工具选择建议
Webpack适用场景:
- 现代前端框架项目(Vue/React)
- 需要复杂模块管理
- 需要强大的代码分割
- 单页应用(SPA)开发
Gulp适用场景:
- 简单的网页开发
- 多页应用开发
- 需要文件流操作
- 自动化任务管理
6. 性能优化实践
- 代码分割
1
2
3
4
5
6
7
8
9
10
11// 动态导入
const Home = () => import(/* webpackChunkName: "home" */ './views/Home.vue');
// 提取公共代码
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
minChunks: 1
}
}
对web页面大数据处理、安全性等的理解
Web页面大数据处理与安全性
1. 大数据处理策略
1.1 虚拟列表
- 基本概念
虚拟列表是一种用于优化长列表性能的技术,核心思想是:只渲染可视区域内的列表项,而不是渲染整个列表。
- 关键要素
- 可视区域高度:视口(viewport)的高度
- 列表项高度:每个列表项的高度(固定或动态)
- 可视区域起始索引:根据滚动位置计算
- 可见列表项数量:根据可视区域高度和列表项高度计算
1.2 分片处理
将大量数据切分成小块,利用浏览器的空闲时间分批处理,避免阻塞主线程。
1.3 Web Worker
Web Worker 提供了在后台线程中运行脚本的能力,不会阻塞主线程。
2. 安全性处理
2.1 XSS防御
1 |
|
2.2 CSRF防御
1 |
|
2.4 数据加密
1 |
|
3. 性能优化
- 数据缓存(IndexedDB)
- 浏览器提供的本地数据库,用于存储大量结构化数据
- 支持异步操作,不会阻塞主线程
- 适用于存储大型数据集、离线数据等场景
- 通过事务机制确保数据完整性
- 请求队列合并
- 将短时间内的多个请求合并成一个批量请求
- 使用防抖机制控制请求发送时机
- 减少服务器压力,优化网络资源利用
- 适用于需要频繁请求的场景
- 性能监控
- 收集页面性能指标(加载时间、响应时间等)
- 使用Performance API进行精确计时
- 通过sendBeacon进行数据上报,避免页面卸载时丢失数据
- 帮助发现性能瓶颈,优化用户体验
Web性能优化
懒加载
- 基本概念
懒加载是一种性能优化技术,核心思想是:延迟加载页面中不可见区域的资源,直到用户需要查看时才进行加载。
Vue路由懒加载按需加载
1 |
|
主要应用场景
图片懒加载
- 长列表中的图片
- 页面底部的图片
- 轮播图中的非当前图片
组件懒加载
- 路由组件
- 复杂组件
- 弹窗组件
数据懒加载
- 分页数据
- 无限滚动
实现原理
监测可视区域
- 使用 Intersection Observer API
- 监听滚动事件(传统方式)
- 计算元素位置
触发加载
- 当元素进入可视区域时加载资源
- 提前一定距离开始加载(预加载)
- 替换占位符为实际内容
1资源加载优化
1.1 资源压缩与合并
1 |
|
1.2 CDN加速
1 |
|
1.3 预加载与预解析
1 |
|
2. 代码优化
防抖(Debounce)
1. 原理
- 在事件被触发n秒后再执行回调
- 如果在这n秒内事件又被触发,则重新计时
- 适合多次事件一次响应的情况
2. 实现
1 |
|
3. 使用场景
1 |
|
节流(Throttle)
1. 原理
- 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行
- 如果在同一个单位时间内某事件被触发多次,只有一次能生效
- 适合大量事件按时间做平均分配触发
2. 实现
1 |
|
3. 使用场景
1 |
|
区别对比
特性 | 防抖(Debounce) | 节流(Throttle) |
---|---|---|
触发时机 | 最后一次事件触发后的delay时间后执行 | 第一次事件触发后的delay时间内只执行一次 |
适用场景 | 输入框实时搜索、窗口大小调整 | 滚动事件处理、按钮点击控制 |
执行频率 | 多次触发,最后一次生效 | 按照时间间隔执行 |
性能消耗 | 较低(只执行最后一次) | 适中(按时间间隔执行) |
实际应用示例
1 |
|
3. 渲染优化
3.1 避免重排重绘
1 |
|
3.2 CSS优化
1 |
|
一、GET与POST请求的核心区别
对比维度 | GET请求 | POST请求 |
---|---|---|
主要用途 | 获取数据(查) | 提交数据(增/改) |
参数位置 | URL查询字符串(?key=value ) |
请求体(Body) |
数据长度限制 | 受URL长度限制(通常2KB-8KB) | 无硬性限制(服务器可配置) |
缓存机制 | 可被浏览器缓存 | 默认不缓存 |
安全性 | 参数明文暴露在URL中 | 数据在请求体中相对隐蔽 |
幂等性 | 幂等(多次请求结果相同) | 非幂等(可能产生副作用) |
浏览器历史记录 | 保留参数 | 不保留请求体数据 |
典型应用场景 | 搜索、分页、资源获取 | 表单提交、文件上传、敏感操作 |
二、常见HTTP状态码分类解析
2xx 成功类
状态码 | 含义 | 应用场景 |
---|---|---|
200 OK | 请求成功 | 标准成功响应 |
201 Created | 资源创建成功 | RESTful API创建新资源后返回 |
204 No Content | 无返回内容 | DELETE请求成功后的响应 |
3xx 重定向类
状态码 | 含义 | 应用场景 |
---|---|---|
301 Moved Permanently | 永久重定向 | 网站改版后旧URL跳转 |
302 Found | 临时重定向 | 登录后跳回原页面 |
304 Not Modified | 资源未修改 | 协商缓存生效时返回 |
4xx 客户端错误类
状态码 | 含义 | 应用场景 |
---|---|---|
400 Bad Request | 请求语法错误 | 参数格式错误/必填字段缺失 |
401 Unauthorized | 未认证 | 未携带有效Token访问需登录的接口 |
403 Forbidden | 禁止访问 | 权限不足(如普通用户访问管理员接口) |
404 Not Found | 资源不存在 | 访问不存在的URL |
429 Too Many Requests | 请求过频 | 接口限流触发 |
5xx 服务端错误类
状态码 | 含义 | 应用场景 |
---|---|---|
500 Internal Server Error | 服务器内部错误 | 代码未捕获的异常 |
502 Bad Gateway | 网关错误 | 反向代理服务器无法获取响应 |
503 Service Unavailable | 服务不可用 | 服务器过载维护/停机 |
504 Gateway Timeout | 网关超时 | 上游服务器响应超时 |
js中的数据类型有哪些
字符串,数值,布尔,undefined,null
常用的原生js函数比如字符串和数组的操作有哪些方法
字符串操作方法
1. 基本操作
1 |
|
2. 转换方法
1 |
|
数组操作方法
1. 增删改查
1 |
|
2. 遍历方法
1 |
|
3. 数组转换
1 |
|
4. 排序方法
1 |
|
实际应用示例
1 |
|
js
1. 数据类型
- 基本类型:
Undefined
、Null
、Boolean、Number、String、Symbol(ES6)。 - 引用类型:Object(包括 Array、Function、Date 等)。
- 区别:基本类型按值存储,引用类型按引用(内存地址)存储。
2. 判断数据类型
- **
typeof
**:快速区分基本类型(typeof null
返回"object"
,需注意)。 - **
instanceof
**:检测对象是否为某构造函数的实例(无法判断基本类型)。 - **
Object.prototype.toString.call()
**:最准确(如[object Array]
)。
3. 重排(Reflow)和重绘(Repaint)
- 重排:元素几何属性变化(如宽高、位置),触发重新计算布局。
- 重绘:元素外观变化(如颜色),不涉及布局,直接重新绘制。
- 优化:避免频繁操作样式,使用
transform
/opacity
(触发 GPU 加速,跳过重排)。
4. 闭包(Closure)
- 定义:函数嵌套时,内层函数访问外层作用域的变量,即使外层函数已执行完毕。
- 作用:封装私有变量、延长变量生命周期。
- 内存泄漏风险:未手动释放的闭包变量会常驻内存(如未清除的 DOM 事件引用)。
5. 内存泄漏其他原因
- 未清除的定时器(
setInterval
)。 - 游离的 DOM 引用(删除 DOM 后仍保留变量引用)。
- 全局变量滥用(未用
let/const
声明)。 - 未解绑的事件监听(尤其重复绑定时)。
6. 深拷贝与浅拷贝
- 浅拷贝:仅复制一层属性,引用类型仍共享地址(如
Object.assign()
、展开运算符)。 - 深拷贝:递归复制所有层级,新旧对象完全独立(需处理循环引用)。
7. 实现深拷贝
- **
JSON.parse(JSON.stringify())
**:简单但无法处理函数、undefined
、循环引用。 - 递归手动实现:遍历对象属性,区分基本/引用类型,处理数组和循环引用。
- 库函数:使用 Lodash 的
_.cloneDeep()
。
实现浅拷贝
Object.assign()
8. 缓存策略
本地存储 (Local Storage & Session Storage)
localStorage: 将数据存储在用户的本地浏览器中,没有过期时间(除非用户手动清除)。
sessionStorage: 将数据存储在用户的当前会话中,关闭浏览器标签页或窗口后,数据会被清除。
IndexedDB: 一个更大更强大的客户端存储方案,用于在浏览器中存储大量结构化数据
CDN 缓存
CDN(内容分发网络)将网站的静态资源(如图片、CSS、JavaScript)缓存到全球各地的边缘节点上。当用户访问网站时,CDN 会从离用户最近的节点提供资源,加快访问速度,降低源服务器的负载。
优点:
加速静态资源访问。
降低源服务器负载。
提高网站的可用性。
缺点:
需要付费。
缓存更新可能存在延迟。
9. ES6 常用特性
- 变量声明:
let
/const
(块级作用域)。 - 箭头函数:简化写法,无自身
this
。 - 解构赋值:
const { a, b } = obj
。 - 模块化:
import
/export
。 - Promise:异步编程,链式调用。
- Class:语法糖,替代构造函数。
10. Vue2 与 Vue3 区别
1. 响应式原理
Vue2 | Vue3 |
---|---|
使用 Object.defineProperty 劫持对象属性(需递归遍历对象) |
使用 Proxy 代理整个对象,直接监听动态属性变化 |
缺陷:无法检测对象属性的新增/删除、数组索引修改 | 优势:天然支持动态属性、性能更优 |
2. 组合式 API
- Vue2:
Options API
(分散在data
、methods
、computed
中)。 - Vue3:
Composition API
(通过setup()
集中管理逻辑,支持逻辑复用)。1
2
3
4
5
6
7
8
9// Vue3 示例
import { ref, computed } from 'vue';
export default {
setup() {
const count = ref(0);
const double = computed(() => count.value * 2);
return { count, double };
}
};
3. 性能优化
优化点 | Vue2 | Vue3 |
---|---|---|
虚拟 DOM | 全量对比 | 静态标记 + 靶向更新(Diff 更高效) |
Tree-shaking | 不支持 | 按需引入 API,打包体积更小 |
静态提升 | 无 | 将静态节点提取为常量,减少渲染开销 |
4. 生命周期
Vue2 | Vue3(Composition API) |
---|---|
beforeDestroy |
onBeforeUnmount |
destroyed |
onUnmounted |
- | onServerPrefetch (SSR) |
5. 新特性
- Fragment:支持多根节点模板(无需包裹
<div>
)。 - Teleport:将组件渲染到任意 DOM 节点(如弹窗全局定位)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94<teleport to="body">
<div class="modal">内容</div>
</teleport>
```
- **Suspense**:异步组件加载状态管理。
- **自定义渲染器**:支持非 DOM 环境(如小程序、Canvas)。
---
#### **6. TypeScript 支持**
- **Vue2**:需通过 `Vue.extend` 或 Class API 支持,类型推断较弱。
- **Vue3**:完全用 TypeScript 重写,提供完整的类型定义。
### **2. Vuex 核心概念与函数**
- **作用**:集中管理 Vue 应用的共享状态,解决组件间复杂数据传递问题。
- **核心函数/概念**:
- **`state`**:存储状态数据(响应式)。
- **`getters`**:计算属性,对 `state` 加工(类似 `computed`)。
- **`mutations`**:唯一修改 `state` 的方式(**同步**操作),通过 `commit` 触发。
- **`actions`**:处理异步逻辑(如请求数据),通过 `dispatch` 触发,内部调用 `commit`。
- **`modules`**:拆分复杂 store 为多个模块(每个模块独立拥有自己的 state、mutations 等)。
---
### **3. 无限加载性能优化** :懒加载
- **数据拼接**:分页加载,每次滚动到底部时追加新数据(`list = [...list, ...newData]`)。
- **性能关键点**:
1. **虚拟滚动**:仅渲染可视区域内的 DOM,减少节点数量(如 `vue-virtual-scroller`)。
2. **防抖/节流**:避免频繁触发滚动事件。
3. **复用 DOM**:使用 `v-for` 时设置唯一 `key`,帮助 Vue 高效复用节点。
4. **内存释放**:移除不可见数据或分页缓存,避免内存泄漏。
---
### **4. 图片懒加载实现**
- **方案一:Intersection Observer API**(推荐,性能好)
```javascript
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // 替换 data-src 为实际 src
observer.unobserve(img); // 加载后停止观察
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
```
- **方案二:滚动监听 + getBoundingClientRect**
```javascript
window.addEventListener('scroll', throttle(() => {
const imgs = document.querySelectorAll('img[data-src]');
imgs.forEach(img => {
if (img.getBoundingClientRect().top < window.innerHeight) {
img.src = img.dataset.src;
}
});
}, 200));
```
- **HTML 原生支持**:`<img loading="lazy">`(部分浏览器支持)。
---
让我来介绍一下 JavaScript 中的同步与异步,特别是 ES6 中的 async/await 特性:
### 同步与异步的区别
同步:
- 代码按顺序执行,前一个任务完成后才执行下一个
- 会阻塞主线程
- 适用于简单运算
异步:
- 不会阻塞主线程
- 适用于网络请求、文件读写等耗时操作
- 主要解决方案包括:回调函数、Promise、async/await
#### async/await 使用
基本语法:
```javascript
async function getData() {
try {
// await 后面接 Promise
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch(err) {
console.error(err);
}
}
主要特点:
- async 函数返回 Promise 对象
- await 只能在 async 函数内使用
- 让异步代码写法更接近同步代码
- 配合 try/catch 进行错误处理
实际应用场景
接口请求:
1
2
3
4
5async function getUsers() {
const users = await axios.get('/api/users');
const orders = await axios.get('/api/orders');
return { users, orders };
}并发请求:
1
2
3
4
5
6
7
8async function getMultiData() {
// Promise.all 并发执行
const [users, orders] = await Promise.all([
axios.get('/api/users'),
axios.get('/api/orders')
]);
return { users, orders };
}
6. 原生 JS 与 Vue 的同步异步
- 原生 JS:
- 同步:普通代码执行。
- 异步:
setTimeout
、XMLHttpRequest
、事件回调(如点击事件)。
- Vue 的异步:
- **
Vue.nextTick()
**:在 DOM 更新后触发回调(微任务)。 - 响应式更新队列:Vue 将数据变更批量处理,异步更新 DOM。
- **
7. Promise 方法
- 实例方法:
- **
then()
**:处理成功状态。 - **
catch()
**:处理失败状态。 - **
finally()
**:无论成功/失败都会执行。
- **
- 静态方法:
- **
Promise.all()
**:所有成功则成功,任一失败则失败。 - **
Promise.race()
**:取第一个完成的结果(无论成功/失败)。 - **
Promise.allSettled()
**:等待所有完成,返回结果数组。 - **
Promise.any()
**:取第一个成功的 Promise(忽略失败)。
- **
8. async/await 原理
- 本质:Generator 函数的语法糖,结合 Promise 实现同步写法。
- 原理:
async
函数返回一个 Promise。await
会暂停代码执行,等待 Promise 完成(底层通过 Generator 的yield
实现)。- 错误处理:用
try/catch
捕获await
后的异常。
9. Git 基础命令
- 仓库操作:
git clone [url]
:克隆远程仓库。git init
:初始化本地仓库。
- 提交代码:
git add .
:添加所有修改到暂存区。git commit -m "msg"
:提交暂存区内容。git push
:推送到远程分支。
- 分支管理:
git branch
:查看分支。git checkout -b [branch]
:创建并切换分支。git merge [branch]
:合并分支。
- 其他:
git pull
:拉取远程更新。git status
:查看当前状态。git log
:查看提交历史。
10. TCP、IP、HTTP 的关系
- IP(网络层):负责路由寻址:将数据包从源地址发送到目标地址(通过 IP 地址寻址)。
- TCP(传输层):在 IP 基础上提供可靠传输(三次握手建立连接、数据校验保证数据完整性和顺序、通过端口号区分应用)。
- HTTP(应用层):基于 TCP/IP 的应用层协议,定义客户端与服务器通信格式(如请求头、响应状态码)。
- HTTP 是无状态的应用层协议
- 各层协议相互独立
三次握手
TCP 三次握手(Three-way Handshake)是建立连接的过程,我来详细解释:
TCP 三次握手流程
1 |
|
详细说明
- 第一次握手 (SYN)
- 客户端发送 SYN 包
- SYN=1, seq=x(随机数)
- 客户端进入 SYN_SENT 状态
- 第二次握手 (SYN + ACK)
- 服务端收到 SYN 包
- 发送 SYN+ACK 包
- SYN=1, ACK=1, seq=y(随机数), ack=x+1
- 服务端进入 SYN_RECV 状态
- 第三次握手 (ACK)
- 客户端收到 SYN+ACK 包
- 发送 ACK 包
- ACK=1, seq=x+1, ack=y+1
- 双方进入 ESTABLISHED 状态
为什么需要三次握手?
- 确认双方收发能力都正常
- 同步双方序列号
- 防止历史连接的建立
- 避免资源浪费
这就是所谓的”三次握手”,建立可靠的 TCP 连接必经的过程。
1. Vue 生命周期钩子函数的作用
钩子函数 | 执行时机 | 典型操作 |
---|---|---|
beforeCreate | 实例初始化后,数据观测前 | 无法访问 data 和 methods ,极少使用 |
created | 实例创建完成,DOM 未生成 | 发起异步请求、初始化数据(如 API 调用) |
beforeMount | 挂载开始前,首次调用 render 函数 |
极少使用 |
mounted | DOM 挂载完成 | 操作 DOM、集成第三方库(地图、图表) |
beforeUpdate | 数据变化后,DOM 更新前 | 获取更新前的 DOM 状态 |
updated | DOM 更新后 | 操作更新后的 DOM(避免修改数据) |
beforeUnmount | 实例销毁前(Vue3) | 清除定时器、解绑事件监听、销毁第三方实例 |
unmounted | 实例销毁后(Vue3) | 清理残留引用 |
2. 销毁定时器的原因与方式
- 为什么销毁:防止组件销毁后定时器仍在执行,导致内存泄漏或操作已销毁的 DOM。
- 如何销毁:在
beforeUnmount
(Vue3)或beforeDestroy
(Vue2)中清除。1
2
3
4
5
6
7
8
9
10
11export default {
data() {
return { timer: null };
},
mounted() {
this.timer = setInterval(() => { /* ... */ }, 1000);
},
beforeUnmount() {
clearInterval(this.timer); // 销毁定时器
}
};
3. Vue 响应式原理
- Vue2:通过
Object.defineProperty
递归遍历对象,劫持属性 getter/setter,在 get 中收集依赖(Watcher),在 set 中通知更新。 - Vue3:使用
Proxy
代理对象,直接监听整个对象,支持动态新增属性。
4. Vue2 数组的响应式处理
- 方法重写:覆盖数组的
push
、pop
、splice
等 7 个方法,触发视图更新。 - 局限性:直接通过索引修改项(如
arr[0] = 1
)或修改长度(arr.length = 0
)不会触发更新。 - 解决方案:使用
Vue.set(arr, index, value)
或arr.splice()
。
5. MVVM 架构理解
- Model:数据层(如 Vue 的
data
)。 - View:UI 层(如模板)。
- ViewModel:连接 Model 和 View,实现数据绑定(如 Vue 实例)。
- 核心:数据驱动视图,自动同步数据与 UI(通过响应式系统)。
6. v-model
原理
- 本质:语法糖,结合
:value
和@input
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<input :value="msg" @input="msg = $event.target.value" />
<!-- 等价于 -->
<input v-model="msg" />
```
- **自定义组件**:通过 `modelValue` 属性和 `update:modelValue` 事件实现。
---
### **7. `$nextTick`**
- **作用**:在 DOM 更新后执行回调(Vue 异步更新队列)。
- **场景**:操作更新后的 DOM。
```javascript
this.msg = '新消息';
this.$nextTick(() => {
console.log(document.getElementById('text').innerHTML); // 获取最新 DOM
});
8. Vue2 的 data
为什么是函数
- 原因:组件可能被复用,函数返回独立对象,避免多个实例共享同一数据对象。
- 错误写法(对象形式):
1
data: { count: 0 } // 所有实例共享同一个 count!
9. 闭包与内存泄漏
- 闭包:函数嵌套时,内层函数引用外层变量,导致外层变量无法释放。
- 内存泄漏场景:未清除的定时器、DOM 事件引用、全局变量。
10. 深拷贝与实现
- 浅拷贝:
Object.assign()
、展开运算符(仅复制一层)。 - 深拷贝实现:
1
2
3
4
5
6
7
8
9
10function deepClone(obj, map = new Map()) {
if (typeof obj !== 'object' || obj === null) return obj;
if (map.has(obj)) return map.get(obj); // 处理循环引用
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
for (const key in obj) {
clone[key] = deepClone(obj[key], map);
}
return clone;
}
11. JS 数据类型
- 基本类型:
Undefined
、Null
、Boolean、Number、String、Symbol(ES6)、BigInt(ES2020)。 - 引用类型:Object(包括 Array、Function、Date 等)。
12. 判断数组
- 方法 1:
Array.isArray(arr)
(推荐)。 - 方法 2:
Object.prototype.toString.call(arr) === '[object Array]'
。 - 方法 3:
arr instanceof Array
(不适用于多窗口环境)。
以上答案覆盖高频考点,代码示例可直接用于面试手写环节,原理需结合理解阐述。
11. 正方形交互场景题
实现代码:
1 |
|
12. 获取标签元素的方式
- ID 获取:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147document.getElementById('id'); // 返回单个元素
```
- **类名获取**:
```javascript
document.getElementsByClassName('class'); // 返回 HTMLCollection(动态)
```
- **标签名获取**:
```javascript
document.getElementsByTagName('div'); // 返回 HTMLCollection
```
- **CSS 选择器**:
```javascript
document.querySelector('#id'); // 返回第一个匹配元素
document.querySelectorAll('.class'); // 返回 NodeList(静态)
```
- **Vue 中**:通过 `ref` 属性获取组件或 DOM 元素。
### 1. 代码问题定位
主要方法:
- console.log/debugger 打点调试
- 浏览器开发者工具断点调试
- 查看浏览器控制台报错信息和堆栈
- 使用 Vue Devtools 调试 Vue 组件和状态
- 代码版本回溯,查看 git 提交记录定位问题代码
### 2. 浏览器调试技巧
常用工具面板:
- Elements: 检查和调试页面元素和样式
- Console: 查看日志和错误信息
- Sources: 断点调试 JavaScript 代码
- Network: 分析网络请求和响应
- Performance: 分析页面性能瓶颈
- Application: 查看存储和缓存
### 3. 抓包经验
工具使用:
- Charles: 常用于移动端调试,可以查看和修改 HTTP/HTTPS 请求
- Fiddler: Windows 平台常用抓包工具
- Chrome DevTools 的 Network 面板:分析请求响应和性能
主要用于:
- 分析接口请求/响应数据
- 调试移动端页面
- 排查网络相关问题
### 为什么使用前端框架,帮我们解决什么问题
- 他提供了很多api,同时组件化开发使得代码复用性提高,便于团队开发、维护和测试;团队协作更容易,可以极大提升前端开发的人员的效率
### 单页面和多页面有哪些区别,单页面有哪些好处;
让我来解释单页面应用(SPA)和多页面应用(MPA)的区别:
### 单页面应用(SPA)
优点:
- 用户体验好,切换页面无刷新
- 前后端分离,开发效率高
- 可以实现更丰富的交互效果
- 更好的缓存机制
缺点:
- 首屏加载时间较长
- SEO 不友好(可通过 SSR 解决)
- 需要额外的路由管理
### 多页面应用(MPA)
优点:
- 首屏加载快
- SEO 友好
- 适合简单的展示型网站
- 开发门槛低
缺点:
- 页面切换需要刷新
- 前后端耦合度高
- 服务器压力大
- 用户体验相对较差
### 主要区别
1. **资源加载**
- SPA: 第一次加载所有必需资源
- MPA: 每个页面都要重新加载资源
2. **页面切换**
- SPA: 路由跳转,不刷新页面
- MPA: 传统链接跳转,需要刷新
3. **数据传递**
- SPA: 可以维护全局状态
- MPA: 需要 cookie/storage 等方式
4. **开发方式**
- SPA: 前后端分离
- MPA: 前后端耦合
### vue生命周期,绑定dom事件和网络请求分别在哪个生命周期里
让我来介绍 Vue 的生命周期钩子函数:
### 主要生命周期钩子
1. **beforeCreate**
- 实例创建前
- 此时无法访问数据和方法
2. **created**
- 实例创建完成
- 可以访问数据和方法
- 适合进行**网络请求**,因为此时可以操作数据
3. **beforeMount**
- 挂载前
- 模板编译完成,但还未挂载到 DOM
4. **mounted**
- 挂载完成
- DOM 已经渲染完成
- 适合进行**DOM 操作和事件绑定**
5. **beforeUpdate**
- 数据更新前
- 可以在更新前访问现有的 DOM
6. **updated**
- 数据更新后
- DOM 已经更新完成
7. **beforeUnmount**
- 组件卸载前
- 适合移除事件监听器等清理工作
8. **unmounted**
- 组件卸载完成
#@## 最佳实践
1. **网络请求放在 created 中**:
```javascript
created() {
// 发起API请求
this.fetchData();
}
- DOM 操作和事件绑定放在 mounted 中:
1
2
3
4
5
6
7
8
9mounted() {
// DOM操作
document.getElementById('app').addEventListener('click', this.handleClick);
}
// 记得在组件销毁前解绑
beforeUnmount() {
document.getElementById('app').removeEventListener('click', this.handleClick);
}
使用场景
- created: 数据初始化、异步请求
- mounted: DOM操作、第三方库初始化
- beforeUnmount: 清理定时器、解绑事件
- updated: 处理数据更新后的DOM操作
git
- 分支管理
- 分支管理一般有feature、dev、master等分支,feature分支是用于开发新功能,dev分支是用于测试,master分支是用于发布;
- 出现冲突怎么办
- 首先肯定是做好避免冲突的措施,从源头上是最有效的,比如规定每次提交只能提交一个小功能,避免大范围冲突,并且提交的时候一定要按照规范写好提交信息,比如fix~;
- 如果发生冲突了,这时候就比较小了,那就直接当事人一起codeView嘛;
11. Git 冲突
- 原因:同一文件在多个分支被修改,合并时无法自动解决差异。
- 解决步骤:
git pull
后出现冲突标记(<<<<<<< HEAD
和>>>>>>>
)。- 手动编辑文件,保留需要的代码,删除冲突标记。
git add <file>
标记为已解决。git commit
完成合并。
浏览器输入URL到页面展示的全流程
1. URL 解析与预处理
- 协议处理:自动补全协议(如
http://
或https://
)。 - 编码转换:处理特殊字符(如空格转为
%20
)。 - HSTS 检查(仅 HTTPS):强制使用 HTTPS 连接(若域名在 HSTS 列表中)。
2. DNS 域名解析
- 查询顺序:
- 浏览器缓存 → 2. 系统(hosts 文件)缓存 → 3. 路由器缓存 → 4. ISP 的 DNS 服务器 → 5. 递归查询根域名服务器。
- 优化:DNS 预解析(
<link rel="dns-prefetch" href="//example.com">
)。
3. 建立 TCP 连接
- 三次握手:
- 客户端发送
SYN
包 → 2. 服务端返回SYN-ACK
→ 3. 客户端发送ACK
。
- 客户端发送
- HTTPS 加密:在 TCP 连接后,进行 TLS 握手(交换密钥、验证证书)。
4. 发送 HTTP 请求
- 请求行:方法(GET/POST)、URL、协议版本。
- 请求头:
Cookie
、User-Agent
、Accept-*
等。 - 请求体:POST/PUT 时的数据(如表单 JSON)。
5. 服务器处理请求
- 负载均衡:请求可能被转发到集群中的某台服务器。
- 后端处理:执行 API、查询数据库、生成响应(HTML/JSON)。
- 返回响应:状态码(200 OK、404 Not Found)、响应头、响应体。
6. 浏览器解析与渲染
构建 DOM 树:
- 解析 HTML → 生成 Token → 构建 DOM 节点树。
- 遇到
<script>
会阻塞 DOM 解析(除非标记async
/defer
)。
构建 CSSOM 树:
- 解析 CSS 样式(内联、外联、
<style>
)→ 生成 CSSOM 树。
- 解析 CSS 样式(内联、外联、
合并渲染树(Render Tree):
- 结合 DOM 和 CSSOM,排除不可见节点(如
display: none
)。
- 结合 DOM 和 CSSOM,排除不可见节点(如
布局(Layout/Reflow):
- 计算每个节点的位置和尺寸(视口大小、盒模型)。
绘制(Paint)与合成(Composite):
- 将渲染树转为屏幕像素(分层绘制 → 合成图层 → GPU 加速)。
7. 加载子资源与执行脚本
- 图片、字体等:异步加载,可能延迟渲染完成时间。
- JavaScript 执行:
- 触发
DOMContentLoaded
事件(DOM 解析完成)。 - 所有资源加载完成后触发
load
事件。
- 触发
8. 连接关闭与缓存
- TCP 连接:完成请求后可能关闭(
Connection: close
)或保持复用(HTTP/1.1 默认 Keep-Alive)。 - 缓存策略:
- 强缓存:
Cache-Control
/Expires
(直接使用本地缓存)。 - 协商缓存:
Last-Modified
/ETag
(向服务器验证缓存有效性)。
- 强缓存:
关键优化点
- 减少 DNS 查询:使用 DNS 预解析、减少域名数量。
- TCP 复用:HTTP/2 多路复用、Keep-Alive。
- 压缩资源:Gzip、Brotli 压缩 HTML/CSS/JS。
- 关键渲染路径优化:内联关键 CSS、异步非关键 JS、延迟加载图片。
- CDN 加速:静态资源分发到边缘节点。
流程图概览
1 |
|
- 生命周期
1
2
3
4
5
6
7// 组件被缓存时的生命周期
activated() {
// 组件被激活时调用
},
deactivated() {
// 组件被停用时调用
}
使用示例
基础用法
1
2
3
4
5<template>
<keep-alive>
<component :is="currentComponent"/>
</keep-alive>
</template>条件缓存
1
2
3
4
5<template>
<keep-alive :include="['ComponentA', 'ComponentB']">
<router-view/>
</keep-alive>
</template>
主要特点
- 缓存策略
- 默认缓存所有子组件
- 可通过 include/exclude 控制缓存组件
- 可通过 max 控制最大缓存数量
- 性能优化
- 避免重复创建和销毁组件
- 减少不必要的 DOM 操作
- 保留组件状态
- 注意事项
- 只能有一个直接子组件
- 不能和 v-show 一起使用
- 需要给缓存的组件设置 name 属性
事件冒泡
让我来解释事件冒泡(Event Bubbling)的概念和应用:
事件冒泡原理
事件冒泡是指事件从最深的节点开始,逐步向上传播到父节点的过程。
1 |
|
- 事件处理
基本使用
1
2
3
4
5
6
7
8// 添加事件监听
child.addEventListener('click', function(e) {
console.log('Child clicked');
});
parent.addEventListener('click', function(e) {
console.log('Parent clicked');
});阻止冒泡
1
2
3
4child.addEventListener('click', function(e) {
e.stopPropagation(); // 阻止事件继续冒泡
console.log('只触发 Child');
});
- 实际应用
事件委托(代理)
1
2
3
4
5
6
7// 利用冒泡实现事件委托
const ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
console.log('点击了列表项:', e.target.textContent);
}
});自定义事件
1
2
3
4
5
6
7
8// 创建自定义事件
const customEvent = new CustomEvent('myEvent', {
bubbles: true, // 允许冒泡
detail: { message: 'Hello' }
});
// 触发自定义事件
element.dispatchEvent(customEvent);
- 注意事项
- 不是所有事件都冒泡
- focus
- blur
- mouseenter
- mouseleave
事件捕获
1
2
3element.addEventListener('click', function(e) {
console.log('捕获阶段');
}, true); // true 表示在捕获阶段处理事件流三个阶段
捕获阶段(从上到下)
目标阶段(到达目标元素)
冒泡阶段(从下到上)
应用场景
- 实现事件委托,提高性能
- 实现事件广播
- 组件通信
- 插件开发
父子组件渲染过程
让我来解释 Vue 中父子组件的生命周期执行顺序:
- 创建过程
1 |
|
更新过程
1
2
3
4父 beforeUpdate
子 beforeUpdate
子 updated
父 updated销毁过程
1
2
3
4父 beforeUnmount
子 beforeUnmount
子 unmounted
父 unmounted重要特点
- 创建阶段:
- created 钩子是从父到子依次执行
- mounted 钩子是从子到父依次执行
- 父组件会等待所有子组件挂载完成后,才会执行自身的 mounted 钩子
Props 传递注意事项:
1
2
3
4
5
6
7
8
9
10
11
12// 父组件
export default {
created() {
// 推荐在 created 中准备数据
this.propData = 'data'
},
mounted() {
// 不推荐在 mounted 中设置 props
// 可能导致子组件重复渲染
this.propData = 'data'
}
}最佳实践:
- DOM 操作放在 mounted 中
- 数据初始化和异步请求放在 created 中
- 避免在父组件 mounted 中修改传递给子组件的 props
跨域问题如何解决
让我来介绍几种常见的跨域解决方案:
- CORS (跨域资源共享)
服务端设置响应头:
1 |
|
- Nginx 反向代理
1
2
3
4location /api {
proxy_pass http://target-domain.com;
proxy_set_header Host $host;
}
2. 代理服务器
- 开发环境 (Vue)
1
2
3
4
5
6
7
8
9
10
11
12
13
14// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://target-domain.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
3. JSONP (仅支持 GET):利用 <script> 标签没有跨域限制的特性,但只支持 GET 请求
1 |
|
4. postMessage
适用于不同窗口间通信:
1 |
|
注意事项
- CORS 是最推荐的跨域解决方案
- 代理方案适合开发环境和生产环境
- JSONP 只支持 GET 请求,已逐渐被淘汰
- 需要注意跨域安全性,合理配置允许的域名
MVC
MVC 全名是 Model View Controller 的简写,是模型 (model)-视图 (view)-控制器 (controller) 的缩
写
在这其中:
Model (模型):负责处理数据的逻辑和从数据库存取数据,也就是数据存取
View (视图):负责展示数据,UI界⾯,⽤户交互,也就是⽤户界⾯
Controller (控制器): 处理业务逻辑
视图层 发送⽤户操作给控制器, 控制器 处理业务逻辑,完成后告诉模型更改状态,模型 将新的的数据
发送给视图层,⽤户得到操作的反馈。
原型链基本概念
原型链是 JavaScript 实现继承的主要方式,它通过 __proto__
属性将对象连接起来。
基本示例
1 |
|
原型链特点
- 属性查找机制
- 先查找对象自身属性
- 如果没找到,查找原型
- 继续向上查找原型的原型
- 直到 null
- 属性设置/修改
- 总是在对象自身上操作
- 不会修改原型上的属性
- 检测方法
1
2
3
4
5// 检查属性是否在原型链上
console.log('name' in person); // true
// 检查是否是自身属性
console.log(person.hasOwnProperty('name')); // true
nextTick
nextTick
是 Vue 提供的一个全局 API,用于在下次 DOM 更新循环结束之后执行延迟回调。
主要用途:
确保DOM更新完成:
1
2
3
4
5
6
7
8
9
10// 修改数据
this.message = '新消息'
// DOM 还未更新
console.log(this.$el.textContent) // 仍然是旧的内容
// 使用 nextTick 确保 DOM 已更新
this.$nextTick(function () {
// DOM 现在已经更新
console.log(this.$el.textContent) // 新消息
})处理依赖DOM更新的操作:
1
2
3
4
5
6
7
8
9
10methods: {
updateHeight() {
this.someData = '更新数据'
// 直接获取高度可能不准确
this.$nextTick(() => {
// 这里能获取到更新后的 DOM 高度
const height = this.$refs.myDiv.offsetHeight
})
}
}
工作原理:
- Vue 的响应式系统会将数据的变化缓存在一个队列中
- 同一个 “tick” 中的多个数据变更会被批量更新
nextTick
会在队列清空(即 DOM 更新完成)后执行回调
使用场景:
- 需要在 DOM 更新后获取新的 DOM 元素尺寸
- 需要在 DOM 更新后进行 DOM 操作
- 需要确保某些依赖最新 DOM 状态的操作在 DOM 更新后执行
注意事项:
nextTick
返回一个 Promise,可以使用 async/await 语法- 在 created 生命周期中访问 DOM 时,需要使用 nextTick
- Vue3 中可以直接导入使用:
1
2
3
4
5
6
7
8import { nextTick } from 'vue'
// 使用方式
await nextTick()
// 或
nextTick(() => {
// 操作
})
综合场景题
问题:如何通过 Node.js 后端生成 3D 场景数据,并通过前端 Three.js 动态渲染?
答:
- 后端:使用 Node.js + Express 提供 REST API,生成模型坐标、材质等信息(如 JSON 格式)。
- 前端:通过
fetch
请求数据,动态创建 Three.js 物体:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38fetch('/api/scene-data').then(res => res.json()).then(data => {
data.objects.forEach(obj => {
const geometry = new THREE.BoxGeometry(obj.size);
const material = new THREE.MeshBasicMaterial({ color: obj.color });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
});
```
---
#### 3. **原型与继承**
- **问题**:原型链是什么?如何实现继承?
- **答案**:
- 原型链是对象通过 `__proto__` 属性向上查找属性和方法的链式结构。
- 继承方式:
- **原型继承**:`Child.prototype = new Parent()`。
- **组合继承**:结合构造函数和原型链(推荐)。
- **ES6 类继承**:`class Child extends Parent`。
#### 4. **事件循环与异步**
- **问题**:解释事件循环(Event Loop)及宏任务与微任务的区别。
- **答案**:
- **事件循环**:JS 单线程通过任务队列处理异步任务。
- **执行顺序**:同步代码 → 微任务(`Promise.then`、`MutationObserver`)→ 宏任务(`setTimeout`、`DOM 事件`)。
- **示例**:`async/await` 的执行顺序结合微任务队列分析。
#### 5. **手写代码题**
- **防抖(Debounce)**:限制高频触发(如输入框搜索)。
```javascript
function debounce(func, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
- 深拷贝:递归复制对象属性,避免引用共享。
1
2
3
4
5
6
7
8function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
const result = Array.isArray(obj) ? [] : {};
for (const key in obj) {
result[key] = deepClone(obj[key]);
}
return result;
}
二、网络协议基础
1. HTTP 协议
- 问题:HTTP 缓存机制有哪些?如何实现?
- 答案:
- 强缓存:
Cache-Control
(max-age
)、Expires
。 - 协商缓存:
Last-Modified
/If-Modified-Since
、ETag
/If-None-Match
(优先级更高)。
- 强缓存:
- 答案:
2. TCP 与 UDP
- 问题:TCP 三次握手和四次挥手的过程?
- 答案:
- 三次握手:客户端发送 SYN → 服务端返回 SYN+ACK → 客户端发送 ACK。
- 四次挥手:客户端发送 FIN → 服务端返回 ACK → 服务端发送 FIN → 客户端返回 ACK。
- 答案:
3. HTTPS 安全性
- 问题:HTTPS 如何保证数据传输安全?
- 答案:
- 使用 SSL/TLS 加密,通过非对称加密交换密钥,对称加密传输数据。
- 证书验证:CA 机构颁发证书,防止中间人攻击。
- 答案:
4. 跨域与解决方案
- 问题:如何解决跨域问题?
- 答案:
- CORS:服务端设置
Access-Control-Allow-Origin
。 - 代理服务器:前端通过代理转发请求。
- JSONP:利用
<script>
标签跨域(仅限 GET 请求)。
- CORS:服务端设置
- 答案:
手撕场景图
- 块级元素居中显示,固定在右下角,具体见居中+固定.html
- 定义一个对象数组,实现筛选、查找、对属性求和、提取对象一个属性映射成数组,考察一些数组函数的使用,具体见try.js
生成8位随机字符串:
1 |
|
判断变量类型:
1 |
|
数字取整:
1 |
|
并行任务处理
Promise.all() 接收一个 Promise 数组,返回一个新的 Promise。当所有 Promise 都完成时才会触发 then。
1 |
|
清洗HTML代码获取文本:
正则表达式 /<[^>]+>/g 的原理:
1 |
|
1 |
|
数组操作方法:js中数组追加元素,压入元素头部,插入元素用什么
1 |
|
自我介绍
面试不是背简历,而是把实习、项目中的你擅长的技术点和相关能力划重点,引导面试官去提问,方便他知道该问什么,你在哪些点上擅长;
面试官面试一般先会前几分钟让你自我介绍的时候看一眼你的简历,所以你自我介绍时间最好在2-3min,太长,面试官时间也宝贵,人家都想好问你啥了,还在一直听你在巴拉巴拉没有重点;时间太短人家简历都没看完;当然这个只是一般情况,主要取决于面试官本人;如果你想深入介绍下一些东西可以在2-3min把自己介绍地差不多之后再询问一下面试官的意见。
介绍内容(按照简历上的顺序)
- 你是谁,哪个学校的,什么学历,有哪些奖项(在校的东西没有什么含金量的话,直接pass,校内的东西在校外大概率没一点用)
- 介绍自己的技术栈,哪些技术比较熟悉、经验丰富,从这引导面试官去提问你准备好的内容:
- 实习经历:在某某处实习,一句话总结,做什么工作,用到什么技术栈,如果有自己擅长的可以引导面试官去问你准备好的和这段工作经历相关的技术点/八股文,提几个擅长的就可以来不要全列出来(篇幅太长);
- 项目经历:一句话介绍一下项目:同样的如果还有希望让面试官提问的自己擅长的点,可以引导;
示例:面试官您好,很荣幸有机会能参加这次贵公司的面试;我是王斌杰,目前在武汉的江汉大学,大三在读;有前端开发和AI应用开发的实习和项目经验;比较熟悉的技术栈有html/css/Vue3及其生态系统以及Element plus和Vant等UI框架的使用,了解python、JAVA等一些后端语言,熟悉cursor、copilot等AI工具的使用;有两端实习经历:第一段是在一个武大初创团队做AI应用web前端开发,项目背景是结合AI设计药物与区块链代币化;我主要是负责项目介绍页面和药物分子生成对话界面的前端开发;根据UI设计图开发界面,和后端对接;第二段实习经历是在北京普测时空,主要负责在搭载国产芯片的边缘AI计算设备AIbox1684x上开发AI应用,参与了智能文件搜索系统和会议总结系统两个项目,主要工作内容是调研和整合各种开源项目使用python进行开发;
实习经历提问
实习过程中最难的地方;
- 如何充分利用有限的资源中,实现项目要求,我们这个盒子它的资源是有限,刚开始我是先调研预处理项目嘛,还没在真正的盒子设备里部署,后面资源有限发现跑不起来,因为MinerU它是只支持CPU和GPU加速的,我们这个是NPU,所以需要进行转移操作:将MinerU中用到的各种识别模型,转yi成支持TPU的bmodel格式,然后在docker中去使用它调用NPU资源
- 我做的应用层的预处理,顶层用的casaos作为交互界面,它是一个开原的开源个人云系统,我们做出来最后其实类似于一个Nas;
技术问题
预处理对于RAG有什么作用?
- 文本标准化
- 将不同格式(PDF、Word、图片等)的文档转换为统一的文本格式,便于后续处理
- 清理和规范化文本,去除噪声数据,提高文本质量
- 提升检索效果
- 标准化的文本更容易建立索引
- 提高语义匹配的准确度
- 减少由格式不一致导致的检索偏差
- 优化向量化效果
- 清晰规范的文本能得到更好的向量表示
- 提高文本嵌入(embedding)的质量
- 使相似文本的向量距离更接近
- 提升系统性能
- 减少处理非结构化数据的开销
- 加快检索速度
- 节省存储空间
项目经历
- 主要是陪诊系统的技术细节
- 项目是一个面向县城小诊所使用的陪诊系统,包括面向诊所人员的PC端后台管理系统和面向用户的移动端H5订单系统,实现了注册、登录、用户移动端能够填写订单信息、选择诊所、陪护师、日期、需求等,发起订单请求,后端管理系统可以展示和管理陪护师信息、订单信息、制定不同的权限菜单,根据不同角色分配不同的权限菜单,实现权限管理;
- 项目亮点
根据用户的权限信息动态生成和添加路由,通过递归组件呈现相应的多层级菜单,提升了系统灵活性,确保菜单栏与路由的同步更新、动态实时渲染,
通过请求头header中的token进行身份验证,通过路由守卫实现登录拦截
使用 Vuex,通过localStorage 存储用户、路由、token信息到本地,实现数据持久化
项目的亮点:参照简历上提炼的去剖细节
- 通过递归组件实现后台系统多层级菜单,支持无限层级配置,提升了系统灵活性
- 根据用户的权限菜单信息动态生成路由,确保菜单栏与路由的同步更新、实现菜单栏动态实时渲染
- src\view\login\index.vue:登录成功之后向后端发起两个请求,一个请求拿到用户的信息和token存到浏览器里,另一个请求用户的权限信息通过sotre的mutations存到vuex的store定义的routerList变量里,这里面就包含所有权限路由信息,同时把这些路由信息通过addRoute添加到根目录中,作为根目录的子路由;
- 然后在公共组件Aside中定义变量menuData通过router.options.routes[0].children来获取所有的菜单路由信息并把它传递到递归组件SubMenu.vue中,SubMenu.vue中通过递归的方式把菜单渲染出来;
- SubMenu组件里用的是elment plus组件库里的template标签在最外层通过v-for遍历Aside传过来的路由信息,(给他赋予一个key属性,这个key由v-for的index还有路由的id信息拼接而成,比如第一个子菜单控制台的key就是1,第二个就是2,如果子菜单还有子菜单,那key就是2-1,2-2,以此类推);里面包含着el-menu-item 标签,通过v-if判断传过来的正在遍历的在这个路由下面有没有子路由,如果没有的话,就只渲染这个子菜单的元素和相关信息,如果有的话,也给你el-sub-menu标签中使用v-else同样赋予它一个Key(和上面的相同),然后在这个标签中继续调用这整个SubMenu组件.有多少个子路由就有多少个菜单;
- 菜单点击之后发亮效果怎么实现的?
- 在store存了一个MenuActive的变量;点击相应的菜单就会通过mutitation调用我定义的改变激活子菜单的实现,将点击菜单的index属性赋值给MenuActive,在css中设计一个类名展示被激活的样式,在el-menu-item中有一个通过判断当前标签的索引是否和store里存储的这个激活菜单项相当,赋予他这个类名,从而实现菜单点击之后发亮效果;
- 使用token进行身份验证通过路由守卫实现登录拦截使用 Vuex 配合 vuex-persistedstate 插件实现数据持久化
- 在创建store的时候用了persistedstate这个插件,作用是当 Vuex store 中的状态发生变化时(通过 mutation),vuex-persistedstate 会将新的状态存储到浏览器中。页面加载时:vuex-persistedstate 从浏览器存储中恢复状态到 Vuex store。状态变化时:vuex-persistedstate 将新的状态存储到浏览器中。
- 通过 localStorage 存储用户、路由、token信息到本地
- 没啥好说
业务上:这个项目的业务背景是什么,在业务上有什么比较牛逼的地方,推动了业务如何运行等等?
- 我是一个从小在小县城长大的人,小时候生病都是直接去小诊所打针/开药,在我印象里,诊所总是爆满,很多人都没位置坐,而且有的护士她能很差,给别人打针都会打漏,我就想能不能设计一个系统,让用户能提前预约陪诊也能选择自己觉得更好的陪护士;然后就和同学一起开始做了,后面想到能不能让诊所里上班的人也能上网看呢有多少要处理呢,不同职位的人看到的信息也不一样,然后就慢慢开发成这个样子了;
- 我觉得最牛的就是能和同学一起开发一个项目, 从无到有,这种吧自己的想法实现变成现实的感觉很好
项目来源于哪?
- 刚开始是自己和同学一起想的然后也参考一些网上的其他项目;慢慢拓展成现在这样;
技术实现上:这个项目的整体技术实现思路是怎样的,项目中用了什么比较牛逼的技术,解决了什么比较困难的问题等等
做这个项目所花费的时间是多少?vue全家桶,最困难的就是这个不同的权限信息展示不同的菜单栏实现权限管理,
难点
- 递归组件,动态菜单栏:前面有讲过
- 前后端联调:
- 我有想法之后 ,同时我前端先搞出来,用mock模拟一下,给同学提需求,和他交流让他把加相应的接口,把详细细节用APIfox补充在接口文档里 ,然后我在按照接口文档去改;
- 途中也遇到很多问题,比如他写的接口参数和响应数据的数据类型、数据内容不对,我就会和他交流,让他改,然后我再去
- 经常遇到bug,刚开始一个bug能修一天,后来掌握了一些技巧debug的技巧就快多了;我得到了一些关于debug的感悟和复盘总结,具体见D:\Lsz\code\font_back_end\fontend\vue_learn\pzadmin\note\note_vue.md中【总结一下debug的经验、流程】
- debug的目的
- 解决问题
- 发现自己的认知缺陷和不足之处;
- 如何debug
- F12打开网页的控制台输出/打断点,network,接口文档,问AI,git回滚
- debug的目的
是否是多人项目,如果是,在项目中担任什么角色? 你负责了项目的哪块内容?
- 同学复责后端,我负责前端所有内容
项目实现了哪些功能,又或者说你参与的部分实现了哪些功能
- 我参与了所有前端部分,包括实现了注册、登录、移动端用户创建预约陪诊订单、后台系统展示和管理诊所人力资源、订单状态及数量、用户权限
做项目的过程中使用了哪些技术栈?为什么使用它?
你使用XX技术栈的时候有没有什么坑,你们怎么解决的?
- 使用了Vue3的组合式 API ,提供更灵活和模块化的代码结构
- Vite作为构建工具和开发服务器,提供更快的开发服务器启动和热更新
- Vue Router于前端路由管理
- Vuex用于状态管理,存储全局并量方法等数据项目中遇到过什么印象比较深的Bug?
- bug:提交表单后无法显示提示”注册成功,请登录”;下面我叙述一下debug的过程
- 首先排除Elment组件的导入问题,因为发送验证码的时候是有提示的
- 在提交时在控制台里输出一下提交信息,打开F12在控制台,是有的,说明参数传进去了
- 点开network一看发现响应失败,也就是说明后端接口不接受这个数据
- 所以我仔细对照了一下接口文档,发现传入的一个参数名字validCode 写成了validcode,字母li的顺序写反了;然后解决
你是根据哪些指标进行针对性优化的?
- 使用 Vite 作为构建工具,提供更快的开发服务器启动和热更新
- 使用Vue Router的hashweb模式,不用每更改一次路由就像服务器发送一次请求,减少了服务器的压力
- vuex-persistedstate 实现状态持久化,不用频繁地向后端请求同一个数据
动态路由实现方式
登录Token验证相关细节
前端优化
- 打包优化
- 首屏优化
- 懒加载
遇到的最困难的技术问题
非技术问题
- 为什么选择前端开发
- 其实我接触前端算是比较晚的了,我们08届师兄陈玉龙他是CSDNCTO,大二暑假的时候来我们学校办了一个Web开发和计算基础训练营,我报名参加了,从那个时候开始接触的Web开发,我也比较喜欢前端这种和用户直接交互、可以用代码搭建所见即所得的界面开发模式,而且前端的生态还比较繁荣,所以就选择深入下去学习了前端开发;
- 你平时怎么学习的
- 我平时会看一些大佬的技术博客,比如阮一峰还有同龄的很优秀的人分享的以及掘金上的技术文章,对于一些比较难以理解的知识或者一个系统性的演示,我一般会去Blibli或者YouTube上看一些视频入门;再进阶一点就会去看一些技术书籍(比如图解Http),去github上研究一些相关技术的优质项目的源码,甚至参与建设;还有就是一定要多动手实践,我学Vue的时候就是一边做项目,一边看文档和做笔记,记忆更深刻,效率更高;
- 你对你未来的职业规划有着怎么样的打算。
- 我目前的打算是现在前端这个领域深耕下去,找到自己一个感兴趣的领域深入研究下去(比如浏览器、3D、AI应用交互等等),然后能学习一些后端,争取能成为一个全栈工程师,如果可以的尝试成为架构师;
- 你对加班怎么看待。
- 如果项目开发时间比较紧张,在身体和情绪可接受的范围内加班我是可以接受的;
- 你情绪低落你会选择什么样的方式缓解。
- 我一般会去运动,打篮球、羽毛球、乒乓球,或者去跑跑步,运动可以让我暂时忘记烦恼,放松心情,同时也能锻炼身体,保持一个良好的身体状态;
- 你的低谷期是多久。
- 我不太容易陷入低谷期,还是比较乐观的,如果真陷入了,大概率2-3天出去走走转转、踢踢足球、跑跑步,想开了就能恢复;
- 遇到的最困难的事
- 我觉得目前遇到最困难的事就是在秋招的时候进入中大厂;市场逐渐饱和、竞争越来越激烈了,优秀的人太多了,我目前这种水平其实没太大机会,所以要更加用心、刻苦地学习和工作、积累经验和简历
- 我的优点
- 第一我比喜欢与人沟通、交流,上一段实习的时候就认识了蛮多朋友的,他们不和我一个方向,但我和他们交往的过程中也学到了蛮多以前我都接触不到的行业的知识,拓展了我的认知,我认为拓展认知就是学习的本质,这也是我出来实习的意义之一,
- 第二我比较乐观,不会轻易出现畏难情绪,生死之外无大事,只要肯学习和钻研,多向优秀的人请教,反正我觉得没什么能难倒我的;
- 我的缺点
- 第一我比较喜欢钻牛角尖,有时候一个问题我会想很久,从客观上来讲是在浪费时间的;不过我在上一段实习过程中,mentor告诉了我一个0.5h法则;在工作中遇到难题,先自己搜集资料尝试各种办法解决,如果半个小时之后还是没有头绪,就直接问同事和mentor;我觉得这个方法蛮好的,可以避免我钻牛角尖,也可以提高工作效率;所以一直记得;
反问
- 部门负责的产品是什么?
- 技术团队人员现状、人数?
- 面试表现怎么样,有哪些可以改进的地方?
面向公司
wjby
部署页面是如何保证用户接受到的页面是最新的
我将详细解释前端页面缓存和如何确保用户加载最新版本的页面。
在前端部署中,浏览器缓存确实会影响用户访问页面时加载的资源。为了解决这个问题,我们有几种常用的方案:
- 文件指纹(Hash)策略
最常用且推荐的方法是在静态资源文件名中添加哈希值。这种方式可以确保当文件内容发生变化时,文件名也会随之改变,浏览器会被迫加载新的资源。
示例:
app.js
→app.abc123.js
style.css
→style.def456.css
webpack、vite等构建工具都默认支持这种方式。代码示例:
1 |
|
- 强制更新策略
可以通过以下几种方式实现:
a. Cache-Control 响应头控制
1 |
|
b. Service Worker 方案
使用 Service Worker 可以精确控制缓存策略,实现秒级更新。
1 |
|
- HTML 版本标记
在 HTML 中添加版本戳:
1 |
|
- 前端构建时间戳
在构建时动态生成时间戳:
1 |
|
推荐实践:
- 使用文件指纹(推荐)
- 配置合理的缓存策略
- 结合 CDN 部署
- 对于关键资源,可以使用
Cache-Control: no-cache
注意事项:
- 不同的缓存策略适用于不同的场景
- 要平衡性能和实时更新
- 考虑用户体验和加载速度
总结:通过文件指纹和缓存控制,可以有效确保用户每次访问都能加载最新的页面资源。选择合适的方案取决于具体的业务需求和技术架构。
Node.js 与 npm 高频问题及参考答案
1. Node.js 事件循环(Event Loop)是什么?各阶段的作用?
答:
- 定义:事件循环是 Node.js 处理异步任务的机制,分为 6 个阶段:
- Timers:执行
setTimeout
/setInterval
回调。 - Pending callbacks:执行系统操作回调(如 TCP 错误)。
- Idle/Prepare:内部使用。
- Poll:检索新的 I/O 事件,执行 I/O 回调。
- Check:执行
setImmediate
回调。 - Close callbacks:执行关闭事件回调(如
socket.on('close')
)。
- Timers:执行
- 关键点:
process.nextTick()
和Promise.then()
属于微任务,在阶段切换前执行。setImmediate
在 Check 阶段执行,setTimeout
在 Timers 阶段执行。
2. CommonJS 和 ES Module 的区别?如何兼容两者?
答:
- 区别:
CommonJS ES Module require()
动态同步加载import
静态编译时加载模块输出是值的拷贝 输出是值的引用(动态绑定) 适用于 Node.js 环境 浏览器原生支持,Node.js 需配置 - 兼容:在
package.json
中设置"type": "module"
,或使用.mjs
/.cjs
扩展名区分。
3. npm 的依赖版本符号 ^
、~
、*
有何区别?
答:
- **
^1.2.3
**:允许更新次版本和补丁版本(1.x.x
,不更新主版本)。 - **
~1.2.3
**:仅允许更新补丁版本(1.2.x
)。 *
或latest
:匹配最新版本(慎用)。- 锁定依赖:
package-lock.json
或npm-shrinkwrap.json
确保安装精确版本。
4. 如何解决 npm 依赖冲突(如不同包依赖同一库的不同版本)?
答:
- 依赖提升:npm/yarn 将共用的依赖提升到顶层
node_modules
。 - 手动指定版本:在
package.json
中显式指定依赖版本。 - 使用 resolutions 字段(yarn):强制统一依赖版本。
Three.js 高频问题及参考答案
1. Three.js 的核心组件有哪些?简述其作用。
答:
- Scene:3D 场景容器,管理所有物体、光源、相机。
- Camera:定义视图投影方式(如
PerspectiveCamera
透视投影)。 - Renderer:将场景渲染到画布(WebGL 或 Canvas 2D)。
- Geometry:物体几何形状(如
BoxGeometry
立方体)。 - Material:物体材质(如颜色、纹理、反光属性)。
- Light:光源(如
DirectionalLight
平行光、AmbientLight
环境光)。
2. 如何优化 Three.js 的渲染性能?
答:
- 减少 Draw Calls:合并几何体(
BufferGeometryUtils.mergeBufferGeometries
)。 - 使用 InstancedMesh:批量渲染相同物体(如大量树木、粒子)。
- LOD(细节分级):根据距离切换不同精度的模型。
- 纹理压缩:使用压缩格式(如
KTX2
)减少显存占用。 - 避免频繁 GC:复用对象(如 ObjectPool 模式管理粒子)。
3. 如何实现点击 3D 物体触发交互?
答:
射线检测(Raycasting):
1 |
|
- 主要用途:
- 存储 DOM 节点相关数据
- 实现私有属性
- 数据缓存
WeakSet
- 特点:
- 只能存储对象
- 同样是弱引用
- 不可遍历
1 |
|
- 应用场景:
- 存储 DOM 元素集合
- 临时对象存储
- 防止对象重复添加
区别
- WeakMap/WeakSet 是弱引用,不会阻止垃圾回收
- 只能使用对象作为键或值
- 不能遍历,没有 size 属性
这两个数据结构主要用于处理需要自动垃圾回收的场景,可以有效防止内存泄漏。
5. Promise 常见的方法(then, catch, finally, all, race)
then(onFulfilled, onRejected)
:处理 Promise 的成功(fulfilled)和失败(rejected)状态。catch(onRejected)
:处理 Promise 的失败状态,相当于.then(null, onRejected)
。finally(onFinally)
:无论 Promise 是成功还是失败,都会执行的回调。Promise.all(iterable)
:接收一个 Promise 数组,当所有 Promise 都成功时,返回一个包含所有结果的 Promise;如果有一个 Promise 失败,则返回该失败的 Promise。Promise.race(iterable)
:接收一个 Promise 数组,当其中一个 Promise 成功或失败时,就返回该 Promise。Promise.allSettled()
: 接收一个 Promise 数组, 只有等到所有这些参数 Promise 实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
获取数组对象中3个成功的,7个失败的,如何获取到成功的。
1 |
|
6. eventloop**
JavaScript 是单线程的,但浏览器是多线程的。
Event Loop 是 JavaScript 实现异步的机制。
执行过程:
- 执行同步任务(主线程)。
- 遇到异步任务(如
setTimeout
、Promise
、事件回调),将其放入任务队列(宏任务队列、微任务队列)。 - 当主线程空闲时,检查微任务队列,执行所有微任务。
- 执行完微任务后,从宏任务队列中取出一个宏任务执行。
- 重复步骤 2-4。
宏任务(Macro Task):
setTimeout
、setInterval
、setImmediate
(Node.js)、I/O、UI 渲染。微任务(Micro Task):
Promise.then
、MutationObserver
、process.nextTick
(Node.js)。
7. Vue 的组件通信**
父子组件:
props
:父组件向子组件传递数据。$emit
:子组件向父组件触发事件。$refs
:父组件直接访问子组件的实例。$parent
/$children
: 访问父/子组件实例(不推荐).
兄弟组件:
- 事件总线(Event Bus):创建一个空的 Vue 实例作为中央事件总线,通过
$emit
触发事件,$on
监听事件。 - Vuex:状态管理模式。
- 事件总线(Event Bus):创建一个空的 Vue 实例作为中央事件总线,通过
跨级组件:
provide
/inject
:祖先组件提供数据,后代组件注入数据。- Vuex。
8. Vue 中获取不到数组下标?(Vue2 数据绑定的局限性,Dep 对象以及 getter/setter, Watcher)**
- Vue2 使用 Object.defineProperty() 对数据进行劫持, 无法检测到数组下标和长度的变化。
- Vue2 的解决方案:
- 使用 Vue 提供的变异方法(
push
、pop
、shift
、unshift
、splice
、sort
、reverse
)来操作数组。 - 使用
Vue.set
或this.$set
来添加或修改数组元素。
- 使用 Vue 提供的变异方法(
- vue3使用了proxy解决了此问题。
- Dep:依赖收集器,用于收集 Watcher。
- getter/setter:Object.defineProperty() 中的访问器属性,用于劫持数据的读取和修改。
- Watcher:观察者,当数据变化时,Dep 会通知 Watcher,Watcher 执行相应的更新操作。
9. $nextTick**
- 将回调函数延迟到下次 DOM 更新循环之后执行。
- 用于在修改数据后立即获取更新后的 DOM。
- 原理:将回调函数放入微任务队列,确保在 DOM 更新后执行。
10. git 方面 pull 与 fetch 的区别**
git fetch
:从远程仓库下载最新的代码和分支信息,但不会自动合并到本地分支。git pull
:相当于git fetch
+git merge
,从远程仓库下载代码并自动合并到当前分支。
11. vuex 为什么可以全局实现?**
- Vuex 使用单一状态树(一个大的对象),包含应用的所有状态。
- 通过 Vue 的插件机制(
Vue.use(Vuex)
),将 store 实例注入到所有组件中。 - 组件可以通过
this.$store
访问 store 中的状态和方法。
4. 为什么提前访问 let 和 const 定义的变量会报错**
- 因为
let
和const
声明的变量存在暂时性死区(Temporal Dead Zone,TDZ)。 - 在变量声明之前访问该变量会抛出
ReferenceError
。 - 这是为了避免
var
声明的变量提升带来的潜在问题,提高代码的可读性和可维护性。
5. var 的作用域
var
声明的变量具有函数作用域。- 这意味着
var
声明的变量在声明它的函数内部是可见的,如果在函数外部声明,则具有全局作用域。 - 如果在块级作用域(如
if
语句、for
循环)中使用var
声明变量,该变量仍然会提升到函数顶部,作用域仍然是函数作用域,而不是块级作用域。
8. Less 和 Sass 有什么优点
Less 和 Sass 都是 CSS 预处理器,它们扩展了 CSS 的功能,使 CSS 更易于维护和编写。
Less 和 Sass 的共同优点:
- 变量: 可以定义和使用变量,避免重复编写相同的值。
- 嵌套: 可以使用嵌套规则,使 CSS 代码更具结构性和可读性。
- 混合(Mixins): 可以定义可重用的 CSS 代码片段,并在需要的地方引用。
- 运算: 可以进行简单的数学运算,如加、减、乘、除。
- 函数: 可以使用内置函数或自定义函数来处理 CSS 值。
- 导入: 可以将多个 CSS 文件合并成一个文件,减少 HTTP 请求。
- 代码组织: 可以将 CSS 代码组织成更小的、更易于管理的模块。
Less 和 Sass 的区别:
特性 | Less | Sass |
---|---|---|
语言 | JavaScript | Ruby(最初),现在有 LibSass(C/C++ 实现) |
语法 | 类似于 CSS | 两种语法:Sass(缩进语法)和 SCSS(类似于 CSS) |
变量符号 | @ |
$ |
混合 | 使用 . 或 # 开头 |
使用 @mixin 定义,使用 @include 引用 |
扩展 | 使用 @extend 继承选择器的样式 |
|
条件语句 | 使用 @if 、@else if 、@else |
|
循环语句 | 使用 @for 、@each 、@while |
|
函数 | 内置函数较少 | 内置函数更丰富 |
社区 | 较小 | 较大 |
总的来说,Sass 的功能更强大,社区更活跃,但 Less 更容易上手,如果你的项目不需要 Sass 的高级功能,Less 可能是一个更轻量级的选择。
9. Vue 的响应式原理,2 和 3 的区别
Vue 2 的响应式原理:
- Vue 2 使用 Object.defineProperty() 来劫持对象属性的 getter 和 setter。
- 当数据发生变化时,setter 会被触发,通知 Watcher 进行更新。
- 缺点:
- 无法检测到对象属性的添加和删除。
- 无法检测到数组通过索引修改元素或修改数组长度。
- 需要对 data 中的每个属性进行遍历,性能开销较大。
Vue 3 的响应式原理:
- Vue 3 使用 Proxy 对象来代理整个对象或数组。
- Proxy 可以拦截对象或数组的各种操作,如读取、修改、添加、删除等。
- 当数据发生变化时,Proxy 会触发相应的 trap(捕获器),通知 Watcher 进行更新。
- 优点:
- 可以检测到对象属性的添加和删除。
- 可以检测到数组通过索引修改元素或修改数组长度。
- 不需要对 data 中的每个属性进行遍历,性能更好。
- 可以代理更复杂的数据类型,如 Map、Set。
10. Vue3 如何定义响应式数据
Vue 3 提供了以下几种方式来定义响应式数据:
ref
:用于定义基本数据类型(如数字、字符串、布尔值)的响应式数据。
返回一个包含
.value
属性的对象,通过.value
访问和修改数据。示例:
1
2
3
4
5
6
7import { ref } from 'vue';
const count = ref(0);
console.log(count.value); // 输出 0
count.value++;
console.log(count.value); // 输出 1
reactive
:用于定义对象或数组的响应式数据。
返回一个响应式代理对象,可以直接访问和修改对象的属性。
示例:
1
2
3
4
5
6
7
8
9
10import { reactive } from 'vue';
const state = reactive({
count: 0,
message: 'Hello',
});
console.log(state.count); // 输出 0
state.count++;
console.log(state.count); // 输出 1
readonly
:- 创建只读版本的响应式数据
shallowReactive
:和shallowRef
仅为对象创建浅层响应式/仅为ref创建浅层作用。
浅层作用的意思是只有对象本身是响应式的,不执行嵌套对象的深层响应式转换 (暴露原始值)。
11. ref 和 shallowRef 的区别
ref
:- 创建一个深层响应式的数据,即如果
ref
的值是一个对象,那么对象内部的属性也会被转换为响应式。 - 通过
.value
访问和修改数据。
- 创建一个深层响应式的数据,即如果
shallowRef
:- 创建一个浅层响应式的数据,即只有
.value
属性是响应式的,如果.value
的值是一个对象,那么对象内部的属性不会被转换为响应式。 - 通过
.value
访问和修改数据。 - 适用于只需要跟踪引用变化,不需要深层响应式的情况,可以提高性能。
- 创建一个浅层响应式的数据,即只有
示例:
1 |
|
12. Vue 组件通信方式
(已在前面的回答中详细解释过,请参考前面的回答)
13. EventBus 的原理是什么
EventBus(事件总线) 是一种用于在 Vue 组件之间进行通信的简单机制。
原理:
- 创建一个空的 Vue 实例作为中央事件总线。
- 组件 A 通过
$emit
方法触发一个事件,并传递数据。 - 组件 B 通过
$on
方法监听该事件,并在事件触发时执行回调函数,接收数据。 - 组件可以通过
$off
方法移除事件监听器
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 创建 EventBus
const EventBus = new Vue();
// 组件 A
export default {
methods: {
sendMessage() {
EventBus.$emit('message', 'Hello from Component A');
},
},
};
// 组件 B
export default {
created() {
EventBus.$on('message', (message) => {
console.log(message); // 输出 "Hello from Component A"
});
},
beforeDestroy() {
// 在组件销毁前移除事件监听,避免内存泄漏
EventBus.$off('message');
}
};
14. 多个回调函数嵌套会怎样
回调地狱(Callback Hell):
- 多个异步操作的回调函数层层嵌套,导致代码难以阅读、维护和调试。
- 代码呈现出“金字塔”形状,可读性差。
问题:
- 可读性差: 代码难以理解和跟踪。
- 难以维护: 修改或添加逻辑困难。
- 错误处理困难: 难以捕获和处理错误。
- 难以复用: 代码难以提取和复用。
解决方案:
- Promise: 使用 Promise 将异步操作链式化,避免回调地狱。
- async/await: 使用 async/await 语法糖,使异步代码看起来像同步代码,更易于阅读和理解。
- 事件发布/订阅模式: 使用事件发布/订阅模式解耦异步操作。
- 将回调函数拆分成独立的函数。
15. Promise 三种状态
- Pending(进行中): 初始状态,既不是成功也不是失败。
- Fulfilled(已成功): 操作成功完成。
- Rejected(已失败): 操作失败。
16. async 和 await
async
:- 用于声明一个异步函数。
- 异步函数总是返回一个 Promise 对象。
- 如果异步函数没有显式返回一个 Promise,它会自动将返回值包装在一个 Promise 中。
await
:- 只能在
async
函数内部使用。 - 用于等待一个 Promise 对象的结果。
await
表达式会暂停async
函数的执行,直到 Promise 对象的状态变为 Fulfilled 或 Rejected。- 如果 Promise 对象的状态变为 Fulfilled,
await
表达式会返回 Promise 的结果值。 - 如果 Promise 对象的状态变为 Rejected,
await
表达式会抛出 Promise 的拒绝原因。
- 只能在
示例:
1 |
|
做题:
1. 二叉树层序遍历
1 |
|
2. Promise 输出题(需要具体题目才能确定输出结果)
3. 判断链表是否有环
快慢指针
1 |
|
JS 基本数据类型,存储位置**
基本数据类型(原始类型):
- Number: 数字(整数、浮点数)
- String: 字符串
- Boolean: 布尔值(true 或 false)
- Null: 空值
- Undefined: 未定义
- Symbol: 符号(ES6 新增)
- BigInt: 大整数 (ES2020新增)
复杂数据类型(引用类型):
- Object:对象
- Array:数组
- Function: 函数
- Date
- RegExp
- Object:对象
存储位置:
- 基本数据类型: 值直接存储在栈内存中。
- 引用数据类型(对象): 对象本身存储在堆内存中,栈内存中存储的是指向堆内存中对象的引用(地址)。
2. 栈和堆存储有什么区别
特性 | 栈(Stack) | 堆(Heap) |
---|---|---|
存储内容 | 基本数据类型的值、引用数据类型的引用(地址) | 对象 |
管理方式 | 自动分配和释放(由操作系统或 JavaScript 引擎管理) | 手动分配和释放(在 C/C++ 中)或通过垃圾回收机制自动管理(在 JavaScript 中) |
存储顺序 | 后进先出(LIFO) | 无特定顺序 |
大小 | 较小,固定大小 | 较大,动态分配 |
速度 | 较快 | 较慢 |
用途 | 存储局部变量、函数参数、函数调用栈等 | 存储对象、数组等复杂数据结构 |
空间分配 | 空间由操作系统自动分配 | 动态分配,大小不定 |
存取方式 | 先进后出 |
3. OSI 七层网络模型
OSI(Open Systems Interconnection,开放系统互连)七层网络模型是一个概念模型,用于描述网络通信的各个层次。
层级 | 名称 | 功能 | 常见协议 |
---|---|---|---|
7 | 应用层(Application) | 为应用程序提供网络服务,如文件传输、电子邮件、远程登录等。 | HTTP、HTTPS、FTP、SMTP、POP3、DNS、Telnet 等 |
6 | 表示层(Presentation) | 数据表示、数据加密、数据压缩等。 | SSL、TLS、JPEG、GIF、ASCII 等 |
5 | 会话层(Session) | 建立、管理和终止会话(应用程序之间的连接)。 | |
4 | 传输层(Transport) | 提供可靠的或不可靠的数据传输服务,如 TCP 和 UDP。 | TCP、UDP |
3 | 网络层(Network) | 负责数据包的路由和转发,将数据包从源主机传输到目标主机。 | IP、ICMP、ARP、RARP 等 |
2 | 数据链路层(Data Link) | 将数据包封装成帧,在物理链路上传输,提供差错检测和纠正。 | Ethernet、Wi-Fi、PPP、HDLC 等 |
1 | 物理层(Physical) | 定义物理介质的特性,如电压、数据速率、连接器类型等,负责传输比特流。 |
4. TCP 和 UDP
特性 | TCP(Transmission Control Protocol,传输控制协议) | UDP(User Datagram Protocol,用户数据报协议) |
---|---|---|
连接方式 | 面向连接(三次握手建立连接,四次挥手断开连接) | 无连接 |
可靠性 | 可靠(数据按序、无差错、不丢失、不重复) | 不可靠(数据可能丢失、重复、乱序) |
传输效率 | 较低 | 较高 |
流量控制 | 支持 | 不支持 |
拥塞控制 | 支持 | 不支持 |
头部开销 | 较大(至少 20 字节) | 较小(8 字节) |
应用场景 | 对数据可靠性要求较高的应用,如文件传输、电子邮件、网页浏览等 | 对数据可靠性要求不高,但对实时性要求较高的应用,如在线视频、语音通话、网络游戏、DNS 查询等 |
传输形式 | 字节流 | 数据报 |
5. HTTP 和 HTTPS
(已在前面的回答中详细解释过,请参考前面的回答)
6. ES6 新特性
- let 和 const: 块级作用域变量声明。
- 箭头函数: 更简洁的函数定义语法,
this
指向定义时的上下文。 - 模板字符串: 使用反引号(`)定义字符串,支持多行字符串和插值表达式。
- 解构赋值: 可以从数组或对象中提取值,并赋给变量。
- 默认参数: 可以为函数参数设置默认值。
- 剩余参数: 使用
...
运算符将剩余的参数收集到一个数组中。 - 展开运算符: 使用
...
运算符将数组或对象展开。 - 类(Class): 基于原型的面向对象编程的语法糖。
- 模块(Module): 使用
import
和export
导入和导出模块。 - Promise: 用于处理异步操作。
- async/await: 基于 Promise 的异步编程语法糖。
- Set 和 Map: 新的数据结构。
- Symbol: 一种新的基本数据类型,表示独一无二的值。
- Proxy 和 Reflect: 用于创建对象的代理和进行元编程。
- 迭代器(Iterator)和生成器(Generator): 用于遍历数据结构。
- for…of 循环: 用于遍历可迭代对象。
7. 事件循环
(已在前面的回答中详细解释过,请参考前面的回答)
8. 安全问题(跨站脚本攻击和伪造跨站请求)
XSS(Cross-Site Scripting,跨站脚本攻击):
- 原理: 攻击者将恶意脚本注入到受信任的网站中,当用户访问该网站时,恶意脚本会在用户的浏览器中执行。
- 类型:
- 存储型 XSS: 恶意脚本存储在服务器端(如数据库中),当其他用户访问包含恶意脚本的页面时,脚本会被执行。
- 反射型 XSS: 恶意脚本通过 URL 参数或其他方式注入到页面中,当用户点击包含恶意脚本的链接时,脚本会被执行。
- DOM 型 XSS: 恶意脚本通过修改页面的 DOM 结构来执行。
- 危害:
- 窃取用户 Cookie 或其他敏感信息。
- 劫持用户会话。
- 篡改页面内容。
- 传播恶意软件。
- 防御:
- 输入验证: 对用户输入的数据进行严格的验证和过滤,去除或转义特殊字符。
- 输出编码: 对输出到页面的数据进行 HTML 编码,将特殊字符转换为 HTML 实体。
- 使用 HttpOnly Cookie: 禁止 JavaScript 访问 Cookie,防止 Cookie 被窃取。
- 设置 CSP(Content Security Policy): 通过 HTTP 响应头或 HTML meta 标签设置内容安全策略,限制浏览器可以加载的资源来源。
- 使用 XSS 过滤器: 使用一些工具或库来自动检测和过滤 XSS 攻击。
CSRF(Cross-Site Request Forgery,跨站请求伪造):
- 原理: 攻击者诱导用户在已登录的网站上执行非本意的操作,利用用户的登录状态发起恶意请求。
- 危害:
- 修改用户资料。
- 发布恶意内容。
- 进行非法交易。
- 防御:
- 验证 Referer 头部: 检查 HTTP 请求的
Referer
头部,确保请求来自合法的来源。 - 使用 CSRF Token: 服务器端生成一个随机的 CSRF Token,并将其嵌入到表单或 URL 中。用户提交请求时,需要携带该 Token。服务器端验证 Token 的合法性。
- 使用 SameSite Cookie: 将 Cookie 的
SameSite
属性设置为Strict
或Lax
,限制 Cookie 在跨站请求中的发送。 - 验证码
- 双重 Cookie 验证
- 验证 Referer 头部: 检查 HTTP 请求的
9. 跨域的几种方法
(已在前面的回答中详细解释过,请参考前面的回答)
10. Vue2 和 Vue3 的区别
(已在前面的回答中详细解释过,请参考前面的回答)
11. Vue 生命周期
(已在前面的回答中详细解释过,请参考前面的回答)
事件循环和手撕快排
(已在前面的回答中详细解释过,请参考前面的回答)
希望这些解答对你有所帮助!
1. HTTP 和 HTTPS 有什么区别**
特性 | HTTP | HTTPS |
---|---|---|
全称 | Hypertext Transfer Protocol | Hypertext Transfer Protocol Secure |
安全性 | 不安全,明文传输 | 安全,通过 SSL/TLS 加密传输 |
端口 | 80 | 443 |
连接方式 | 无状态 | 需要建立 SSL/TLS 连接,有状态 |
性能 | 较快 | 较慢(因为加密和解密需要消耗计算资源) |
证书 | 不需要 | 需要 CA 证书 |
作用 | 用于在 Web 浏览器和服务器之间传输数据 | 用于在 Web 浏览器和服务器之间安全地传输数据,保护数据隐私和完整性,防止中间人攻击。 |
URL开头 | http:// |
https:// |
总结区别:
- 安全性: HTTPS 通过 SSL/TLS 协议对数据进行加密,提供了身份验证、数据完整性和机密性,而 HTTP 是明文传输,容易被窃听和篡改。
- 端口: HTTP 默认端口是 80,HTTPS 默认端口是 443。
- 连接方式: HTTPS 需要建立 SSL/TLS 连接,这个过程涉及到证书验证和密钥交换,因此 HTTPS 连接比 HTTP 连接更复杂,也更耗时。
- 证书: HTTPS 需要向 CA(证书颁发机构)申请证书,用于验证服务器的身份,而 HTTP 不需要。
2. HTTPS 怎么校验**
HTTPS 的校验过程(也称为 SSL/TLS 握手)主要包括以下几个步骤:
客户端发起 HTTPS 请求:
- 客户端向服务器发送一个 HTTPS 请求,请求中包含客户端支持的 SSL/TLS 版本、加密算法等信息。
服务器返回证书:
- 服务器收到请求后,返回其 SSL/TLS 证书。
- 证书中包含服务器的公钥、证书颁发机构(CA)的信息、证书的有效期等。
客户端验证证书:
- 客户端收到证书后,会验证证书的合法性:
- 检查证书是否由受信任的 CA 签发。 浏览器内置了受信任的 CA 列表。
- 检查证书是否过期。
- 检查证书中的域名是否与请求的域名匹配。
- 检查证书的吊销状态。 浏览器会通过 CRL(证书吊销列表)或 OCSP(在线证书状态协议)来检查证书是否已被吊销。
- 如果证书验证失败,浏览器会向用户发出警告。
- 客户端收到证书后,会验证证书的合法性:
客户端生成随机密钥:
- 如果证书验证通过,客户端会生成一个随机的对称密钥(也称为会话密钥)。
客户端使用服务器公钥加密密钥:
- 客户端使用服务器证书中的公钥加密生成的对称密钥,并将加密后的密钥发送给服务器。
服务器使用私钥解密密钥:
- 服务器收到加密后的密钥后,使用自己的私钥解密,得到客户端生成的对称密钥。
建立安全连接:
- 客户端和服务器都拥有了相同的对称密钥,后续的通信都使用该密钥进行加密和解密。
总结:
HTTPS 的校验过程主要依靠 SSL/TLS 证书和非对称加密、对称加密算法。证书用于验证服务器的身份,公钥用于加密对称密钥,对称密钥用于加密后续的通信数据。
3. 浏览器怎么渲染页面**
浏览器渲染页面的过程(也称为关键渲染路径)大致如下:
解析 HTML:
- 浏览器从上到下解析 HTML 文档,构建 DOM(Document Object Model)树。DOM 树表示了 HTML 文档的结构。
解析 CSS:
- 浏览器解析 CSS 样式表,构建 CSSOM(CSS Object Model)树。CSSOM 树表示了 CSS 样式的结构。
构建渲染树(Render Tree):
- 浏览器将 DOM 树和 CSSOM 树合并成渲染树。渲染树只包含可见的节点,以及这些节点的样式信息。
布局(Layout):
- 浏览器计算渲染树中每个节点在屏幕上的位置和大小。这个过程也称为重排(Reflow)。
绘制(Paint):
- 浏览器根据渲染树和布局信息,将每个节点绘制到屏幕上。这个过程也称为重绘(Repaint)。
合成(Composite):
- 浏览器将多个层进行和成。
4. 手写一个树结点的 search,要求返回节点和节点以上的路径的值
1 |
|
代码解释:
searchTreeNode(root, targetValue)
:root
:树的根节点。targetValue
:要查找的目标值。- 返回值:如果找到目标节点,返回一个包含
node
(找到的节点)和path
(从根节点到目标节点的路径值数组)的对象;如果未找到,返回null
。
path
数组:用于存储从根节点到当前节点的路径值。traverse(node)
:递归遍历树的函数。- 将当前节点的值添加到
path
数组。 - 如果当前节点的值等于目标值,返回包含
node
和path
的对象。 - 如果当前节点有子节点,递归遍历子节点。
- 如果子节点中找到目标节点,直接返回结果。
- 如果当前节点的所有子节点都没有找到目标节点,将当前节点从
path
数组中移除(回溯),并返回null
。
- 将当前节点的值添加到
希望这些解答对你有所帮助!
1. 前端怎么携带 Cookie,后端怎么配置携带 Cookie**
前端携带 Cookie:
- 自动携带: 浏览器会自动携带符合以下条件的 Cookie:
- Cookie 的
domain
属性与当前请求的域名匹配(或为父域名)。 - Cookie 的
path
属性与当前请求的路径匹配。 - Cookie 没有过期。
- Cookie 没有设置
HttpOnly
属性(如果设置了,则只能通过 HTTP 请求携带,JavaScript 无法访问)。 - Cookie 的
Secure
属性与当前请求的协议匹配(如果设置了Secure
,则只能通过 HTTPS 请求携带)。 - SameSite 属性:
- Strict: 仅允许一方请求携带 Cookie,即浏览器将只发送相同站点请求的 Cookie,即当前网页 URL 与请求目标 URL 完全一致。
- Lax: 允许部分第三方请求携带 Cookie
- None: 无论是否跨站都会发送 Cookie
- Cookie 的
- 手动设置: 前端可以使用 JavaScript 的
document.cookie
属性来设置、读取和删除 Cookie。但是需要注意,手动设置的 Cookie 同样需要满足上述条件才能被自动携带。
- 自动携带: 浏览器会自动携带符合以下条件的 Cookie:
后端配置携带 Cookie:
- 后端通过在 HTTP 响应头中设置
Set-Cookie
字段来告诉浏览器设置 Cookie。 Set-Cookie
字段的格式:1
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Path=<path-value>; Expires=<date>; Max-Age=<number>; Secure; HttpOnly; SameSite=<Strict|Lax|None>
<cookie-name>=<cookie-value>
:Cookie 的名称和值。Domain=<domain-value>
:Cookie 的作用域(域名)。Path=<path-value>
:Cookie 的作用路径。Expires=<date>
:Cookie 的过期时间(绝对时间)。Max-Age=<number>
:Cookie 的过期时间(相对时间,单位为秒)。Secure
:仅通过 HTTPS 连接传输 Cookie。HttpOnly
:禁止 JavaScript 访问 Cookie。SameSite
: 控制 Cookie 在跨站请求中的行为
- 后端通过在 HTTP 响应头中设置
2. 跨域问题
什么是跨域?
- 当一个请求的协议、域名、端口号三者之一与当前页面的协议、域名、端口号不一致时,就会发生跨域。
- 跨域是浏览器的同源策略(Same-Origin Policy)导致的,同源策略是一种安全机制,用于限制不同源之间的资源访问。
跨域解决方案:
- CORS(Cross-Origin Resource Sharing):跨域资源共享,是一种 W3C 标准,也是最常用的跨域解决方案。
- 服务器端设置 HTTP 响应头
Access-Control-Allow-Origin
来允许指定的域名访问资源。 - 还可以设置其他相关的响应头,如
Access-Control-Allow-Methods
、Access-Control-Allow-Headers
、Access-Control-Allow-Credentials
等。
- 服务器端设置 HTTP 响应头
- JSONP:利用
<script>
标签可以跨域请求资源的特性,通过动态创建<script>
标签来实现跨域数据访问。- 只支持 GET 请求。
- 需要服务器端配合返回特定格式的数据。
- 代理服务器:在同源服务器上设置代理,将跨域请求转发到目标服务器,并将结果返回给客户端。
- 可以隐藏真实的请求地址。
- 需要配置代理服务器。
- postMessage: HTML5 XMLHttpRequest Level 2 中的 API,且是为数不多可以跨域操作的 window 属性之一
- WebSocket: WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯.
- CORS(Cross-Origin Resource Sharing):跨域资源共享,是一种 W3C 标准,也是最常用的跨域解决方案。
3. 浏览器合成层**
什么是合成层?
- 浏览器渲染页面时,会将页面分成多个层(Layer),每个层独立渲染。
- 合成层(Compositing Layer)是指具有特定属性的层,它们会被提升到 GPU 中进行渲染,从而提高渲染性能。
哪些属性会触发合成层?
transform: translateZ(0)
或transform-3d
will-change: transform
opacity
filter
- 具有 CSS 动画或过渡的元素
- 具有
position: fixed
的元素 - 视频元素 (
<video>
)
合成层的好处:
- GPU 加速,提高渲染性能。
- 减少重绘和重排,优化页面流畅度。
合成层的缺点:
- 占用额外的内存。
- 过多的合成层可能会导致性能下降。
4. 浏览器缓存机制,挖的比较深**
浏览器缓存的分类:
- 强缓存:
- 浏览器直接从本地缓存中读取资源,不发送请求到服务器。
- 通过
Expires
和Cache-Control
响应头控制。Expires
:指定资源的过期时间(绝对时间)。Cache-Control
:更灵活的缓存控制,可以设置max-age
(相对时间)、no-cache
、no-store
、public
、private
等指令。
- 协商缓存:
- 浏览器发送请求到服务器,服务器判断资源是否过期,如果未过期则返回 304 Not Modified,浏览器从本地缓存中读取资源;如果已过期则返回新的资源。
- 通过
Last-Modified
/If-Modified-Since
和ETag
/If-None-Match
响应头控制。Last-Modified
:服务器返回资源的最后修改时间。If-Modified-Since
:浏览器发送请求时携带上次获取到的Last-Modified
值。ETag
:服务器返回资源的唯一标识符。If-None-Match
:浏览器发送请求时携带上次获取到的ETag
值。
- 强缓存:
缓存流程:
- 浏览器发起请求。
- 检查是否有强缓存,如果有且未过期,则直接从本地缓存读取资源(状态码 200 OK (from cache))。
- 如果没有强缓存或强缓存已过期,则发送请求到服务器。
- 服务器检查协商缓存,如果资源未修改,则返回 304 Not Modified,浏览器从本地缓存读取资源。
- 如果资源已修改,则服务器返回新的资源(状态码 200 OK)。
更深入的缓存机制:
- Service Worker 缓存:可以拦截和处理网络请求,实现更精细的缓存控制,甚至可以实现离线访问。
- HTTP/2 Server Push:服务器可以主动推送资源到浏览器缓存,减少请求延迟。
- Cache API: 提供了对缓存的编程控制。
5. option 预检查是什么意思**
OPTIONS 请求是 HTTP 协议中的一种方法,用于获取服务器支持的请求方法、允许的请求头、是否允许跨域等信息。
预检查 (Preflight Request): 浏览器在发送跨域请求前自动发起的 options 请求。
什么情况下会触发预检请求?
- 跨域请求。
- 请求方法不是 GET、HEAD、POST。
- 请求头中包含自定义头部(除了
Accept
、Accept-Language
、Content-Language
、Content-Type
等安全头部)。 Content-Type
的值不是application/x-www-form-urlencoded
、multipart/form-data
、text/plain
。
预检请求的作用:
- 检查服务器是否允许跨域请求。
- 获取服务器支持的请求方法和请求头。
- 确保跨域请求的安全性。
6. Vite 的预构建是什么意思**
- Vite 的预构建(Pre-bundling)是指在开发环境下,Vite 会将项目中的依赖(如 npm 包)预先打包成 ES 模块,并缓存起来。
- 预构建的好处:
- 提高开发服务器启动速度:Vite 不需要每次启动都重新打包所有依赖,只需要加载缓存的预构建结果。
- 减少模块转换次数:Vite 将 CommonJS 或 UMD 格式的依赖转换为 ES 模块,只需要转换一次,后续可以直接使用。
- 优化模块解析:Vite 将具有多个内部模块的依赖(如 lodash-es)合并成一个模块,减少 HTTP 请求数量。
7. 手写防抖和防抖的进阶版本**
防抖(Debounce):
- 在事件触发后,延迟一段时间执行回调函数。如果在这段时间内再次触发事件,则重新计时。
- 适用于只需要最后一次触发结果的场景,如搜索框输入、窗口大小调整。
1
2
3
4
5
6
7
8
9function debounce(func, delay) {
let timerId;
return function (...args) {
clearTimeout(timerId);
timerId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}防抖的进阶版本(立即执行):
- 第一次触发事件时立即执行回调函数,后续触发事件则延迟执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function debounce(func, delay, immediate = false) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
let callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, delay)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, delay);
}
}
}
- 第一次触发事件时立即执行回调函数,后续触发事件则延迟执行。
8. 虚拟列表定高和不定高
虚拟列表(Virtual List):
- 一种优化长列表渲染性能的技术,只渲染可见区域的列表项,不可见区域的列表项不渲染或延迟渲染。
定高虚拟列表:
- 每个列表项的高度是固定的。
- 实现简单,只需要计算可见区域的起始索引和结束索引即可。
- 适用于列表项高度一致的场景。
不定高虚拟列表:
- 每个列表项的高度是不确定的。
- 实现复杂,需要预估每个列表项的高度,并在渲染过程中动态调整。
- 适用于列表项高度不一致的场景。
- 常用方法:
- 预估高度:根据列表项的平均高度或典型高度进行预估。
- 测量高度:在渲染过程中,使用
getBoundingClientRect()
等方法测量实际高度,并更新缓存的高度。 - 二分查找:对于已排序的列表,可以使用二分查找来快速定位可见区域的起始索引。
- 使用 Intersection Observer API: 监听元素是否出现在可视区域内。
9. 重绘和重排
重排(Reflow):
- 当 DOM 结构、元素的尺寸、位置、隐藏/显示等发生变化时,浏览器需要重新计算元素的几何属性,并重新构建渲染树。
- 开销较大,应尽量避免。
重绘(Repaint):
- 当元素的样式(如颜色、背景)发生变化,但不影响其几何属性时,浏览器只需要重新绘制元素的外观。
- 开销较小,但仍应尽量减少。
触发重排的操作:
- 添加或删除可见的 DOM 元素。
- 改变元素的尺寸、位置。
- 改变窗口大小。
- 获取元素的偏移量(
offsetLeft
、offsetTop
、offsetWidth
、offsetHeight
等)。
触发重绘的操作:
- 改变元素的颜色、背景、边框样式等。
优化建议:
- 减少 DOM 操作:合并多次 DOM 操作为一次,使用 DocumentFragment 或虚拟 DOM。
- 避免频繁获取元素的偏移量:将偏移量缓存起来,避免重复计算。
- 使用 CSS3 动画:CSS3 动画通常会创建合成层,减少重绘和重排。
- 使用 will-change 属性:提前告知浏览器元素将要发生变化,优化渲染性能。
3. 懒加载具体是怎么做的**
原理:
- 懒加载(Lazy Loading)是一种延迟加载资源(如图片、视频)的技术,只在资源进入可视区域时才加载。
- 可以减少页面初始加载时间,提高页面性能。
实现方式:
设置占位符:
- 将
<img>
标签的src
属性设置为一个占位符图片(如 1x1 像素的透明 GIF 或低质量的缩略图)。 - 将真实的图片 URL 存储在
<img>
标签的自定义属性中(如data-src
)。
- 将
监听滚动事件:
- 监听
window
对象的scroll
事件(或使用Intersection Observer API
)。 - 在滚动事件处理函数中,检查图片是否进入可视区域。
- 监听
判断是否进入可视区域:
- 使用
getBoundingClientRect()
方法获取图片元素相对于视口的位置信息。 - 判断图片的
top
、bottom
、left
、right
属性是否在视口的范围内。
- 使用
加载图片:
- 如果图片进入可视区域,将
data-src
属性的值赋给src
属性,浏览器会自动加载图片。 - (可选)移除
data-src
属性。
- 如果图片进入可视区域,将
使用 Intersection Observer API(推荐):
- Intersection Observer API 是一种更现代、更高效的监听元素可见性的方法。
- 它可以异步观察目标元素与其祖先元素或视口的交叉状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img); // 加载完成后停止观察
}
});
});
images.forEach(img => {
observer.observe(img);
});
4. 关于图片的渲染,除了懒加载还有没有其他的方式可以提升性能
- 图片压缩:
- 使用工具(如 TinyPNG、ImageOptim)或在线服务压缩图片大小,减少带宽消耗。
- 选择合适的图片格式(如 WebP、JPEG、PNG)。
- 使用适当的图片尺寸:
- 根据图片在页面上的显示大小,提供相应尺寸的图片,避免加载过大的图片。
- 使用响应式图片技术(如
<picture>
元素、srcset
属性)根据不同的屏幕尺寸加载不同尺寸的图片。
- 图片懒加载: (已在上面详细解释)
- 渐进式图片: 使用渐进式 JPEG 或交错式 PNG,图片会先显示模糊的轮廓,然后逐渐清晰。
- CSS Sprites(雪碧图):
- 将多个小图标合并成一张大图,通过 CSS 的
background-position
属性来显示不同的图标。 - 减少 HTTP 请求数量。
- 将多个小图标合并成一张大图,通过 CSS 的
- 使用 CDN:
- 将图片等静态资源部署到 CDN(内容分发网络)上,从离用户最近的服务器加载资源,加快加载速度。
- 使用 Base64 编码:
- 将小图标或小图片转换为 Base64 编码,直接嵌入到 HTML 或 CSS 中,减少 HTTP 请求。
- 适用于非常小的图片,不适用于大图。
- 使用 WebP 格式:
- WebP 是一种现代的图片格式,提供更好的压缩效果和图像质量。
- 需要考虑浏览器兼容性。
- 使用矢量图(SVG):
- 对于图标、logo 等简单图形,使用 SVG 格式可以实现无损缩放,并且文件体积通常更小。
- 预加载:
* 使用<link rel="preload">
预加载关键图片。
*<link rel="preload" as="image" href="important.jpg">
- HTTP缓存:
- 对图片开启HTTP缓存
5. ES6 里 var 和 let 的区别
特性 | var |
let |
---|---|---|
作用域 | 函数作用域 | 块级作用域({} ) |
变量提升 | 存在变量提升(声明会被提升到函数顶部,但初始化不会) | 不存在变量提升(在声明之前访问会抛出 ReferenceError ) |
重复声明 | 允许重复声明 | 不允许在同一作用域内重复声明 |
全局对象属性 | 在全局作用域中声明的 var 变量会成为全局对象(如 window )的属性 |
在全局作用域中声明的 let 变量不会成为全局对象的属性 |
暂时性死区 | 无 | 存在暂时性死区(Temporal Dead Zone,TDZ),在声明之前访问变量会抛出错误 |
循环中的行为 | 在循环中使用 var 声明的变量,循环结束后该变量的值是最后一次循环的值 |
在循环中使用 let 声明的变量,每次循环都会创建一个新的变量,作用域仅限于当前循环 |
const |
const 声明一个常量,其值不能被重新赋值。 |
与let 一样具有块级作用域 |
6. 在浏览器里没有声明一个 const 变量但先使用它,后面再去声明它,这样会出现什么问题吗
会抛出 ReferenceError
错误。
- 原因:
const
声明的变量存在暂时性死区(TDZ)。- 在变量声明之前访问该变量会抛出
ReferenceError
。
示例:
1 |
|
7. HTTP 的请求方法有哪些
- GET: 获取资源。
- POST: 提交数据(如表单提交)。
- PUT: 更新资源(提供完整资源)。
- PATCH: 更新资源(提供部分资源)。
- DELETE: 删除资源。
- HEAD: 获取资源的元信息(如响应头),不返回响应体。
- OPTIONS: 获取服务器支持的请求方法、允许的请求头等信息(用于跨域预检请求)。
- TRACE: 回显服务器收到的请求,主要用于测试或诊断。
- CONNECT: 建立到服务器的隧道,用于代理服务器。
8. 什么是跨域?解决跨域问题有哪些方式(已在前面的回答中详细解释过,请参考前面的回答)
9. 为什么这些请求不能跨域,但这些 js 和 css 静态资源可以跨域
不能跨域的请求:
- XMLHttpRequest(XHR)和 Fetch API 发起的请求受到同源策略的限制,不能跨域。
- 这是为了防止恶意网站窃取用户数据或进行跨站脚本攻击(XSS)。
可以跨域的静态资源:
<script>
、<img>
、<link>
、<video>
、<audio>
、<iframe>
等标签引入的资源不受同源策略的限制,可以跨域。- 这是因为这些标签通常用于加载外部资源,如 JavaScript 库、图片、样式表等,如果限制跨域会影响 Web 的正常使用。
根本原因:
- 同源策略是针对 XMLHttpRequest 和 Fetch API 等用于数据交互的 API 的限制,而不是针对所有网络请求的限制。
- 浏览器认为加载 JavaScript、CSS、图片等静态资源通常是安全的,不会导致数据泄露或安全问题。
10. 手写题
1. 找最大版本号
1 |
|
- 分割子串的函数: JavaScript 中分割字符串的函数是
split()
。
2. 对象路径解构(对象扁平化)
1 |
|
希望这些解答对你有所帮助!
1. CSS 盒模型**
什么是盒模型?
- CSS 盒模型是 CSS 用于布局和渲染 HTML 元素的一种模型。
- 每个 HTML 元素都被视为一个矩形的盒子,由内容(content)、内边距(padding)、边框(border)和外边距(margin)组成。
盒模型的两种类型:
- 标准盒模型(content-box):
- 元素的宽度和高度只包含内容区域(content),不包括内边距、边框和外边距。
box-sizing: content-box;
(默认值)
- IE 盒模型(border-box):
- 元素的宽度和高度包含内容区域、内边距和边框,不包括外边距。
box-sizing: border-box;
- 标准盒模型(content-box):
盒模型的组成部分:
- Content(内容):元素的实际内容,如文本、图片等。
- Padding(内边距):内容区域与边框之间的空白区域,用于控制内容与边框的距离。
- Border(边框):围绕内边距和内容的边框。
- Margin(外边距):边框之外的空白区域,用于控制元素与其他元素之间的距离。
2. CSS 优先级
CSS 优先级(也称为 CSS 特异性)是指当多个 CSS 规则应用于同一个元素时,浏览器用来确定哪个规则具有更高优先级的机制。
优先级计算规则:
- 内联样式(
style
属性) > ID 选择器(#id
) > 类选择器、属性选择器、伪类选择器(.class
、[attribute]
、:hover
) > 元素选择器、伪元素选择器(div
、::before
) > 通配符选择器(*
)、关系选择器(+
、>
、~
、 - 相同类型的选择器,后定义的规则覆盖先定义的规则。
!important
规则具有最高优先级,会覆盖其他所有规则(慎用)。
- 内联样式(
优先级计算方法:
- 通常使用一个四位数的权重值来表示优先级,格式为:
a, b, c, d
a
:内联样式(有则为 1,无则为 0)b
:ID 选择器的数量c
:类选择器、属性选择器、伪类选择器的数量d
:元素选择器、伪元素选择器的数量
- 比较优先级时,从左到右逐位比较,数值越大优先级越高。
例如:
#id .class
的优先级为:0, 1, 1, 0
.class div
的优先级为:0, 0, 1, 1
div
的优先级为:0, 0, 0, 1
- 通常使用一个四位数的权重值来表示优先级,格式为:
3. CSS 行内和块级元素区别
特性 | 行内元素(Inline Elements) | 块级元素(Block-level Elements) |
---|---|---|
默认宽度 | 内容的宽度 | 父元素的宽度(或 100%) |
默认高度 | 内容的高度 | 内容的高度 |
宽度设置 | 无效(width 属性无效) |
有效 |
高度设置 | 无效(height 属性无效) |
有效 |
水平排列 | 从左到右水平排列,直到一行排不下才换行 | 独占一行,垂直排列 |
垂直排列 | 不支持垂直外边距(margin-top 、margin-bottom ) |
支持所有外边距 |
内边距 | 支持所有内边距 | 支持所有内边距 |
边框 | 支持所有边框 | 支持所有边框 |
盒子模型 | 只有内容区域、内边距和边框 | 包含内容区域、内边距、边框和外边距 |
转换 | 可以通过 display: block; 转换为块级元素 |
可以通过 display: inline; 转换为行内元素,display: inline-block 转换为行内块元素 |
4. 常见的行内元素和块级元素有哪些
行内元素:
<span>
<a>
<img>
<strong>
<em>
<i>
<b>
<input>
(部分类型)<textarea>
<select>
<label>
<button>
(部分类型)
块级元素:
<div>
<p>
<h1>
-<h6>
<header>
<footer>
<nav>
<article>
<aside>
<section>
<ul>
<ol>
<li>
<form>
<hr>
<table>
<figure>
<video>
<canvas>
5. Position 几种
- static: 默认值,没有定位。
- relative: 相对定位,相对于自身原本位置进行偏移。
- absolute: 绝对定位,相对于最近的已定位的祖先元素进行定位,如果没有,则相对于初始包含块(body)。
- fixed: 固定定位,相对于浏览器窗口进行定位。
- sticky: 粘性定位,它是 relative 和 fixed 的结合体。
- inherit: 继承父元素的 position 属性的值。
6. DOM
什么是 DOM?
- DOM(Document Object Model,文档对象模型)是 HTML 和 XML 文档的编程接口。
- 它将文档表示为一个树形结构,每个节点代表文档的一部分(如元素、属性、文本等)。
- 通过 DOM,JavaScript 可以访问和操作文档的内容、结构和样式。
DOM 的作用:
- 动态修改页面内容: 可以添加、删除、修改 HTML 元素和属性。
- 响应用户事件: 可以监听用户的鼠标点击、键盘输入等事件,并执行相应的操作。
- 实现交互效果: 可以实现动画、表单验证、数据交互等功能。
7. 虚拟 DOM
什么是虚拟 DOM?
- 虚拟 DOM(Virtual DOM)是真实 DOM 的 JavaScript 对象表示。
- 它是一个轻量级的、与平台无关的 DOM 抽象,用于优化 DOM 操作的性能。
虚拟 DOM 的作用:
- 减少 DOM 操作次数: 在虚拟 DOM 中进行修改,然后一次性更新到真实 DOM 中,减少不必要的 DOM 操作。
- 提高渲染性能: 通过 Diff 算法比较新旧虚拟 DOM 树的差异,只更新发生变化的部分,避免不必要的重绘和重排。
- 跨平台: 虚拟 DOM 可以用于不同的渲染环境,如浏览器、服务器端渲染(SSR)、原生应用(如 React Native)。
8. Vue2 和 Vue3 区别
特性 | Vue 2 | Vue 3 |
---|---|---|
响应式系统 | Object.defineProperty() | Proxy |
API 风格 | Options API(选项式 API) | Composition API(组合式 API) |
性能 | 较好 | 更好(更小的包体积、更快的渲染速度) |
TypeScript 支持 | 较差 | 更好 |
代码组织 | 基于选项(data、methods、computed、watch 等) | 基于函数,将相关的逻辑放在一起 |
逻辑复用 | Mixins | Composition API(更好的逻辑复用和可读性) |
模板编译 | 运行时编译 | 编译时优化(静态分析、模板优化) |
Tree Shaking | 不支持 | 支持(更好的 Tree Shaking,移除未使用的代码) |
其他 | Teleport(传送门)、Fragments(片段)、Suspense(异步组件加载)、更好的错误处理、自定义渲染器 API | |
全局API | Vue 2 中,我们会通过全局 Vue 对象来使用一些功能,比如通过Vue.use() 来安装插件。 |
Vue 3 中,这些全局 API 发生了变化,它们现在需要通过应用的实例来调用。例如,使用createApp 创建一个应用实例,然后在这个实例上调用use 、component 等方法 |
9. 路由两种模式区别
Hash 模式:
- 使用 URL 的 hash(
#
)部分来模拟路由,例如:http://example.com/#/home
。 - hash 的变化不会导致页面重新加载,只会触发
hashchange
事件。 - 兼容性好,支持所有浏览器。
- 不需要服务器配置。
- 使用 URL 的 hash(
History 模式:
- 使用 HTML5 History API(
pushState
、replaceState
)来管理路由,例如:http://example.com/home
。 - URL 更加美观,更符合传统的 URL 格式。
- 需要服务器配置,将所有路由请求都重定向到入口文件(如
index.html
)。 - 兼容性较差,不支持 IE9 及以下浏览器。
- 使用 HTML5 History API(
10. 项目里写到了 JS 升级为 TS,讲一下项目升级的步骤
环境准备:
- 安装 TypeScript:
npm install -g typescript
- 安装相关的构建工具(如 Webpack、Rollup)的 TypeScript 插件。
- 配置
tsconfig.json
文件,指定 TypeScript 编译选项。
- 安装 TypeScript:
逐步迁移:
- 从简单模块开始: 选择项目中相对独立、简单的模块开始迁移。
- 将
.js
文件重命名为.ts
文件。 - 添加类型注解: 为变量、函数参数、函数返回值等添加类型注解。
- 处理类型错误: 解决 TypeScript 编译器报告的类型错误。
- 逐步扩大范围: 逐步将更多的模块迁移到 TypeScript。
类型定义:
- 为第三方库添加类型定义:
- 使用 DefinitelyTyped 提供的类型定义(
npm install @types/xxx
)。 - 如果 DefinitelyTyped 没有提供,可以自己编写类型定义文件(
.d.ts
)。
- 使用 DefinitelyTyped 提供的类型定义(
- 为项目中的自定义代码添加类型定义。
- 为第三方库添加类型定义:
构建和测试:
- 配置构建工具,使用 TypeScript 编译器编译 TypeScript 代码。
- 运行测试用例,确保代码功能正常。
持续集成:
- 将 TypeScript 编译和类型检查集成到持续集成流程中。
11. Ts 和 Js 有哪些区别 优点
特性 | JavaScript | TypeScript |
---|---|---|
类型系统 | 动态类型(运行时检查类型) | 静态类型(编译时检查类型) |
类型注解 | 无 | 支持 |
错误检查 | 运行时发现错误 | 编译时发现错误 |
代码可读性 | 较差 | 更好(类型注解可以提高代码的可读性和可维护性) |
代码可维护性 | 较差 | 更好 |
重构 | 较难 | 更容易 |
IDE 支持 | 较弱 | 更好(IDE 可以提供更准确的代码补全、错误提示等) |
学习曲线 | 较低 | 较高 |
适用场景 | 小型项目、快速原型开发 | 大型项目、长期维护的项目、需要更高代码质量的项目 |
优点 | 简单易学、灵活、生态系统庞大、跨平台 | 静态类型检查、更好的代码可读性和可维护性、更早发现错误、更好的 IDE 支持、更易于重构、更适合大型项目 |
缺点 | 动态类型容易出错、代码可读性和可维护性较差、重构困难 | 学习曲线较陡峭、需要编写类型注解、可能会增加开发时间(但长期来看可以减少调试时间) |
TypeScript增加了代码的可读性和可维护性,减少了出错几率。 |
12. Js 里能不能实现枚举
JavaScript 本身没有内置的枚举类型,但可以通过以下几种方式模拟实现枚举:
对象字面量:
1
2
3
4
5
6
7const Color = {
RED: 'red',
GREEN: 'green',
BLUE: 'blue',
};
console.log(Color.RED); // 输出 "red"使用
Object.freeze()
冻结对象:1
2
3
4
5
6
7
8
9const Color = Object.freeze({
RED: 'red',
GREEN: 'green',
BLUE: 'blue',
});
console.log(Color.RED); // 输出 "red"
Color.YELLOW = "yellow"; // 无法添加新属性
delete Color.RED //无法删除属性类模拟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Weekday {
static Monday = new Weekday("Monday");
static Tuesday = new Weekday("Tuesday");
static Wednesday = new Weekday("Wednesday");
static Thursday = new Weekday("Thursday");
static Friday = new Weekday("Friday");
static Saturday = new Weekday("Saturday");
static Sunday = new Weekday("Sunday");
constructor(name) {
this.name = name;
}
toString() {
return this.name;
}
}使用第三方库:
enumify
enum
13. 跨域问题
(已在之前的回答中详细解释过,请参考前面的回答)
14. 算法:找出数组中第 K 大的数 堆排序/快排
方法一:快速选择(基于快速排序)
1 |
|
时间复杂度:平均情况下为 O(n),最坏情况下为 O(n^2)。
空间复杂度:O(log n)(递归栈的平均深度),最坏情况下为 O(n)。
方法二:最小堆
维护一个大小为k的最小堆
1 |
|
时间复杂度:O(n log k),其中 n 是数组的长度,k 是堆的大小。
空间复杂度:O(k),用于存储堆的额外空间。
希望这些解答对你有所帮助!
3. 内部表单组件和 Formily 之间的差异
我们把“内部表单组件”理解为在公司或项目内部自行开发的、用于特定业务场景的表单组件。它们和 Formily 这样的专业表单解决方案的主要差异体现在以下几个方面:
1. 定位与目标:
内部表单组件:
- 通常针对特定业务需求定制,解决特定场景下的表单问题。
- 可能更注重与内部系统(如权限、数据字典)的集成。
- 可能更贴合内部 UI 规范和交互习惯。
Formily:
- 通用、专业的表单解决方案,旨在解决各种复杂表单场景。
- 注重表单的性能、可扩展性、可维护性,以及与各种 UI 库(如 Ant Design、Element UI)的集成。
- 提供更全面的表单功能,如字段联动、校验、异步数据加载、自定义渲染等。
2. 功能与灵活性:
内部表单组件:
- 功能可能相对简单,主要满足基本表单需求。
- 灵活性可能较低,难以应对需求变化。
Formily:
- 功能强大,提供丰富的 API 和配置选项,可以灵活应对各种复杂表单场景。
- 高度可定制,可以通过 Schema、自定义组件、自定义校验等方式扩展功能。
3. 开发与维护成本:
内部表单组件:
- 前期开发成本较低,但长期维护成本可能较高。
- 可能依赖于特定开发者,存在人员变动风险。
Formily:
- 学习成本相对较高,但长期维护成本较低。
- 有活跃的社区支持,文档齐全,更容易找到解决方案。
4. 生态系统与社区支持:
- 内部表单组件:
* 通常没有生态系统与社区支持。
* 问题需要靠内部人员解决。
- Formily:
- 拥有庞大的生态系统和活跃的社区。
- 有大量的插件、工具和示例可供参考。
- 遇到问题可以更容易地找到解决方案。
总结:
特性 | 内部表单组件 | Formily |
---|---|---|
定位 | 针对特定业务需求定制 | 通用、专业的表单解决方案 |
目标 | 解决特定场景下的表单问题 | 解决各种复杂表单场景 |
功能 | 相对简单,满足基本需求 | 强大,提供丰富的 API 和配置选项 |
灵活性 | 较低 | 高度可定制 |
开发成本 | 前期较低,长期可能较高 | 学习成本较高,长期维护成本较低 |
维护成本 | 可能较高 | 较低 |
生态/社区 | 无 | 活跃 |
选择使用内部表单组件还是 Formily,取决于具体的项目需求、团队规模、技术栈以及长期维护成本的考虑。如果只需要解决简单的表单问题,并且有足够的人力进行维护,那么内部表单组件可能是一个更轻量级的选择。如果需要构建复杂、可扩展的表单,并且希望降低长期维护成本,那么 Formily 这样的专业表单解决方案可能更合适。
Merge Two Sorted Lists 算法题**
这道题是 LeetCode 上的经典题目,题目编号 21. 合并两个有序链表。
题目描述:
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
1 |
|
JavaScript 解法(迭代):
1 |
|
代码解释:
- 创建哑节点:
dummyHead
是一个虚拟的头节点,它的作用是简化代码逻辑,避免对空链表的特殊处理。 - 迭代比较: 使用
while
循环遍历两个链表,比较当前节点的值,将较小的节点添加到新链表的末尾。 - 连接剩余部分: 当其中一个链表遍历完后,将另一个链表的剩余部分直接连接到新链表的末尾。
- 返回结果: 返回
dummyHead.next
,即新链表的头节点。
JavaScript 解法(递归):
1 |
|
代码解释:
如果 l1 或者 l2 一开始就是 null ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个的头元素更小,然后递归地决定下一个添加到结果里的值。
2. 手撕 Promise.all**
1 |
|
代码解释:
- 参数检查: 检查传入的参数是否为数组。
- 空数组处理: 如果传入的是空数组,直接
resolve
一个空数组。 - 结果数组和计数器:
results
:用于存储每个 Promise 的结果。completedCount
:用于记录已完成的 Promise 数量。
- 遍历 Promise 数组:
- 使用
Promise.resolve()
将每个元素转换为 Promise 对象,以处理非 Promise 值。 - 使用
.then()
监听每个 Promise 的状态变化。 - 如果 Promise 成功,将结果存储到
results
数组中,并增加completedCount
。 - 如果 Promise 失败,直接
reject
整个Promise.all
。 - 当
completedCount
等于 Promise 数组的长度时,说明所有 Promise 都已完成,resolve
结果数组。
- 使用
3. JavaScript 的闭包能说一下吗
什么是闭包?
闭包是指有权访问另一个函数作用域中变量的函数。更简单地说,闭包是一个能够记住并访问其所在词法环境(创建时的环境)的函数,即使该函数在其原始词法环境之外执行。
闭包的形成:
闭包的形成通常涉及两个关键要素:
- 嵌套函数: 一个函数内部定义了另一个函数。
- 内部函数引用外部函数变量: 内部函数引用了外部函数的变量(或参数)。
示例:
1 |
|
解释:
outerFunction
内部定义了innerFunction
。innerFunction
访问了outerFunction
的变量outerVar
。outerFunction
返回了innerFunction
。- 即使
outerFunction
执行完毕,myClosure
(即innerFunction
)仍然可以访问outerVar
。
闭包的用途:
- 实现私有变量和方法: 可以将变量和方法隐藏在函数内部,只暴露必要的接口。
- 状态保持: 可以让函数记住之前的状态,例如在事件处理函数中。
- 柯里化(Currying): 将一个接受多个参数的函数转换为一系列接受单个参数的函数。
- 模块化: 可以将代码组织成独立的模块,避免全局命名空间污染。
- 实现高阶函数: 例如JavaScript中的回调函数。
闭包的缺点:
- 内存泄漏: 如果闭包引用的变量一直不被释放,可能会导致内存泄漏。
- 性能问题: 过多的闭包可能会影响性能,因为闭包会增加作用域链的长度。
4. JS 的定时器有哪些,setTimeout,setInterval 有什么区别
JavaScript 的定时器:
JavaScript 提供了两种定时器:
setTimeout(callback, delay, ...args)
:- 在指定的延迟时间(
delay
,单位为毫秒)后执行一次回调函数(callback
)。 ...args
是可选参数,会作为参数传递给回调函数。- 返回一个唯一的定时器 ID,可以用于
clearTimeout()
取消定时器。
- 在指定的延迟时间(
setInterval(callback, delay, ...args)
:- 每隔指定的延迟时间(
delay
,单位为毫秒)重复执行一次回调函数(callback
)。 ...args
是可选参数,会作为参数传递给回调函数。- 返回一个唯一的定时器 ID,可以用于
clearInterval()
取消定时器。
- 每隔指定的延迟时间(
setTimeout
和 setInterval
的区别:
特性 | setTimeout |
setInterval |
---|---|---|
执行次数 | 单次 | 多次(重复) |
执行方式 | 延迟指定时间后执行一次 | 每隔指定时间重复执行 |
取消定时器 | clearTimeout() |
clearInterval() |
适用场景 | 只需要执行一次的任务,如延迟加载、动画效果 | 需要重复执行的任务,如轮播图、实时数据更新 |
注意事项 | 如果回调函数执行时间超过延迟时间,不会累积执行 | 如果回调函数执行时间超过间隔时间,可能会导致累积执行 |
- 关于setInterval累计执行的问题
如果回调函数执行时间过长,超过了间隔时间,setInterval
可能会出现“堆积”现象,即回调函数会连续执行多次,而没有间隔。为了避免这种情况,可以使用setTimeout
模拟setInterval
:1
2
3
4
5
6
7function mySetInterval(callback, delay) {
function interval() {
callback();
setTimeout(interval, delay);
}
setTimeout(interval, delay);
}
4. 内部表单组件如何实现联动关系?
内部表单组件实现联动关系,通常有以下几种方式:
1. 基于事件监听和状态管理:
原理:
- 监听触发联动字段的
change
事件(或其他相关事件,如input
、blur
)。 - 在事件处理函数中,根据触发字段的值,更新其他相关字段的状态(如
value
、disabled
、visible
)。 - 使用组件内部的状态管理(如 Vue 的
data
、React 的state
)来存储和更新字段状态。
- 监听触发联动字段的
示例(Vue):
1 |
|
2. 基于计算属性(Computed Properties):
原理:
- 将联动字段的状态(如
disabled
、visible
)定义为计算属性。 - 计算属性依赖于触发联动字段的值,当触发字段的值变化时,计算属性会自动重新计算,从而更新联动字段的状态。
- 将联动字段的状态(如
示例(Vue):
1 |
|
3. 基于自定义指令(Custom Directives):
原理:
- 创建自定义指令,用于处理联动逻辑。
- 在指令的
update
钩子函数中,根据触发字段的值,更新联动字段的状态。
示例(Vue):
这种方法较为复杂,需要对vue的底层实现有了解,不推荐。
4. 基于表单状态管理库(如 Vuex、Redux):
原理:
- 将表单状态存储在全局状态管理库中。
- 在触发联动字段的组件中,通过
dispatch
或commit
修改状态。 - 在联动字段的组件中,通过
mapState
或useSelector
获取状态,并根据状态更新组件。
这种方法适用于大型复杂表单,小型表单不推荐。
5. 基于Schema描述配置
- 原理:
- 通过一份JSON配置来描述表单的结构,字段,校验规则与联动关系。
- 运行时根据Schema配置渲染表单,并处理联动,
- Formily就是基于这种方式实现。
选择哪种方式取决于表单的复杂程度、团队的技术栈以及项目的具体需求。对于简单的联动关系,基于事件监听和状态管理或计算属性就足够了。对于复杂的联动关系,可以考虑使用自定义指令或表单状态管理库。
场景题代码手撕**
1. CSS 水平垂直居中
1 |
|
2. Promise 看输出
这个需要具体代码才能确定输出结果。
3. 递归 + 定时器
1 |
|
面试技巧
- 既然给你面试机会,那么简历上的东西肯定是有吸引面试官的地方;面试的过程中一定不要吹技术,要是问到不会的就要扣分了;
- 展现自信,不要太紧张和拘束;
- 针对JD去写简历,去准备面试
以下是前端开发面试中 JavaScript 核心知识点和 网络协议基础的常见考题及参考答案整理,结合最新技术趋势和面试动态:
vue基础
vue3核心语法
- hook
简单来讲就是自定义的包/库(python等各种语言中都有);是Vue 3中的重要特性,通过组合式API实现了对状态和副作用的管理,使得逻辑复用变得更加简单和高效。与传统的mixins相比,Hooks提供了更好的灵活性和可维护性,是现代Vue开发中不可或缺的一部分。主要用于封装可重用的逻辑和管理组件的状态、生命周期以及副作用。允许开发者以函数的形式组织和复用代码,从而提高代码的可维护性和可读性。
Vue Hook的作用
状态逻辑复用:Vue Hook允许在多个组件之间共享状态逻辑,避免重复代码。例如,可以创建一个自定义Hook来处理表单输入状态,然后在多个表单组件中使用。
生命周期管理:Hooks提供了一系列生命周期钩子函数,如
onMounted
、onUpdated
、onUnmounted
等,帮助开发者在组件不同阶段执行特定操作。例如,可以在组件挂载后发起数据请求,或在组件卸载前清理定时器。简化代码结构:通过将逻辑封装到Hooks中,可以使组件代码更简洁,逻辑更集中,便于理解和维护。这种方式比传统的mixins更清晰,因为Hooks是以函数形式存在,避免了命名冲突和逻辑散落的问题。
响应式数据处理:使用Hooks时,可以利用Vue 3中的响应式API(如
ref
、reactive
、computed
)来创建响应式数据,使得状态管理更加直观和高效。
自定义Hooks:是开发者根据需要创建的函数,通常以
use
开头,例如useFetchData
。这些函数可以调用其他Hooks,并返回响应式数据或方法,以供组件使用。自定义Hooks的基本规范包括:函数名以
use
开头。在组件的
setup
函数中调用自定义Hook。返回响应式变量或方法,以便在组件中解构使用。
例如,一个简单的自定义Hook可以用于处理本地存储:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44import { ref, watch } from 'vue';
export default function useLocalStorage(key, initialValue) {
const data = ref(initialValue);
if (localStorage.getItem(key)) {
data.value = JSON.parse(localStorage.getItem(key));
} else {
localStorage.setItem(key, JSON.stringify(initialValue));
}
watch(data, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
});
return { data };
}
/*
1. **数据持久化存储**:
if (localStorage.getItem(key)) {
data.value = JSON.parse(localStorage.getItem(key));
} else {
localStorage.setItem(key, JSON.stringify(initialValue));
}
- 当首次使用时,如果 localStorage 中不存在数据,则存入初始值
- 如果已存在数据,则读取已保存的数据
1. **响应式数据管理**:
const data = ref(initialValue);
- 使用 ref创建响应式数据,可以在组件中实时反映数据变化
1. **自动同步到 localStorage**:
watch(data, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
});
- 通过 watch监听数据变化
- 当数据发生变化时,自动更新到 localStorage 中
使用示例(在src/view/try.vue中):
const { data } = useLocalStorage('myData', { name: 'default' });
这样就创建了一个在组件和 localStorage 之间自动同步的响应式数据。
*/在组件中使用时,可以这样引入并使用这个Hook:
1
2
3
4
5<script setup>
import useLocalStorage from './useLocalStorage';
const { data } = useLocalStorage('myData', { name: 'default' });
</script>
<RouterView />
<RouterView />
是 Vue Router 的核心组件之一,它的主要作用是作为路由匹配到的组件的渲染出口。是实现前端路由的关键组件,它让我们能够根据 URL 的变化动态渲染不同的组件内容。主要作用
组件渲染容器
- 它会根据当前的路由路径(URL)
- 自动渲染与该路径匹配的组件
- 相当于一个动态组件的占位符
动态更新
- 当路由发生变化时
<RouterView />
会自动更新渲染的内容,无需手动干预
- 当路由发生变化时
使用场景:内容需要根据路由变化而变化
- 看他的上一级路由的组件的相应变化位置就是它的出口;
- 比如项目中整个/下的第一级路由的出口就在src\App.vue(在/xx下随着路由整个页面都变化)
- 菜单嵌套路由的出口在src\view\Main.vue(Layout组件)中的el-main中;在/path/xx下切换路由只相应修改页面布局中的el-main中的内容
布局组件中
- 作为内容区域的容器
- 常见于后台管理系统的布局中
嵌套路由
- 当需要实现多层级路由时,父路由组件相应位置放置
<RouterView />
,子路由的组件会在这里渲染
- 当需要实现多层级路由时,父路由组件相应位置放置
路由过渡动画
- 可以配合
<transition>
使用 - 为路由切换添加动画效果
- 可以配合
1
2
3<transition name="fade">
<RouterView />
</transition>- 看他的上一级路由的组件的相应变化位置就是它的出口;
- 在 Vue 2、3 中,
@
是v-on
指令的简写,用于监听 DOM 事件并执行相应的 JavaScript 代码。在 Vue 3 中,使用:
符号来表示属性绑定是一个重要的语法特性。这种写法源于 Vue 的响应式特性,允许开发者将组件的属性(props)与数据模型进行绑定,从而实现动态更新。注意绑定的值必须满足如下规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//可行的情况
变量::value="message"//message必须是被定义过的变量
表达式::value="count + 1"//count必须是被定义过的变量
字符串字面量::value="'hello'"(三个引号)
对象::class="{ active: isActive }"
数组::class="[activeClass, errorClass]"
布尔值::disabled="false"
//不可行
:value='hello'
:value="message"//但是message没有被定义
:value="count + 1"//但是count没有被定义
//举例说明:当我插入ep input组件时
<el-input v-model="loginForm.vaildCode" placeholder="验证码" :prefix-icon='Search' >显示不了,因为v-bing绑定了Search但它没有被定义
//其实我想要的search就是一个常量字符串,正确写法如下:去掉:或者使用三引号表示字符串常量
<el-input v-model="loginForm.vaildCode" placeholder="验证码" prefix-icon='Search' >
<el-input v-model="loginForm.vaildCode" placeholder="验证码" :prefix-icon="'Search'" >属性绑定语法
基本用法
- 属性绑定:使用
:
符号(相当于v-bind
的简写)来将数据绑定到组件的属性上。例如:
1
2
3<my-component :some-prop="dataValue"></my-component>
<my-component :style="{width:'230px'}"> </my-component>在这个例子中,
some-prop
是子组件的一个 prop,而dataValue
是父组件中的一个数据属性。动态绑定
动态属性:可以使用表达式动态计算属性值。例如:
1
<input :value="inputValue" :disabled="isDisabled" />
这里,
value
和disabled
属性会根据inputValue
和isDisabled
的值动态更新。使用对象语法
对象语法:可以将多个属性一起绑定到组件上。例如:
1
<my-component v-bind="{ propA: valueA, propB: valueB }"></my-component>
使用这种方式,可以在一个对象中传递多个属性。
- 属性绑定:使用
事件处理语法
基本用法
监听事件:使用
v-on
指令(简写为@
)来监听事件。例如:1
<button @click="handleClick">Click me</button>
上述代码中的
handleClick
是一个在组件中定义的方法,当按钮被点击时,该方法将被调用。内联事件处理器:可以直接在模板中编写 JavaScript 代码,例如:
1
<button @click="alert('Button clicked!')">Click me</button>
在这个例子中,点击按钮会弹出一个警告框。
事件参数
访问原生事件:在事件处理方法中,可以访问原生 DOM 事件对象。该对象会自动作为参数传递给方法。例如:
1
2
3
4
5methods: {
handleClick(event) {
console.log(event.target); // 输出触发事件的元素
}
}**使用
$event
**:在内联处理器中,可以通过特殊变量$event
来访问原生事件。例如:1
<button @click="handleClick($event)">Click me</button>
修饰符
Vue 提供了一些修饰符来控制事件的行为,例如:
**
.stop
**:阻止事件冒泡。1
<button @click.stop="doSomething">Click me</button>
**
.prevent
**:阻止默认行为。1
<form @submit.prevent="submitForm">Submit</form>
**
.self
**:仅当事件目标是元素本身时才触发处理器。1
<div @click.self="doThat">Click me only if clicked on this div</div>
自定义事件
Vue 的组件之间可以通过自定义事件进行通信。子组件可以使用
$emit
方法向父组件发送事件。例如:1
2// 子组件
this.$emit('custom-event', payload);父组件则可以通过
@custom-event
来监听这个自定义事件:1
<ChildComponent @custom-event="parentMethod" />
变量: Vue3 中变量的使用场景
- 使用
{{ }}
插值语法
- 在模板中显示变量值
1
2
3
4
5
6<template>
<!-- 显示数据 -->
<div>{{ username }}</div>
<p>{{ count + 1 }}</p>
<span>{{ isActive ? '激活' : '未激活' }}</span>
</template>
- 使用单引号
''
- 字符串字面量
- 静态值
- 变量作各种参数,比如store.commit/dispatch等等
1
2increment: () => store.commit('increment'),//提交一个名为increment的变化
- 使用
:
或v-bind
- 动态绑定属性
1
2
3
4
5
6
7<template>
<!-- 动态绑定 -->
<div :class="{'click-enabled': !flag}">//代表flag为false时,class为click-enabled,否则没有class
<img :src="imgUrl">
<el-button :type="buttonType">
<div :style="{ width: width + 'px' }">
</template>
- 使用
@
或v-on
- 事件绑定
1
2
3
4
5<template>
<!-- 事件绑定 -->
<button @click="handleClick">
<input @input="handleInput">
</template>
- 使用
v-model
- 双向数据绑定
1
2
3
4
5<template>
<!-- 双向绑定 -->
<input v-model="message">
<el-input v-model="username">
</template>
- 使用
v-if/v-show
条件渲染
1
2
3
4
5
6
7
8
9
10<template>
<!-- 条件渲染 -->
<div v-if="isShow">
这个元素会根据 isShow 的值来添加或删除
</div>
<div v-show="isVisible">
这个元素会根据 isVisible 的值来显示或隐藏
</div>
</template>总结:
{{ }}
: 显示变量值''
: 静态值:
: 动态属性绑定@
: 事件绑定v-model
: 双向绑定v-if/v-show
: 条件渲染
- 使用
nextTick&object.assign
nextTick
nextTick
是 Vue.js 提供的一个方法,用于在下一次 DOM 更新循环结束之后执行延迟回调。在修改数据后立即使用 nextTick
,可以在回调中获取更新后的 DOM。
用法示例:
1 |
|
说明:
- 当你在修改响应式数据后,需要在 DOM 更新完成后执行某些操作时,使用
nextTick
。 - 常见场景包括获取更新后的 DOM 元素的尺寸或状态。
Object.assign
Object.assign
是 JavaScript 中的一个静态方法,用于将一个或多个源对象的可枚举属性复制到目标对象中。返回目标对象。也称为合并对象。
语法:
- 如果target存在source中的属性就会被重写赋值,如果不存在就会创造这个属性然后赋值过去;
1 |
|
target
:目标对象。sources
:源对象。
用法示例:
1 |
|
说明:
Object.assign
常用于对象的合并或浅拷贝。- 如果目标对象与源对象有相同的属性,后面的属性会覆盖前面的属性。
文件结构及其作用、执行顺序:
- 根目录:
- package.json:这个文件包含了项目的依赖项、脚本等信息,是整个项目的元数据。
- vue.config.js:这是用于配置 Vite 的主要文件。在这里,你可以定义环境变量、设置插件、调整打包选项等。这是一个非常重要的配置文件,它会影响到你的开发体验和生产部署过程
- public:包含了一些静态资源,比如图标、favicon.ico、robots.txt 等。这些文件会被直接复制到构建目录中,不会经过 Vite 的处理。
- 在组件中导入public中的静态资源时不需要完整的路径,直接/文件名即可比如
import qrImg from '/qr.png'
;导入public中的二维码图片
- 在组件中导入public中的静态资源时不需要完整的路径,直接/文件名即可比如
- src 文件夹:在 Vite 中最核心的一个文件夹,因为它存放着所有的源代码: index.htm-main.js-index.js、App.vue-其他view、components组件-静态资源(图片、cdn等等)
- assets 文件夹:用来存储静态资源,如图片、样式表等,这些资源通常不会经常变化,可以直接复制到服务器上
- components 文件夹:存放的是你写好的复用组件,每个子文件夹代表一类相关的组件,比如说有一个 base 或者 app 组件,然后再根据具体功能划分出不同的模块(例如:button、table 等)。在vue项目中可以将其中的文件称为vue文件:封装和组织组件的模板、逻辑和样式,使得组件的开发和管理更加高效和模块化(往往在components下创造组件,在view下的vue文件中引用组件),提高了代码的可维护性和复用性:其基础结构如下:
<template></template>
:内含html代码,用于组成页面结构- 浏览器中不会存在
<template>
标签的原因是,Vue会在编译过程中将<template>
中的内容转换为JavaScript渲染函数。在这个过程中,Vue会解析<template>
的内容并生成虚拟DOM,而这个虚拟DOM最终会被渲染到实际的DOM中。 - 编译过程:
- 解析模板:Vue使用vue-template-compiler库将
<template>
块中的HTML字符串解析为抽象语法树(AST),这表示了模板的结构。 - 生成渲染函数:AST会被转换为JavaScript代码,这些代码负责创建虚拟DOM节点。每当组件需要渲染时,Vue会调用这些渲染函数,而不是直接使用
<template>
中的内容。 - 更新DOM:当数据变化时,Vue会根据虚拟DOM的变化来更新实际的DOM。
- 解析模板:Vue使用vue-template-compiler库将
- 浏览器中不会存在
调用组件
- 引入组件有两种方式
- 直接在要使用的页面中导入
- 在main.js中导入,然后app.component(”组件名称”,组件)注入全局这样就可以在全局使用了
- 在组件中调用的组件名称也有两种方式
在 Vue.js 中,当您在main.js
中全局注册组件时,例如:
1
2
3
4
5
6
7
8// main.js
import { createApp } from 'vue';
import App from './App.vue';
import PanelHead from './components/PanelHead.vue';
const app = createApp(App);
app.component('PanelHead', PanelHead);
app.mount('#app');尽管您注册的组件名称是
'PanelHead'
,但在模板中可以使用<panel-head></panel-head>
或<PanelHead></PanelHead>
的形式来引用,原因如下:
组件名的大小写不敏感解析:
Vue 在解析模板时,会将自定义组件标签名中的连字符形式(kebab-case)和大驼峰形式(PascalCase)都识别为同一个组件。例如:
<PanelHead></PanelHead>
<panel-head></panel-head>
上述两种写法都会被解析为注册的
'PanelHead'
组件。组件名的规范化:
根据 Vue 的组件名解析规则,注册时的组件名会被标准化处理,模板中的标签名也会被转换为相应的格式,以进行匹配。
- 注册组件时使用 PascalCase(大驼峰命名)的名称。
- 在模板中,可以使用 PascalCase 或 kebab-case(短横线命名)形式的组件标签。
HTML 中的大小写不敏感性:
- 在浏览器解析 HTML 时,标签名是大小写不敏感的。
- 为了遵循 HTML 规范,Vue 推荐在模板中使用 kebab-case 的组件名。
总结:
- 注册组件: 使用 PascalCase 命名,例如
'PanelHead'
。 - 在模板中引用: 可以使用
<PanelHead></PanelHead>
或<panel-head></panel-head>
,Vue 都能够正确解析。 - 推荐使用: 在模板中使用 kebab-case 形式,即
<panel-head></panel-head>
,以符合 HTML 规范和提高可读性。
- 引入组件有两种方式
组合式API与选项式API的对比(本项目中使用组合式API)
组合式API:Vue3提供了丰富的组合式API,帮助开发者管理组件的行为,核心功能如下:
<script setup>
是在单文件组件中使用组合式 API 的编译时语法糖。主要特点:顶层变量/函数自动暴露给模板:意思是说
<script setup>
中定义的变量和函数会自动暴露给当前组件的模板使用;这种暴露仅限于当前组件内部的 template 部分;- 无需 return 和 export 语句
- 引入的组件自动注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const count = ref(0)
const increment = () => count.value++
</script>
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">+1</button>
<ChildComponent />
</div>
</template>
```
2. 组件实例的访问:父组件通过 ref 访问子组件时,访问的是子组件的实例;出于安全考虑,Vue 3 默认关闭了组件实例的属性访问需要通过 defineExpose 明确声明哪些属性/方法可以被父组件访问;
```js
<!-- 子组件 Child.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0) // ✅ 自动暴露给当前模板使用
const increment = () => count.value++
defineExpose({ count }) // ✅ 显式暴露给父组件访问
</script>
<template>
<div>
{{ count }} <!-- ✅ 可以直接访问 count -->
<button @click="increment">+1</button> <!-- ✅ 可以直接访问 increment -->
</div>
</template>
<!-- 父组件 Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
// 访问子组件实例的属性
console.log(childRef.value.count) // ✅ 可以访问(因为被 defineExpose 暴露)
childRef.value.increment // ❌ 不能访问(未被暴露)
</script>
<template>
<Child ref="childRef" />
</template>
这种设计的目的是:
提高代码的可维护性 增强组件的封装性 避免父组件随意访问和修改子组件的内部状态
- 响应式系统:数据变更自动触发视图更新
1
2
3
4
5
6
7
8
9
10
11
12import { ref, reactive } from 'vue'
// ref 基本类型响应式
const count = ref(0)
count.value++ // 修改值会触发视图更新
// reactive 对象响应式
const state = reactive({
name: '张三',
age: 25
})
state.age++ // 修改值会触发视图更新 - 生命周期钩子(选项式 API:Vue 通过组件选项自动处理逻辑,无需手动导入响应式函数和生命周期钩子。组合式 API:需要手动导入所需的响应式函数和生命周期钩子,从而提供更灵活和模块化的代码结构,他们俩周期钩子也有差别;比如组合式没有beforecreate,把setup当created用,其它就当改了个名)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42<template>
<div>{{ count }}</div>
<button @click="onClick">
增加 1
</button>
</template>
<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';
const count = ref(1);
const onClick = () => {
count.value += 1;
};
onBeforeMount(() => {
alert("组件渲染之前调用的时beforeMount函数");
});
onMounted(() => {
alert("组件渲染完成调用的时mounted函数");
});
onBeforeUpdate(() => {
alert("组件更新之前调用的时beforeUpdate函数");
});
onUpdated(() => {
alert("组件更新之后调用的时updated函数");
});
onBeforeUnmount(() => {
alert("组件卸载之前调用的时beforeUnmount函数");
});
onUnmounted(() => {
alert("组件卸载之后调用的时unmounted函数");
});
</script>
4. 响应式监听
1. watch的两个参数
1. watch 的第一个参数需要是一个响应式数据(可以是多个),或者是一个返回值的函数。(比如返回一个对象obj的属性() => obj.age)
2. 箭头函数(newValue,oldValue) => {}
1. oldValue:监听的值在变化之前的值
2. newValue:监听的值在变化之后的值
2. watch的触发条件
1. 要让 watch 监听器生效,需要在运行时修改被监听的响应式数据。直接在代码中修改变量的初始值(即在组件加载前设置的值)并不会触发 watch 的回调函数。watch 的作用是监听响应式数据的变化,当数据在运行时发生改变时(例如用户点击按钮触发事件函数),watch 才会检测到这种变化并执行回调函数。
3. watch的两个属性:
1. immediate(获取初始化):当设置为 true 时,监听器会在绑定后立即触发回调函数。这意味着在初始化阶段,即使被监听的值没有发生变化,回调函数也会被调用一次。作用如下
1. 在组件加载时,需要根据初始值执行一些操作,例如根据初始参数请求数据。而不必等待被监听的值发生变化。
2. 在组件创建时,需要将某些响应式数据同步到其他地方
3. 在初始化组件的时候oldValue是undefined,newValue是当前值(初始值)
2. deep:true
1. Vue3 的 reactive 会自动对对象进行深层代理(Proxy),所以:当直接修改 监听对象的内置嵌套对象时,两个监听器都会触发
2. deep: true 主要用于以下场景:
1. 当你使用 reactive 对象作为 watch 的源,且需要在对象被整个替换时触发监听
2. 当监听的是一个返回非响应式对象的 getter 函数时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const deep = reactive({
name: 'JJ',
age: 19,
info: {
address: 'Beijing',
contacts: {
phone: '123456',
email: 'test@example.com'
}
}
})
watch(deep, () => {
console.log('obj changed')
})//vue3自带不需要deep
// 如果是这种情况,则需要 deep: true:当改变的是 deep.info时,returnNonReactiveObj会被整个替换
const returnNonReactiveObj = () => ({
info: deep.info
})
watch(returnNonReactiveObj, (newValue, oldValue) => {
console.log('non-reactive obj changed:', newValue)
}, { deep: true })
</script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29<script setup>
import { ref, reactive, watch } from 'vue'
const counter1 = ref(0)
const counter2 = ref(0)
// 监听多个
watch([counter1, counter2], (newValue, oldValue) => {
console.log('The new counter1 value is: ' + counter1.value)
console.log('The new counter2 value is: ' + counter2.value)
})
const obj = reactive({
name: 'JJ',
age: 18
})
//监听初始化值
watch(obj, (newValue, oldValue) => {
console.log('The new obj value is: ' + obj.value)
}, {
immediate: true
})
// watch监听单个属性
watch(() => obj.name, (newValue, oldValue) => {
console.log('The new obj value is: ' + obj.value)
}, {
immediate: true
})
</script>计算属性:更加灵活,可以在定义响应式变量时声明;
computed
可以用于任何类型的数据处理,不仅限于数值计算。- 核心优势
- 可处理任何数据类型
- 自动跟踪依赖关系
- 具有缓存机制
- 提高代码可读性和维护性
- 以下是一些常见用例
字符串处理
1
2
3// 字符串转换
const upperCase = computed(() => name.value.toUpperCase())
const fullName = computed(() => `${firstName.value} ${lastName.value}`)数组处理
1
2
3
4// 数组过滤
const activeTodos = computed(() => todos.value.filter(todo => !todo.completed))
// 数组排序
const sortedList = computed(() => items.value.sort((a, b) => a.id - b.id))对象处理
1
2
3
4
5
6// 对象转换
const userInfo = computed(() => ({
fullName: `${user.value.firstName} ${user.value.lastName}`,
age: user.value.age,
isAdult: user.value.age >= 18
}))复杂逻辑
1
2
3
4
5
6// 购物车计算
const cartTotal = computed(() => {
return cart.value.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
})字符倒序
1
2
3
4
5
6
7
8
9
10
const value = ref('this is a value')
// 注意这里
const reversedValue = computed(() => {
// 使用 ref 需要 .value
return value.value
.split('').reverse().join('');
})
- 核心优势
条件渲染和列表渲染
v-for的参数
- 如下代码中的v-for=”(item, index) in props.menuData” 中:这个 index 是 Vue 提供的数组索引值,是 v-for 指令内置提供的第二个参数表示当前遍历项在数组中的索引值(从0开始)是可选参数,如果不需要索引值,可以省略
1
2
3
4
5
6
7
8
9
10!-- 假设 props.menuData 是这样的数组 -->
[
{ meta: { id: 1, name: '菜单1' } }, // index = 0
{ meta: { id: 2, name: '菜单2' } }, // index = 1
{ meta: { id: 3, name: '菜单3' } } // index = 2
]
v-for="(item, index) in array"
// item: 当前项
// index: 当前项的索引
- 如下代码中的v-for=”(item, index) in props.menuData” 中:这个 index 是 Vue 提供的数组索引值,是 v-for 指令内置提供的第二个参数表示当前遍历项在数组中的索引值(从0开始)是可选参数,如果不需要索引值,可以省略
v-if 中 index 和 key
index=”
${index}-${item.meta.id}
“- 这是 Element Plus 菜单组件特有的属性
- 用于标识每个菜单项的唯一路径
- 帮助菜单组件追踪当前选中的菜单项
- 通常用于控制菜单的激活状态
key
属性:1
:key="`${index}-${item.meta.id}`"
- 这是 Vue 框架要求的特殊属性
- 用于给 v-for 循环中的每个元素一个唯一标识
- 帮助 Vue 在虚拟 DOM 更新时准确识别节点
- 提高渲染性能和准确性
它们的主要区别:
index
是功能性的唯一标识,用于菜单组件的功能实现key
是结构性的唯一标识,用于 Vue 框架的 DOM 更新优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63<!-- 项目中的菜单组件 -->
<template>
<template v-for="(item, index) in props.menuData">
<!-- 无子菜单 -->
<el-menu-item
v-if="!item.children || item.children.length == 0"
:indx="`${props.index}-${item.meta.id}`"
:key="`${props.index}-${item.meta.id}`"
>
<!-- 结合路由信息以及动态元素实现图标渲染 -->
<el-icon size="20">
<component :is="item.meta.icon"></component>
</el-icon>
<!-- 菜单标题 -->
<span>{{item.meta.name}}</span>
</el-menu-item>
<!-- 有子菜单:分两个部分:标题+递归调用子菜单 -->
<el-sub-menu
v-else
:index="`${props.index}-${item.meta.id}`"
>
<template #title>
<!-- 标题区域的内容: #title是 Element Plus 的 el-sub-menu 组件预定义的插槽名称 -->
<el-icon size="20">
<component :is="item.meta.icon"></component>
</el-icon>
<span>{{item.meta.name}}</span>
</template>
<!-- 递归调用此组件实现子菜单 -->
<!-- index:1 1-2 1-3 实现每个菜单项的唯一标识 -->
<SubMenu
:index="`${props.index}-${item.meta.id}`"
:menuData="item.children"
/>
</el-sub-menu>
</template>
</template>
```
```js
<template>
<!-- 条件渲染 -->
<div v-if="show">显示内容</div>
<div v-else>其他内容</div>
<!-- 列表渲染 -->
<ul>
<li v-for="item in list" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
const list = ref([
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' }
])
</script>v-if和v-show的区别
v-if
和v-show
都是 Vue.js 中用于条件渲染的指令,但它们有不同的作用和使用场景。总结:
- 使用
v-if
时,元素在条件为假时不会存在于 DOM 中。 - 使用
v-show
时,元素始终存在于 DOM 中,只是通过 CSS 控制显示或隐藏。
- 使用
v-if
- 作用:根据表达式的真假值来有条件地渲染元素。
- 特点:元素及其绑定的事件和子组件在条件为假时不会被渲染或销毁。
- 性能:初始渲染时有更高的开销,因为需要添加或删除 DOM 元素。
v-show
- 作用:根据表达式的真假值来切换元素的显示状态。
- 特点:元素始终会被渲染并保留在 DOM 中,只是通过 CSS 的
display
属性来控制显示或隐藏。 - 性能:初始渲染开销较小,但频繁切换显示状态时性能更好。
使用场景
- **
v-if
**:适用于在运行时条件很少改变的场景,因为它会在条件变化时进行 DOM 的添加和删除。 - **
v-show
**:适用于需要频繁切换显示状态的场景,因为它只会切换display
属性。
- **
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<template>
<!-- v-if 示例 -->
<div v-if="isShow">
这个元素会根据 isShow 的值来添加或删除
</div>
<!-- v-show 示例 -->
<div v-show="isVisible">
这个元素会根据 isVisible 的值来显示或隐藏
</div>
</template>
<script>
export default {
data() {
return {
isShow: true,
isVisible: true
};
}
};
</script>
事件处理与双向绑定
v-model
的用途:获取表单中的输入数据赋值给变量;v-model
是 Vue.js 中用于创建双向数据绑定的指令,主要用于表单输入元素与应用状态之间的同步。主要功能如下双向绑定:
- 自动将用户输入的值同步到组件的状态(如
data
或ref
)。 - 同时,当状态发生变化时,更新输入元素的显示值。
- 自动将用户输入的值同步到组件的状态(如
简化代码:
- 替代手动编写
:value
和@input
事件处理器,实现更简洁的双向绑定。
- 替代手动编写
- 常见用法
文本输入:
1
<input v-model="变量名" />
复选框:
1
<input type="checkbox" v-model="变量名" />
- 选择框:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<select v-model="变量名">
<option value="A">A</option>
<option value="B">B</option>
</select>
```js
<template>
<input v-model="username" />
<button @click="handleClick">点击</button>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
const handleClick = () => {
console.log(username.value)
}
</script>
/*
- **`v-model="username"`**:
- 将输入框的值与 `username` 变量绑定。
- 用户在输入框中输入内容时,`username` 会自动更新。
- 如果在代码中修改 `username` 的值,输入框的显示内容也会相应更新。
*/
对比一下选项式API:在 Vue 2 和 Vue 3 的选项式 API 中的
<script>
中需要 return 和 export 的原因:(这些在组合式API中都不用考虑)- export default 的目的:
- 将组件配置对象导出,使其可以被其他组件引入使用
- 这是 ES6 模块系统的要求
- Vue 通过这个导出的对象来创建组件实例
- return 的目的:
- 在 data() 函数中返回数据对象,使其成为响应式数据
- 每个组件实例都需要独立的数据副本,避免数据共享
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31<script>
// 选项式 API
export default {
name: 'MyComponent',
// data 必须是函数并返回对象,确保每个组件实例有独立的数据副本
data() {
return {
count: 0,
message: 'Hello'
}
},
methods: {
increment() {
this.count++
}
},
computed: {
doubleCount() {
return this.count * 2
}
}
}
</script>
// ❌ 错误示例
data: {
count: 0
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34//组合式API中:组合式API基础语法
<script setup>
import { ref, reactive } from 'vue'
// 方式1:使用 ref() 定义基础类型
const count = ref(0)
const message = ref('Hello')
// 方式2:使用 reactive() 定义对象类型
const state = reactive({
count: 0,
message: 'Hello'
})
// 使用时:(在template之外)
// ref 需要 .value
console.log(count.value)
console.log(message.value)
// reactive 直接使用
console.log(state.count)
console.log(state.message)
</script>
<template>
<!-- 在模板中使用 ref 不需要 .value -->
<div>{{ count }}</div>
<div>{{ message }}</div>
<!-- reactive 对象的使用 -->
<div>{{ state.count }}</div>
<div>{{ state.message }}</div>
</template>
- export default 的目的:
选项式与组合式API的区别(除了上述之外):
选项式 API:Vue 通过组件选项自动处理逻辑,无需手动导入响应式函数和生命周期钩子等等。
组合式 API:需要手动导入所需的响应式函数和生命周期钩子等等,从而提供更灵活和模块化的代码结构。(除了编译器宏比如defineProps,编译器宏:由 Vue 的编译器在编译时处理的特殊语法糖。它并非普通的 JavaScript 函数,因此不需要通过 import 从 ‘vue’ 中导入。自动可用:在
<script setup>
块内,defineProps 会被自动识别并处理。)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33//拿子传父举例
// 选项式 API
<template>
<button @click="sendMessage">Send Message</button>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$emit('message-event', 'Hello from child');//自动处理this.$emit
}
}
};
</script>
// 组合式 API
<template>
<ChildComponent @message-event="handleMessageEvent" />
<p>{{ messageFromChild }}</p>
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue'
// 必须遵循js的规范,任何变量使用前必须申明
let messageFromChild = ref('');
const handleMessageEvent = (message) => {
messageFromChild.value = message;
};
</script>选项式 API 和 组合式 API 的主要区别补充:
组织代码的方式
- 选项式 API:通过选项(如
data
、methods
、computed
等)组织代码,适合简单组件。 - 组合式 API:通过组合函数(如
setup
)组织代码,更适合复杂逻辑和代码复用。
- 选项式 API:通过选项(如
逻辑复用
- 选项式 API:依赖混入(mixins)和高阶组件(HOCs),可能导致命名冲突和难以追踪。
- 组合式 API:使用组合函数(composables)实现逻辑复用,结构更清晰,避免命名冲突。
类型推导和 TypeScript 支持
- 选项式 API:TypeScript 支持有限,类型推导较复杂。
- 组合式 API:与 TypeScript 集成更好,提供更精准的类型推导。
代码组织与可维护性
- 选项式 API:按选项分割,功能分散,难以管理大型组件。
- 组合式 API:按功能分割,相关逻辑集中,提升可维护性。
生命周期钩子
- 选项式 API:通过选项直接定义生命周期钩子(如
mounted
、created
)。 - 组合式 API:在
setup
内使用函数(如onMounted
、onCreated
)定义生命周期钩子。
- 选项式 API:通过选项直接定义生命周期钩子(如
响应式系统
- 选项式 API:使用
data
对象进行响应式管理,自动处理响应式属性。 - 组合式 API:使用
ref
、reactive
等函数显式创建响应式数据,更灵活。
- 选项式 API:使用
学习曲线
- 选项式 API:更直观,适合 Vue 新手。
- 组合式 API:需要理解响应式原理和组合函数,学习曲线稍陡。
- 调试和测试
- 选项式 API:由于逻辑分散,单独测试某一功能较困难。
- 组合式 API:逻辑集中,单元测试和调试更容易。
最后总结一下两者的异同:
- 同:无论是选项式还是组合式API,都得遵循js的规范,比如任何变量使用前必须申明,否则会报错;调用其他组件时都需要导入相应文件;
- 异:
- 选项式需要export default导出组件对象,并在其中使用components申明导入的组件、data()函数return 返回数据变量/对象(定义申明)以实现相应,而组合式直接ref/reactive即可;
- 组合式中需要导入响应式函数和生命周期钩子等等,而选项式不需要;(除了编译器宏比如defineProps之外)
- 在这个项目中我主要使用组合式,往后就不管选项式了
<style scoped></style>
:用于定义组件的样式。开发者可以使用CSS或预处理器(如Sass、Less、Tailwind CSS,问perplexity)编写样式:<style>
标签内编写的 CSS 样式默认是全局作用域,会影响到其他路由页面。要使样式仅作用于当前组件,需要在<style>
标签上添加 scoped 属性:1
2
3
4
5
6
7
8
9
10
11<style lang="less" scoped>
/* 在这里添加样式 */
.logo-lg {
font-size: 20px;
text-align: center;
height: 50px;
line-height: 50px;
color: style
}
</style>
- 根组件App.vue:包含了整个应用程序的布局结构
- 页面渲染的入口点。所有其他组件都会嵌套在App.vue中
- 通常会与Vue Router的组件
<RouterView />
结合使用,处理页面之间的导航和路由,实现不同页面的切换和展示;如果使用了路由,但是App.vue中没有<RouterView />
,则路由无法正常工作;经典内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<RouterView />
</template>
<script setup>
</script>
<style scoped>
</style>
- Vue应用的入口文件main.js:初始化Vue实例并将其挂载到DOM中,导出了整个应用程序所需的全局状态和方法。一般来说,在这里初始化一些全局变量或函数,以便于后续操作,其基础操作如下:
- 导入router实例
import router from './router'
;(访问router/index.js文件) - 创建app实例
const app = createApp(App)
,这里的App是根组件App.vue; - 挂载router实例
app.use(router)
- 挂载app实例到指定ID的div元素上:
app.mount('#app')
- 这里的#app是index.html中的一个div元素的id,Vue会将app实例挂载到这个元素上,从而渲染整个应用
- 你也可以将app实例挂载到其他元素上,但要在index.html中添加相应的元素以及id属性
- 默认使用#app的原因
- 约定俗成:使用id=”app”是Vue文档和许多示例中的常见做法,主要是为了简化学习和开发过程。开发者可以快速识别出这是Vue应用的挂载点。
- 避免冲突:在大型应用中,使用特定的id可以帮助避免与其他JavaScript库或框架的冲突,确保Vue应用的DOM元素明确且独特。
- 导入router实例
- router文件夹:
- 功能:路由管理
- 导入组件,定义路由规则,控制页面间的跳转逻辑
- 定义 URL 路径与组件的对应关系
- 管理路由参数传递
- router/index.js负责初始化和配置Vue应用; 主要功能如下
- 定义路由routes
- 创造并导出router实例
- 注意:不能重复导入同一个组件,否则会导致组件重复注册,从而引发错误
- 典型结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
{
path: '/',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
component: () => import('@/views/About.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
- 功能:路由管理
- View文件夹:
- 视图功能:负责页面渲染
- 存放页面级别的组件
- 对应具体的路由页面
- 组合和管理其他小组件
- 与components的区别
- views:负责页面级别的组件,对应具体路由逻辑的页面(也就是说路由怎么设置的,view下就要有相应的页面组件),组合和管理其他小组件(可嵌入components中或者view中子组件)
- components:负责通用的小组件,可以在多个页面中复用,不涉及具体的路由逻辑
- 举例 Main.vue
- 作用:主要负责页面的整体布局和结构,包含了整个页面的主要内容
- 典型结构
1
2
3
4
5
6
7
8<!-- views/Main.vue -->
<template>
<div class="main">
<Header />
<Aside />
<MainContent />
</div>
</template>
- 视图功能:负责页面渲染
1. services :存放业务逻辑层面的代码,包括多个组件共用的服务端接口调用函数等
2. utils:存放通用工具函数,不同的地方不重复书写相同的代码片段
- 当启动应用程序时,Vite 会按照如下顺序加载和处理这些文件:
1
2
3
4
5
6读取 package.json:获取项目依赖项和脚本信息。
解析 vue.config.js:配置Vite的行为,包括环境变量管理、插件注册等。
载入 src/App.vue:作为应用程序的根组件,负责渲染页面结构。
执行 src/main.js:初始化应用程序,全局状态和方法的创建。
递归查找并加载各个组件:从根组件开始,逐级向下寻找并注入子组件,形成完整的DOM树结构。
优化与缓存处理:利用Vite提供的内置优化机制,对JS/CSS进行压缩混淆,以及对第三方库进行tree-shaking以减少无效代码。
传递数据
- 父传子
- 是组件间传递数据的主流方式,尤其是在父子组件关系明确且数据流向单一的情况下。这种方式简单且直观,适合大多数场景。
- 子组件使用 props 属性接收父组件传递的数据;
- 语法:
<子组件 :属性名="父组件数据" :属性名="父组件数据"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36<!-- 父组件 -->
<template>
<ChildComponent :message="parentMessage" />
</template>
<!-- 选项式 -->
<script>
import ChildComponent from './ChildComponent.vue';
export default {
data() {
return {
parentMessage: 'Hello from parent'
};
},
components: { ChildComponent }
/* 这行代码是关于组件注册的,必要的,但是如果使用<script setup>则可以省略
组件注册目的:
告诉 Vue 这个父组件要使用 ChildComponent 子组件
建立组件间的关联关系
为什么必要:
Vue 规定必须先注册组件才能在模板中使用
如果不注册就直接在模板中使用 <ChildComponent>,Vue 会报错
*/
};
</script>
<!-- 组合式API -->
<script setup>
import ChildComponent from './ChildComponent.vue';
const parentMessage = 'Hello, Child!';
</script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<!-- ChildComponent.vue -->
<template>
<p>{{ message }}</p>
</template>
<!-- 选项式API
<script >
export default {
props: ['message']
};
</script> -->
<!-- 组合式API -->
<script setup>
</script>
- 子传父
- 在子组件中触发事件
- 在父组件中监听事件
- 原理
- 事件触发
- 子组件通过 $emit() 触发自定义事件
- 可传递数据作为第二个参数
- 事件监听
- 父组件使用 @事件名 语法监听
- 通过回调函数接收数据
- 数据流向:子组件 -> 触发事件 -> 父组件接收 -> 更新数据
- 事件触发
1 |
|
1 |
|
- 直接通过useRoot获取全局数据,想要谁就直接通过options属性获取
- 全局状态管理:使用 Vuex 或者 Pinia 来管理全局状态,适用于复杂的应用场景。如下介绍vuex
- Provide/Inject API:适用于跨级组件间传递数据。
- 事件总线:使用事件总线(Event Bus)来传递数据,但这种方式在 Vue 3 中不推荐使用。
- Context API:在组合式 API 中,可以通过 context 来传递数据。
vuex
安装:npm install vuex@next –save
核心概念
创建和定义 store
- state: 存储状态信息
- store/下定义状态信息
- 在组件中通过有三种方式获取state
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28//1内置函数computed (推荐)
/*
优点:
响应式: 状态变化时会自动更新视图
缓存: 计算结果会被缓存,只有依赖变化时才重新计算
*/
count: computed(() => store.state.count)
doubleCount: computed(() => store.getters.doubleCount),
//2直接访问 (不推荐)
/*
缺点:
非响应式: 状态变化时不会自动更新视图
需要手动触发更新
*/
count: store.state.count
//3使用 ref + watch (特殊场景)
/*
使用场景:
需要对状态变化做额外处理
需要本地维护状态副本
*/
const count = ref(store.state.count)
watch(() => store.state.count, (newVal) => {
count.value = newVal
})
- mutations: 同步修改状态
- store/下定义修改状态的方法
- 当需要修改状态时,在组件中通过commit相应的mutation和参数(见store/下定义修改状态的方法)触发重新处理&赋值给state中的变量来实现状态更新
- actions: 异步操作
- 异步请求是指发出请求后,不会立即得到结果,而是在未来某个时间点才会收到响应的操作。
- 为什么需要 Actions
- Mutations 必须是同步的
- Actions 可以包含异步操作
- Actions 可以组合多个 mutations
- 使用场景
- API 请求等延时操作
- 复杂的状态修改流程
- 需要组合多个 mutation
- 涉及异步操作的业务逻辑:比如定时器、延时操作、文件上传/下载等
- 特点:
- async 定义异步异步函数
- 通过 context 对象访问 store 实例的方法和属性
- 可以触发其他 actions (dispatch)
- 最终通过 commit 提交 mutation 修改状态
- 在组件中通过dispatch提交action
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37// store/index.js
const store = createStore({
state: {
userInfo: null,
loading: false
},
mutations: {
setUserInfo(state, info) {
state.userInfo = info
},
setLoading(state, status) {
state.loading = status
}
},
actions: {
// 1. 处理异步操作
async login({ commit }, credentials) {
try {
commit('setLoading', true)
const response = await api.login(credentials)
commit('setUserInfo', response.data)
} finally {
commit('setLoading', false)
}
},
// 2. 复杂业务逻辑
async checkout({ commit, state, dispatch }) {
// 检查库存
await dispatch('checkStock')
// 创建订单
await dispatch('createOrder')
// 清空购物车
commit('clearCart')
}
}
})
- getters: 计算属性
- store/下定义计算相关的方法
- 组件中通过store.getters调用相应的方法
- modules: 模块化管理状态
- 针对不同联动事件之间有不同的关联状态和操作,因此划分成多个模块,每个模块管理相关的一部分的共享状态会更方便操作;
- 比如Aside和headerNav之间的通信(联动)都是关于菜单的,于是将所有的菜单相关的状态(信息、操作等等)放在一个模块sotore/menu.js中
- 需要在/store/index.js中导入所需模块,将创建的store导出;供main.js挂载和其他组件使用;同时模块中只需要定义被其他组件需要的state、mutations、actions、getters并导出
1
2
3
4
5
6
7
8
9// src/sotre/index.js
import { createStore } from "vuex";
import menu from "./menu";
export default createStore({
modules: {
menu
}
})1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//menu.js
const state={
isCollapse:false,//是否折叠
selectMenu:[]//选中的菜单
}
const mutations={
collapseMenu(state){
state.isCollapse=!state.isCollapse//取反
}
}
//从此模块中导出此需要的数据
export default{
state,
mutations
}
- 在vuex中如果有模块的话,组件在调用 state 时需要加上模块名,而调用 mutations、actions、getters 时则不需要加模块名。(在vue3_study_basic中亲测)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24export default {
setup() {
const store = useStore()//获取store实例
const newUsername = ref('') // 新增响应式变量存储输入值
return {
// 读取状态
count: computed(() => store.state.Try.count),
doubleCount: computed(() => store.getters.doubleCount),
username: computed(() => store.state.Try.username),
newUsername,//这其实是newUsername:newUsername的简写;代表将newUsername暴露给模板即将输入框的值暴重新赋值给newUsername(不能忽略)
// 状态修改:mutations
increment: () => store.commit('increment'),//提交一个名为increment的变化
// 重置用户名
resetUsername: () => {
store.commit('setUsername', newUsername.value)//提交一个名为setUsername的变化
newUsername.value = '' // 清空输入框
},
//action:异步操作
login: (username) => store.dispatch('login', username),//分发一个名为login的action
}
}
}
- 针对不同联动事件之间有不同的关联状态和操作,因此划分成多个模块,每个模块管理相关的一部分的共享状态会更方便操作;
- state: 存储状态信息
在入口文件main.js中使用:
- 创建 store 实例并挂载
组件中通过 useStore() 访问和使用
- 如下是一个基础案例额
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// store/index.js
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
count: 0,
username: ''
}
},
mutations: {
// 修改状态的方法
increment(state) {
state.count++
},
setUsername(state, username) {
state.username = username
}
},
actions: {
// 异步操作
async login({ commit }, username) {
// 模拟API调用
//使用 new Promise 和 setTimeout 模拟了一个耗时1秒的API调用
//await 会暂停执行,直到Promise完成
await new Promise(resolve => setTimeout(resolve, 1000))
commit('setUsername', username)
}
},
getters: {
// 计算属性
doubleCount(state) {
return state.count * 2
}
}
})
export default store1
2
3
4
5
6
7
8<!-- main.js -->
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48<!-- 组件.vue -->
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<p>Username: {{ username || '未登录' }}</p>
<button @click="increment">+1</button>
<!-- 点击按钮触发login方法,传入参数admin到login action -->
<button @click="login('admin')">Login</button>
<!-- 添加重置按钮 -->
<input
v-model="newUsername"
placeholder="输入新用户名"
>
<button @click="resetUsername">Reset User</button>
</div>
</template>
<script>
import { useStore } from 'vuex'
import { computed } from 'vue'
export default {
setup() {
const store = useStore()//获取store实例
const newUsername = ref('') // 新增响应式变量存储输入值
return {
// 读取状态
count: computed(() => store.state.count),
doubleCount: computed(() => store.getters.doubleCount),
username: computed(() => store.state.username),
newUsername,//这其实是newUsername:newUsername的简写;代表将newUsername暴露给模板即将输入框的值暴重新赋值给newUsername(不能忽略)
// 状态修改:mutations
increment: () => store.commit('increment'),//提交一个名为increment的变化
// 重置用户名
resetUsername: () => {
store.commit('setUsername', newUsername.value)//提交一个名为setUsername的变化
newUsername.value = '' // 清空输入框
},
//action:异步操作
login: (username) => store.dispatch('login', username),//分发一个名为login的action
}
}
}
</script>
vuex持久化
vuex-persistedstate 将 store 存储到浏览器的时机如下:这种机制确保了在页面刷新或重新打开后能够恢复之前的状态。
主要存储时机
Store 发生变化时
- 当通过 mutation 修改 state 时
- 在每次 state 更新后自动触发
具体触发点
1
2
3
4
5// 监听 store 的变化
store.subscribe((mutation, state) => {
// 将数据持久化到 localStorage
localStorage.setItem('vuex', JSON.stringify(state))
})
- 存储行为特点
实时性
- 同步执行,立即存储
- 不需要手动触发
选择性存储
1
2
3
4
5
6
7
8
9import createPersistedState from 'vuex-persistedstate'
export default new Vuex.Store({
plugins: [
createPersistedState({
paths: ['需要持久化的state路径'] // 可以选择性存储
})
]
})存储位置
- 默认使用 localStorage
- 可配置使用 sessionStorage 或其他存储方式
vuex-persistedstate
插件在页面刚刚打开进行渲染时,不会立即将 store 存储到浏览器。相反,它会从浏览器的存储(如
localStorage或 sessionStorage
)中读取之前保存的状态,并将其恢复到 Vuex store 中。
- 工作流程:
页面加载时:
vuex-persistedstate
插件会从浏览器存储中读取之前保存的 Vuex 状态。- 将读取到的状态合并到当前的 Vuex store 中。
状态变化时:
- 当 Vuex store 中的状态发生变化时(通过 mutation),
vuex-persistedstate
会将新的状态存储到浏览器中。
- 当 Vuex store 中的状态发生变化时(通过 mutation),
总结:
页面加载时:
vuex-persistedstate
从浏览器存储中恢复状态到 Vuex store。状态变化时:
vuex-persistedstate
将新的状态存储到浏览器中。
因此,vuex-persistedstate
插件在页面刚刚打开进行渲染时,不会立即将 store 存储到浏览器,而是从浏览器中读取之前保存的状态并恢复。
异步操作
什么是异步请求?
Promise 是处理异步操作的一种方式,它代表一个异步操作的最终完成(或失败)及其结果值。
基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 1. 基础语法
const promise = new Promise((resolve, reject) => {
// 异步操作
if (成功) {
resolve(结果)
} else {
reject(错误)
}
})
// 2. 使用Promise
promise
.then(result => {
// 处理成功
})
.catch(error => {
// 处理失败
})实际示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 1. 封装API请求
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
axios.get(`/api/users/${userId}`)
.then(response => resolve(response.data))
.catch(error => reject(error))
})
}
// 2. 使用async/await简化
async function getUserData(userId) {
try {
const data = await fetchUserData(userId)
return data
} catch (error) {
console.error('获取用户数据失败:', error)
}
}
//3. 延时
async login({ commit }, username) {
//延时1秒
await new Promise(resolve => setTimeout(resolve, 1000))
commit('setUsername', username)//最终通过 commit 提交 mutation 修改状态
}
凡是内部调用多个函数且要讲究执行顺序的的函数要用异步async申明;异步操作内部要使用await调用已存在的方法,外部也要用async申明参数,每一环都是如此;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48//group/index.vue的提交弹窗逻辑
const localData=localStorage.getItem('pz_v3pz')
import { computed, toRaw } from 'vue';
const routerList=computed(()=>store.state.menu.routerList)
//内有异步操作
const confirm=async (formEl)=>{
if (!formEl) return
//内有异步操作
await formEl.validate( async(valid,fields)=>{
if (valid) {
//根据APi文档可知,其中一个参数为permissions:string,故要将选中的权限字符串化
const permissions = JSON.stringify(treeRef.value.getCheckedKeys())
// console.log(permissions)
try {
// 1. 先执行 userSetMenu请求重新设置菜单数据
await userSetMenu({
name: form.name,
permissions,//参数名与变量名同名时,可以简写:一个即可,不需要:
id: form.id
})
// 2. 更新列表数据
await getListData()
// 3. 关闭弹窗
beforeClose()
// 4. 再执行 menuPermissions
const { data } = await menuPermissions()
store.commit('dynamicMenu', data.data)
// 5. 添加路由
toRaw(routerList.value).forEach(item => {
router.addRoute('main', item)
})
} catch (error) {
console.error('操作失败:', error)
}
}else{
console.log('error submit!',fields)
}
})
}
axios
API
Router路由管理
在 Vue 3 中,useRoute
和 useRouter
都是 Vue Router 提供的组合式 API,用于在组合式函数中访问路由。
区别:
useRoute
用于访问当前路由的状态,是只读的。useRouter
用于执行路由操作,如导航、切换、添加路由等。
useRoute
返回当前激活的路由信息,是一个只读的响应式对象。
可以获取路由的‘meta’、
path
、params
、query
、name
等属性。示例:
1
2
3
4
5import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.path) // 输出当前路径
console.log(route.params) // 获取路由参数
useRouter
返回当前组件的路由实例对象,可以使用它进行添加路由、导航等操作。
提供了‘addRoute’、
push
、replace
等方法示例:
1
2
3
4
5
6
7
8
9
10import { useRouter } from 'vue-router'
const router = useRouter()
const goHome = () => {
router.push({ name: 'Home' }) // 导航到名为 'Home' 的路由
//遍历数据中的路由信息添加到main路由下
toRaw(routerList.value).forEach(item => {
router.addRoute('main',item)
});
}
html+css+js基础补充
html+css
- img等行内元素不吃/同时吃高度和宽度,必须将其设置为块级元素:如下实现图片填充满整个父元素
1
<img :src="imgUrl" alt="" style="width: 100%;height: 100%;display: block;">
js
- 导出导入
- export default:导出一个默认的对象,一个文件只能有一个默认导出
- 导入时可以自定义名字from ‘文件路径‘,
1
2
3
4
5
6
7
8// 模块A.js
const name = 'John';
export default name;
// 模块B.js
import myName from './A.js'; // 可以使用任意名称
console.log(myName); // 输出: 'John'
- 导入时可以自定义名字from ‘文件路径‘,
- 直接export导出( 非默认),导入时必须使用与导出时相同的名称,并且需要使用花括号
1
2
3
4
5
6
7
8
9
10
11// 模块A.js
export const name = 'John';
// 模块B.js
import { name } from './A.js'; // 必须使用相同的名称
console.log(name); // 输出: 'John'
//导入组件
import { ElMessage } from 'element-plus/es'
//导入函数
import { reactive } from 'vue'; - 导出时的路径
- 在JavaScript中,使用
import
语句时,路径是否必须包含文件后缀取决于多个因素,包括所使用的模块系统(如ES模块或CommonJS)以及运行环境(如Node.js或浏览器)。 - 在使用ES模块时,路径后面必须包含文件后缀,以确保代码在不同环境中的兼容性和可预测性。而在CommonJS模块中,虽然可以省略后缀,但为了提高代码的清晰度和一致性,建议始终使用完整的路径。
- 在JavaScript中,使用
- 最好把所有导入的东西写在script最前面,导出的东西写在最后面,这样方便查看和修改以及debug
- export default:导出一个默认的对象,一个文件只能有一个默认导出
- 字符串中解析变量(即常量和变量混在一个字符串里),需要使用模板字符串,即用反引号(esc下方)包裹字符串,而不是单引号’
1
2const url = `../view${route.meta.path}/index.vue;
token的作用以及为什么要检测是否过期
Token(令牌)是一种用于身份验证和授权的机制,通常在用户登录时生成,并在后续请求中用于验证用户身份。以下是Token的功能和检测Token是否过期的作用:
Token的功能
- 身份验证:Token用于验证用户的身份,确保请求是由合法用户发起的。
- 授权:Token可以包含用户的权限信息,用于控制用户对资源的访问权限。
- 无状态:Token通常是无状态的,服务器不需要存储用户的会话信息,减轻了服务器的负担。
- 用户的会话信息是指在用户与服务器交互期间,用于跟踪和管理用户状态的数据。会话信息通常包含以下内容:
- 用户身份:用户的唯一标识符,如用户ID或用户名。
- 认证信息:用于验证用户身份的凭证,如Token或Session ID。
- 用户权限:用户在系统中的权限和角色信息,用于控制用户对资源的访问权限。
- 会话状态:用户当前的状态信息,如登录状态、购物车内容等。
- 其他上下文信息:与用户操作相关的临时数据,如表单数据、浏览历史等。
- 在传统的会话管理中,服务器会在内存或数据库中存储这些会话信息,并通过Session ID在客户端和服务器之间进行关联。而在使用Token的无状态认证机制中,会话信息通常包含在浏览器的cookie/Token中,由客户端保存在本地,并在每次请求时发送给服务器进行验证,减轻了服务器的负担。
- 安全性:Token可以通过加密和签名来确保数据的完整性和安全性,防止篡改和伪造。
检测Token是否过期的作用
- 安全性:检测Token是否过期可以防止长期未使用的Token被恶意利用,确保用户的账户安全。
- 用户体验:及时检测Token过期并提示用户重新登录,可以确保用户的操作不会因为Token失效而中断。
- 资源保护:通过检测Token过期,可以防止未经授权的访问,保护服务器资源和数据的安全。
- 合规性:一些安全标准和法规要求定期验证用户身份(类比身份证每隔一段时间也得检查),检测Token过期是实现这一要求的有效手段。
检测Token是否过期的实现
- Token中包含过期时间:在生成Token时,将过期时间(如
exp
字段)包含在Token中,客户端和服务器可以根据这个时间判断Token是否过期。 - 服务器端验证:每次请求时,服务器验证Token的有效性和过期时间,如果Token过期,则返回相应的错误码(如401 Unauthorized)。
- 客户端处理:客户端在接收到Token过期的响应后,可以清除本地存储的Token,并引导用户重新登录。
- 通过上述机制,可以有效地管理Token的生命周期,确保系统的安全性和用户体验。