当前位置: 首页 > article >正文

前端八股之Vue

目录

有使用过vue吗?说说你对vue的理解

你对SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用呢

一、SPA 是什么

二、SPA 和 MPA 的区别

三、SPA 的优缺点

四、实现 SPA

五、给 SPA 做 SEO 的方式(基于 Vue)

v-show和v-if有什么区别?使用场景分别是什么?

Vue实例挂载的过程

1. new Vue () 做了什么

2. _init 方法执行流程

3. $mount 方法执行流程

4. render 和 _update 方法

5. 总结

1. 开始创建 Vue 实例

2. 初始化准备工作

3. 初始化各个功能部分

4. 挂载阶段(如果配置了挂载元素)

5. 解析模板变成 “画画指南”(render 函数)

6. 真正的渲染组件

7. 生成虚拟 DOM(_render 方法)

8. 把虚拟 DOM 变成真实 DOM 并更新到页面(_update 方法)

请描述下你对vue生命周期的理解?在created和mounted这两个生命周期中请求数据有什么区别呢?

一、Vue 生命周期通俗理解

二、Vue 生命周期的各个阶段

三、在 created 和 mounted 中请求数据的区别

v-if和v-for的优先级是什么?

Vue2 中 v - for 优先级高于 v - if

Vue3 中 v - if 优先级高于 v - for

SPA首屏加载速度慢的怎么解决?

资源加载优化

页面渲染优化

网络及服务器优化

一、什么是首屏加载

#关于计算首屏时间

#二、加载慢的原因

为什么data属性是一个函数而不是一个对象?

vue3有了解过吗?能说说跟vue2的区别吗?

一、Vue3介绍

哪些变化

一、性能与体积

二、维护与开发体验

三、新增特性

四、 非兼容变更 

Global API

 模板指令

组件

 渲染函数

其他小改变

五、 移除 API

Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?

Vue3.0的设计目标是什么?做了哪些优化

设计目标

Vue3.0 的优化方案

Vue3.0性能提升主要是通过哪几方面体现的?

Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?

Object.defineProperty 实现响应式原理

Proxy 实现响应式原理

总结两者差异

说说Vue 3.0中Treeshaking特性?举例说明一下?

Vue组件之间的通信方式都有哪些?

Vue中的双向数据绑定

一、双向绑定概念

二、双向绑定原理

说说你对slot的理解?slot使用场景有哪些?

1. 什么是插槽?

2. 插槽的核心作用

插槽综合对比表

你了解vue的diff算法吗?说说看

一、Vue 的 diff 算法概述

二、diff 算法的比较方式

三、diff 算法的原理分析

四、小结

你知道vue中key的原理吗?说说你对它的理解

一、Key 的定义与作用

二、场景背后的逻辑

三、设置 key 与不设置 key 的区别

四、设置 key 值与 diff 效率的关系

五、原理分析

Vue Router

问:什么是 Vue Router(v - router)?

问:Vue Router 在 Vue 项目中的作用是什么?

问:如何定义和配置路由?

问:路由配置中的 mode 有什么作用?有哪些取值?

问:如何在 Vue 组件中实现路由导航?

问:router.push 和 router.replace 有什么区别?

问:路由参数有哪些类型?如何使用?

问:什么是嵌套路由?如何配置和使用?


有使用过vue吗?说说你对vue的理解

对比维度详情
Web 发展历程石器时代:静态网页,无数据库,后 CGI 技术实现网页与数据库交互,如 1998 年的 Google
文明时代:2005 年左右 ASP、JSP 出现,取代 CGI 增强交互安全性,但 JSP 不灵活,同年 Ajax 兴起
工业革命时代:移动设备普及,Jquery、SPA 雏形及相关前端框架出现,SPA 面临 SEO 等问题
百花齐放时代:多种技术涌现
Vue 是什么开源 JavaScript 框架,用于创建用户界面和单页应用,关注 MVC 视图层,2014 年 2 月发布,作者尤雨溪曾参与 AngularJS 工作
Vue 核心特性数据驱动(MVVM):Model 处理业务逻辑与服务器交互;View 展示数据为 UI;ViewModel 连接 Model 和 View
组件化:将逻辑抽象为组件,.vue 文件可视作组件,具有降低耦合度、方便调试和维护等优势
指令系统:v - 前缀特殊属性,表达式值改变响应式影响 DOM,常用指令有 v - if、v - for、v - bind、v - on、v - model
Vue 与传统开发区别以注册账号为例,Jquery 操作 DOM 实现交互,Vue 通过双向绑定操作数据控制 DOM 节点属性,界面变动由数据自动绑定实现
Vue 和 React 对比
相同点:组件化思想、支持服务器端渲染、有 Virtual DOM、数据驱动视图、有 native 方案、有构建工具
区别
数据流向:React 单向,Vue 双向
数据变化原理:React 用不可变数据,Vue 用可变数据
组件化通信:React 用回调函数,Vue 中子组件向父组件传消息可用事件和回调函数
diff 算法:React 用 diff 队列得 patch 树批量更新 DOM,Vue 用双向指针边对比边更新 DOM

你对SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用呢

一、SPA 是什么

SPA (single-page application)即单页应用,是一种网络应用程序或网站模型。通过动态重写当前页面与用户交互,避免页面切换打断体验。所有必要代码(HTML、JavaScript 和 CSS)通过单个页面加载检索,或按需动态装载资源添加到页面,页面不会重新加载或转移控制到其他页面。常见的 JS 框架如 react、vue、angular、ember 都属 SPA。

二、SPA 和 MPA 的区别

多页应用MPA(MultiPage-page application),翻译过来就是多页应用在MPA中,每个页面都是一个主页面,都是独立的当我们在访问另一个页面的时候,都需要重新加载htmlcssjs文件,公共文件则根据需求按需加载如下图

对比项目单页面应用(SPA)多页面应用(MPA)
组成一个主页面和多个页面片段多个主页面
刷新方式局部刷新整页刷新
url 模式哈希模式历史模式
SEO 搜索引擎优化难实现,可使用 SSR 方式改善容易实现
数据传递容易通过 url、cookie、localStorage 等传递
页面切换速度快,用户体验良好切换加载资源,速度慢,用户体验差
维护成本相对容易相对复杂

三、SPA 的优缺点

  1. 优点
    • 具有桌面应用即时性、网站可移植性和可访问性。
    • 用户体验好且快,内容改变无需重新加载整个页面。
    • 良好的前后端分离,分工更明确。
  2. 缺点
    • 不利于搜索引擎抓取。
    • 首次渲染速度相对较慢。

四、实现 SPA

  1. 原理
    • 监听地址栏中 hash 变化驱动界面变化。
    • 用 pushState 记录浏览器历史,驱动界面变化
  2. 实现方式
    • hash 模式:核心是监听 url 中的 hash 进行路由跳转。
// 定义 Router  
class Router {  constructor () {  this.routes = {}; // 存放路由path及callback  this.currentUrl = '';  // 监听路由change调用相对应的路由回调  window.addEventListener('load', this.refresh.bind(this), false);  window.addEventListener('hashchange', this.refresh.bind(this), false);  }  route(path, callback){  this.routes[path] = callback;  }  push(path) {  this.routes[path] && this.routes[path]()  }  refresh() {  this.currentUrl = location.hash.slice(1) || '/';  this.routes[this.currentUrl] && this.routes[this.currentUrl]();  }  
}  // 使用 router  
window.miniRouter = new Router();  
miniRouter.route('/', () => console.log('page1'))  
miniRouter.route('/page2', () => console.log('page2'))  miniRouter.push('/') // page1  
miniRouter.push('/page2') // page2  
  • history 模式:核心借用 HTML5 history api,该 api 提供丰富 router 相关属性。
// 定义 Router  
class Router {  constructor () {  this.routes = {};  this.listerPopState();  }  init(path) {  history.replaceState({path: path}, null, path);  this.routes[path] && this.routes[path]();  }  route(path, callback){  this.routes[path] = callback;  }  push(path) {  history.pushState({path: path}, null, path);  this.routes[path] && this.routes[path]();  }  listerPopState () {  window.addEventListener('popstate' , (e) => {  const path = e.state && e.state.path;  this.routes[path] && this.routes[path]();  });  }  
}  // 使用 Router  
window.miniRouter = new Router();  
miniRouter.route('/', () => console.log('page1'));  
miniRouter.route('/page2', () => console.log('page2'));  // 跳转  
miniRouter.push('/page2');  // page2  

五、给 SPA 做 SEO 的方式(基于 Vue)

  1. SSR 服务端渲染:将组件或页面通过服务器生成 html,再返回给浏览器,如 nuxt.js。
  2. 静态化
    • 通过程序将动态页面抓取并保存为静态页面,实际存在于服务器硬盘。
    • 通过 WEB 服务器的 URL Rewrite 方式,按规则将外部 URL 请求转化为内部文件地址,把外部请求的静态地址转化为实际动态页面地址,静态页面实际不存在。
  3. 使用 Phantomjs 针对爬虫处理:通过 Nginx 配置,判断访问来源是否为爬虫,若是则搜索引擎的爬虫请求转发到一个 node server,再通过 PhantomJS 解析完整的 HTML,返回给爬虫。

v-show和v-if有什么区别?使用场景分别是什么?

对比项目v - showv - if
共同点- 作用效果:都能控制元素在页面是否显示(不含 v - else)
- 用法:相似,如<Model v - show="isShow" /><Model v - if="isShow" />
- 显示隐藏表现:表达式为true时占据页面位置,为false时不占据页面位置
控制手段通过添加css -- display:none隐藏元素,DOM 元素保留根据条件添加或删除整个 DOM 元素
编译过程仅基于 CSS 切换,无局部编译 / 卸载过程切换时有局部编译 / 卸载过程,会销毁和重建内部事件监听及子组件
编译条件无论初始条件如何,元素总会先渲染,通过 CSS 控制显示或隐藏条件为假时不操作,条件为真才渲染
对组件生命周期影响false变为true时,不触发组件生命周期false变为true时,触发beforeCreatecreatebeforeMountmounted钩子;由true变为false时,触发beforeDestorydestoryed方法
性能消耗初始渲染消耗高切换消耗高
原理- 无论初始条件,元素先渲染
- 有transition执行transition,无则直接设置display属性
- 依据表达式值决定是否生成 DOM 节点
- 处理elseelse - if等复杂条件
使用场景适合频繁切换显示隐藏状态的场景,如频繁展开收起的下拉菜单适用于运行时条件很少改变的场景,如仅管理员可见的高级设置按钮

通常情况下,回流的代价比重绘更高,因为回流不仅需要重新绘制元素,还需要重新计算布局。而 display: none 这种操作既改变了布局(触发回流),又改变了外观(触发重绘) 

Vue实例挂载的过程

1. new Vue () 做了什么

  • 调用构造函数:执行 Vue 构造函数,接收用户传递的配置项 options(如 datamethods 等)。若未使用 new 关键字调用,在非生产环境下会抛出警告。构造函数内部调用 _init 方法进行初始化。
  • 查找 _init 方法来源_init 方法在 initMixin(Vue) 中定义在 Vue 原型上。

2. _init 方法执行流程

  • 初始化准备工作
    • 为实例分配唯一标识 _uid
    • 标记 _isVue 为 true,表明是 Vue 实例。
    • 合并选项:判断是否为组件初始化,若为组件,执行 initInternalComponent 优化内部组件实例化;否则,通过 mergeOptions 合并 Vue 属性。
    • 在非生产环境下初始化 proxy 拦截器。
    • 暴露 _self 指向自身。
  • 初始化各部分功能
    • 初始化生命周期标志位:执行 initLifecycle(vm)
    • 初始化组件事件侦听:执行 initEvents(vm)
    • 初始化渲染方法:执行 initRender(vm)
    • 调用 beforeCreate 钩子:此时数据初始化未完成,无法访问 dataprops 等属性。
    • 初始化依赖注入内容:执行 initInjections(vm),在初始化 dataprops 之前。
    • 初始化 propsdatamethodswatchcomputed:执行 initState(vm),其内部执行顺序为 propsmethodsdata 等。data 可定义为函数或对象形式(组件必须为函数形式),在 initData(vm) 中,将 data 挂载到实例 vm 上并进行响应式监听
    • 初始化提供内容:执行 initProvide(vm),在初始化 dataprops 之后。
    • 调用 created 钩子:此时数据已初始化完成,可访问 dataprops,但尚未完成 DOM 挂载,无法访问 DOM 元素。
  • 挂载元素:若配置项中有 el,调用 vm.$mount(vm.$options.el) 进行挂载。

3. $mount 方法执行流程

  • 解析模板为 render 函数
    • 检查是否直接挂载到 body 或 html 上,若如此,在非生产环境下抛出警告并返回。
    • 若没有 options.render,尝试从 options.template 或通过 el 获取模板内容。
    • 对获取到的模板内容,通过 compileToFunctions 方法将其解析为 render 函数和 staticRenderFns,解析步骤大致为:将 HTML 文档片段解析成 AST 描述符,再将 AST 描述符解析成字符串,最后生成 render 函数。生成的 render 函数挂载到 options.render 上。
  • 调用 mountComponent 渲染组件
    • 检查是否有 render 函数,若没有则抛出警告。
    • 调用 beforeMount 钩子。
    • 定义 updateComponent 函数,该函数在 Vue 初始化时执行 render 生成虚拟 DOM(vnode),并执行 _update 方法将虚拟 DOM 转换为真实 DOM 并更新到页面。在非生产环境且开启性能标记时,updateComponent 函数会添加性能测量代码。
    • 创建一个 Watcher 实例监听当前组件状态,当数据变化时,触发 beforeUpdate 钩子,并调用 updateComponent 更新组件。
    • 手动挂载的实例,标记 _isMounted 为 true 并调用 mounted 钩子。

4. render 和 _update 方法

  • _render 方法:该方法定义在 Vue.prototype 上,从 vm.$options 中获取 render 函数,调用 render 函数并传入 vm.$createElement 创建虚拟 DOM(vnode)。若渲染过程出错,会尝试调用 renderError 处理错误。最后对生成的 vnode 进行一些处理,如确保其为单个节点,并设置其父节点。
  • _update 方法:该方法定义在 Vue.prototype 上,接收新的虚拟 DOM vnode。若为首次渲染(无前一个虚拟 DOM prevVnode),调用 vm.__patch__(vm.$el, vnode, hydrating, false) 执行具体的挂载逻辑;否则,调用 vm.__patch__(prevVnode, vnode) 进行更新。__patch__ 方法会将虚拟 DOM 转换为真实 DOM,并更新到页面中。

5. 总结

  • new Vue() 时调用 _init 方法,完成各种初始化工作,包括合并选项、初始化生命周期、事件、渲染、状态等。
  • 调用 $mount 进行挂载,挂载过程主要通过 mountComponent 方法,定义更新函数 updateComponent,执行 render 生成虚拟 DOM,再通过 _update 将虚拟 DOM 转换为真实 DOM 并渲染到页面。
  • 同时,在挂载过程中会触发 beforeCreatecreatedbeforeMountmounted 等生命周期钩子函数,以及在数据变化时触发 beforeUpdate 钩子函数。

1. 开始创建 Vue 实例

当你写下 new Vue() 的时候,就好像在跟 Vue 说 “我要创建一个应用啦” 。Vue 首先会检查你是不是用了 new 关键字来调用它。这就好比你要进一个特定的房间,得用正确的开门方式(用 new 关键字),不然在不是正式发布的环境下(非生产环境),它就会提醒你 “哎呀,你得用正确方式开门呀”(抛出警告) 。

如果开门方式正确,它就会去做初始化的事情,也就是调用 _init 方法 。

2. 初始化准备工作

  • 发 “身份证”:Vue 会给这个实例发一个独一无二的编号,就像每个人都有身份证号一样,这个编号叫 _uid 。
  • 做标记:给这个实例做个标记,标记它是一个正儿八经的 Vue 实例,这个标记就是 _isVue ,把它设成 true 。
  • 合并 “包裹” 内容:它要看看你给它的配置项(就像一个包裹里的各种东西,有 data 、methods 这些) 。如果是组件的初始化,它会用一种优化的方式来处理;如果不是,就把这些配置项合并起来 。
  • 特殊环境处理(非生产环境):要是你是在测试等非正式发布的环境下,它还会做一些额外的设置,比如初始化 proxy 拦截器 。
  • “自我” 暴露:让这个实例能找到自己,就像给自己取个小名方便称呼,把 _self 指向它自己 。

3. 初始化各个功能部分

  • 给生命加标记:给这个实例的生命周期做一些标记,就像给一个旅程的不同阶段做记号一样,这是通过 initLifecycle 来做的 。
  • 设置 “传话筒”:设置怎么去监听和触发事件,就像给房间里安装传话筒,让不同部分能互相 “说话”,这个工作是 initEvents 来做的 。
  • 准备 “画画” 工具:为后面把数据画到页面上做准备,也就是初始化渲染方法,由 initRender 来完成 。
  • 出发前的提醒:在这个时候,会调用 beforeCreate 钩子函数,就像出发前的提醒,但这个时候数据还没准备好,像 data 、props 这些东西你还不能用 。
  • 注入 “宝贝”:把一些依赖的东西先准备好,就像出门前把重要的宝贝先装进行李箱,这是 initInjections 在做的事,而且是在初始化 data 、props 之前做 。
  • 整理 “行李”(初始化数据等):开始整理最重要的行李啦,也就是初始化 props 、data 、methods 、watch 、computed 这些 。这里面的顺序是先处理 props ,再处理 methods ,然后是 data 。data 可以是函数或者对象的形式(但如果是组件,就只能是函数形式哦) ,在处理 data 的时候,会把 data 里的数据挂载到实例上,还会让这些数据能响应变化 。
  • 分享 “宝贝”:把一些要提供出去的东西准备好,就像你到了目的地,把自己的宝贝分享给别人,这是 initProvide 在做的,而且是在初始化 data 、props 之后 。
  • 创建完成的欢呼:调用 created 钩子函数,这个时候数据已经准备好了,你可以用 data 、props 啦,但这个时候页面还没把东西显示出来,因为还没完成 DOM 挂载 。

4. 挂载阶段(如果配置了挂载元素)

如果在最开始给 Vue 的配置项里有指定的挂载元素(vm.$options.el 有东西 ),那就开始挂载啦,也就是调用 vm.$mount(vm.$options.el) 。

5. 解析模板变成 “画画指南”(render 函数)

  • 检查 “房子” 位置:看看你要挂载的地方是不是 body 或者 html ,要是这两个地方,在不是正式发布的环境下,它会跟你说 “别挂在这两个地方呀”(抛出警告) 。
  • 找 “画画指南”:如果没有现成的 render 函数(就像没有画画指南 ),它就会从 options.template 或者通过 el 去找模板内容 。
  • 制作 “画画指南”:找到模板内容后,它会用 compileToFunctions 这个工具,把模板变成 render 函数和 staticRenderFns 。这个过程就像是把一堆建筑材料(模板内容 )变成详细的建筑图纸(render 函数 ),步骤大概是先把模板解析成一种描述结构(AST 描述符 ),再把这个描述结构变成字符串,最后生成 render 函数 。生成的 render 函数就会挂载到 options.render 上 。

6. 真正的渲染组件

  • 检查 “画画指南”:看看有没有 render 函数,如果没有,就会说 “哎呀,没有画画指南可不行呀”(抛出警告 )。
  • 开始前的准备:调用 beforeMount 钩子函数,就像画画前再检查一遍工具 。
  • 定义 “更新画画” 函数:定义一个叫 updateComponent 的函数,这个函数会去执行 render 函数,生成虚拟 DOM(就像是在脑海里先画好一个草图 ),然后再执行 _update 函数,把这个草图变成真正画在纸上的画(把虚拟 DOM 变成真实 DOM 并更新到页面 ) 。
  • 安排 “小助手” 监听:创建一个 Watcher 实例,就像安排一个小助手,专门盯着组件的状态。要是数据变了,小助手就会触发 beforeUpdate 钩子函数,然后调用 updateComponent 来更新组件 。
  • 完成后的庆祝(如果是手动挂载):如果是手动挂载的实例,就标记一下说 “我已经挂载好啦”(_isMounted 设为 true ),然后调用 mounted 钩子函数,就像庆祝终于画完画啦 。

7. 生成虚拟 DOM(_render 方法)

在执行 render 生成虚拟 DOM 的时候,_render 方法会从实例的配置项里找到 render 函数,然后调用这个 render 函数,并且给它一些画画的工具(vm.$createElement ),让它去创建虚拟 DOM 。要是创建过程中出问题了,就会想办法处理错误,最后再对这个虚拟 DOM 做一些调整,给它安排个 “家长”(设置父节点 ) 。

8. 把虚拟 DOM 变成真实 DOM 并更新到页面(_update 方法)

_update 方法会拿到新的虚拟 DOM 。如果是第一次渲染(以前没有虚拟 DOM ),它就会用一种方式(vm.__patch__(vm.$el, vnode, hydrating, false) )把虚拟 DOM 挂载到页面上;如果不是第一次,就用另一种方式(vm.__patch__(prevVnode, vnode) )来更新页面上的 DOM 。

请描述下你对vue生命周期的理解?在created和mounted这两个生命周期中请求数据有什么区别呢?

一、Vue 生命周期通俗理解

想象你要制作一个手工艺品。Vue 的生命周期就像是制作这个手工艺品的一系列流程。从你决定要制作(创建实例)开始,到准备材料(初始化数据),然后开始动手制作(编译模板、挂载 DOM),在制作过程中可能会对某些地方进行修改调整(更新),最后完成制作或者决定放弃这个手工艺品(销毁) 。

在 Vue 里,组件实例从诞生到消失的整个过程就是生命周期。每个阶段都有对应的 “钩子函数”,就像是在制作手工艺品流程中的一个个检查点,到了这个点,就可以做一些特定的事情。而且这些钩子函数里的 this 会自动指向组件实例本身,所以在里面可以很方便地访问组件的数据和方法。但不能用箭头函数来定义钩子函数,因为箭头函数没有自己的 this ,会导致访问不到组件实例相关的东西。

二、Vue 生命周期的各个阶段

  1. 创建前后
    • beforeCreate:这时候组件实例刚刚开始创建,就像你刚有了制作手工艺品的想法,还没开始准备材料呢。在这个阶段,组件的很多东西都还没准备好,比如数据还没初始化,你不能访问 data 里的内容,也不能调用组件里定义的方法。这个阶段一般在开发插件的时候,可能会用来做一些初始化的设置。
    • created:组件实例已经基本创建好了,就像材料都准备齐了。这时候数据观测已经完成,也就是 Vue 已经知道哪些数据是需要关注变化的了,属性和方法也都准备好了,watch 和事件回调也配置好了。你可以调用 methods 里的方法,也能访问和修改 data 里的数据。不过,这个时候页面上的 DOM 节点还没有创建出来哦。这个阶段很适合去获取异步数据,比如从服务器请求一些数据来填充组件展示的内容。
  2. 载入前后
    • beforeMount:在把组件真正放到页面上(挂载)之前的阶段。就像你要把做好的手工艺品摆到展示台上,还没放上去呢。这个时候可以获取到 vm.el(组件对应的 DOM 元素相关),虽然 DOM 已经初始化了,但还没有真正挂载到页面指定的位置上。
    • mounted:组件成功挂载到页面实例上了,就像手工艺品已经摆到展示台上展示了。此时页面的 DOM 已经创建并渲染好了,你可以通过 vm.$el 访问到真正在页面上的 DOM 元素。可以在这里做一些依赖于 DOM 存在才能做的操作,比如获取 DOM 元素的尺寸等。
  3. 更新前后
    • beforeUpdate:当组件里的数据发生变化,要更新页面之前会触发这个钩子。就像你发现手工艺品有些地方可以改进,在动手改之前的那个时刻。不过要注意,只有被渲染在模板上的数据变化了才会触发这个钩子。这个时候页面还没有更新,而且如果在这个钩子函数里再次修改数据,不会再次触发更新流程。
    • updated:数据更新完成,页面也更新好了。就像你把手工艺品改好了。但要小心,如果在这个钩子函数里又修改了数据,会再次触发更新流程,又会调用 beforeUpdate 和 updated 。
  4. 销毁前后
    • beforeDestroy:在组件实例要被销毁之前调用,就像你要把展示的手工艺品收起来之前。这个时候组件的属性和方法还是可以访问的,你可以在这里做一些清理工作,比如取消定时器或者一些订阅。
    • destroyed:组件实例已经完全销毁了,就像手工艺品已经被彻底收起来不存在了。Vue 会解除它和其他实例的连接,解绑所有的指令和事件监听器。不过要注意,它并不会把 DOM 从页面上清除掉,只是组件相关的实例被销毁了。
  5. 特殊场景
    • activated:当使用 keep-alive 组件缓存了某个组件,然后这个被缓存的组件再次被激活显示的时候,就会触发这个钩子。
    • deactivated:同样是在 keep-alive 组件缓存的情况下,当被缓存的组件不再显示(停用时)会触发这个钩子。
    • errorCaptured:当捕获到来自子孙组件的错误时会被调用,就像家长发现孩子(子孙组件)出问题了。

三、在 created 和 mounted 中请求数据的区别

  1. 触发时机
    • created:组件实例一创建完成就会立刻调用,这个时候页面的 DOM 节点还没有生成呢。它就像你刚把材料准备好,还没开始真正动手制作手工艺品,更没把它摆到展示台上。
    • mounted:是在页面的 DOM 节点都已经渲染完毕之后才执行的,就像手工艺品已经做好并且摆到展示台上了。所以 created 的触发时机比 mounted 要早。
  2. 可能产生的页面效果
    • 在 mounted 里发起请求数据,如果请求时间比较长,而此时页面 DOM 结构已经生成了,就有可能出现页面闪动的情况。比如页面一开始没有数据展示,是空白的或者有默认内容,等数据请求回来后,页面内容突然改变,就会让用户感觉到页面闪了一下。
    • 而在 created 里请求数据,因为页面 DOM 还没生成,要是能在页面加载完成前就把数据请求回来并处理好,就可以避免这种页面闪动的问题。所以一般建议如果是对页面内容的改动相关的数据请求,放在 created 生命周期里会比较好。
  3. 相同点:在 created 和 mounted 这两个阶段,都可以访问到组件实例的属性和方法。因为这两个阶段组件实例都已经创建好了,只是 DOM 的状态不一样。
对比维度createdmounted
触发时机组件实例创建完成时立刻调用,此时页面 DOM 节点尚未生成页面 DOM 节点渲染完毕后执行,DOM 已创建并挂载
数据请求对页面的影响若在页面加载前完成请求,可避免页面闪动问题请求时间长时,可能导致页面闪动(因 DOM 结构已生成,数据返回后页面内容改变)
可进行的操作可调用methods中的方法,访问和修改data数据,触发响应式渲染 DOM,可通过computedwatch完成数据计算;适合发起异步数据请求,填充组件展示内容可获取访问数据和 DOM 元素,能进行依赖于 DOM 存在才能做的操作,如获取 DOM 元素尺寸等
钩子函数作用组件初始化完毕,各种数据可使用,常用于异步数据获取初始化结束,可用于在 DOM 创建并渲染好后执行一些操

v-if和v-for的优先级是什么?

对比项详情
作用v-if:条件性渲染内容,表达式为 true 时渲染
v-for:基于数组渲染列表,需用item in items语法,建议设唯一key值优化 diff 算法
优先级(Vue2)v-for 优先级高于 v-if。即同一元素同时使用时,先执行 v-for 循环,再依据 v-if 条件判断是否渲染元素
优先级(Vue3)v-if 优先级高于 v-for。即同一元素同时使用时,先进行 v-if 条件判断,再执行 v-for 循环
同时使用的问题无论 Vue2 还是 Vue3,都不建议在同一元素同时使用。在 Vue2 中同时使用会先循环再条件判断,造成性能浪费;在 Vue3 中同时使用可能使指令优先级不清晰,导致代码难理解维护,还可能因 v-if 条件无法访问 v-for 作用域变量别名引发意外行为(会报错)
优化建议外层嵌套 template:在外层嵌套<template>标签,在这一层进行 v-if 判断,内部进行 v-for 循环
计算属性过滤:通过计算属性提前过滤不需要显示的项,如computed: { visibleItems() { return this.items.filter(item => item.isShow); } }

Vue2 中 v - for 优先级高于 v - if

  1. 示例分析:当在同一元素上同时使用 v - if 和 v - for 时,如 <div id="app"><p v - if="isShow" v - for="item in items">{{ item.title }}</p></div> ,生成的 render 函数中,_l(列表渲染函数)内部先进行循环,再依据 isShow 条件判断是否渲染 <p> 标签。这表明在 Vue2 的模板编译过程中,会先处理 v - for 指令,再处理 v - if 指令 。
  2. 源码分析:在 \vue - dev\src\compiler\codegen\index.js 的 genElement 函数中,判断顺序是 v - for 比 v - if 先进行判断,即先处理 v - for 相关逻辑,再处理 v - if 相关逻辑,进一步证明了 v - for 优先级高于 v - if 。

Vue3 中 v - if 优先级高于 v - for

  1. 示例分析:同样在同一元素上同时使用 v - if 和 v - for,在 Vue3 中生成的渲染逻辑与 Vue2 不同。Vue3 会先判断 v - if 的条件,再进行 v - for 的循环。例如有代码 <div><p v - if="condition" v - for="item in list">{{ item }}</p></div>,Vue3 会先检查 condition 是否为真,只有为真时才会对 list 进行循环渲染。如果 condition 一开始就为假,那么 v - for 不会执行循环,从而避免了不必要的循环操作。
  2. 设计意图:Vue3 这样改变优先级的设计,主要是为了性能优化和逻辑的合理性。在 Vue2 中同一元素上同时使用 v - for 和 v - if 时,每次渲染都要先循环再判断,即使条件为假,循环也会执行,造成性能浪费。而 Vue3 先判断条件,只有条件满足才进行循环,减少了不必要的计算,提升了性能 。

综上所述,在使用 v - if 和 v - for 时,需要根据 Vue 的版本注意它们优先级的差异,合理编写代码以避免性能问题和不符合预期的渲染结果。

SPA首屏加载速度慢的怎么解决?

资源加载优化

  1. 减小入口文件体积
    • 路由懒加载:这是常用手段。在 Vue - Router 配置路由时,采用动态加载路由的形式,如routes: [ { path: 'Blogs', name: 'ShowBlogs', component: () => import('./components/ShowBlogs.vue') } ] 。这样不同路由对应的组件会被分割成不同代码块,只有在路由被请求时才单独打包加载,减小了入口文件大小,加快加载速度。
    • 代码分割:借助 Webpack 等打包工具,将代码按功能或模块进行分割,避免将所有代码都打包进一个大文件。比如把公共代码、第三方库等分离出来单独打包,使首屏加载时只需下载必要的代码。
  2. 静态资源本地缓存
    • HTTP 缓存:在后端设置Cache - Control(如设置缓存策略为max - age=31536000表示缓存有效期为一年 )、Last - Modified(标记资源最后修改时间 )、Etag(资源的唯一标识 )等响应头,让浏览器根据规则判断是否使用缓存资源,减少重复请求。
    • Service Worker 离线缓存:利用 Service Worker 在浏览器端缓存静态资源。它可以拦截网络请求,优先从缓存中读取资源,在网络不佳或离线时也能快速展示页面。比如可以使用 Workbox 等工具简化 Service Worker 的配置和管理。
    • 前端合理利用 localStorage:将一些不常变化的静态数据(如配置信息、用户信息等)存储在localStorage中,下次页面加载时直接读取,减少后端请求。但要注意控制存储数据量,避免过度占用空间。
  3. UI 框架按需加载:在使用 UI 框架(如 Element - UI、Antd 等)时,避免直接引入整个 UI 库,而是按需引用实际用到的组件。例如,从element - ui按需引入组件:import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element - ui'; Vue.use(Button); Vue.use(Input); Vue.use(Pagination); ,减少不必要的代码引入,降低文件体积。
  4. 图片资源的压缩
    • 压缩图片:使用工具(如 TinyPNG、ImageOptim 等)对图片进行无损或有损压缩,在不影响图片质量的前提下减小文件大小。
    • 使用在线字体图标或雪碧图:对于页面上的图标,使用在线字体图标(如 Iconfont),或者将众多小图标合并成雪碧图。这样可以减少 HTTP 请求数量,提升加载速度
  5. 组件重复打包:在 Webpack 的配置文件中,通过调整CommonsChunkPlugin(在 Webpack4 及以上版本中可使用optimization.splitChunks替代 )的配置,例如设置minChunks: 3,表示将被使用 3 次及以上的包抽离出来,放进公共依赖文件,避免重复加载相同组件,减少整体打包体积。
  6. 开启 GZip 压缩
    • 前端配置:安装compression - webpack - plugin ,在vue.config.js(以 Vue 项目为例)中引入并修改 Webpack 配置,对超过一定大小(如设置threshold: 10240,即超过 10KB )的文件进行压缩。示例配置如下:
const CompressionPlugin = require('compression - webpack - plugin');
module.exports = {configureWebpack: (config) => {if (process.env.NODE_ENV === 'production') {config.mode = 'production';return {plugins: [new CompressionPlugin({test: /\.js$|\.html$|\.css/, // 匹配文件名threshold: 10240, // 对超过10k的数据进行压缩deleteOriginalAssets: false // 是否删除原文件})]};}}
};
  • 服务器配置:如果服务器使用 Express 框架,安装compression中间件,然后在其他中间件使用之前调用app.use(compression()) 。这样当发送请求的浏览器支持 GZip 时,就会发送 GZip 格式的文件,减小传输文件大小,加快传输速度。

页面渲染优化

  1. 使用 SSR(Server - Side Rendering,服务端渲染)
    • 原理:组件或页面通过服务器生成 HTML 字符串,再发送到浏览器。相比客户端渲染(CSR),SSR 能让用户更快看到页面内容,因为不需要等待浏览器下载 JavaScript 文件并执行渲染逻辑。
    • 工具:对于 Vue 应用,建议使用 Nuxt.js 实现服务端渲染。Nuxt.js 封装了很多 SSR 相关的复杂逻辑,如路由处理、数据预取等,降低了开发成本。但 SSR 也会带来一些额外的开发及维护成本,比如需要关注服务端开发及运维,处理潜在的内存泄露、变量污染等隔离问题,以及 SSR 失败时回退到 CSR 的容灾方案等。
  2. 优化渲染逻辑
    • 减少重绘和回流:避免频繁修改 DOM 样式和结构。例如,不要在循环中多次修改元素的样式,而是一次性修改 class 来改变样式;在操作 DOM 前,先使用display: none隐藏元素,操作完成后再显示,减少回流对性能的影响。
    • 虚拟 DOM 优化:合理使用 Vue 等框架提供的特性,如利用key属性帮助 Diff 算法更高效地更新 DOM。确保key值唯一且稳定,避免不必要的 DOM 重新创建和销毁。
  3. 预渲染:使用工具(如 prerender - spa - plugin)对页面进行预渲染,在构建阶段生成静态 HTML 文件。当用户首次访问时,直接展示预渲染的 HTML 内容,然后再加载 JavaScript 进行交互,加快首屏展示速度。

网络及服务器优化

  1. 优化网络请求
    • 合并请求:将多个小的 HTTP 请求合并为一个,减少请求开销。例如,将多个 CSS 文件或 JavaScript 文件合并成一个文件加载(但要注意控制文件大小,避免加载时间过长 )。
    • 优化请求顺序:优先加载关键资源,如首屏展示所需的 CSS 和 JavaScript 文件,确保页面能尽快渲染出内容。可以通过设置资源的async(异步加载,不阻塞页面渲染 )、defer(延迟到 HTML 解析完成后加载 )属性,或者使用 HTTP/2 协议(支持多路复用,可并行传输多个资源,提高传输效率 )来优化请求顺序。
  2. 提升服务器性能
    • 选择优质服务器:选择性能好、带宽充足的服务器,确保服务器能够快速响应客户端请求。
    • 服务器端代码优化:优化服务器端的业务逻辑代码,提高数据处理和响应速度。例如,对数据库查询进行优化,合理使用缓存(如 Redis 缓存数据 ),减少数据库压力,加快数据返回速度。

一、什么是首屏加载

首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容

首屏加载可以说是用户体验中最重要的环节

#关于计算首屏时间

利用performance.timing提供的数据:

通过DOMContentLoad或者performance来计算出首屏时间

// 方案一:
document.addEventListener('DOMContentLoaded', (event) => {console.log('first contentful painting');
});
// 方案二:
performance.getEntriesByName("first-contentful-paint")[0].startTime// performance.getEntriesByName("first-contentful-paint")[0]
// 会返回一个 PerformancePaintTiming的实例,结构如下:
{name: "first-contentful-paint",entryType: "paint",startTime: 507.80000002123415,duration: 0,
};

#二、加载慢的原因

在页面渲染的过程,导致加载速度慢的因素可能如下:

  • 网络延时问题
  • 资源文件体积是否过大
  • 资源是否重复发送请求去加载了
  • 加载脚本的时候,渲染内容堵塞了
优化方向具体方法说明
减小入口文件体积路由懒加载将路由组件分割,按需加载,缩小入口文件
静态资源本地缓存HTTP 缓存
Service Worker 离线缓存
前端利用 localStorage
设置相关响应头缓存资源
拦截请求,优先用缓存资源
存储不常变数据,减少后端请求
UI 框架按需加载只引入实际使用组件避免引入整个 UI 库,减少代码体积
图片资源压缩图片压缩工具
使用字体图标或雪碧图
减小图片文件大小
减少 HTTP 请求数量
解决组件重复打包调整 Webpack 配置抽离多次使用的包,避免重复加载
开启 GZip 压缩前端配置 compression - webpack - plugin
服务器配置 compression 中间件
前后端配合,压缩传输文件,加快速度
页面渲染优化使用 SSR(如 Nuxt.js)
优化渲染逻辑
预渲染
服务器生成 HTML,加快首屏展示
减少重绘回流,合理用虚拟 DOM
构建时生成静态 HTML,加快首次访问
网络及服务器优化优化网络请求
提升服务器性能
合并、优化请求顺序
选优质服务器,优化服务器端代码

为什么data属性是一个函数而不是一个对象?

  1. Vue 实例与组件定义 data 的差异
    • Vue 实例:在定义 Vue 实例时,data属性既可以是对象,也可以是函数。例如:
const app = new Vue({el: "#app",// 对象格式data: {foo: "foo"},// 函数格式data() {return {foo: "foo"}}
})
  • 组件:在组件定义data属性时,只能是函数。若直接定义为对象,如:
Vue.component('component1', {template: `<div>组件</div>`,data: {foo: "foo"}
})

会收到警告,提示返回的data应该是一个函数,用于每个组件实例。

2. 组件 data 定义为函数与对象的区别

  • 对象形式的问题:当以对象形式(vue实例)定义组件的data时,多个组件实例会共用同一个data对象。例如:
function Component() { }
Component.prototype.data = {count: 0
};
const componentA = new Component();
const componentB = new Component();
console.log(componentB.data.count); // 0
componentA.data.count = 1;
console.log(componentB.data.count); // 1

这是因为它们共用了相同的内存地址,导致componentA修改数据影响到componentB

  • 函数形式的优势:以函数形式定义data,每个实例都会得到一个新的data对象。例如:
function Component() {this.data = this.data();
}
Component.prototype.data = function () {return {count: 0};
};
const componentA = new Component();
const componentB = new Component();
console.log(componentB.data.count); // 0
componentA.data.count = 1;
console.log(componentB.data.count); // 0

在 Vue 中,组件可能有多个实例,使用函数返回全新data对象,可避免实例间数据污染

3. 原理分析

  • initData 对 data 的处理:在 Vue 源码/vue - dev/src/core/instance/state.js中的initData函数里,data既可以是对象也可以是函数。
function initData (vm: Component) {let data = vm.$options.data;data = vm._data = typeof data === 'function'? getData(data, vm): data || {};//...
}
  • 选项合并与数据校验

组件创建时会进行选项合并,在/vue - dev/src/core/util/options.js中,自定义组件会进入mergeOptions/vue - dev/src/core/instance/init.js中对data进行校验,当vm实例为undefined时,如果data不是函数类型,在非生产环境下会发出警告。

strats.data = function (parentVal: any,childVal: any,vm?: Component
):?Function {if (!vm) {if (childVal && typeof childVal!== "function") {process.env.NODE_ENV!== "production" &&warn('The "data" option should be a function'+"that returns a per - instance value in component " +"definitions.",vm);return parentVal;}return mergeDataOrFn(parentVal, childVal);}return mergeDataOrFn(parentVal, childVal, vm);
};

4.总结

  • 根实例:根实例对象的data可以是对象也可以是函数,因为根实例是单例,不存在多个实例共用data导致数据污染的问题。
  • 组件实例:组件实例对象的data必须为函数。这样在initData时,每个组件实例将函数作为工厂函数,都会返回全新的data对象,从而防止多个组件实例之间共用一个data而产生数据污染。

vue3有了解过吗?能说说跟vue2的区别吗?

一、Vue3介绍

关于vue3的重构背景,尤大是这样说的:

「Vue 新版本的理念成型于 2018 年末,当时 Vue 2 的代码库已经有两岁半了。比起通用软件的生命周期来这好像也没那么久,但在这段时期,前端世界已经今昔非比了

在我们更新(和重写)Vue 的主要版本时,主要考虑两点因素:首先是新的 JavaScript 语言特性在主流浏览器中的受支持水平;其次是当前代码库中随时间推移而逐渐暴露出来的一些设计和架构问题」

简要就是:

  • 利用新的语言特性(es6)
  • 解决架构问题

哪些变化

从上图中,我们可以概览Vue3的新特性,如下:

  • 速度更快
  • 体积减少
  • 更易维护
  • 更接近原生
  • 更易使用

一、性能与体积

  1. 速度更快
    • 虚拟 DOM 重写Vue 3 对虚拟 DOM 进行了重写,优化了其实现方式,使得虚拟 DOM 的比对和更新更加高效,从而提升整体渲染性能。
    • 编译模板优化:通过对编译模板过程的改进,生成更高效的渲染代码,减少不必要的计算。
    • 组件初始化优化更高效的组件初始化过程,减少初始化时间这些改进使得 Vue 3 的 update 性能提高 1.3 - 2 倍,SSR(服务器端渲染)速度提高 2 - 3 倍。
  2. 体积更小:借助 webpack 的 tree - shaking 功能,Vue 3 能够将未使用的模块去除,仅打包实际需要的部分。这对开发者而言,可以在不担忧整体体积大幅增加的情况下,为 Vue 添加更多功能;对于使用者,最终打包出来的文件体积变小,加载速度更快。

二、维护与开发体验

  1. 更易维护
    • Composition API
      • 与 Options API 兼容:可与现有的 Options API 一起使用,开发者可以根据实际情况灵活选择使用方式。
      • 逻辑组合复用:方便将相关逻辑进行组合与复用,例如将数据逻辑、生命周期钩子逻辑等按照功能进行集中管理,提高代码的可维护性和复用性。不同功能的逻辑可以独立开发和复用,使得代码结构更清晰。
      • 框架搭配灵活:Vue 3 模块可以和其他框架搭配使用,拓展了 Vue 的应用场景。
    • 更好的 TypeScript 支持Vue 3 基于 TypeScript 编写,开发者能享受到自动的类型定义提示。这在开发大型项目时,有助于提前发现类型错误,提高代码的稳定性和可维护性,减少潜在的运行时错误。
    • 编译器重写:重写编译器进一步优化了代码生成和编译过程,使得 Vue 的编译效率更高,生成的代码质量更好,从底层提升了框架的性能和可维护性。
  2. 更接近原生:Vue 3 可以自定义渲染 API,开发者能够将 Vue 的开发模型扩展到其他平台,如将其渲染到 canvas 画布上,为 Vue 的应用拓展了更多可能性,使其能够更好地与原生平台特性相结合。
  3. 更易使用
    • 响应式 Api 暴露响应式 API 更加直观地暴露出来,开发者可以更方便地使用响应式系统,对数据的响应式处理更加灵活和直接。
    • 渲染原因识别:轻松识别组件重新渲染原因,方便开发者调试和优化代码,快速定位性能问题。

三、新增特性

特性名称描述示例
Fragments组件支持多个根节点<!-- Layout.vue -->
<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

Teleport

任意门)

一种能够将我们的模板移动到 DOMVue app 之外的其他位置的技术

<button @click="showToast" class="btn">打开 toast</button>
<!-- to 属性就是目标位置 -->
<teleport to="#teleport-target">
    <div v-if="visible" class="toast-wrap">
        <div class="toast-msg">我是一个 Toast 文案</div>
    </div>
</teleport>
createRenderer构建自定义渲染器,拓展 Vue 到其他平台

import { createRenderer } from '@vue/runtime-core'

const { render, createApp } = createRenderer({
  patchProp,
  insert,
  remove,
  createElement,
  // ...
})

export { render, createApp }

export * from '@vue/runtime-core'

Composition API

组合式api,通过这种形式,我们能够更加容易维护我们的代码,将相同功能的变量进行一个集中式的管理

四、 非兼容变更 

Global API
对比项Vue 2Vue 3
全局 API 使用方式旧的全局 API 使用方式使用应用程序实例

全局和内部 API 已经被重构为tree-shakable

 模板指令
对比项Vue 2Vue 3
v - model 用法旧的组件 v - model 用法组件上 v - model 用法更改
key 用法<template v - for> 和非 v - for 节点上 key 旧用法<template v - for> 和非 v - for 节点上 key 用法更改
v - if 和 v - for 优先级v - for 优先级高于 v - ifv - if 优先级高于 v - for
v - bind="object"排序不敏感

排序敏感
v - for 中的 ref注册 ref 数组不再注册 ref 数组
组件
对比项Vue 2Vue 3
功能组件创建多种方式创建功能组件只能使用普通函数创建功能组件,functional 属性在 SFC 中有不同用法
异步组件创建旧的异步组件创建方式需使用 defineAsyncComponent 方法创建异步组件
 渲染函数
对比项Vue 2Vue 3
API旧的渲染函数 API渲染函数 API 改变
插槽访问通过 $scopedSlots 访问特定插槽删除,所有插槽通过slots 作为函数暴露
自定义指令 API旧的自定义指令 API自定义指令 API 更改,与组件生命周期一致

一些转换 class 被重命名

v - enter、v - leave 等v - enter -> v - enter - from
v - leave -> v - leave - from
watch 用法支持点分隔字符串路径监听不再支持点分隔字符串路径,改用计算函数作为参数
其他小改变
对比项Vue 2Vue 3
生命周期选项destroyed、beforeDestroyunmounted(原 destroyed)
beforeUnmount(原 beforeDestroy)
data 声明可声明为对象或函数(组件中推荐函数)应始终声明为函数
mixin 的 data 选项合并旧的合并方式简单合并
attribute 强制策略旧策略策略更改
template 渲染无特殊指令时渲染内部内容无特殊指令时视为普通元素,生成原生<template>元素
根容器渲染应用根容器 outerHTML 替换为根组件模板应用容器 innerHTML 用于渲染,容器本身不再视为模板一部分

五、 移除 API

移除的 API描述
keyCode 支持不再支持将 keyCode 作为 v - on 的修饰符
,off,$once 实例方法移除这三个用于事件处理的实例方法
过滤 filter移除过滤器功能
内联模板 attribute不再支持内联模板 attribute
$destroy 实例方法

用户不应再手动管理单个Vue 组件的生命周期。

Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?

对比维度Options APIComposition API
定义方式在.vue 文件中,通过定义datacomputedmethodswatch等属性与方法来组织页面逻辑基于逻辑功能组织组件,将一个功能涉及的所有 API 放在一起,以函数形式进行封装和复用
逻辑组织
  • 组件根据选项类别组织代码,状态放data,方法放methods等。
  • 组件变大时,各选项列表增长,导致代码碎片化,理解和维护困难。处理单个逻辑关注点需在不同选项块间跳转
  • 将某个逻辑关注点相关代码集中在一个函数内,提高代码的内聚性
  • 便于定位和修改特定功能代码,即使组件复杂也能快速找到相关逻辑
逻辑复用
  • 通过mixin复用逻辑,将共用逻辑抽离到mixin文件,在组件中引入。
  • 存在命名冲突问题,多个mixin可能定义相同名称属性或方法。
  • 数据来源不清晰,多个mixin混合使用时,难以追踪数据和方法来源
  • 将复用逻辑封装为函数(如useXXX形式),在需要的组件中导入使用。
  • 数据来源清晰,每个函数明确提供特定功能数据和方法。
  • 不易出现命名冲突,不同功能函数可独立定义,互不干扰
类型推断对 TypeScript 支持有限,随着组件复杂度增加,类型声明和推断变得困难由于多以函数形式存在,有更好的类型推断,在使用 TypeScript 时更友好
Tree - shaking 友好度不太友好,整个组件的选项内容都会被打包,即使部分逻辑未使用Tree - shaking 友好,可按需引入所需功能函数,未使用的函数不会被打包,利于代码压缩
this使用频繁使用this来访问组件实例的属性和方法,容易出现this指向不明问题,尤其在箭头函数或复杂作用域嵌套中几乎不见this的使用,避免了this指向带来的潜在问题,代码逻辑更清晰
适用场景小型组件中使用简单直观,代码结构清晰,开发成本低适用于大型复杂组件,能有效组织复杂逻辑,提高代码复用性和可维护性

Vue3.0的设计目标是什么?做了哪些优化

设计目标

    1. 解决实际业务痛点
      • 复杂组件维护困难:随着功能增加,Vue2 中复杂组件代码维护难度增大,Vue3 旨在改善这一状况。
      • 逻辑提取与复用机制缺失:缺少简洁有效的在多个组件间提取和复用逻辑的机制,Vue3 期望提供更好的解决方案。
      • 类型推断不友好:Vue2 对类型推断支持不足,Vue3 致力于改善以满足现代前端开发需求。
      • bundle 时间过长:优化打包过程,缩短 bundle 时间,提升开发效率。
    2. 具体目标
      • 更小:精简体积,移除不常用 API,利用 tree - shaking 技术,仅打包实际需要的模块,减小整体体积。
      • 更快:着重在编译层面进行优化,如优化 diff 算法、实现静态提升、缓存事件监听以及优化 SSR 等,提升性能。
      • 更友好:兼顾 Vue2 的 Options API,推出 Composition API,增强代码的逻辑组织和复用能力;基于 TypeScript 编写,提供自动类型定义提示。

    Vue3.0 的优化方案

    1. 源码层面
      • 源码管理:采用 monorepo 方式维护,将不同功能模块拆分到packages目录下的不同子目录。优点是模块拆分细化,职责明确,依赖关系清晰,提高代码可维护性,部分模块(如reactivity响应式库)可独立于 Vue 使用。
      • TypeScript:基于 TypeScript 编写,提供更好的类型检查,支持复杂类型推导,增强代码的健壮性和可维护性。
    2. 性能层面
      • 体积优化:移除不常用 API 并结合 tree - shaking,减小打包体积。
      • 编译优化:包括 diff 算法优化(添加静态标记提升对比效率)、静态提升(不参与更新的元素只创建一次并复用)、事件监听缓存(缓存事件处理函数减少重复操作)、SSR 优化(静态内容量大时优化生成静态节点方式)。
      • 数据劫持优化:Vue2 使用Object.defineProperty存在缺陷,如无法检测对象属性添加和删除,嵌套层级深时性能问题突出。Vue3 采用Proxy监听整个对象,能检测属性的添加、删除,在getter中递归实现响应式,仅对真正访问到的内部对象进行响应式处理,提升性能并减轻用户心智负担。
    3. 语法 API 层面
      • 优化逻辑组织:Composition API 使相同功能代码集中编写,相较于 Options API,逻辑结构更清晰,便于理解和维护。
      • 优化逻辑复用:Vue2 通过 mixin 实现功能混合存在命名冲突和数据来源不清晰问题。Vue3 的 Composition API 可将复用代码抽离为函数,使用时直接调用,数据来源清晰,有效避免命名冲突。

    Vue3.0性能提升主要是通过哪几方面体现的?

    优化维度具体优化点优化详情性能提升表现
    编译阶段
     
    diff 算法优化

    vue3diff算法中相比vue2增加了静态标记。关于这个静态标记,其作用是为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较

    已经标记静态节点的p标签在diff过程中则不会比较,把性能进一步提高

    减少不必要比较操作,提高 diff 效率
    静态提升不参与更新的元素只创建一次,在渲染时复用,且标记为不参与 Diff避免重复创建节点,优化运行时内存占用,大型应用受益明显
    事件监听缓存默认事件绑定视为动态,开启缓存后事件处理函数缓存,diff 时直接使用减少事件处理的重复操作
    SSR 优化静态内容量大时,用 createStaticVNode 在客户端生成静态 node,直接 innerHTML 插入减少对象创建和渲染开销,加快 SSR 页面生成和传输速度
    源码体积Tree - shaking未使用模块不打包,仅打包实际用到的模块打包体积变小,加载速度加快,提升用户体验
    响应式系统
     
    实现方式改变Vue 2 用 Object.defineProperty 需深度遍历,Vue 3 用 Proxy 直接监听整个对象
    • 可以监听动态属性的添加
    • 可以监听到数组的索引和数组length属性
    • 可以监听删除属性

    vue2中采用 defineProperty来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加gettersetter,实现响应式

    vue3采用proxy重写了响应式系统,因为proxy可以对整个对象进行监听,所以不需要深度遍历

    优势体现更全面及时监听数据变化,优化响应式机制

    Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?

    Object.defineProperty 实现响应式原理

    • 基本定义与用途Object.defineProperty() 是 JavaScript 中用于在对象上定义新属性,或者修改现有属性的方法,调用后会返回该对象。它对于实现数据的响应式非常关键。
    • get 和 set 方法
    • get 方法:它是属性的访问器函数。当代码尝试访问对象被 Object.defineProperty 定义的属性时,get 函数就会被调用。它不接收显式传入的参数,但内部的 this 会指向访问该属性时所在的对象(不过由于继承关系,this 不一定是定义该属性的对象)。get 函数的返回值就是该属性被访问时返回的值。例如在以下代码中:
    function defineReactive(obj, key, val) {Object.defineProperty(obj, key, {get() {console.log(`get ${key}:${val}`);return val},//...})
    }
    

    当访问 obj 的 key 属性时,get 函数执行,打印日志并返回 val

    • set 方法:是属性的设置器函数。当属性值被修改时,set 函数会被调用,它接收一个参数,即被赋予的新值,同时内部的 this 指向赋值时的对象。在示例代码中:
    function defineReactive(obj, key, val) {Object.defineProperty(obj, key, {//...set(newVal) {if (newVal!== val) {val = newValupdate()}}})
    }
    

    当 obj 的 key 属性值被改变时,set 函数执行,先判断新值与旧值是否不同,若不同则更新 val 并调用 update 函数,这里的 update 函数可用于触发视图更新等操作,从而实现数据的响应式。

    • 对象多属性与嵌套对象处理
      • 多属性遍历:当对象存在多个属性时,需要遍历对象的所有属性,并对每个属性都使用 Object.defineProperty 来定义,以实现所有属性的响应式。如:
    function observe(obj) {if (typeof obj!== 'object' || obj == null) {return}Object.keys(obj).forEach(key => {defineReactive(obj, key, obj[key])})
    }
    

    这段代码通过 Object.keys 获取对象的所有键,然后对每个键值对调用 defineReactive 函数

    • 嵌套对象递归处理:对于嵌套对象,不仅要对最外层对象的属性进行响应式定义,还要递归处理内部嵌套的对象。在 defineReactive 函数中,会对传入的 val 进行判断,如果 val 是对象,则再次调用 observe 函数,对其进行递归处理,以确保嵌套对象的属性也能实现响应式。
    function defineReactive(obj, key, val) {observe(val)Object.defineProperty(obj, key, {//...})
    }
    
    • 存在的问题
      • 属性添加与删除检测问题Object.defineProperty 无法检测对象属性的添加和删除操作。例如:
    const obj = {foo: "foo",bar: "bar"
    }
    observe(obj)
    delete obj.foo // 无法被劫持
    obj.jar = 'xxx' // 无法被劫持
    

    即使对 obj 进行了响应式处理,删除 foo 属性或添加 jar 属性时,无法触发相应的更新操作。

    • 数组监听问题:直接使用 Object.defineProperty 监听数组时,数组的大部分 API 方法(如 pushpopshiftunshift 等)无法被监听到。如:
    const arrData = [1, 2, 3, 4, 5];
    arrData.forEach((val, index) => {defineProperty(arrData, index, val)
    })
    arrData.push() // 无法被劫持
    arrData.pop()  // 无法被劫持
    arrData[0] = 99 // 可以劫持,因为直接修改索引值触发了 `set`
    

    虽然可以通过遍历数组的每个元素,使用 Object.defineProperty 对每个元素进行定义来劫持单个元素的变化,但数组的方法调用不会触发更新。

    • 性能问题:对于深层嵌套对象,需要进行深层的递归监听,随着嵌套深度的增加,性能开销会急剧增大,因为每次都要递归遍历对象的所有属性来设置响应式。

    Proxy 实现响应式原理

    • 整体监听优势Proxy 可以直接对整个对象进行监听,而不是像 Object.defineProperty 那样逐个属性处理。它创建一个代理对象,该代理对象对目标对象的所有操作进行拦截。例如:
    function reactive(obj) {if (typeof obj!== 'object' && obj!= null) {return obj}const observed = new Proxy(obj, {get(target, key, receiver) {const res = Reflect.get(target, key, receiver)return res},set(target, key, value, receiver) {const res = Reflect.set(target, key, value, receiver)return res},deleteProperty(target, key) {const res = Reflect.deleteProperty(target, key)return res}})return observed
    }
    

    这里通过 Proxy 创建了一个对 obj 的代理对象 observed,对 obj 的 getset 和 deleteProperty 操作都进行了拦截,并使用 Reflect 对象来执行实际的操作。

    • 简单数据操作劫持:对于简单数据操作,Proxy 能够很好地劫持各种操作。如:
    const state = reactive({foo: 'foo'
    })
    // 1.获取
    state.foo // 可以劫持获取操作
    // 2.设置已存在属性
    state.foo = 'fooooooo' // 可以劫持设置操作
    // 3.设置不存在属性
    state.dong = 'dong' // 可以劫持添加属性操作
    // 4.删除属性
    delete state.dong // 可以劫持删除属性操作
    
    • 嵌套对象处理:最初,Proxy 对嵌套对象内部属性的设置无法直接劫持,如:
    const state = reactive({bar: { a: 1 }
    })
    state.bar.a = 10 // 无法直接劫持
    

    为解决这个问题,需要在 get 方法中对返回值进行判断,如果是对象则再次调用 reactive 进行代理,即:

    function reactive(obj) {if (typeof obj!== 'object' && obj!= null) {return obj}const observed = new Proxy(obj, {get(target, key, receiver) {const res = Reflect.get(target, key, receiver)return isObject(res)? reactive(res) : res},})return observed
    }
    

    这样就可以对嵌套对象内部属性的变化进行劫持。

    • 数组监听Proxy 可以直接监听数组的变化,包括 pushshiftsplice 等操作。例如:
    const obj = [1, 2, 3]
    const proxyObj = reactive(obj)
    obj.push(4) // 可以被劫持
    
    • 丰富的拦截方法Proxy 拥有多达 13 种拦截方法,比如 applyownKeysdeletePropertyhas 等。这些丰富的拦截方法使开发者能够更全面地控制对象的各种操作,而 Object.defineProperty 仅通过 get 和 set 来控制属性访问和赋值。

    总结两者差异

    • 劫持方式Object.defineProperty 需要遍历对象的每个属性来进行劫持,对于嵌套对象还需深层递归,操作较为繁琐。而 Proxy 直接对整个对象进行劫持,并返回一个新的代理对象,通过操作代理对象实现响应式,更为直接和高效。
    • 功能完整性Object.defineProperty 存在检测不到对象属性添加和删除、数组 API 方法监听困难等问题,导致在 Vue2 中需要额外实现 setdelete API 以及重写数组方法来弥补这些缺陷。而 Proxy 能够直接监听对象属性的添加、删除以及数组的各种操作,功能更加完整。
    • 兼容性Object.defineProperty 能支持到 IE9,而 Proxy 不兼容 IE,并且没有 polyfill。尽管 Proxy 存在兼容性问题,但由于其在实现响应式方面的显著优势,在 Vue3.0 中被用于替代 Object.defineProperty 来实现响应式系统。                        

    说说Vue 3.0中Treeshaking特性?举例说明一下?

    对比项详情
    特性定义Tree - shaking 是一种通过清除多余代码优化项目打包体积的技术,即 Dead code elimination,在保持代码运行结果不变的前提下,去除无用代码
    Vue 2 与 Vue 3 对比

    Vue 2 的 Vue实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用,无论用何功能都会出现在生产代码中,如import Vue from 'vue'; Vue.nextTick(() => {});

    Vue 3 引入tree shaking特性,分块全局 API,不使用的功能不包含在基础包,如import { nextTick, observable } from 'vue'; nextTick(() => {});

    实现原理基于 ES6 模块语法(importexports),利用 ES6 模块静态编译思想,编译阶段确定模块依赖关系及输入输出变量,判断哪些模块已加载以及哪些模块和变量未被使用或引用,删除对应代码
    示例 - Vue 2 项目简单使用data属性:<script> export default { data: () => ({ count: 1, }), }; </script>,打包记录体积
    增加computedwatch属性:export default { data: () => ({ question:"", count: 1, }), computed: { double: function () { return this.count * 2; }, }, watch: { question: function (newQuestion, oldQuestion) { this.answer = 'xxxx' } };,再次打包体积无变化,表明未使用代码仍被打包
    示例 - Vue 3 项目简单使用reactiveimport { reactive, defineComponent } from "vue"; export default defineComponent({ setup() { const state = reactive({ count: 1, }); return { state, }; } });,打包记录体积
    引入computedwatchimport { reactive, defineComponent, computed, watch } from "vue"; export default defineComponent({ setup() { const state = reactive({ count: 1, }); const double = computed(() => { return state.count * 2; }); watch(() => state.count, (count, preCount) => { console.log(count); console.log(preCount); }); return { state, double, }; } });再次打包体积变大,体现 Tree - shaking 特性,按需打包
    特性作用

    减少程序体积(更小),

    减少程序执行时间(更快),

    便于将来对程序架构进行优化(更友好)

    Vue组件之间的通信方式都有哪些?

    通信方式适用场景实现方式
    props 传递父组件传递数据给子组件子组件通过props属性定义接收参数,如props: { name: String, age: { type: Number, default: 18, require: true } };父组件在子组件标签中以字面量传值,如<Children name="jack" age=18 />
    $emit 触发自定义事件子组件传递数据给父组件子组件通过this.$emit('事件名', 数据)触发自定义事件传递数据,父组件通过<子组件标签 @事件名="方法($event)" />绑定监听器获取数据,如<Children @add="cartAdd($event)" />
    ref父组件获取子组件数据父组件在使用子组件时设置ref,如<Children ref="foo" />,通过this.$refs.foo获取子组件实例及数据
    EventBus兄弟组件传值创建中央事件总线类(如class Bus {... })并挂载到 Vue 实例原型(Vue.prototype.$bus = new Bus()Vue.prototype.$bus = new Vue());
    • 兄弟组件通过$emit触发自定义事件,$emit第二个参数为传递的数值
    • 另一个兄弟组件通过$on监听自定义事件
    parent或root兄弟组件通信通过共同祖辈$parent或者$root搭建通信桥梁,如一个兄弟组件this.$parent.on('事件名', 回调函数),另一个this.$parent.emit('事件名')
    attrs与listeners祖先传递数据给子孙父组件设置特性,如<HelloWorld foo="foo"/>;子组件通过v-bind="$attrs"传入内部组件,孙组件可使用{{$attrs.属性名}},并可通过@click="$emit('事件名', '数据')"触发事件
    Provide 与 Inject祖先传递数据给后代祖先组件通过provide()返回传递的值,如provide() { return { foo: 'foo' } };后代组件通过inject:['foo']接收
    Vuex复杂关系的组件数据传递

    利用 Vuex 的state存放共享变量,getter获取共享变量值,mutations存放修改state的方法,actions基于mutations进行异步操作修改state

    Vue中的双向数据绑定

    双向数据绑定是一种在前端开发中使数据层(Model)和视图层(View)能够自动相互更新的技术。

    一、双向绑定概念

    • 从单向绑定引入:单向绑定是将 Model 绑定到 View,当通过 JavaScript 更新 Model 时,View 会自动更新。
    • 双向绑定定义:在单向绑定基础上,当用户更新 View 时,Model 的数据也能自动更新,实现 Model 与 View 的双向关联。例如用户填写表单,View 状态更新的同时,Model 状态也随之更新。

    二、双向绑定原理

    • MVVM 架构:Vue 作为数据双向绑定框架,其双向绑定基于 MVVM(Model - View - ViewModel)架构,由数据层(Model)、视图层(View)和业务逻辑层(ViewModel)构成。ViewModel 是框架封装的核心,负责关联数据与视图。

      ViewModel 的主要职责就是:数据变化后更新视图,视图变化后更新数据

    • ViewModel 的组成
      • 监听器(Observer):对所有数据的属性进行监听。
      • 解析器(Compiler):对每个元素节点的指令进行扫描和解析,依据指令模板替换数据,并绑定相应的更新函数。

    说说你对slot的理解?slot使用场景有哪些?

    1. 什么是插槽?

    插槽(Slot)是Vue提供的一种内容分发机制,允许父组件向子组件传递模板片段,子组件可以在自己的模板中决定这些内容显示的位置。

    2. 插槽的核心作用

    • 🧩 组件复用:增强组件灵活性
    • 🎨 UI定制:允许父组件控制部分子组件UI
    • 🔄 数据流控制:作用域插槽实现子向父数据传递

    插槽综合对比表

    特性匿名插槽具名插槽作用域插槽
    语法<slot><slot name=""><slot name= "url" :user="a">
    数量1个多个多个
    数据流向父→子父→子子→父
    典型应用场景基础内容插入多区域内容分发数据驱动UI定制
    简写语法<slot />#name#url="data"data用来接收数据 v-slot:url简写 #url

    你了解vue的diff算法吗?说说看

    一、Vue 的 diff 算法概述

    1. 定义:diff 算法是一种通过同层的树节点进行比较的高效算法 ,旨在最小化 DOM 操作,提升渲染性能。
    2. 特点
      • 层级比较:比较只会在同层级进行,不会跨层级比较,这样可以有效减少比较次数,提升效率。
      • 双端比较:在 diff 比较过程中,循环从两边向中间比较,采用双指针策略,即设置新旧 VNode 的头尾指针,从两端向中间靠拢进行比较。
    3. 应用场景:在 Vue 中,该算法作用于虚拟 dom 渲染成真实 dom 的过程,通过比较新旧 VNode 节点,找出差异并更新真实 DOM,从而实现高效的视图更新。

    二、diff 算法的比较方式

    1. 整体策略:采用深度优先,同层比较策略。
    2. 具体比较过程示例
      • 假设有新旧 VNode 节点,首次循环时,对比旧节点的开头与新节点的开头,若相同则复用该节点,并将新旧节点的开头索引后移;若不同则对比旧节点的末尾与新节点的开头。
      • 以给定例子来看,第一次循环发现旧节点 D 与新节点 D 相同,复用旧节点 D 作为 diff 后的第一个真实节点,旧节点的末尾索引(endIndex)移动到 C,新节点的开头索引(startIndex)移动到 C。
      • 第二次循环,旧节点末尾 C 与新节点开头 C 相同,创建 C 的真实节点插入到 D 节点后面,旧节点 endIndex 移动到 B,新节点 startIndex 移动到 E。
      • 第三次循环,E 未找到匹配,直接创建新的真实节点 E 插入到 C 节点之后,新节点 startIndex 移动到 A,旧节点索引不变。
      • 第四次循环,新旧节点开头 A 相同,创建 A 的真实节点插入到 E 节点后面,旧节点 startIndex 移动到 B,新节点 startIndex 移动到 B。
      • 第五次循环,与第四次类似,创建 B 真实节点插入到 A 节点后面,旧节点 startIndex 移动到 C,新节点 startIndex 移动到 F。
      • 当新节点的 startIndex 大于 endIndex 时,创建新节点 startIndex 和 endIndex 之间的所有节点(此处为 F),并插入到 B 节点后面。

    三、diff 算法的原理分析

    1. 数据变化触发更新流程:当数据发生改变时,set方法会调用Dep.notify通知所有订阅者Watcher,订阅者调用patch函数给真实的 DOM 打补丁,更新相应的视图。patch函数位于src/core/vdom/patch.js 。
    2. patch 函数逻辑
      • 无新节点:如果没有新节点,直接执行旧节点的destory钩子函数。
      • 无旧节点:说明是页面初始化,无需比较,直接根据新节点创建 DOM 元素。
      • 新旧节点相同:通过sameVnode判断节点是否相同,相同则调用patchVnode处理这两个节点。
      • 新旧节点不同直接创建新节点,删除旧节点。
    3. patchVnode 函数逻辑
      • 节点相同处理:如果新旧节点完全一致,不进行操作。
      • 静态节点处理:若新旧节点都是静态节点且key相同,同时满足vnode是克隆节点或由v - once指令控制,只需将旧节点的相关属性复制到新节点,无需其他操作。
      • 更新钩子处理:若新节点存在data且包含prepatch钩子,执行该钩子。
      • 子节点处理
        • 文本节点处理:若新节点是文本节点,且与旧节点文本内容不同,直接更新 DOM 的文本内容为新节点的文本内容。
        • 都有子节点:若新旧节点都有子节点且不完全一致,调用updateChildren函数比较更新子节点。
        • 只有新节点有子节点:旧节点无,此时将新节点的子节点全部新建并添加进父节点。
        • 只有旧节点有子节点:新节点无,此时删除所有旧节点。
    4. updateChildren 函数逻辑
      • 初始化指针:设置旧节点的开始索引(oldStartIdx)、结束索引(oldEndIdx),新节点的开始索引(newStartIdx)、结束索引(newEndIdx),并获取对应的开始和结束 VNode 节点。
      • 循环比较:在oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx条件下循环,处理以下五种情景:
        • 新老节点 start 相同:调用patchVnode,同时新旧节点开始索引加 1。
        • 新老节点 end 相同:调用patchVnode,同时新旧节点结束索引减 1。
        • 老节点 start 与新节点 end 相同:调用patchVnode,将当前真实 dom 节点移动到oldEndVnode后面,老节点开始索引加 1,新节点结束索引减 1。
        • 老节点 end 与新节点 start 相同:调用patchVnode,将当前真实 dom 节点移动到oldStartVnode前面,老节点结束索引减 1,新节点开始索引加 1。
        • 其他情况:从旧 VNode 生成以key为值,对应index序列为value的哈希表(oldKeyToIdx),尝试在其中寻找与newStartVnode具有相同key的 VNode。若未找到,则创建新的 VNode;若找到,再判断找到的 VNode 与newStartVnode是否为同一个节点,若是则patchVnode并移动该真实 dom 到oldStartVnode对应的真实 dom 前面,若不是则创建新节点。最后新节点开始索引加 1。

    四、小结

    1. 更新触发:数据改变时,Watcher调用patch给真实 DOM 打补丁。
    2. 节点比较:通过isSameVnode判断节点是否相同,相同则调用patchVnode
    3. patchVnode 操作:确定真实 DOM,处理文本节点、子节点有无及更新等情况。
    4. updateChildren 操作:设置并移动新旧 VNode 的头尾指针,按不同比较结果调用patchVnodecreateElem等函数进行节点更新或创建。

    你知道vue中key的原理吗?说说你对它的理解

    在 Vue 中,key是一个重要的概念,它与虚拟 DOM 的更新机制紧密相关,对提升 Vue 应用的性能起到关键作用。以下是对key原理及相关理解的详细阐述:

    一、Key 的定义与作用

    1. 定义key是赋予每个vnode(虚拟节点)的唯一标识符。在 Vue 的渲染过程中,每个节点都会被抽象为一个vnodekey用于在这些节点中进行区分和标识。
    2. 作用key是 diff 算法的一种优化策略,通过为节点提供唯一标识,Vue 能够更准确、快速地找到对应的vnode节点,从而高效地更新 DOM。

    二、场景背后的逻辑

    1. v-for 中使用 key
      • key情况:Vue 采用 “就地复用” 原则,即最小化元素的移动,并尽可能在相同位置对相同类型的元素进行patch(更新)或reuse(复用)。这意味着当数据顺序变化时,Vue 不会移动 DOM 元素来匹配数据项顺序,而是直接复用现有元素。例如,在列表渲染中,如果列表项顺序改变,Vue 可能会错误地复用某些元素,导致状态混乱。
      • key情况:Vue 会根据keys的顺序记录元素。若曾经拥有key的元素不再出现,会直接将其remove(移除)或destroyed(销毁)。这样,当数据发生变化时,Vue 能依据key准确判断哪些元素是新增的、哪些是需要移动的、哪些是要移除的,从而正确更新 DOM。
    2. 手动强制触发重新渲染:使用+new Date()生成时间戳作为key,当key值发生变化,如每次重新渲染生成新的时间戳,Vue 会认为这是一个全新的组件实例。于是,旧key对应的组件会被移除,新key对应的组件触发渲染,从而实现强制重新渲染的效果。

    三、设置 key 与不设置 key 的区别

    通过具体例子可以更直观地理解两者差异:

    1. 不设置key:在上述往items数组插入数据的例子中,Vue 在比较节点时,即便节点顺序变化,也会尽量复用相同类型的节点。这导致在数据更新时,虽然部分节点数据未变,但由于位置变化,仍需对多个节点进行patch操作以更新 DOM。整个过程共发生 3 次更新和 1 次插入操作,DOM 操作相对频繁。
    2. 设置key:设置key后,Vue 在进行 diff 比较时,能依据key准确识别每个节点。当数据更新时,只需对实际发生变化的节点进行操作,如在该例中仅发生 1 次插入操作,0 次更新操作,大大减少了对页面的 DOM 操作,显著提高了 diff 效率,提升了渲染性能。

    四、设置 key 值与 diff 效率的关系

    虽然设置key通常能提高 diff 效率,但并非绝对。Vue 默认的 “就地复用” 策略在某些情况下也是高效的,例如遍历输出的 DOM 内容非常简单,或者开发者刻意依赖这种默认行为以获取性能提升时,不设置key可能也是合理的选择。然而,当列表渲染输出依赖子组件状态或临时 DOM 状态(如表单输入值)时,不设置key可能导致状态混乱,此时必须设置key以确保正确的更新行为。所以,建议在使用v-for时尽可能提供key,以保证 Vue 应用的性能和稳定性。

    五、原理分析

    1. sameVnode函数:在core/vdom/patch.js源码中,sameVnode函数用于判断两个vnode是否为同一个节点。其中,首先判断的就是key值是否相等。若未设置key,则keyundefined,此时两个undefinedkey会被认为相等。但这种相等判断在复杂的节点比较中可能导致不准确的结果,因为没有唯一标识来区分节点。
    2. updateChildren函数:该函数负责对新旧vnode进行 diff 操作,并根据比对结果更新真实 DOM。在循环比较过程中,通过sameVnode函数判断节点是否相同,从而决定是复用、更新还是创建新节点。若设置了key,在查找匹配节点时,会先从根据旧节点生成的oldKeyToIdx(以key为索引的对象)中查找与新节点key相同的旧节点,然后再进一步判断是否为同一个节点。这一过程利用key提高了节点查找和比对的准确性和效率,进而优化了 DOM 更新操作。

    Vue Router

    1. 基本概念
      • :什么是 Vue Router(v - router)?

        • :Vue Router 是 Vue.js 官方的路由管理器,它与 Vue.js 深度集成,用于构建单页面应用(SPA)。在单页面应用中,页面的切换通常不是通过传统的页面跳转,而是通过路由的切换来实现视图的更新。Vue Router 允许我们定义不同的路由规则,将不同的 URL 映射到相应的组件,使得应用能够根据 URL 的变化来渲染不同的组件内容,提供类似于多页面应用的用户体验,但整个过程在一个页面内完成,避免了页面的重新加载。
      • :Vue Router 在 Vue 项目中的作用是什么?

        • :它为 Vue 应用提供了路由导航功能,使得应用可以根据用户在浏览器地址栏输入的 URL 或者通过代码触发的路由切换,展示不同的视图组件。这有助于实现应用的页面结构和导航逻辑,提升用户体验。例如,在一个电商应用中,通过 Vue Router 可以实现商品列表页、商品详情页、购物车页等不同页面之间的切换,每个页面由不同的 Vue 组件来渲染,用户在浏览过程中感觉像是在不同页面间跳转,但实际上只是组件的切换,页面没有重新加载,提高了应用的响应速度和交互性。
    2. 路由定义与配置
      • :如何定义和配置路由?

        • :首先,需要安装 Vue Router(如果项目创建时没有默认安装)。然后在项目中创建一个路由配置文件(通常是 router.js)。在这个文件中,引入 Vue Router 并创建一个 Router 实例。例如:
    import Vue from 'vue';
    import VueRouter from 'vue - router';
    import Home from '@/views/Home.vue';
    import About from '@/views/About.vue';Vue.use(VueRouter);const routes = [{path: '/',name: 'Home',component: Home},{path: '/about',name: 'About',component: About}
    ];const router = new VueRouter({mode: 'history',routes
    });export default router;
    

    这里通过 Vue.use(VueRouter) 安装插件,定义了一个 routes 数组,每个对象代表一个路由规则,包含 path(匹配的 URL 路径)、name(路由名称,用于命名路由,方便在代码中引用)和 component(该路由对应的组件)。最后创建 VueRouter 实例并导出,在 Vue 应用入口文件(通常是 main.js)中引入该实例,将其挂载到 Vue 实例上。

    • :路由配置中的 mode 有什么作用?有哪些取值?

      • mode 用于指定路由的工作模式。它有三个取值:hashhistory 和 abstract
        • hash 模式:URL 中会带有 # 符号,例如 http://example.com/#/home# 后面的部分不会被发送到服务器,改变 # 后面的内容不会触发页面的重新加载。这种模式兼容性好,适用于不支持 HTML5 History API 的浏览器。
        • history 模式:使用 HTML5 的 History API 来管理路由,URL 看起来更像传统的 URL,例如 http://example.com/home。这种模式下,服务器需要进行额外的配置,以确保所有的 URL 都能正确返回应用的入口页面,否则可能会出现 404 错误。它提供了更美观、更符合用户习惯的 URL 形式。
        • abstract 模式:主要用于运行在没有浏览器环境(如 Node.js 服务器端渲染)的场景中,它不会依赖浏览器的历史记录 API,通过内部维护一个栈来模拟路由的历史记录。

    1. 路由导航
      • :如何在 Vue 组件中实现路由导航?

        • :有两种常见方式:
    • 使用 <router - link> 组件:这是 Vue Router 提供的用于创建链接的组件。例如:
    <template><div><router - link to="/home">首页</router - link><router - link to="/about">关于</router - link></div>
    </template>
    

    to 属性指定要导航到的路由路径<router - link> 组件会渲染成一个 <a> 标签,点击链接会触发路由切换。

    • 通过编程式导航:在 Vue 组件的方法中,可以使用 this.$router.pushthis.$router.replace 等方法进行路由导航。例如:
    export default {methods: {goToHome() {this.$router.push('/home');},replaceToAbout() {this.$router.replace('/about');}}
    }
    

    $router.push 方法会向历史记录栈中添加一个新的记录,而 $router.replace 方法会替换当前的历史记录,不会留下历史记录。

    • router.push 和 router.replace 有什么区别?

      • router.push 会在浏览器历史记录中添加一个新的条目,当用户点击浏览器的后退按钮时,可以回到上一个页面。例如,从首页通过 router.push 导航到详情页,点击后退按钮可以回到首页。而 router.replace 则是替换当前的历史记录条目不会在历史记录中新增,点击后退按钮不会回到替换前的页面。比如在一些登录成功后直接跳转到首页并替换登录页历史记录的场景中,使用 router.replace 可以防止用户通过后退按钮回到登录页。

    1. 路由参数
      • :路由参数有哪些类型?如何使用?

                    答:主要有两种类型:

    • 动态路由参数:在路由配置中,通过在 path 中使用冒号 : 来定义动态参数。例如:
    const routes = [{path: '/user/:id',name: 'User',component: User}
    ];
    

    在组件中,可以通过 $route.params 来访问动态参数。例如在 User 组件中:

    export default {created() {console.log(this.$route.params.id);}
    }
    

    当访问 /user/123 时,$route.params.id 的值就是 123。这种方式适用于需要根据不同的参数值展示不同内容的场景,如根据用户 ID 展示不同用户的详情页。

    • 查询参数:通过在 URL 中使用 ? 来添加查询参数,例如 /home?name=John&age=30。在组件中,可以通过 $route.query 来访问查询参数。例如:
    export default {created() {console.log(this.$route.query.name);console.log(this.$route.query.age);}
    }
    

    查询参数常用于一些过滤、筛选条件的传递等场景。

    5. 嵌套路由

    • :什么是嵌套路由?如何配置和使用?

      • :嵌套路由是指在一个路由组件内部再包含其他子路由组件。例如,在一个文章详情页中,可能有文章内容、评论区等不同部分,每个部分可以作为一个子路由组件来展示。
        配置时,在父路由的 children 属性中定义子路由。例如:
    const routes = [{path: '/article/:id',name: 'Article',component: Article,children: [{path: 'content',name: 'ArticleContent',component: ArticleContent},{path: 'comments',name: 'ArticleComments',component: ArticleComments}]}
    ];
    

    在父组件模板中,需要使用 <router - view> 来显示子路由的内容。例如 Article.vue 的模板:

    <template><div><h1>文章详情</h1><router - link to="/article/123/content">文章内容</router - link><router - link to="/article/123/comments">评论区</router - link><router - view></router - view></div>
    </template>
    

    这样,当访问 /article/123/content 时,ArticleContent 组件会显示在父组件的 <router - view> 位置;访问 /article/123/comments 时,ArticleComments 组件会显示。

    记忆口诀
    Vue 路由 v - router,单页应用导航优。配置路由规则明,pathname 组件凑。
    mode 模式有三种,hashhistory 及 abstract。路由导航俩方法,标签编程皆可求。
    push 新增记录留,replace 替换无回首。路由参数分两类,动态查询各有用。
    嵌套路由层级清,children 里面配分明,父组件中 view 来显,子路内容按需呈。

    相关文章:

    前端八股之Vue

    目录 有使用过vue吗&#xff1f;说说你对vue的理解 你对SPA单页面的理解&#xff0c;它的优缺点分别是什么&#xff1f;如何实现SPA应用呢 一、SPA 是什么 二、SPA 和 MPA 的区别 三、SPA 的优缺点 四、实现 SPA 五、给 SPA 做 SEO 的方式&#xff08;基于 Vue&#xff…...

    Matlab数值计算

    MATLAB数值计算 数值计算函数句柄匿名函数线性与非线性方程组求解1. \&#xff08;左除运算&#xff09;2. fzero3. fsolve4. roots 函数极值的求解1. fminbnd2. fmincon3. fminsearch与fminunc 数值积分1. quad / quadl2. quadgk3. integral4. trapz5. dblquad, quad2d, integ…...

    谷歌地图高清卫星地图2026中文版下载|谷歌地图3D卫星高清版 V7.3.6.9796 最新免费版下载 - 前端工具导航

    谷歌地图高清卫星地图2024中文版是一款非常专业的世界地图查看工具。通过使用该软件&#xff0c;你就可以在这里看到外太空星系、大洋峡谷等场景&#xff0c;通过高清的卫星地图&#xff0c;可以清晰查看地图、地形、3D建筑、卫星图像等信息&#xff0c;让你可以更轻松的探索世…...

    条形进度条

    组件 <template><view class"pk-detail-con"><i class"lightning" :style"{ left: line % }"></i><i class"acimgs" :style"{ left: line % }"></i><view class"progress&quo…...

    悟饭游戏厅iOS版疑似流出:未测试版

    网传悟饭游戏厅iOS版安装包流出&#xff0c;提供百度网盘/夸克网盘双渠道下载。本文客观呈现资源信息&#xff0c;包含文件验证数据、安装风险预警及iOS正版替代方案。苹果用户请谨慎测试&#xff0c;建议优先考虑官方渠道。 一、资源基本信息 1.1 文件验证数据 属性夸克网盘…...

    95. Java 数字和字符串 - 操作字符串的其他方法

    文章目录 95. Java 数字和字符串 - 操作字符串的其他方法一、分割字符串二、子序列与修剪三、在字符串中搜索字符和子字符串四、将字符和子字符串替换为字符串五、String 类的实际应用 —— 文件名处理示例示例&#xff1a;Filename 类示例&#xff1a;FilenameDemo 类 总结 95…...

    IBM DB2分布式数据库架构

    一、什么是分布式数据库架构 分布式数据库架构是现代数据库系统的重要发展方向&#xff0c;特别适合处理大规模数据、高并发访问和高可用性需求的应用场景。下面我们从原理、架构模式、关键技术、实现方式和常见产品几个方面来系统讲 1、分布式数据库的基本概念与原理 1. 什…...

    初始化已有项目仓库,推送远程(Git)

    初始化Git仓库&#xff08;如果还没初始化&#xff09; git init 添加并提交文件 git add . ("."表示当前项目所有文件) git commit -m “first commit” 关联远程仓库&#xff08;如果还没关联&#xff09; git remote add origin http://xxxxxxxx 推送代码 …...

    Android Studio 向模拟器手机添加照片、视频、音乐

    Android Studio 向模拟器手机添加照片、视频、音乐(其实都是一样的&#xff0c;只是添加到不同的文件夹&#xff09;&#xff0c;例如我们在很多程序中功能例如&#xff1a;选择头像&#xff0c;跳转到手机相册选择头像&#xff0c;此时相册为空&#xff0c;即模拟器没有图片资…...

    数据结构-算法学习C++(入门)

    目录 03二进制和位运算04 选择、冒泡、插入排序05 对数器06 二分搜索07 时间复杂度和空间复杂度08 算法和数据结构09 单双链表09.1单双链表及反转09.2合并链表09.2两数相加09.2分隔链表 013队列、栈、环形队列013.1队列013.2栈013.3循环队列 014栈-队列的相互转换014.1用栈实现…...

    访谈 | 吴恩达全景解读 AI Agents 发展现状:多智能体、工具生态、评估体系、语音栈、Vibe Coding 及创业建议一文尽览

    在最新的 LangChain Interrupt 大会上&#xff08;2025&#xff09;&#xff0c;LangChain 联合创始人 & CEO Harrison Chase 与吴恩达&#xff08;Andrew Ng&#xff09;就 AI Agnets 的发展现状&#xff0c;进行了一场炉边谈话。 吴恩达回顾了与 LangChain 的渊源&#…...

    连接关键点:使用 ES|QL 联接实现更丰富的可观测性洞察

    作者&#xff1a;来自 Elastic Luca Wintergerst ES|QL 的 LOOKUP JOIN 现已进入技术预览阶段&#xff0c;它允许你在查询时对日志、指标和追踪进行丰富处理&#xff0c;无需在摄取时进行非规范化。动态添加部署、基础设施或业务上下文&#xff0c;减少存储占用&#xff0c;加速…...

    Tiktok App 登录账号、密码、验证码 XOR 加密算法

    抖音 App 登录账号、密码、验证码 XOR 加密算法% E9 n z, \& R1 a4 b. ^ 流程分析 登录 Tiktok APP 时&#xff0c;通过抓包发现账号密码是非明文传输的。 <?php// http://xxx.xx.x.x.x/tiktok/$tiktok new TikTokClient();$userId 7212597544604484614; $secUid …...

    Flask + Celery 应用

    目录 Flask Celery 应用项目结构1. 创建app.py2. 创建tasks.py3. 创建celery_worker.py4. 创建templates目录和index.html运行应用测试文件 Flask Celery 应用 对于Flask与Celery结合的例子&#xff0c;需要创建几个文件。首先安装必要的依赖&#xff1a; pip install flas…...

    奥威BI+AI数据分析:企业数智化转型的加速器

    在当今数据驱动的时代&#xff0c;企业对于数据分析的需求日益增长。奥威BIAI数据分析的组合&#xff0c;正成为众多企业数智化转型的加速器。 奥威BI以其强大的数据处理和可视化能力著称。它能够轻松接入多种数据源&#xff0c;实现数据的快速整合与清洗。通过内置的ETL工具&…...

    python打卡day43

    复习日 作业&#xff1a; kaggle找到一个图像数据集&#xff0c;用cnn网络进行训练并且用grad-cam做可视化 进阶&#xff1a;并拆分成多个文件 找了个街头食物图像分类的数据集Popular Street Foods&#xff08;其实写代码的时候就开始后悔了&#xff09;&#xff0c;原因在于&…...

    MySQL 如何判断某个表中是否存在某个字段

    在MySQL中&#xff0c;判断某个表中是否存在某个字段&#xff0c;可以通过查询系统数据库 INFORMATION_SCHEMA.COLUMNS 实现。以下是详细步骤和示例&#xff1a; 方法&#xff1a;使用 INFORMATION_SCHEMA.COLUMNS 通过查询系统元数据表 COLUMNS&#xff0c;检查目标字段是否存…...

    Linux --进程优先级

    概念 什么是进程优先级&#xff0c;为什么需要进程优先级&#xff0c;怎么做到进程优先级这是本文需要解释清楚的。 优先级的本质其实就是排队&#xff0c;为了去争夺有限的资源&#xff0c;比如cpu的调度。cpu资源分配的先后性就是指进程的优先级。优先级高的进程有优先执行的…...

    安装和配置 Nginx 和 Mysql —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录6

    前言 昨天更新了四篇博客&#xff0c;我们顺利的 安装了 ubuntu server 服务器&#xff0c;并且配置好了 ssh 免密登录服务器&#xff0c;安装好了 服务器常用软件安装, 配置好了 zsh 和 vim 以及 通过 NVM 安装好Nodejs&#xff0c;还有PNPM包管理工具 。 作为服务器的运行…...

    Linux 测试本机与192.168.1.130 主机161/udp端口连通性

    Linux 测试本机与 192.168.1.130 主机 161/UDP 端口连通性 161/UDP 端口是 SNMP&#xff08;简单网络管理协议&#xff09;的标准端口。以下是多种测试方法&#xff1a; &#x1f6e0;️ 1. 使用 nmap 进行专业测试&#xff08;推荐&#xff09; sudo nmap -sU -p 161 -Pn 1…...

    OpenCV 滑动条调整图像亮度

    一、知识点 1、int createTrackbar(const String & trackbarname, const String & winname, int * value, int count, TrackbarCallback onChange 0, void * userdata 0); (1)、创建一个滑动条并将其附在指定窗口上。 (2)、参数说明: trackbarname: 创建的…...

    图解gpt之注意力机制原理与应用

    大家有没有注意到&#xff0c;当序列变长时&#xff0c;比如翻译一篇长文章&#xff0c;或者处理一个长句子&#xff0c;RNN这种编码器就有点力不从心了。它把整个序列信息压缩到一个固定大小的向量里&#xff0c;信息丢失严重&#xff0c;而且很难记住前面的细节&#xff0c;特…...

    硬件学习笔记--65 MCU的RAM及FLash简介

    MCU&#xff08;微控制器单元&#xff09;内部的 RAM 和 Flash 是最关键的两种存储器&#xff0c;它们直接影响MCU的性能、功耗和编程方式。以下是它们的详细讲解及作用&#xff1a; 1. RAM&#xff08;随机存取存储器&#xff09; 1.1 特性 1&#xff09;易失性&#xff1a…...

    【Oracle】视图

    个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 视图基础概述1.1 视图的概念与特点1.2 视图的工作原理1.3 视图的分类 2. 简单视图2.1 创建简单视图2.1.1 基本简单视图2.1.2 带计算列的简单视图 2.2 简单视图的DML操作2.2.1 通过视图进行INSERT操作2.2.2 通…...

    数据库 MongoDB (NoSQL) 与 MySQL (SQL) 的写法对比

    MongoDB (NoSQL) 与 MySQL (SQL) 的写法对比及优劣势分析 基本概念差异 MySQL/SQL&#xff1a;关系型数据库&#xff0c;使用结构化查询语言(SQL)&#xff0c;数据以表格形式存储&#xff0c;有预定义的模式(schema)MongoDB/NoSQL&#xff1a;文档型数据库&#xff0c;无固定…...

    基于粒子滤波的PSK信号解调实现

    基于粒子滤波的PSK信号解调实现 一、引言 相移键控(PSK)是数字通信中广泛应用的调制技术。在非高斯噪声和动态相位偏移环境下,传统锁相环(PLL)性能受限。粒子滤波(Particle Filter)作为一种序列蒙特卡洛方法,能有效处理非线性/非高斯系统的状态估计问题。本文将详细阐…...

    更强劲,更高效:智源研究院开源轻量级超长视频理解模型Video-XL-2

    长视频理解是多模态大模型关键能力之一。尽管OpenAI GPT-4o、Google Gemini等私有模型已在该领域取得显著进展&#xff0c;当前的开源模型在效果、计算开销和运行效率等方面仍存在明显短板。近日&#xff0c;智源研究院联合上海交通大学等机构&#xff0c;正式发布新一代超长视…...

    2025.6.3学习日记 Nginx 基本概念 配置 指令 文件

    1.初始nginx Nginx&#xff08;发音为 “engine x”&#xff09;是一款高性能的开源 Web 服务器软件&#xff0c;同时也具备反向代理、负载均衡、邮件代理等功能。它由俄罗斯工程师 Igor Sysoev 开发&#xff0c;最初用于解决高并发场景下的性能问题&#xff0c;因其轻量级、高…...

    【连接器专题】案例:产品测试顺序表解读与应用

    在查看SD卡座连接器的规格书,一些测试报告时,你可能会看到如下一张产品测试顺序表。为什么会出现一张测试顺序表呢? 测试顺序表的使用其实定义测试环节的验证的“路线图”和“游戏规则”,本文就以我人个经验带领大家一起看懂这张表并理解其设计逻辑。 测试顺序表结构 测试…...

    星动纪元的机器人大模型 VPP,泛化能力效果如何?与 VLA 技术的区别是什么?

    点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 VPP 利用了大量互联网视频数据进行训练&#xff0c;直接学习人类动作&#xff0c;减轻了对于高质量机器人真机数据的依赖&#xff0c;且可在不同人形机器人本体之间自如切换&#xff0c;这有望…...