vue面试题(自用)

B站影视 韩国电影 2025-09-01 12:11 2

摘要:init_initLifecycle/Event ,往 vm 上挂载各种属性callHook: beforeCreated : 实例刚创建initInjection/initState : 初始化注入和 data 响应性created : 创建完成,属性已经绑

_init_initLifecycle/Event ,往 vm 上挂载各种属性callHook: beforeCreated : 实例刚创建initInjection/initState : 初始化注入和 data 响应性created : 创建完成,属性已经绑定, 但还未生成真实 dom进行元素的挂载: $el / vm.$mount是否有 template : 解析成 render function*.vue 文件: Vue-loader 会将 编译成 render functionbeforeMount : 模板编译/挂载之前执行 render function ,生成真实的 dom ,并替换到 dom tree 中mounted : 组件已挂载update : #技术分享执行 diff 算法,比对改变是否需要触发UI更新flushScheduleQueuewatcher.before : 触发 beforeUpdate 钩子 - watcher.run : 执行 watcher 中的 notify ,通知所有依赖项更新UI触发 updated 钩子: 组件已更新actived / deactivated(keep-alive) : 不销毁,缓存,组件激活与失活destroy :beforeDestroy : 销毁开始销毁自身且递归销毁子组件以及事件监听remove : 删除节点watcher.teardown : 清空依赖vm.$off : 解绑监听destroyed : 完成后触发钩子

| 生命周期 | 描述 | | ---

| beforeCreate | 组件实例被创建之初,组件的属性生效之前 | | created | 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用 | | beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用 | | mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子 | | beforeUpdate | 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 | | update | 组件数据更新之后 | | activited | keep-alive 专属,组件被激活时调用 | | deactivated | keep-alive 专属,组件被销毁时调用 | | beforeDestory | 组件销毁前调用 | | destoryed | 组件销毁后调用 |

可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是本人推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

能更快获取到服务端数据,减少页面 loading 时间;ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

typescript 体验AI代码助手 代码解读复制代码mounted { this.$emit("mounted"); }

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

typescript 体验AI代码助手 代码解读复制代码doSomething { console.log('父组件监听到 mounted 钩子函数 ...'); },mounted{ console.log('子组件触发 mounted 钩子函数 ...'); },

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

1.beforeCreate(父组件)=> created(父组件)=> beforeCreate(子组件)=> created(子组件)=>beforeMount(父组件)=> beforeMount(子组件)=> mounted(子组件)=> mounted(父组件)2.子组件更新父组件 beforeUpdate(父组件)=> beforeUpdate(子组件)=> updated(子组件)=> updated(父组件) 父组件更新父组件 beforeUpdate(父组件)=> updated(父组件)3.beforeUnmount(父组件)=> beforeUnmount(子组件)=> unmounted(子组件)=> unmounted(父组件) beforeDestroy destroyed

错误捕获阶段

当子组件中发生未捕获的错误时,可以通过父组件的 errorCaptured 钩子捕获错误。顺序如下:

子组件发生错误,触发 errorCaptured 钩子:父组件的 errorCaptured 钩子会被调用。如果父组件没有捕获错误,错误会继续向祖先组件传播。全局错误捕获:如果没有任何组件捕获错误,全局错误捕获器( config.errorHandler )会处理它。

Model–View–ViewModel (MVVM) 是一个软件架构设计模式。MVVM 源自于经典的 Model–View–Controller(MVC)模式 ,MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。如下图所示:

(1)View 层

View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。

(2)Model 层

Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。

(3)ViewModel 层

ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。

(1)View 层

xml 体验AI代码助手 代码解读复制代码

{{message}}

Click me

(2)ViewModel 层

javascript 体验AI代码助手 代码解读复制代码var app = new Vue({ el: '#app', data: { message: 'Hello Vue!', }, methods: { showMessage{ let vm = this; alert(vm.message); } }, created{ let vm = this; ajax({ url: '/your/server/data/api', success(res){ vm.message = res; } }); }})

(3) Model 层

json 体验AI代码助手 代码解读复制代码{ "url": "/your/server/data/api", "res": { "success": true, "name": "IoveC", "domain": "www.cnblogs.com" }}

Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据,如下图所示:

即:

输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。

其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View。

Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。

实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

看完生命周期后,里面的 watcher 等内容其实是数据响应中的一部分。数据响应的实现由两部分构成: 观察者( watcher )依赖收集器( Dep ) ,其核心是 defineProperty 这个方法,它可以 重写属性的 get 与 set 方法,从而完成监听数据的改变。

Observe (观察者)观察 props 与 state遍历 props 与 state,对每个属性创建独立的监听器( watcher )使用 defineProperty 重写每个属性的 get/set( defineReactive )get : 收集依赖Dep.dependwatcher.addDepset : 派发更新Dep.notifywatcher.updatequeenWatchernextTickflushScheduleQueuewatcher.runupdateComponent

大家可以先看下面的数据相应的代码实现后,理解后就比较容易看懂上面的简单脉络了。

体验AI代码助手 代码解读复制代码let data = {a: 1}observe(data)new Watcher(data, 'name', updateComponent) data.a = 2function updateComponent { vm._update }function observe(obj) { Object.keys(obj).map(key => { defineReactive(obj, key, obj[key]) }) }function defineReactive(obj, k, v) { if (type(v) == 'object') observe(v) let dep = new Dep Object.defineProperty(obj, k, { enumerable: true, configurable: true, get: function reactiveGetter { if (Dep.target) { dep.addSub(Dep.target) } return v }, set: function reactiveSetter(nV) { v = nV dep.nofify }, }) }class Dep { constructor { this.subs = } addSub(sub) { this.subs.push(sub) } notify { this.subs.map(sub => { sub.update }) } }Dep.target = nullclass Watcher { constructor(obj, key, cb) { Dep.target = this this.cb = cb this.obj = obj this.key = key this.value = obj[key] Dep.target = null } addDep(Dep) { Dep.addSub(this) } update { this.value = this.obj[this.key] this.cb(this.value) } before { callHook('beforeUpdate') } }

如果被问到 Vue 怎么实现数据双向绑定,大家肯定都会回答 通过 Object.defineProperty 对数据进行劫持,但是 Object.defineProperty 只能对属性进行数据劫持,不能对整个对象进行劫持,同理无法对数组进行劫持,但是我们在使用 Vue 框架中都知道,Vue 能检测到对象和数组(部分方法的操作)的变化,那它是怎么实现的呢?我们查看相关代码如下:

scss 体验AI代码助手 代码解读复制代码observeArray (items: Array) { for (let i = 0, l = items.length; i

通过以上 Vue 源码部分查看,我们就能知道 Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty 也能对对象和数组(部分方法的操作)进行监听。

Proxy 的优势如下:

Proxy 可以直接监听对象而非属性;Proxy 可以直接监听数组的变化;Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

Object.defineProperty 的优势如下:

兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。

我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

text 和 textarea 元素使用 value 属性和 input 事件;checkbox 和 radio 使用 checked 属性和 change 事件;select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:

ini 体验AI代码助手 代码解读复制代码相当于

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

javascript 体验AI代码助手 代码解读复制代码父组件:子组件:{{value}}props:{ value: String }, methods: { test1{ this.$emit('input', '小红') }, }

object.defineProperty 导致,vue3不会有这个问题,因为使用了 proxy

所有的 prop 都使得其父子 prop 之间形成了一个 单向下行绑定 :父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

有两种常见的试图改变一个 prop 的情形 :

这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:kotlin 体验AI代码助手 代码解读复制代码props: ['initialCounter'],data: function { return { counter: this.initialCounter }}这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性javascript 体验AI代码助手 代码解读复制代码props: ['size'],computed: { normalizedSize: function { return this.size.trim.toLowerCase }}

由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:

当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue当你修改数组的长度时,例如:vm.items.length = newLength

为了解决第一个问题,Vue 提供了以下操作方法:

scss 体验AI代码助手 代码解读复制代码Vue.set(vm.items, indexOfItem, newValue)vm.$set(vm.items, indexOfItem, newValue)vm.items.splice(indexOfItem, 1, newValue)

为了解决第二个问题,Vue 提供了以下操作方法:

scss 体验AI代码助手 代码解读复制代码vm.items.splice(newLength)

vue-router 有 3 种路由模式:hash、history、abstract,hash 模式兼容性好,但是不美观,不利于 SEO,history 美观,historyAPI+popState,但是刷新会出现404,abstract 在不支持浏览器的 API 换景使用

hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':

www.word.com#search 复制代码

hash 路由模式的实现主要是基于下面几个特性:

URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState 和 history.repalceState。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:

window.history.pushState(null, null, path); window.history.replaceState(null, null, path); 复制代码

history 路由模式的实现主要基于存在下面几个特性:

pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);history.pushState 或 history.replaceState 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

钩子函数有三种:

全局守卫路由守卫组件守卫

第一种:全局钩子函数

router.beforeEach((to, from, next) => { next})router.afterEach( => { if (width

to :router 即将进入的路由对象

from :当前导航即将离开的路由

next :Function,进行管道中的一个钩子,如果执行完了,则导航的状态就是 confirmed (确认的);否则为 false,终止导航。

next:如果一直正常,则调用该方法进入下一个钩子;next(false):中断当前导航,即路由地址不发生变化;next('/xxx') 或 next({path: '/xxx'}):强制跳转到指定路径;next(error):如果传入的是一个Error实例,则导航会被中断且该错误会被传递给 router.onError 注册过的回调。

第二种:针对单个路由钩子函数

写在路由配置中,只有访问到这个路径,才能触发钩子函数

const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { next } } ]})

这些钩子与全局 before 钩子的方法参数是一样的

第三种:组件内的钩子

写在组件中,访问路径,即将渲染组件的时候触发的

const Foo = { template: `...`, beforeRouteEnter (to, from, next) { next((vm) =>{ vm就是实例 }) }, beforeRouteUpdate (to, from, next) { }, beforeRouteLeave (to, from, next) { }}

注意 :beforeRouteEnter 是支持给 next 传递回调的唯一守卫。

一些使用场景:

beforeRouteLeave:

1.beforeRouteLeave:通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。清除定时器,清除缓存

javascript 体验AI代码助手 代码解读复制代码beforeRouteLeave (to, from, next) { window.clearInterval(this.timer) next}

2.禁止用户在还未保存修改前突然离开。

scss 体验AI代码助手 代码解读复制代码beforeRouteLeave (to, from, next) { const answer = window.confirm('你的修改内容未保存,你确定要离开吗?') if(answer){ next }else{ next(flase) }}

3.当用户需要关闭页面时, 可以将公用的信息保存到 session 或 Vuex 中

scss 体验AI代码助手 代码解读复制代码beforeRouteLeave (to, from, next) { localStorage.setItem(name, content); next}

beforeEach

使用该函数,一定要调用 next,否则钩子函数不能 resolve;

1.验证用户访问权限。一个系统需要先验证用户是否登录,如果登录了就可以访问,否则直接跳转到登录页面。

javascript 体验AI代码助手 代码解读复制代码import Vue from 'vue'import VueRouter from 'vue-router'import { getToken } from '@Utils/session.utils'import Login from '../pages/Login.vue'const Home = => import('../pages/Home.vue')Vue.use(VueRouter)const routes = [ { path: '/login', name: 'login', component: Login }, { path: '/home', name: 'home', component: Home } ] const router = new VueRouter({ routes })router.beforeEach((to, from, next) => { if (to.name !== 'login' && !getToken) next('/login') else next }) export default router

afterEach

1.路由切换,将页面的滚动位置返回到顶部。页面比较长,当滚动到某个位置后切换路由,这时跳转的页面滚动条位置默认是前一个页面离开时停留的位置,可以通过该钩子函数将滚动条位置重置。

javascript 体验AI代码助手 代码解读复制代码router.afterEach((to, from) => { window.scrollTo(0, 0) })

2.当页面跳转后,判断当前页面的宽度大小后,选择是否隐藏侧边栏

ini 体验AI代码助手 代码解读复制代码 const width = document.documentElement.clientWidth const menuVisible = ref(width > 500) provide('menuVisible',menuVisible)//set router.afterEach(=> { if (width

beforeRouteEnter

从一个列表页进入到详情页,然后再返回到列表页,要求保留离开列表页之前访问的数据及滚动位置,从其他页面重新进入列表页,获取最新的数据,

(在组件切换过程中将状态保留在内存中,等再次访问的时候,还保持着离开之前的所有状态,而不是重新初始化。)使用的是 vue 缓存之

完整的导航解析流程

导航被触发;在失活的组件里调用 beforeRouteLeave 守卫;调用全局的 beforeEach 守卫;在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+);在路由配置里调用 beforeEnter;解析异步路由组件;在被激活的组件里调用 beforeRouteEnter;调用全局的 beforeResolve 守卫 (2.5+);导航被确认;调用全局的 afterEach 钩子;触发 DOM 更新;

-

props / $emit适用 父子组件通信parent,children获取当前组件的父组件和当前组件的子组件$ref获取实例attrs和listeners 。$attrs是为了实现批量传递数据。父组件通过provide提供,子组件通过inject注入变量,跨级组件间的通信问题eventBus平级组件数据传递Vuexattrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(class和style除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v−bind="attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(class和style除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v−bind="attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。listeners:包含了父作用域中的(不含.native修饰器的)v−on事件监听器。它可以通过v−on="listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="listeners:包含了父作用域中的(不含.native修饰器的)v−on事件监听器。它可以通过v−on="listeners" 传入内部组件

store,Vuex 的状态存储是响应式的

state : 状态中心getters : 获取状态,允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。mutations : 是唯一更改 store 中状态的方法,且必须是同步函数。actions : 异步更改状态,用于提交 mutation,而不是直接变更状态,可以包含任意异步操作modules : 允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

运用场景:

当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

v-if真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的 :如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。

所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。

控制手段不同 css dom节点控制编译过程编译条件 v-if会重建和销毁内部监听和子组件,从而触发生命周期,v-show则不会性能消耗 v-if切换消耗 v-show初始消耗大 v-if消耗性能更大

首先,v-for 和 v-if 不能在同一个标签中使用。

先处理 v-for,再处理 v-if。

如果同时遇到的时候,应该考虑先用计算属性处理数据,在进行 v-for,可以减少循环次数。

用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;diff 算法 — 比较两棵虚拟 DOM 树的差异;pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

实际上 diff 算法它的前提一定是同层级和同类型的节点,核心一定是列表循环中的 diff 算法,加 key 之后元素怎么移动,删除和创建。

vue2 双端交叉指针,新老 vdom 各有两个指针,分别是队头队头,队尾队尾 ,队头队尾,队尾队头,就跟一个 x 一样,会对比四次,如果说四次寻找到元素的 key 相同,就回去进行复用,移动元素的位子,如果说这四种情况都没有匹配,就会在 vdom 的队头开始,再去寻找,看老的 vdom 里有没有对应的元素,进行相应的移动删除和创建

vue3 双端快速 diff,两个指针,新老 vdom,只对比两种情况,队头队头,队尾队尾,能够匹配上和2.0是完全一样的,一旦没有匹配上,会触发对新的 vdom 去进行最长递增子序列的计算,在新的 vdom 里寻找依次递增的元素有哪些,找到之后,那这些元素它的顺序就是固定的,去寻找不在这些列表里面的元素和老的 vdom 进行对比,再进行移动删除和创建

目的:为了减少 dom 的移动,提升了 js 的消耗,但节省了浏览器的性能,总体利大于弊

Vue2 采用了双端 Diff 算法,算法流程主要是:

对比头头、尾尾、头尾、尾头是否可以复用,如果可以复用,就进行节点的更新或移动操作。如果经过四个端点的比较,都没有可复用的节点,则将旧的子序列保存为节点 key 为 key ,index 为 value 的 map 。拿新的一组子节点的头部节点去 map 中查找,如果找到可复用的节点,则将相应的节点进行更新,并将其移动到头部,然后头部指针右移。然而,拿新的一组子节点中的头部节点去旧的一组子节点中寻找可复用的节点,并非总能找到,这说明这个新的头部节点是新增节点,只需要将其挂载到头部即可。经过上述处理,最后还剩下新的节点就批量新增,剩下旧的节点就批量删除。

Vue3 的 Diff 算法与 Vue2 的 Diff 算法一样,也会先进行双端比对,只是双端比对的方式不一样。Vue3 的 Diff 算法借鉴了字符串比对时的双端比对方式,即优先处理可复用的前置元素和后置元素。Vue3 的 Diff 算法的流程如下

处理前置节点处理后置节点新节点有剩余,则挂载剩余的新节点旧节点有剩余,则卸载剩余的旧节点乱序情况(新、旧节点都有剩余),则构建最长递增子序列节点在最长递增子序列中,则该节点不需移动节点不在最长递增子序列中,则移动该节点

优点:

保证性能下限无需手动操作 DOM跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。

Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex 和 oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了 key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。

Vue在patch过程中,通过key可以判断两个虚拟节点是否是相同节点。没有key会导致更新的时候出问题尽量不要采用索引作为keyfunction createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

优点:

用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;基于上面一点,SPA 相对对服务器压力小;前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

(1)代码层面的优化

v-if 和 v-show 区分使用场景computed 和 watch 区分使用场景v-for 遍历必须为 item 添加 key,且避免同时使用 v-if长列表性能优化,数据层级不要过深,合理的设置响应式数据事件的销毁图片资源懒加载路由懒加载第三方插件的按需引入优化无限列表性能服务端渲染 SSR or 预渲染使用keep-alive来缓存组件虚拟滚动、时间分片等策略

(2)Webpack 层面的优化

Webpack 对图片进行压缩减少 ES6 转为 ES5 的冗余代码提取公共代码模板预编译提取组件的 CSS优化 SourceMap构建结果输出分析Vue 项目的编译优化

(3)基础的 Web 技术的优化

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:

一般结合路由和动态组件一起使用,用于缓存组件,避免组件重新创建;提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。

使用有两个场景,一个是动态组件,一个是 router-view

这里创建了一个白名单和一个黑名单。表明哪些需要需要做缓存,哪些不需要做缓存。以及最大的缓存个数。

缓存的是组件的实例,用 key 和 value 对象保存。

加载的时候,监控 include 和 exclude。

如果不需要缓存,直接返回虚拟节点。

如果需要缓存,就用组件的 id 和标签名,生成一个 key,把当前 vnode 的 instance 作为 value,存成一个对象。这就是缓存列表

如果设置了最大的缓存数,就删除第0个缓存。新增最新的缓存。

并且给组件添加一个 keepAlive 变量为 true,当组件初始化的时候,不再初始化。

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

即:SSR 大致的意思就是 vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。

服务端渲染 SSR 的优缺点如下:

(1)服务端渲染的优点:

更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;更快的内容到达时间(首屏加载更快): SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;

(2) 服务端渲染的缺点:

更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。

为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?

new Vue 是一个单例模式,不会有任何的合并操作,所以根实例不必校验 data 一定是一个函数。组件的 data 必须是一个函数,是为了防止两个组件的数据产生污染。如果都是对象的话,会在合并的时候,指向同一个地址。而如果是函数的时候,合并的时候调用,会产生两个空间。

javascript 体验AI代码助手 代码解读复制代码data { return { message: "子组件", childName:this.name } }new Vue({ el: '#app', router, template: '', components: {App} })

nextTick 是一个微任务。

.stop.prevent.capture.self.once.passive.right.center.middle.alt

vue2中必须要有根标签。

vue3中可以没有根标签,会默认将多个根标签包裹在一个 fragement 虚拟标签中,有利于减少内存。

在 vue2中采用选项式 API,将数据和函数集中起来处理,将功能点切割了当逻辑复杂的时候不利于代码阅读。

在 vue3中采用组合式 API,将同一个功能的代码集中起来处理,使得代码更加有序,有利于代码的书写和维护。

创建前:beforeCreate -> 使用setup创建后:created -> 使用setup挂载前:beforeMount -> onBeforeMount挂载后:mounted -> onMounted更新前:beforeUpdate -> onBeforeUpdate更新后:updated -> onUpdated销毁前:beforeDestroy -> onBeforeUnmount销毁后:destroyed -> onUnmounted异常捕获:errorCaptured -> onErrorCaptured被激活:onActivated 被包含在 中的组件,会多出两个生命周期钩子函数。被激活时执行。切换:onDeactivated 比如从 A 组件,切换到 B 组件,A 组件消失时执行

我们通常会用 onMounted 钩子在组件挂载后发送异步请求,获取数据并更新组件状态。

这是因为 onMounted 钩子在组件挂载到 DOM 后调用,而发送异步请求通常需要确保组件已经挂载,以便正确地操作 DOM 或者更新组件的状态。

在 vue2中 v-for 的优先级高于 v-if,可以放在一起使用,但是不建议这么做,会带来性能上的浪费

在 vue3中 v-if 的优先级高于 v-for,一起使用会报错。可以通过在外部添加一个标签,将 v-for 移到外层

vue3提供了 Teleport 组件可将部分 Dom 移动到 vue app 之外的位置,比如 Dialog 组件

vue2中的 diff 算法

遍历每一个虚拟节点,进行虚拟节点对比,并返回一个 patch 对象,用来存储两个节点不同的地方。用 patch 记录的消息去更新 dom

缺点:比较每一个节点,而对于一些不参与更新的元素,进行比较是有点消耗性能的。特点:特别要提一下 Vue 的 patch 是即时的,并不是打包所有修改最后一起操作 DOM,也就是在 vue 中边记录变更新。(React 则是将更新放入队列后集中处理)。

vue3中的 diff 算法

在初始化的时候会给每一个虚拟节点添加一个 patchFlags,是一种优化的标识。只会比较 patchFlags 发生变化的节点,进行识图更新。而对于 patchFlags 没有变化的元素作静态标记,在渲染的时候直接复用。

Vue3 相比于 Vue2 虚拟 DOM 上增加 patchFlag 字段。patchFlag 字段帮助 diff 时区分静态节点,以及不同类型的动态节点。一定程度地减少节点本身及其属性的比对。

vue2通过 Object.definedPropertygetset 来做数据劫持、结合和发布订阅者模式来实现,Object.definedProperty会遍历每一个属性。

vue3通过 proxy 代理的方式实现。 通过 reactive函数给每一个对象都包一层 Proxy,通过 Proxy 监听属性的变化,从而实现对数据的监控。

proxy 的优势: 不需要像 Object.definedProperty的那样遍历每一个属性,有一定的性能提升 proxy 可以理解为在目标对象之前架设一层“拦截”,外界对该对象的访问都必须通过这一层拦截。这个拦截可以对外界的访问进行过滤和改写。

当属性过多的时候利用 Object.definedProperty要通过遍历的方式监听每一个属性。利用 proxy 则不需要遍历,会自动监听所有属性,有利于性能的提升

1.defineProperty 只能监听某个属性,不能对全对象监听;可以省去 for in、闭包等内容来提升效率(直接绑定整个对象即可)

2.可以监听数组,不用再去单独的对数组做特异性操作,通过 Proxy 可以直接拦截所有对象类型数据的操作,完美支持对数组的监听。

Vue3 由 TS 重写,相对于 Vue2 有更好地 TypeScript 支持。

Vue3 的 cacheHandler 可在第一次渲染后缓存我们的事件。相比于 Vue2 无需每次渲染都传递一个新函数,加一个 click 事件。

tree-shaking :移除 js 中上下文未引用的代码,主要依赖 import 和 export 语句,用来检测代码模块是否被导入导出,且被 js 文件使用。

一些全局 API 在 vue2中,是暴露在 vue 实例上,即使未用过,也无法通过 tree-shaking 消除

Vue3 中针对全局 和内部的 API 进行了重构,并考虑到 tree-shaking 的支持。因此,全局 API 现在只能作为 ES 模块构建的命名导出进行访问。

通过这一更改,只要模块绑定器支持 tree-shaking ,则 Vue 应用程序中未使用的 api 将从最终的捆绑包中消除,获得最佳文件大小。

Vue3 提供的 createApp 默认是将 template 映射成 html。但若想生成 canvas 时,就需要使用 custom renderer api 自定义 render 生成函数。

js 体验AI代码助手 代码解读复制代码//自定义runtime-render函数 import{createApp}from'./runtime-render'import App from './src/App' createApp(App).mount('#app')

Vue3 提供 Suspense 组件,允许程序在等待异步组件时渲染兜底的内容,如 loading ,使用户体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback 。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。若想在 setup 中调用异步请求,需在 setup 前加 async 关键字。这时,会受到警告 async setup is used without a suspense boundary 。

来源:墨码行者

相关推荐