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

前端八股文 Vue上

文章目录

  • 1.Vue的基本原理
    • 1.2 Vue的优点
  • 2.Vue响应式的原理
    • 2.1 什么是数据劫持?
    • 2.2 发布者模式 / 订阅者模式
    • 2.3 Vue的响应式原理
  • 3. Object.defineProperty的使用方式,有什么缺点
    • 3.2 Object.defineProperty(target, key, options),options可传什么参数?
  • 4. MVVM、MVC、MVP的区别
    • 4.2 MVVM的优缺点?
  • 5. Vue的常用指令及作用
    • 5.2 Vue怎么动态绑定Class 与 Style
  • 6. vue常用的修饰符
  • 7. Vue的内置组件
  • 8. v-if、v-show、v-html 的原理
  • 9. v-show和v-if的区别
  • 10. 为什么避免v-for和v-if在一起使用?
  • 11. v-for 循环为什么一定要绑定key ?
    • 11.2 为什么不建议用index索引作为key?
  • 12. v-model 是如何实现的,语法糖实际是什么?
  • 13. v-model 可以被用在自定义组件上吗?如果可以,如何使用?
  • 14. 计算属性computed 和watch 的区别是什么?
  • 16. 什么是组件
  • 18. 什么是插件
    • 18.2 Vue3怎么注册全局组件
    • 18.3 Vue3怎么封装自定义插件并使用/ Vue.use()
  • 19. 组件通信/ 组件传值的方法
  • 20. Vue的生命周期
    • 20.2 Vue 子组件和父组件执行顺序
    • 20.3 created和mounted的区别
    • 20.4 一般在哪个生命周期请求异步数据
  • 21 说说你对slot插槽的理解?slot使用场景有哪些?
  • 22 $nextTick 原理及作用
  • 23 data为什么是一个函数而不是对象
  • 24 Vue的性能优化(项目优化)有哪些
  • 25 Vue的template模版编译原理
  • 26 对SSR的理解
  • 28 vue初始化页面闪动问题
  • 29 虚拟DOM
  • 30 Diff算法
  • 31 SPA单页面应用

1.Vue的基本原理

当 Vue 实例创建时

Vue 会遍历你在 data 里定义的属性。

  • Vue2 用 Object.defineProperty 给这些属性加上 getter/setter
  • Vue3 改用 Proxy,更强大。

依赖收集
当某个组件用到这个数据时,Vue 就会记录下“谁依赖了它”。
这个记录工作由 Watcher 程序实例 完成。

变化通知
当数据被修改时(setter 触发),Vue 就会通知相关的 Watcher:“数据变了,你得重新算一下”。Watcher 重新计算后,驱动页面重新渲染。

1.2 Vue的优点

  • 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb ;
  • 简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
  • 双向数据绑定:保留了 angular 的特点,在数据操作方面更为简单;
  • 组件化:保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势;
  • 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
  • 虚拟DOM:dom 操作是非常耗费性能的,不再使用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种方式;
  • 运行速度更快:相比较于 react 而言,同样是操作虚拟 dom,就性能而言, vue 存在很大的优势。

2.Vue响应式的原理

2.1 什么是数据劫持?

什么叫“劫持”?

  • 平常我们访问对象属性:
let obj = { name: "Tom" };
console.log(obj.name); // 读
obj.name = "Jerry";    // 写

这时候,JS 就是老老实实地返回或者修改。

  • 数据劫持就是在这里动点小手脚:
    • 你一读 → 我先执行一些操作,再把值给你。
    • 你一写 → 我先执行一些操作,再保存新值。

用 Object.defineProperty 来实现

let person = {};
let nameVal = "Tom";Object.defineProperty(person, "name", {get() {console.log("有人读取了 name");return nameVal;},set(newVal) {console.log("有人修改了 name,变成:", newVal);nameVal = newVal;}
});console.log(person.name); // 触发 get
person.name = "Jerry";    // 触发 set

2.2 发布者模式 / 订阅者模式

在软件架构中,发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
这里很明显了,区别就在于,不同于观察者和被观察者,发布者和订阅者是互相不知道对方的存在的,发布者只需要把消息发送到订阅器里面,订阅者只管接受自己需要订阅的内容

2.3 Vue的响应式原理

Vue响应式的原理就是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:

Observe(被劫持的数据对象)
Compile(vue的编译器)
Wather(订阅者)
Dep(用于收集Watcher订阅者们)

1.把 Vue 实例中 data 里的所有属性(包括嵌套对象的属性)都用 getter/setter 劫持一遍,这样任何层级的数据变化都能被监听到。 给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。

2.模板编译时(Compile 工作),解析模板指令(如 {{ msg }}、v-model),为每个依赖数据的 DOM 节点创建一个更新函数,并在这个过程中 实例化数据的监听者即对应的 Watcher。Watcher 在初始化时也会负责“第一次渲染页面视图”.实例化出的 Watcher 自身存在一个 update()方法,一旦数据发生了变化,Watcher 就是通过自身的 update()方法,通知Compile,更新视图.

3.Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(Dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice() 通知时,能调用自身的update() 方法,并触发Compile中绑定的回调,则功成身退。

4.MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

M (Model):数据对象(被 Observer 劫持)。
V (View):模板/DOM(由 Compile 解析)。
VM (ViewModel):Vue 实例,负责把 M 和 V 连起来。
工作流程串起来就是:
数据改了(M 变化)
Observer 的 setter 被触发 → 通知 Dep → Dep 通知 Watcher → Watcher 执行 update() → Compile 更新 DOM → 视图变了(V 变化)。
视图改了(比如输入框 input 事件)
Compile 监听到 input → 改变数据 Model → 又触发 Observer → 同样的链路执行 → 数据和视图保持一致。
这就是所谓的 双向绑定。
ps:同样的链路执行:触发了上面的 setter 链路:setter → Dep → Watcher → update → DOM 更新

3. Object.defineProperty的使用方式,有什么缺点

基本语法

Object.defineProperty(obj, prop, descriptor)
  • obj:要定义属性的对象。
  • prop:属性名,可以是字符串或 Symbol。
  • descriptor:配置对象,告诉 JS 这个属性怎么表现。

关于descriptor(配置对象)它里面可以写很多选项,最关键的是 get 和 set:
get(getter 函数)

  • 当你 访问属性时 会触发。
  • 不用传参,系统自动帮你调用。
  • 返回值会作为属性的值。
  • this 指向的是访问属性的那个对象(注意可能是继承链上的)。
在这里插入代码片const obj = {};
Object.defineProperty(obj, "foo", {get() {console.log("有人访问 foo");return 123;}
});console.log(obj.foo); // 打印 “有人访问 foo”,再输出 123

set(setter 函数)

  • 当你 修改属性时 会触发。
  • 会接收一个参数,就是你赋的新值。
  • this 指向的是被修改的对象。
const obj = {};
Object.defineProperty(obj, "bar", {set(newVal) {console.log("有人修改 bar 为", newVal);}
});obj.bar = 456; // 打印 “有人修改 bar 为 456”

Object.defineProperty 的缺点:
它只能拦截已经存在的属性的 读写,没法劫持下标变化和 length 改变。有些操作它拦不住:

  • 对象新增属性
  • 数组下标赋值
  • 数组长度变化
对象新增属性
let obj = {};
Object.defineProperty(obj, "a", {get() { return 1; },set(v) { console.log("set", v); }
});obj.b = 2;  // ❌ 新增的 b 不会有 getter/setter,所以 Vue2 监听不到数组下标赋值```javascript
let arr = [1,2,3];
arr[0] = 100; // ❌ 下标方式不会触发 setter数组长度变化
arr.length = 0; // ❌ 无法被监听到

Vue 内部通过重写函数的方式解决了这个问题,内部定义了一个“增强版数组原型”,把 7 个会修改数组的方法重写了一遍:push、pop、shift、unshift、splice、sort、reverse

到了 Vue3,换成 Proxy

let obj = new Proxy({}, {get(target, key) {console.log("get", key);return target[key];},set(target, key, val) {console.log("set", key, val);target[key] = val;return true;}
});obj.a = 1;   // set a 1 ✅
obj.b = 2;   // set b 2 ✅ 新增属性也能监听

Proxy 可以拦截,几乎所有对象的操作都能捕捉到

  • 属性新增 / 删除
  • 数组下标赋值

3.2 Object.defineProperty(target, key, options),options可传什么参数?

  • value:给target[key]设置初始值
  • get:调用target[key]时触发
  • set:设置target[key]时触发
  • writable:规定target[key]是否可被重写,默认false
  • enumerable:规定了key是否会出现在target的枚举属性中,默认为false
  • configurable:规定了能否改变options,以及删除key属性,默认false

enumerable设置为 false 表示,在 for…in 等枚举中拿不到这个属性,configurable设置为 false 表示,不能够删除掉这个属性,也不能修改,但是如果设置过 writable:true 即使configurable设置为 false,也能修改值

例如:

const obj = {};
Object.defineProperty(obj, "a", {value: 1,enumerable: false // ❌ 不可枚举
});Object.defineProperty(obj, "b", {value: 2,enumerable: true  // ✅ 可枚举
});console.log("for...in 遍历结果:");
for (let key in obj) {console.log(key); // 只会输出 b
}console.log("Object.keys(obj):", Object.keys(obj)); 
// 只会输出 ["b"]

4. MVVM、MVC、MVP的区别

(1) MVC
M: model数据模型, V:view视图模型, C: controller控制器
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知View视图更新。

(2) MVP
M: model数据模型, V:view视图模型, P: Presenter 控制器
MVP 模式与 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。这样 View 层和 Model 层耦合在一起,当项目逻辑变得复杂的时候,可能会造成代码的混乱,并且可能会对代码的复用性造成一些问题。
MVP 的模式通过使用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的Controller 只知道 Model 的接口,因此它没有办法控制 View 层的更新,MVP 模式中,View 层的接口暴露给了 Presenter 因此可以在 Presenter 中将 Model 的变化和 View 的变化绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还包含了其他的响应逻辑

(3) MVVM
MVVM 分为 Model、View、ViewModel:

Model代表数据模型,数据和业务逻辑都在Model层中定义;
View代表UI视图,负责数据的展示;
ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;

Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
这种模式实现了 Model和View的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作DOM。

拓展:
Vue 项目目录 和 MVVM 架构 对应
在 Vue 脚手架项目里

src/├─ assets/       # 静态资源├─ components/   # 组件(小 View)├─ views/        # 页面级组件(大 View)├─ store/        # 全局状态管理(Model 的一部分,Vuex/Pinia)├─ router/       # 路由(页面切换逻辑)├─ App.vue       # 根组件└─ main.js       # 入口文件,创建 Vue 应用(ViewModel)

对应关系:

  • Model:store/(Vuex/Pinia 的 state),或者每个组件里的 data()、props。
  • View:.vue 文件里的 部分(HTML 模板)。
  • ViewModel:Vue 框架本身(它代理数据、劫持 getter/setter,负责渲染 DOM 和响应更新)。

4.2 MVVM的优缺点?

优点:
分离视图(View)和模型(Model),降低代码耦合,提⾼视图或者逻辑的重⽤性: ⽐如视图(View)可以独⽴于Model变化和修改,⼀个ViewModel可以绑定不同的"View"上,当View变化的时候Model不可以不变,当Model变化的时候View也可以不变。你可以把⼀些视图逻辑放在⼀个ViewModel⾥⾯,让很多view重⽤这段视图逻辑
提⾼可测试性: ViewModel的存在可以帮助开发者更好地编写测试代码
⾃动更新dom: 利⽤双向绑定,数据更新后视图⾃动更新,让开发者从繁琐的⼿动dom中解放

缺点:
Bug很难被调试: 因为使⽤双向绑定的模式,当你看到界⾯异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得⼀个位置的Bug被快速传递到别的位置,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的
⼀个⼤的模块中model也会很⼤,虽然使⽤⽅便了也很容易保证了数据的⼀致性,当时⻓期持有,不释放内存就造成了花费更多的内存
对于⼤型的图形应⽤程序,视图状态较多,ViewModel的构建和维护的成本都会⽐较⾼

5. Vue的常用指令及作用

  • v-on 给标签绑定函数,可以缩写为@,例如绑定一个点击函数 函数必须写在methods里面
  • v-bind 动态绑定 作用: 及时对页面的数据进行更改, 可以简写成:冒号
  • v-slot: 缩写为#, 组件插槽
  • v-for 根据数组的个数, 循环数组元素的同时还生成所在的标签
  • v-show 显示内容
  • v-if 显示与隐藏
  • v-else 必须和v-if连用 不能单独使用 否则报错
  • v-text 解析文本
  • v-html 解析html标签

5.2 Vue怎么动态绑定Class 与 Style

动态绑定 Class (v-bind:class)

<div v-bind:class="{ '类名': bool, '类名': bool ......}"></div>

核心思想:
v-bind:class 指令(可以简写为 :class)后面跟着一个 JavaScript 对象。

  • 对象的键 (key): 是一个字符串,代表你想要应用的 CSS 类名。
  • 对象的值 (value): 是一个布尔值 (true 或 false),通常来自你 Vue 实例的 data 中。

示例如下:

data: {isActive: true,hasError: false
}
<div v-bind:class="{ 'active': isActive, 'text-danger': hasError }">这是一个示例文本
</div>
<div class="active">这是一个示例文本
</div>

动态绑定 Style (v-bind:style)

<div v-bind:style="styleObject"></div>data: {styleObject: {color: 'red',fontSize: '13px'}
}

6. vue常用的修饰符

v-on

  • .stop - 调用 event.stopPropagation()。 阻止默认事件
  • .prevent - 调用 event.preventDefault()。阻止默认行为
  • .native - 监听组件根元素的原生事件。

v-bind

  • .prop - 作为一个 DOM property 绑定而不是作为 attribute 绑定。
  • .camel - (2.1.0+) 将 kebab-case attribute 名转换为 camelCase。(从 2.1.0 开始支持)
  • .sync (2.3.0+) 语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器。

v-model

  • [.lazy]- 取代 input 监听 change 事件
  • [.number] - 输入字符串转为有效的数字
  • [.trim] - 输入首尾空格过滤

7. Vue的内置组件

component
渲染一个“元组件”为动态组件。依 is 的值,来决定哪个组件被渲染在一个多标签的界面中使用 is attribute 来切换不同的组件:tap栏切换

transition
用于在 Vue 插入、更新或者移除 DOM 时, 提供多种不同方式的应用过渡、动画效果。
transition-group
<transition-group> 用于给列表统一设置过渡动画。

keep-alive
主要用于保留组件状态或避免组件重新渲染。
include 属性用于指定哪些组件会被缓存,具有多种设置方式。
exclude 属性用于指定哪些组件不会被缓存。
max 属性用于设置最大缓存个数。

slot
name - string,用于命名插槽。
< slot> 元素作为组件模板之中的内容分发插槽。< slot> 元素自身将被替换。

8. v-if、v-show、v-html 的原理

补充:

Vue 的工作流程简化为两步:
-编译 (Compile):Vue 把你的模板(.vue 文件里的 <template>)转换成一个渲染函数 (Render Function)。VNode 是在组件的 渲染函数 (Render Function) 被执行时

渲染 (Render):执行这个渲染函数,生成虚拟 DOM (Virtual DOM, 简称 VNode),然后 Vue 再根据 VNode 去创建或更新真实的 DOM。

v-if会调用addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染;

v-show会生成vnode,render的时候也会渲染成真实节点,只是在render过程中会在节点的属性中修改show属性值,也就是常说的display;

v-html会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。

9. v-show和v-if的区别

  • v-show和v-if的区别? 分别说明其使用场景?

  • 相同点: v-show 和v-if都是true的时候显示,false的时候隐

  • 不同点1:原理不同

    • v-show:一定会渲染,只是修改display属性
    • v-if:根据条件渲染
  • 不同点2:应用场景不同

    • 频繁切换用v-show,不频繁切换用v-if

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建,操作的实际上是dom元素的创建或销毁。

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

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好

10. 为什么避免v-for和v-if在一起使用?

Vue 处理指令时,v-for 比 v-if 具有更高的优先级, 虽然用起来也没报错好使, 但是性能不高, 如果你有5个元素被v-for循环, v-if也会分别执行5次.

11. v-for 循环为什么一定要绑定key ?

1.vue在渲染的时候,会 先把 新DOM 与 旧DOM 进行对比, 如果dom结构一致,则vue会复用旧的dom。 (此时可能造成数据渲染异常)
2.使用key可以给dom添加一个标识符,让vue强制更新dom

不写 key → Vue 默认复用,容易出 bug。

  • key 的作用主要是为了更高效的更新虚拟 DOM,因为它可以非常精确的找到相同节点,因此 patch 过程会非常高效
  • Vue 在 patch 过程中会判断两个节点是不是相同节点时,key 是一个必要条件。比如渲染列表时,如果不写 key,Vue 在比较的时候,就可能会导致频繁更新元素,使整个 patch 过程比较低效,影响性能

11.2 为什么不建议用index索引作为key?

举个例子来说,假如用 index 作为 key,index 是一直变化的,就会导致,Vue 错误的把新旧节点弄混淆,比如 index=0 是张三,输入框是张三的笔记,假如删除掉张三,李四就变成 index=0 了,此时 Vue 发现 index=0 还在,那就复用 index=0 的 dom 节点,但是遗留了张三的笔记,张三的笔记还在旧的 dom 节点上,只有 key 绑定 id,才能不把新旧节点弄混淆

 <ul><li v-for="(user, index) in users" :key="index">{{ user.name }} <input type="text"></li>
</ul>
<button @click="deleteFirstUser">删除第一个用户</button>
const users = ref([{ id: 101, name: '张三' },{ id: 102, name: '李四' },{ id: 103, name: '王五' }
])//删除第一条数据
const deleteFirstUser = () => {// 当 users 是一个 ref 时,需要通过 .value 访问和修改其值。users.value.shift();
};

12. v-model 是如何实现的,语法糖实际是什么?

  1. v-model 在表单元素上的实现
<input v-model="message" />

等价于

<input :value="message" @input="message = $event.target.value" />

其中 $event,代指当前触发的事件对象
$event.target,指代当前触发的事件对象的 dom;
$event.target.value 就是当前 dom 的 value 值

  1. v-model 在组件上的实现

假设我们有一个子组件:

<MyInput v-model="msg" />

本质等价于:

<MyInput :modelValue="msg" @update:modelValue="msg = $event" />

@事件名=“表达式”,就是“监听子组件发的事件”。
@update:modelValue其中,update:modelValue是事件名

父组件:

<MyInput :modelValue="msg" @update:modelValue="msg = $event" />

子组件:

<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script><template><input:value="props.modelValue"@input="emit('update:modelValue', $event.target.value)" />
</template>

defineProps → 声明子组件可以接收的 props(父传子)。
defineEmits → 声明子组件可以触发的 事件(子传父)。

父传的值叫 modelValue,子要告诉父更新,就必须触发一个事件:update:modelValue

13. v-model 可以被用在自定义组件上吗?如果可以,如何使用?

父组件:

<MyInput v-model="msg" /> 不一定非得是 msg,可以自定义名称叫什么都可以,对应的改变绑定 modelValue 是不同的

Vue 会自动转换成:

<MyInput :modelValue="msg" @update:modelValue="msg = $event" />

14. 计算属性computed 和watch 的区别是什么?

特点computedwatch
是否缓存支持缓存,只在依赖变化时重新计算不支持缓存,每次变化都触发回调
是否支持异步不支持异步,computed 中有异步操作时,无法监听数据变化支持异步监听
使用场景依赖别的值,得到一个新值(比如全名、总价)依赖别的值,做一些操作(比如发请求、打印日志)
返回值有返回值,直接当属性用没返回值,执行副作用逻辑
触发时机获取它的值时,如果依赖没变,直接走缓存只要依赖变,就执行回调,不管你用不用这个值

如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。

//若属性值是函数值
const fullName = computed(() => {return firstName.value + ' ' + lastName.value
})//若属性值是对象
const fullName = computed({get() {return firstName.value + ' ' + lastName.value},set(newValue) {const parts = newValue.split(' ')firstName.value = parts[0]lastName.value = parts[1]}
})

监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数:

  • immediate:组件加载立即触发回调函数
  • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。
watch(user, (newVal, oldVal) => {console.log('深度监听到内部变化')
}, { deep: true })

16. 什么是组件

组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件
组件的优势

降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求
调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级

18. 什么是插件

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

添加全局方法或者属性。如: vue-custom-element
添加全局资源:指令/过滤器/过渡等。如 vue-touch
添加全局公共组件 Vue.component()
添加全局公共指令 Vue.directive()
通过全局混入来添加一些组件选项。如vue-router
添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如vue-router

18.2 Vue3怎么注册全局组件

写在 main.js 中

import { createApp } from 'vue'
import App from './App.vue'
import MyButton from './components/MyButton.vue'const app = createApp(App)app.component('MyButton', MyButton)  // 注册全局组件app.mount('#app')

18.3 Vue3怎么封装自定义插件并使用/ Vue.use()

在compoents.index.ts里,定义一个函数或对象,在里面可以使用app.compoent全局注册组件,并暴露出去

在main.ts里使用app.use( ),参数类型必须是 object 或 Function

例子如下:

在 components/index.ts 里:

import { App } from 'vue'
import MyButton from './MyButton.vue'const MyPlugin = {install(app: App) {// 全局组件app.component('MyButton', MyButton)// 全局方法app.config.globalProperties.$hello = (name: string) => {console.log('你好,' + name)}}
}export default MyPlugin

在 main.ts 里:

import { createApp } from 'vue'
import App from './App.vue'
import MyPlugin from './components/index.ts'const app = createApp(App)app.use(MyPlugin)   // 安装插件
app.mount('#app')

假如是现成的插件

//Vue3 的 Element Plusimport { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'const app = createApp(App)
app.use(ElementPlus)   // 安装插件
app.mount('#app')

19. 组件通信/ 组件传值的方法

这里介绍 Vue3 组件通信,父传子 子传父 ,祖孙跨层级通信及 pinia 全局管理

20. Vue的生命周期

vue的声明周期常见的主要分为4大阶段8大钩子函数

第一阶段:创建前 / 后
beforeCreate(创建前) :数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染的节点还未挂载到 DOM,所以不能访问到 $el 属性。

第二阶段: 渲染前 / 后
beforeMount(挂载前) :在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
mounted(挂载后) :在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。

第三阶段: 更新前 / 后
beforeUpdate(更新前) :响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

第四阶段: 销毁前 / 后
beforeDestroy(销毁前) :实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。
destroyed(销毁后) :实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

beforeCreate:初始化事件等都没开始,data,computed,watch,methods 都不能访问
created:data,computed,watch,methods都配置完成,但是渲染的节点(虚拟 dom 节点)还未挂载到 DOM 中,所以访问不到$el属性
beforeMount :render函数首次被调用,把 data 里面的数据和模板生成html,此时还没有挂载到页面上
mounted:真实的 DOM节点被创建的虚拟 DOM 节点替换,挂载到实例上调用,完成模版的渲染到 html 页面中
beforeUpdate:响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染
updated:真实的 DOM 根据响应式的数据变化更新
beforeDestroy:实例销毁前,当前实例仍然可用,this 仍能获取到实例
destroyed:实例销毁后调用,Vue 实例中所有的东西都会解绑定,所有的事件监听器都会被消除,子实例也会被销毁

20.2 Vue 子组件和父组件执行顺序

加载渲染过程:
父组件 beforeCreate
父组件 created
父组件 beforeMount
子组件 beforeCreate
子组件 created
子组件 beforeMount
子组件 mounted
父组件 mounted

更新过程:
父组件 beforeUpdate
子组件 beforeUpdate
子组件 updated
父组件 updated

销毁过程:
父组件 beforeDestroy
子组件 beforeDestroy
子组件 destroyed
父组件 destoryed

20.3 created和mounted的区别

created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

20.4 一般在哪个生命周期请求异步数据

我们可以在钩子函数created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

  • 能更快获取到服务端数据,减少页面加载时间,用户体验更好;
  • SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。

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

插槽(slot)就是 子组件里的“占位口”,由 父组件来决定填什么内容。
它体现了 Vue 的 内容分发机制::子组件决定“位置”,父组件决定“内容”

子组件,通过设置 slot 的位置,决定了我们所看到的页面的排布,所以是子组件决定位置
父组件提供填充进子组件中的数据,哪怕是作用域插槽,虽然是子组件提供的数据,但本质还是父组件决定内容

  • 默认插槽

<!-- Dialog.vue -->
<div class="dialog"><slot>这是默认内容</slot>
</div><!---->
<Dialog> <p>我是父组件传的内容</p> </Dialog>
  • 具名插槽
<!---->
<!-- Layout.vue -->
<header><slot name="header"></slot></header>
<main><slot></slot></main>
<footer><slot name="footer"></slot></footer><!---->
<Layout><template #header>导航栏</template><div>主体内容</div><template #footer>底部信息</template>
</Layout>
  • 作用域插槽
<!-- 子组件 UserList.vue -->
<ul><li v-for="u in users" :key="u.id"><slot :user="u"></slot></li>
</ul>
<script setup>
const users = [{ id: 1, name: '小明', age: 20 },{ id: 2, name: '小红', age: 22 }
]
</script>
<!-- 父组件 -->
<UserList><template #default="{ user }"><!-- 父组件决定渲染方式 --><b>{{ user.name }}</b> ({{ user.age }})</template>
</UserList>

22 $nextTick 原理及作用

$nextTick 是什么?
Vue 的一个异步方法,等到 DOM 更新完成后再执行回调。
为什么需要 $nextTick?
Vue 的 数据驱动视图更新过程是 异步的:

  • 你修改了 data。
  • Vue 不会立刻更新 DOM,而是把更新放进 异步队列里,等当前事件循环结束后再统一更新(批量处理,性能更好)。
  • 所以如果你在数据更新后立刻访问 DOM,会拿到旧 DOM。
  • $nextTick 就是告诉 Vue:“等你把 DOM 更新完,再来执行我这段代码”。

👉 常见场景:

  • 修改数据后要获取最新的 DOM。
  • 数据更新后立刻调用依赖 DOM 的第三方库(图表、滚动条)。

样例如下:

<template><div><p ref="msgRef">{{ msg }}</p><button @click="updateMsg">更新消息</button></div>
</template><script setup>
import { ref, nextTick } from 'vue'const msg = ref('初始文本')
const msgRef = ref(null)function updateMsg() {// 1. 修改数据msg.value = '更新后的文本'// 2. 立刻访问 DOM —— 还是旧的console.log('立即访问 DOM:', msgRef.value.innerText)// 3. 等待 DOM 更新后再访问nextTick(() => {console.log('nextTick 后访问 DOM:', msgRef.value.innerText)})
}
</script>

23 data为什么是一个函数而不是对象

在组件中,data 必须是一个函数而不是对象,因为组件是可复用的。
如果用对象,所有组件实例会共享这一个对象的引用,修改一个实例的数据会影响到其他实例。
用函数返回新对象,每个对象指向的不同的内存.

24 Vue的性能优化(项目优化)有哪些

(1)编码阶段

尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载

(2)SEO优化

预渲染
服务端渲染SSR

(3)打包优化

压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap优化

(4)用户体验

骨架屏 PWA 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

25 Vue的template模版编译原理

26 对SSR的理解

28 vue初始化页面闪动问题

Vue 里常见的 初始化闪动问题(模板闪烁、花屏)

问题表现:

在 Vue 还没完成初始化时,页面上只是普通 HTML。
模板里的 {{ message }} 等插值表达式会直接显示在页面上。
结果就是:用户会短暂看到一堆 {{xxx}},然后再被 Vue 替换掉。
这就是所谓的 “页面闪动 / 花屏”。

为什么会这样:

Vue 初始化流程:
1.浏览器先加载 HTML + CSS,渲染出 DOM(包括 {{ message }} 这种模板语法)。
2.Vue 脚本加载并执行,开始编译模板,挂载实例。
3.Vue 把虚拟 DOM 渲染成真实内容,替换掉 {{ message }}。
在 步骤1 → 步骤3 之间,就会短暂暴露出原始模板。

常见解决方法
方法一:v-cloak + CSS
Vue 提供了一个特殊属性 v-cloak,在实例挂载完毕后会自动移除。

<div id="app" v-cloak>{{ message }}
</div>
[v-cloak] {display: none;
}

方法二:内联 display:none,Vue 初始化后显示

<div id="app" style="display:none;" :style="{ display: 'block' }">{{ message }}
</div>
  • 一开始隐藏(style=“display:none;”)。
  • Vue 接管后,通过绑定的 :style 显示出来。
    ⚠️ 缺点:容易闪整个页面空白,不如 v-cloak 优雅

29 虚拟DOM

什么是虚拟 DOM(VNode)?
本质:一个用 JavaScript 对象 来描述真实 DOM 节点的抽象。

作用:作为 JS 和真实 DOM 中间的缓存层,配合 Diff 算法来高效更新。

每个 VNode 通常包含三个关键属性:

tag:节点的类型(如 div、span、组件名)。

props:节点的属性、事件(class、style、onClick 等)。

children:子节点(可以是字符串 → 文本节点,或数组 → 子 VNode)。

👉 就是把 DOM “翻译”成一个普通的 JS 对象。

虚拟 DOM 的更新流程(Vue 的典型实现)

真实 DOM 是浏览器的对象,不适合做高频计算。
虚拟 DOM 是轻量的 JS 对象,适合高效 Diff。
流程就是
1.新旧虚拟 DOM 对比 → 标记差异
2.Patch → 一次性更新真实 DOM(对真实的 DOM 把差异更新)

详细流程:
1.创建虚拟 DOM → 挂载真实 DOM

  • 根据模板或 JSX,生成一棵 VNode 树。
  • 根据 VNode 树,构建真实 DOM,并挂载到页面上。
    2.数据变化 → 生成新虚拟 DOM
  • Vue 监听数据(state)变化。
  • 数据变了,重新生成一棵新的 VNode 树。
    3.Diff 比较 → Patch 更新
  • 用新 VNode 树和旧 VNode 树对比(Diff,通常是同层比较)。
  • 找出差异(增/删/改/移)。
  • 最后把差异 Patch 到真实 DOM 上。

30 Diff算法

Diff 算法为什么需要?

理论上,比较两棵树的最小差异是一个 O(n³) 的复杂问题(经典“树编辑距离”问题)。
对前端 DOM 来说,O(n³) 根本不可接受(n=1000 时已经算不动)。
Vue 采用了 启发式策略:只比较同层级节点,不做跨层比较 → 复杂度降低为 O(n)。

Vue2 Diff 算法流程

在更新时,新旧虚拟 DOM 树进行对比,大致有三步:
(1) 判断是否为同一节点(sameVnode)
比较 key、tag、是否为注释节点等。
如果不是同一个节点 → 直接删掉旧的,重新创建新的。

(2) patchVnode(更新节点本身)
如果是同一个节点,继续比较:

  • 属性(class、style、事件监听器)是否更新 → patchProps。
  • 子节点情况:
    • 新 children 有,旧 children 没有 → 新增子节点。
    • 新 children 没有,旧 children 有 → 删除子节点。

新旧都有 children → 进入 updateChildren。

(3) updateChildren(核心)

采用“双端指针”算法对比新旧子节点数组。

31 SPA单页面应用

SPA 是单页面应用,页面只有一个主 HTML,切换依靠前端路由和组件切换完成。
优点是体验好、响应快,但缺点是首屏加载慢、SEO 不友好。
优化首屏加载的方式有:减少入口文件体积、静态资源缓存、UI 框架按需加载、路由懒加载。
SEO 问题一般用 SSR 或预渲染来解决。

http://www.dtcms.com/a/418272.html

相关文章:

  • OpenHarmony SELinux全面技术指南:从原理到实践的系统安全防护(全网最全)
  • 分布式计算的集大成时刻:从技术烟囱到架构交响乐
  • 嘉兴网站建设网址织梦博客网站模板
  • 网站备案查询工信部官网泵网站建设
  • 香港科技大学工学院2026/2027年度研究生课程招生宣讲会-厦门大学专场
  • 基于Hadoop+Spark的商店购物趋势分析与可视化系统技术实现
  • 中科亿海微SoM模组——国产散热控制板
  • display ospf peer brief 概念及题目
  • verilog中的FIR滤波器和自控中一阶低通滤波器的区别和共性
  • 网络技术进阶:详解 /31 位掩码的六大常见问题
  • 【FPGA+DSP系列】——(2)DSP最小核心板进行ADC采样实验(采集电位器输出电压)
  • 青岛网站推广怎么做好西安建设工程信息网平台变更
  • XA7A75T-1FGG484Q 赛灵思 Xilinx AMD Artix-7 XA 系列 FPGA
  • 基于Hadoop+Spark的人体体能数据分析与可视化系统开源实现
  • 麒贺丝网做的网站优化pscc下载
  • OpenLayers地图交互 -- 章节十七:键盘缩放交互详解
  • ubuntu中卸载软件的几种方法
  • 网站建设与规划实验报告网站建设平台排名
  • rust徒手编写模拟tokio异步运行时
  • 【mdBook】4.5 test 命令
  • 在网站后台备案号怎么改商城网站建设视频教程
  • 漏洞修复 CentOS x86_64 OpenSSH 升级操作文档
  • HarmonyOS 地图手势操作全解析
  • 生态碳汇涡度相关监测与通量数据分析
  • Android-kotlin MVVM框架搭建+Retrofit二次封装
  • QML学习笔记(十八)QML的信号处理器的Connections写法
  • Spring Cloud Gateway 实战:全局过滤器日志统计与 Prometheus + Grafana 接口耗时监控
  • CTFHub RCE通关笔记7:命令注入 过滤cat(9种渗透方法)
  • Kotlin Value Class 全面解析:类型安全与零开销封装
  • 【Android】kotlin.flow简介