核心
什么是MVVM?
model-view-viewModel(MVVM) 是一个软件架构设计模式,能够实现前端开发和后端业务逻辑的分离,其中 model 指数据模型,负责后端业务逻辑处理,view 指视图层,负责前端整个用户界面的实现,viewModel 则负责 view层 和 model层 的交互。
Vue 的双向数据绑定原理是什么?
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的 setter 和 getter ,在数据变动时发布消息给订阅者,触发相应的监听回调。
$nextTick 是什么?
$nextTick 是 Vue.js 中的一个核心方法,用于在下次 DOM 更新循环结束之后执行延迟回调 。它确保代码在 DOM 更新后执行,常用于操作更新后的 DOM 或依赖 DOM 状态的逻辑。
使用场景
javascript
// 1. 操作更新后的 DOM
// 修改数据后立即操作 DOM 可能无法获取最新状态,`$nextTick` 可确保 DOM 已更新。
this.message = '更新后的值';
this.$nextTick(() => {
console.log(document.getElementById('text').innerHTML); // 获取更新后的 DOM 内容
});
// 2. 依赖 DOM 的第三方库初始化
// 某些库(如图表库)需要在 DOM 渲染完成后初始化。
this.dataLoaded = true;
this.$nextTick(() => {
new Chart(document.getElementById('chart'), options);
});
实现原理
$nextTick 利用了 JavaScript 的事件循环机制。Vue 将回调函数推入一个队列,在当前的同步任务和 DOM 更新完成后异步执行。默认优先使用微任务(如 Promise),降级到宏任务(如 setTimeout)。
与 setTimeout 的区别
| 特性 | $nextTick |
setTimeout |
|---|---|---|
| 执行时机 | DOM 更新后立即执行 | 延迟固定时间后执行 |
| 优先级 | 微任务优先 | 宏任务 |
| 与 Vue 生命周期关联 | 确保在下次渲染周期前完成 | 无关 |
Vue 3.0 使用 Proxy API 替代 defineProperty 的原因
- 性能优化
- Proxy API 直接代理整个对象,无需递归遍历对象属性进行劫持。defineProperty 需要对每个属性单独设置 getter/setter,初始化时性能开销较大。
- Proxy 的拦截操作在运行时动态处理,defineProperty 需要在初始化阶段静态定义所有响应式属性。
- 完整对象监听
- Proxy 可以检测到对象属性的新增、删除操作,而 defineProperty 只能监听已存在的属性变化。对于动态添加的属性,defineProperty 需要额外调用 Vue.set 方法。
- Proxy 支持对数组变化的全面监听,包括 push/pop/shift/unshift 等原生方法操作。defineProperty 需要重写数组原型方法实现类似功能。
- 代码简化
- Proxy 提供统一的拦截器(handler)处理各种操作,包括 get/set/deleteProperty 等。defineProperty 需要为每个属性单独编写 getter/setter 逻辑。
- Proxy 支持对嵌套对象的自动代理,defineProperty 需要手动处理嵌套结构的响应式转换。
- 功能扩展
- Proxy 可以拦截更多操作类型,如 in 操作符、Object.keys() 等。defineProperty 仅限于属性读写操作的拦截。
- Proxy 支持对 ES6+ 特性(如 Map、Set)的代理,defineProperty 主要针对普通对象设计。
示例对比
javascript
// defineProperty 实现
const obj = {}
Object.defineProperty(obj, 'foo', {
get() {
console.log('get foo')
return this._foo
},
set(val) {
console.log('set foo')
this._foo = val
}
})
// Proxy 实现
const handler = {
get(target, key) {
console.log(`get ${key}`)
return target[key]
},
set(target, key, value) {
console.log(`set ${key}`)
target[key] = value
return true
}
}
const proxy = new Proxy({}, handler)
这种改进使 Vue 3.0 的响应式系统更高效、功能更全面,同时减少了框架内部的复杂性。
watch、method、computed 的区别?
watch
watch 用于观察和响应 Vue 实例上的数据变化。适用于需要在数据变化时执行异步或开销较大的操作。
-
特性:
- 监听特定的数据属性,当属性变化时触发回调函数。
- 支持深度监听(
deep: true)和立即执行(immediate: true)。 - 适合处理异步任务或复杂逻辑。
-
示例 :
javascriptwatch: { someProperty(newVal, oldVal) { // 数据变化时执行的操作 } }
method
method 是 Vue 实例中定义的方法,通过调用执行逻辑。适用于需要手动触发的操作。
-
特性:
- 通过事件绑定或直接调用触发。
- 每次调用都会重新执行函数体。
- 适合处理用户交互或需要灵活调用的逻辑。
-
示例:
javascriptmethods: { doSomething() { // 执行某些操作 } }
computed
computed 是计算属性,基于依赖的响应式数据动态计算值。适用于需要缓存结果的场景。
-
特性:
- 依赖的数据变化时自动重新计算,否则返回缓存值。
- 必须返回一个值,适合模板中频繁使用的复杂逻辑。
- 不支持异步操作。
-
示例:
javascriptcomputed: { fullName() { return this.firstName + ' ' + this.lastName; } }
核心区别
- 缓存 :
computed缓存结果,依赖不变时直接返回缓存;method每次调用重新计算;watch无缓存。 - 用途 :
computed用于派生数据;method用于事件或主动调用;watch用于响应数据变化执行副作用。 - 异步支持 :
watch支持异步;computed和method通常不推荐异步操作。
选择建议
- 需要派生数据且依赖其他响应式数据时,用
computed。 - 需要主动触发或传递参数时,用
method。 - 需要在数据变化时执行异步或复杂逻辑时,用
watch。
生命周期
说说Vue的生命周期,什么时候被调用?
| 阶段 | 钩子函数 | 说明 |
|---|---|---|
| 创建阶段 | beforeCreate | 在实例初始化之后调用,此时 data、methods 等选项尚未初始化,无法访问数据或方法。 |
| created | 实例创建完成,data 和 methods 已初始化,可访问数据并调用方法。但 DOM 未挂载,无法操作 DOM 元素。 | |
| 挂载阶段 | beforeMount | 在挂载开始之前调用,此时模板编译完成,但尚未将虚拟 DOM 渲染为真实 DOM。 |
| mounted | 实例挂载到 DOM 后调用,可操作 DOM 元素。通常用于发起异步请求或初始化第三方库。 | |
| 更新阶段 | beforeUpdate | 数据变化触发虚拟 DOM 重新渲染之前调用,此时可获取更新前的 DOM 状态。 |
| updated | 虚拟 DOM 重新渲染并应用更新后调用,此时可操作更新后的 DOM。避免在此钩子中修改数据,可能导致无限循环。 | |
| 销毁阶段 | beforeDestroy | 实例销毁之前调用,此时实例仍完全可用。适合清理定时器、取消事件监听等操作。 |
| destroyed | 实例销毁后调用,所有绑定和监听被移除,子实例也被销毁。 | |
| 其他 | activated | 用于 <keep-alive> 缓存的组件,在组件激活时触发。 |
| deactivated | 用于 <keep-alive> 缓存的组件,在组件停用时触发。 |
什么阶段(生命周期)才能访问操作DOM? 为什么
在钩子函数 mounted() 中才能开始访问操作dom,因为 mounted() 生命周期前,dom刚好渲染好,但还未挂载到页面,如果在这之前进行dom操作,将找不到dom节点。
路由
$route 和 $router 的区别?
$route 和 $router 是 Vue Router 提供的两个核心对象,它们在路由管理中扮演不同的角色。
$route 是一个当前激活的路由信息对象,包含以下关键属性:
path:当前路由的路径(如/user/1)。params:动态路由参数(如{ id: '1' })。query:URL 查询参数(如?name=foo解析为{ name: 'foo' })。hash:URL 的哈希值(如#section)。fullPath:完整 URL(如/user/1?name=foo#section)。matched:匹配的路由配置数组(包含嵌套路由信息)。meta:路由元信息(如权限标识)。
$router 是 Vue Router 的实例,提供路由操作方法:
push(location):导航到新路由(记录历史栈)。replace(location):替换当前路由(不记录历史)。go(n):在历史栈中前进/后退(如go(-1)返回上一页)。back():等同于go(-1)。forward():等同于go(1)。resolve(location):解析目标路由的完整信息。
$route |
$router |
|
|---|---|---|
| 功能差异 | $route 是只读的路由信息对象,用于获取当前路由状态。 |
$router 是路由操作实例,用于主动控制导航。 |
| 使用场景 | 需要读取参数或监听路由变化时使用 $route。 |
需要编程式导航(如跳转、返回)时使用 $router。 |
关系: $router 修改路由后,$route 会自动更新反映最新状态。例如调用 push() 后,$route.path 会同步变化。
注意事项: 避免直接修改 $route 的属性(如 $route.query = {}),应通过 $router 的方法更新。 路由守卫(如 beforeEach)中通过 to 和 from 参数访问 $route 信息。
路由导航守卫有哪些?
| 类型 | 名称 | 说明 |
|---|---|---|
| 全局 | 全局前置守卫(beforeEach) | 在路由跳转前触发,常用于权限验证或登录状态检查。接收to、from和next参数,需调用next()才能继续导航。 |
| 全局解析守卫(beforeResolve) | 在导航被确认前触发,适合处理需要等待异步操作完成的场景(如数据预加载)。同样接收to、from和next参数。 |
|
| 全局后置钩子(afterEach) | 在导航完成后触发,无next参数。常用于页面统计或日志记录,不影响导航结果。 |
|
| 独享 | 路由独享守卫(beforeEnter) | 在路由配置中单独定义,仅对特定路由生效。用法与全局前置守卫类似,适用于某个路由的特定逻辑(如动态权限校验)。 |
| 组件内 | beforeRouteEnter | 在组件渲染前调用,此时组件实例未创建。可通过next(vm => {})访问实例。 |
| beforeRouteUpdate | 当前路由改变但组件复用时触发(如动态参数变化)。可访问组件实例this。 |
|
| beforeRouteLeave | 在离开当前路由前触发,常用于阻止用户未保存离开(如表单填写)。需调用next()或next(false)。 |
执行顺序示例
- 导航触发 → 调用失活组件的
beforeRouteLeave - 调用全局
beforeEach - 调用复用的
beforeRouteUpdate(如存在) - 调用目标路由的
beforeEnter - 解析异步路由组件
- 调用目标组件的
beforeRouteEnter - 调用全局
beforeResolve - 导航确认 → 调用全局
afterEach
代码示例
javascript
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) next('/login')
else next()
})
// 路由独享守卫
const routes = [
{
path: '/admin',
component: AdminPanel,
beforeEnter: (to, from, next) => {
checkAdminRole() ? next() : next('/403')
}
}
]
// 组件内守卫
export default {
beforeRouteLeave(to, from, next) {
if (this.hasUnsavedChanges) {
confirm('未保存更改,确定离开?') ? next() : next(false)
} else next()
}
}
指令
v-if 与 v-show 的区别?
相同点: v-show 和 v-if 都能控制元素的显示和隐藏。
不同点:
- 实现本质方法不同:v-show 本质就是通过设置 css 中的 display 设置为 none 来控制隐藏。而 v-if 是动态的向 DOM 树内添加或者删除 DOM 元素;
- 编译不同: v-show 都会编译,初始值为 false,只是将 display 设置为 none, 但它也编译了; v-if 初始值为 false 时,就不会被编译。
总结: v-show 只编译一次,后面其实就是控制 css, 而 v-if 不停的销毁和创建,如果要频繁切换某节点时,v-show 性能更好一点。
Vuex
Vuex有哪些属性?
| 核心属性 | 说明 |
|---|---|
| state | 存储应用状态数据,所有组件共享的单一状态树。通过 this.$store.state 访问。 |
| getters | 计算派生状态,类似组件的计算属性。通过 this.$store.getters 调用。支持传递参数和嵌套访问其他 getter。 |
| mutations | 唯一修改 state 的方法,必须是同步函数。通过 commit 触发,例如 this.$store.commit('mutationName', payload)。 |
| actions | 处理异步操作或复杂逻辑,通过 dispatch 触发。可调用多个 mutation,例如 this.$store.dispatch('actionName', payload)。 |
| modules | 将 store 分割成模块,每个模块拥有独立的 state、getters、mutations 和 actions。通过 namespaced: true 启用命名空间隔离。 |
Vuex 和 localStorage 的区别?
Vuex 是 Vue.js 的官方状态管理库,专为 Vue 应用程序设计,用于集中管理组件间的共享状态。
localStorage 是浏览器提供的 Web Storage API,用于在客户端持久化存储数据。
- Vuex 是内存中的响应式状态管理工具,适合短期、动态数据。
- localStorage 是持久化存储方案,适合长期、静态数据。
- 实际项目中可结合使用,例如用 Vuex 管理运行时状态,用 localStorage 持久化部分状态。
以下是两者的主要区别:
| 特性 | Vuex | localStorage |
|---|---|---|
| 数据存储位置与生命周期 | * 数据存储在内存中,仅在当前页面会话期间有效。 * 刷新页面或关闭浏览器后数据丢失。 * 适合管理动态的、临时的应用状态(如用户界面状态、表单数据)。 | * 数据存储在浏览器本地,即使关闭浏览器或重启设备也会保留。 * 需手动清除(通过代码或浏览器设置)才会失效。 * 适合持久化存储(如用户偏好设置、登录令牌)。 |
| 数据访问与响应性 | * 数据是响应式的,状态变化会自动触发视图更新。 * 通过 commit 和 dispatch 修改状态,确保可追踪性。 * 支持模块化和插件扩展(如时间旅行调试)。 |
* 数据非响应式,需手动监听或触发更新(如通过 window.addEventListener('storage', callback))。 * 直接通过 setItem 和 getItem 操作,无状态管理机制。 * 存储的数据类型仅限于字符串,需手动序列化(如 JSON.stringify)。 |
| 使用场景 | * 组件间共享复杂状态(如购物车、全局弹窗控制)。 * 需要中间件处理异步逻辑(如 API 请求)。 * 需要状态快照或回滚功能。 | * 长期保存用户配置(如主题、语言)。 * 离线缓存数据(如草稿、历史记录)。 * 存储简单键值对且无需响应式更新。 |
| 性能与安全性 | * 内存操作速度快,但频繁大量数据可能影响内存占用。 * 数据仅在当前标签页有效,无法跨标签页共享。 | * 读写速度较慢(涉及磁盘 I/O),存储容量较大(通常 5MB)。 * 需注意敏感数据的安全问题(如避免存储未加密的密码)。 |
Vuex 状态管理
javascript
// 定义状态
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) { state.count++; }
}
});
// 组件中触发更新
this.$store.commit('increment');
localStorage 操作
javascript
// 存储数据
localStorage.setItem('count', JSON.stringify(10));
// 读取数据
const count = JSON.parse(localStorage.getItem('count'));
其他
GET 和 POST 的区别?
GET 用来获取数据(参数在 URL),POST 用来提交数据(参数在 Body);前者可缓存、可书签、长度有限,后者无大小限制、不在历史中、可传文件。
核心语义区别(HTTP 规范定义)
- GET :请求获取 指定的资源。不应有其他作用(即幂等、安全)。
- POST :请求服务器处理所附实体(如表单数据)。通常会产生状态变化(如新建资源)。
技术细节对比
| 特性 | GET | POST |
|---|---|---|
| 数据传输位置 | 放在 URL 的查询字符串中(?key=value) |
放在请求体(Body)中 |
| 数据长度限制 | 受 URL 长度限制(浏览器/服务器限制,通常 2K-8K) | 无理论限制(由服务器配置决定) |
| 编码类型 | 仅支持 URL 编码(application/x-www-form-urlencoded) |
支持多种(multipart/form-data 用于文件,application/json 等) |
| 缓存 | 可被浏览器/CDN 主动缓存 | 默认不缓存(除非特殊配置) |
| 历史记录 | 保留在浏览器历史中 | 不保留 |
| 书签 | 可以保存为书签 | 不可以 |
| 后退/刷新行为 | 无害(重新请求页面) | 浏览器通常会弹窗提示"重新提交表单" |
| HTTP 包结构 | 只有请求行 + 头部(没有 Body) | 有请求行 + 头部 + Body |
常见误区澄清
- "POST 比 GET 安全" 说法不准确。两者都是明文传输(HTTP 下)。HTTPS 中两者均加密。区别仅在于:GET 的 URL 会留在浏览器历史、服务器日志中(可能泄露敏感数据);POST 参数不在 URL 中,相对少一处暴露点。 更准确的说法:GET 请求应避免传递密码等敏感信息。
- "POST 传输数据量远大于 GET" 说法不准确。POST 本身无大小限制,限制来自服务器(如
nginx client_max_body_size)或 PHP 配置。但现代 API 中,POST 传几 MB 很正常;而 GET 受 URL 长度限制,不适合大体积数据。 - "POST 不能缓存" 说法不完全正确。通过设置
Cache-Control和Expires头,POST 响应也可被缓存,但极少这么用(违背语义)。
实际开发中的选择原则
| 场景 | 推荐方法 |
|---|---|
| 查询数据、搜索、分页列表、获取详情 | GET(可缓存、可分享链接) |
| 登录、提交订单、发帖、上传文件、新增数据 | POST(有副作用、数据量大或敏感) |
| 删除资源 | 通常用 DELETE(RESTful) |
| 更新整个资源 | PUT |
| 局部更新 | PATCH |
cookie 和 session 的区别?
基本概念
Cookie 是存储在用户浏览器中的小型文本文件,用于保存用户信息。每次请求时,浏览器会自动将 Cookie 发送到服务器。
Session 是服务器端存储的用户会话数据,通常通过唯一的 Session ID 标识。Session ID 可能通过 Cookie 或 URL 传递给服务器。
| 特性 | Cookie | Session |
|---|---|---|
| 存储位置差异 | Cookie 数据存储在客户端浏览器中,用户可以直接查看或修改。 | Session 数据存储在服务器端,用户无法直接访问。 |
| 安全性对比 | Cookie 由于存储在客户端,可能存在安全风险,如被窃取或篡改。 | Session 存储在服务器端,安全性较高,但需注意 Session ID 的传输安全。 |
| 生命周期管理 | Cookie 可以设置过期时间,即使关闭浏览器也可能保留。 | Session 通常依赖于会话活动,浏览器关闭后可能失效(取决于服务器配置)。 |
| 数据容量限制 | 单个 Cookie 大小通常限制为 4KB 左右,浏览器对域名下的 Cookie 数量也有限制。 | Session 存储在服务器端,理论上容量更大,但需考虑服务器资源。 |
| 使用场景选择 | Cookie 适合存储不敏感的小型数据,如用户偏好设置。 | Session 适合存储敏感或临时数据,如登录状态、购物车信息。 |
| 性能影响 | Cookie 每次请求都会发送,可能增加网络流量。 | Session 数据存储在服务器,可能增加服务器内存负担,需合理管理。 |