1: data为什么是一个函数不是一个对象
对象是引用数据类型,如果多个实例引用同一个对象,其中一个实例对这个对象进行操作,其他实例当中的数据也会发生变化,封装组件的目的是为了复用,所以为了组件之间的数据互不影响,数据以函数返回值的形式返回,每次调用组件都将返回一个新的data,每个组件维护自己的数据,不干扰其他组件的正常运行
2: keep-alive的作用,如何实现缓存?缓存的是什么?
keep-alive 的作用:
- 性能优化:通过缓存组件实例,避免重复渲染和初始化,从而提高页面切换的性能。
- 保持组件状态:缓存的组件实例会保持其状态,即使组件从 DOM 中移除也不会丢失状态。
如何实现缓存:
Vue.js 通过内部维护一个缓存列表来实现 keep-alive 的功能。当组件被 keep-alive 包裹时,Vue 会检查缓存列表,如果组件已经存在,则直接从缓存中取出并复用;如果组件不存在,则创建一个新的实例并将其添加到缓存列表中。
缓存的是什么:
keep-alive 缓存的是组件的实例。这意味着组件的状态(包括 data、computed、watch 等)会被保留,但生命周期钩子函数(如 created、mounted 等)不会被重新调用。当组件再次被激活时,Vue 会跳过创建和挂载阶段,直接从缓存中取出组件实例并重新渲染。
3: $nextick的原理及作用
原理:
在Vue.js中,当数据发生变化时,Vue会异步地更新DOM。这意味着当你修改数据后,视图不会立即更新,当数据变化时,Vue会将这个变化放入一个异步队列中。然后,在当前的同步代码执行完毕后,Vue会在事件循环的下一个“tick”中执行这个队列中的变化,并更新DOM。这个过程中,Vue使用了浏览器的异步任务队列(如Promise的microtask队列或MutationObserver的callback等)来实现异步更新。
作用:
nextTick方法的主要作用是确保在DOM更新完成后执行某些操作。由于Vue的DOM更新是异步的,直接在修改数据后访问DOM元素可能获取到的是更新前的状态。因此,当你需要在数据变化后等待DOM更新完成再执行某些操作时,就可以使用nextTick方法。
$nextTick方法接受一个回调函数作为参数,并将这个回调函数放入一个队列中。当DOM更新完成后,Vue会执行这个队列中的回调函数。这样,你就可以在回调函数中安全地访问和操作更新后的DOM元素了。
使用场景:
$nextTick在Vue.js中常用于以下几种场景:
- 在数据变化后需要访问DOM元素时,使用$nextTick确保DOM已经更新。
- 在需要等待某个异步操作(如网络请求)完成后执行某些操作时,也可以使用$nextTick。
- 在处理与视图更新相关的逻辑时,使用$nextTick可以避免由于DOM更新延迟而导致的问题。
4: data中的属性值发生改变,视图会立即重新渲染吗?
在Vue中,当data中的属性值发生改变时,视图并不会立即重新渲染。Vue 实现了一种异步队列更新机制,当数据发生变化时,Vue 会开启一个队列,并将所有的数据变化都缓存到这个队列中。如果同一个 watcher 被多次触发,只会被推入到队列中一次。然后,在下一个事件循环 “tick” 中,Vue 刷新队列并执行实际(已去重的)工作。
这种在缓冲期间去重和异步执行更新的机制有两个好处:
- 避免不必要的计算和 DOM 操作。例如,你可能有十次数据改变,但实际上只需要一次渲染,因为最后一次数据改变可能覆盖了前面九次的结果。
- 在同一堆栈帧内允许更改多个依赖项,而无需立即重新计算或重新渲染
5: 子组件可以直接修改父组件的数据吗?
在Vue.js中,子组件不应该直接修改父组件的数据。这是因为Vue.js的数据流是单向的,从父组件流向子组件。如果子组件需要修改数据,它应该通过事件(events)来通知父组件,然后由父组件来修改数据。
这种设计有几个原因:
- 组件独立性:如果子组件能够直接修改父组件的数据,那么子组件的行为将变得难以预测和控制,这将破坏组件的独立性和可复用性。
- 数据流向清晰:单向数据流使得数据的来源和去向更加清晰,有助于减少bug和调试的困难。
- 易于维护:当数据变化时,由于我们知道数据只能从父组件流向子组件,我们可以更容易地追踪和理解这些变化是如何影响整个应用的。
因此,在Vue.js中,如果你需要在子组件中修改数据,你应该这样做:
- 在子组件中定义一个方法,这个方法会触发一个自定义事件,并将需要修改的数据作为事件的参数。
- 在父组件中,使用
v-on或@监听这个自定义事件,并在事件处理函数中修改父组件的数据。
6: static和assets的区别
在Vue项目中,static和assets是两个用于存放静态资源的文件夹,但它们在使用和处理方式上存在一些关键的区别。
- 打包和处理:
- assets:存放在这个文件夹中的静态资源,如图片、CSS、JavaScript文件等,在项目打包(如使用
npm run build)时会被webpack处理。这意味着这些资源会经过压缩、格式化等优化步骤,以减小最终打包文件的大小,从而提高加载效率。压缩后的静态资源文件最终会被放置在打包后的目录中,与index.html一同上传至服务器。 - static:与
assets不同,static文件夹中的静态资源不会经过webpack的打包和处理流程。这些文件会直接被复制到最终的打包目录(默认是dist/static)下。因此,它们的体积可能相对较大,占用的服务器空间也较多。但避免了打包处理的步骤,可以在一定程度上提高打包效率。
- 引用方式:
- assets:由于
assets中的资源会经过webpack处理,因此在代码中引用这些资源时,路径需要经过webpack的file-loader或其他加载器的编译。这意味着你不能直接使用绝对路径来引用assets中的文件,而需要使用相对路径或webpack提供的特殊语法。 - static:
static文件夹中的文件则可以使用绝对路径来引用。这是因为它们不会被webpack处理,所以你可以直接通过文件的实际位置来引用它们。
- 使用建议:
- 通常建议将项目中template需要的CSS文件、JavaScript文件等放置在
assets文件夹中,以便利用webpack的打包和优化功能。这样可以减小最终打包文件的大小,提高加载速度。 - 而对于某些不需要经过打包处理的文件,如某些第三方库或特定格式的媒体文件,可以放置在
static文件夹中。
7: mixin
8: vue模板编译原理
模板编译的主要步骤
1. 解析模板: 将模板字符串转化为 AST(抽象语法树)。
2. 优化 AST: 遍历 AST,标记静态节点,用于后续优化渲染性能。
3. 生成代码: 根据 AST 生成渲染函数的字符串形式。
4. 创建渲染函数: 使用 new Function 将字符串形式的渲染函数转化为可执行的 JavaScript 函 数。
5. 执行渲染函数: 运行渲染函数,得到虚拟 DOM。
9: 使用vue框架的时候,有哪些性能可以进行优化
(1)编译阶段
- v-for和v-if不要连用
- v-for设置key值
- 如果需要使用v-for给每一项元素绑定事件,可以使用事件代理:将事件代理到父节点,减少内存占用率;动态生成子节点时,并不会影响父节点的代理事件
- 组件懒加载:懒加载的意义在于按需加载
- vue路由设置为懒加载:首屏只需要加载需要的资源,大大加快首屏渲染
- 单页应用可以使用keep-alive缓存组件
- 不要将所有的数据都放在data中:data中的数据会增加getter和setter,又会收集对应的watcher,造成不必要的开销,把不需要响应式的数据可以定义在实例上
- 使用cdn的方式加载外部资源:比如vue-router,axios等周边资源
10: 组件name的作用?
方便组件递归(组件自己调用自己)
可以通过name实现缓存功能(keep-alive)
可以通过name识别组件
方便vue-tool工具调试
11: computed和watch的区别?
(1)computed
1.支持缓存,依赖的数据发生变化时,才会重新计算
2.不支持异步,如果computed中有异步操作,就无法监听数据变化
3.computed的属性值为函数,则函数的返回值为属性的属性值,
4.如果一个属性值由其他计算属性而来,这个属性依赖的其他属性,一般也会使用computed
(2)watch
1.不支持缓存,数据发生变化时,就会触发操作
2.支持异步监听
3.监听的数据来自data或者父组件传递过来的props,当发生变化时,会触发操作,函数有两个参数: immediate:表示是否要在第一次渲染时就执行此函数;deep:用于判断data对象中某个key的值是否发生变化,因为当某个元素中某个key的值变化时,vue可能认为该对象的值没有发生变化,因为该对象的内存地址没有变化
12: vue3.0相比于vue2.0的变化?
(1)vue3.0
1.支持多个根节点
2.组合式API(Composition API),允许使用函数来创建组件,可以把组件上的逻辑拆分成更小,可复用性更高的代码单元,有利于组件代码的重组和复用
3.Vue3.0提供异步组件Suspense,允许程序在等待异步加载的时候做其他内容渲染,如loading,使得用户体验更流畅,需在模板中声明两个命名插槽,default和fallback,异步加载完成显示default插槽,将fallback插槽作为加载状态
4.Vue3.0的响应式系统,主要通过Proxcy代理,拦截对象上的一些操作,比如:读取,修改,删除,新增,从而实现更加灵活的响应式更新
5.Vue3.0中的diff算法采用了编译时的动态标记,并使用静态分析技术来确定哪些是静态的节点,哪些是动态的节点,这样只需要对动态节点进行比较,避免了对静态节点的不必要操作,从而提高了性能
(2)vue2.0
1.不支持多个根节点
2.选项式API(Options API),变量及代码逻辑分布在不同的选项内容中,逻辑比较分散
3.Vue2.0的响应式系统,主要通过Object.defineProperty方法实现的,每个数据的属性被定义成可观察的,具有getter和setter方法,Vue会自动追踪和重新计算渲染函数,这种方法的缺点在于,无法扩展属性和删除属性,所以无法对新增的属性进行响应化
4.在Vue2.0中diff算法,是通过比较新旧虚拟DOM树来确定需要更新的DOM元素,在这个过程中涉及到DOM树的遍历与对比,非常消耗性能,尤其是在更新大量数据的时候,会有性能瓶颈
13: vue有哪些修饰符?
-.lazy:改变输入框的值时,value不会改变,当光标离开输入框时,v-model绑定的value值才会改变
-.trim:过滤v-model中的值的首尾空格
-.number:将值转换为数字,先输入数字,只取数字部分,字母部分舍弃,如果先输入字母,.number无效
-.stop:阻止时间冒泡
-.capture:阻止事件捕获
-.self:点击事件绑定的本身才会触发
-.once:事件只执行一次
-.prevent:阻止浏览器的默认事件(如:a标签的跳转)
-.passive:在监听元素滚动事件的时候,会一直触发onscroll事件,在移动端,会让我们的页面很卡,因此使用.passive修饰符,相当于给onscroll事件添加了一个.lazy修饰符
14: 设置动态class和动态style的方式?
(1)动态设置class
:class="['color','shape']" (数组语法)
:class="['color',flag?'yellow':'blue']" (数组中使用三元表达式)
:class="['color','shape',{'isActive':flag}]" (数组中使用对象)
:class="{color:flag,shape:true}" (直接使用对象)
(2)动态设置style
:style="{color:textColor,fontSize:'20px'}" (对象语法)
:style="[{color:textColor,fontSize:'20px'},{fontWeight:'400'},isActive]" (数组语法)
15: v-if和v-show的区别?
(1)实现方式
-v-if:是一种条件渲染指令,当表达式的值为true时,元素会被插入到DOM中,为false时,元素会被从DOM中移除掉
-v-show:是一种简单的显示/隐藏指令,当表达式的值为true时,元素显示,为false时,元素隐藏
(2)性能不同
-v-if:由于v-if是从DOM中添加或者删除元素,有一个局部编译卸载的过程,所以每次切换时,v-if都需要重新渲染元素及其所有子元素,因此,他对于频繁切换的元素具有较高的开销
-v-show:v-show不会从DOM中删除元素,而是使用css中的display属性来控制元素的显示和隐藏,因此,v-show对于需要频繁切换的元素更友好,不需要重新渲染
16: vue3.0和vue2.0生命周期的区别?
| Vue2.0 | Vue3.0 |
|---|---|
beforeCreate (组件实例被创建之前调用,data属性未初始化,此时不能访问组件实例和属性,可以做一些异步操作准备) | setup()的执行顺序是在beforeCreate之前的 |
created (组件实例已创建,属性已绑定,可以使用实例和data属性,DOM未生成) | setup() |
beforeMount (在组件挂载之前调用,DOM未生成,不能进行DOM操作) | onBeforeMount |
mounted (在组件被挂载后调用,生成DOM,可以操作DOM) | onMounted |
beforeUpdate (数据更新时调用,在虚拟DOM重新渲染之前) | onBeforeUpdate |
updated (虚拟DOM重新渲染,组件DOM已更新) | onUpdated |
beforeDestroy(组件销毁之前调用,实例仍可访问) | onBeforeUnmount |
destroyed (组件销毁之后调用,vue实例的所有东西都会解绑,所有的事件监听器会被移除,所有的子实例会被销毁) | onUnmounted |
setup是组合式API的入口函数,可以在里面定义变量和方法(将数据和业务逻辑联系的更加紧密),通过对象的形式返回暴露出去
17: vue中不需要响应化的数据该怎么处理?
(1)将数据定义在data之外
(2)在created和mounted钩子函数中定义
(3)自定义Options
(4)Object.freeze: 冻结对象,禁止对该对象的属性进行修改,这个方法返回传递的对象,而不是被冻结的副本;冻结对象为浅冻结,对对象的对象不起作用
<templete>
<div>{{mydiv}}<div>
<div>{{$options.myOptions.mydiv}}<div>
</templete>
<script>
//方法一:
const mydiv= 'Hello World'
export default{
data(){
.....
},
created(){
//方法二:注意data中不要声明该变量
this.mydiv='Hello World'
},
myOptions:{
//取值时:this.$options.myOptions.mydiv
mydiv:'Hello World'
}
}
</script>
17: vue中父子组件的生命周期?
创建过程自上而下,挂载过程自下而上,因为vue的创建过程是一个递归的过程,先创建父组件,有子组件再创建子组件
(1)加载渲染过程:先父后子
父beforeCreate --> 父created --> 父beforeMount --> 子beforeCreate --> 子created --> 子beforeMount --> 子mounted --> 父mounted
(2)子组件更新过程
父beforeUpdate --> 子beforeUpdate --> 父update --> 子update
(3)父组件更新过程
父beforeUpdate --> 父update
(4)销毁过程
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
18: 页面第一次加载会触发哪几个钩子?
beforeCreate --> created --> beforeMount --> mounted
19: 插槽与作用域插槽的区别与作用?
插槽(Slots)是Vue.js中的一种基础特性,它允许父组件向子组件传递内容,并在子组件的模板中定义这些内容的显示位置
作用域插槽(Scoped Slots)则是Vue.js的高级插槽机制。与标准插槽不同,作用域插槽允许父组件不仅传递内容,还传递数据给子组件,并在子组件中自定义渲染这些数据
20: vue的SSR?
Vue的服务器端渲染(SSR)是一种技术,它将Vue组件在服务器上执行,并生成完整的HTML页面,然后这个HTML页面被发送至客户端的浏览器进行展示。这种技术旨在解决传统客户端渲染在搜索引擎优化(SEO)和首屏加载速度方面存在的不足。
Vue SSR服务器端需要如何配置
- 安装必要的库:首先,你需要安装支持SSR的库。对于Vue 3,你需要安装
vue-server-renderer
npm install vue-server-renderer
- 创建Vue实例:在服务器端,你需要创建一个Vue实例,并标记为服务器端渲染。
import Vue from 'vue'
import App from './App.vue'
const serverVm = new Vue({
render: h => h(App),
$server: true // 标记为服务器端渲染
})
- 创建服务器:你需要创建一个服务器来处理客户端的请求并发送渲染后的HTML响应。这通常涉及到使用如Express等Node.js框架来设置服务器。
const express = require('express')
const server = express()
// 配置其他中间件和路由...
server.get('*', (req, res) => {
// 渲染Vue实例并发送响应
})
server.listen(3000, () => {
console.log('Server is running on port 3000')
})
- 处理请求和渲染:在服务器中,你需要处理传入的HTTP请求,并使用
vue-server-renderer来渲染Vue实例。
const { createRenderer } = require('vue-server-renderer')
const renderer = createRenderer()
server.get('*', (req, res) => {
renderer.renderToString(serverVm, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(`<!DOCTYPE html>${html}`)
})
})
- 配置路由:如果你的Vue应用使用了Vue Router,你还需要配置路由以支持SSR。确保你的路由配置可以正确地在服务器端运行。
- 优化和缓存:为了提高性能,你可以考虑实施缓存策略,以减少不必要的渲染。还可以考虑使用预渲染技术,预先生成某些页面的HTML。
- 处理服务端渲染特有的问题:例如访问平台特有API、跨请求状态污染、组件生命周期钩子问题等,这些都需要在服务器端进行适当的处理。
21: vue模板编译?
模板编译的主要步骤
1. 解析模板: 将模板字符串转化为 AST(抽象语法树)。
2. 优化 AST: 遍历 AST,标记静态节点,用于后续优化渲染性能。
3. 生成代码: 根据 AST 生成渲染函数的字符串形式。
4. 创建渲染函数: 使用 new Function 将字符串形式的渲染函数转化为可执行的 JavaScript 函 数。
5. 执行渲染函数: 运行渲染函数,得到虚拟 DOM。
22: hash模式和history模式的区别?
URL的表现形式:Hash模式的URL中带有“#”符号以及后面的字符,而History模式的URL则不带有“#”,外观更加美观,更符合用户的直观感受。
实现原理:Hash模式是通过监听hashChange事件来实现的,当前hash地址对应的组件会被前端JS渲染到浏览器中。而History模式则是通过调用history.pushState方法(或者replaceState)并监听popstate事件来实现的。history.pushState会追加历史记录,并更换地址栏地址信息,但页面不会刷新,需要手动调用地址变化之后的处理函数,并在处理函数内部决定跳转逻辑。监听popstate事件则是为了响应浏览器的前进后退功能。
兼容性:Hash模式支持所有现代浏览器,并且在不支持HTML5 History API的旧版浏览器上也能正常工作,因此其兼容性更好。而History模式则对浏览器的兼容性有要求,例如需要IE >= 10。
服务器配置需求:Hash模式在每次刷新页面时直接更改“#”后的内容,后端即使没有做到对路由的全覆盖,也不会返回404错误。但History模式每次刷新页面会重新向后端请求整个网址,如果后端没有及时响应,就会报错404。因此,使用History模式需要服务器进行相应的配置,以确保在直接访问URL时返回正确的页面。
- URL中的hash值是客户端的一种状态,也就是说向服务器发送请求的时候,hash部分不会被发送
- hash值的改变,都会在浏览器的历史访问历史中新增一个记录,因此我们能通过浏览器的前进和后退按钮控制hash的切换
- 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
- history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
23: router和route的区别?
-route:当前正在跳转的路由对象(可以从route对象中获取hash,name,path,query等属性方法)
-router:是VueRouter的实例对象,全局的路由器对象(具有push,replace,back,go等方法)
24: VueRouter有哪几种导航守卫?
1:全局导航守卫:beforeEach,beforeResolve,afterEach
2:路由独享守卫:beforeEnter
3:组件内的路由守卫:beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave
25: VueRouter路由守卫完整的解析流程?
1:导航被触发
2:在失活的组件里面调用离开守卫beforeRouteLave
3:调用全局的beforeEach
4:在重用的组件里面调用beforeRouteUpdate
5:在路由配置里调用beforeEnter
6:解析异步路由组件
7:在被激活的组件里面调用beforeRouteEnter
8:调用全局的解析守卫beforeRouteResolve
9:导航被确认
10:调用全局的afterEach
11:触发DOM更新
12:用创建好的实例调用beforeRouteEnter守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入
26: vue中的scoped属性
1:为了防止css样式污染,每个组件中提供了scoped属性进行限定css作用域
2:vue中的scoped属性,其实就是给每一个DOM节点元素都添加了不重复的自定义属性(如data-v-hash);然后编译时,使用css类选择 + 属性选择器,这样就达到了私有化模块化
27: proxcy和Object.definedProperty
功能和使用方式
Object.defineProperty
- 功能:允许直接在一个对象上定义新属性,或者修改一个对象的现有属性,并返回这个对象。
- 使用方式:需要指定对象、属性名和属性描述符(包括数据描述符和存取描述符)。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是。存取描述符是由getter-setter函数对组成的属性。
Proxy
- 功能:创建一个对象的代理,对目标对象施加某些操作前的拦截。
- 使用方式:通过构造函数创建一个新的代理对象,需要传入目标对象和一个处理器对象。处理器对象定义了一些陷阱函数,这些函数会在特定的操作(如属性查找、赋值、枚举、函数调用等)被拦截时调用。
拦截范围
Object.defineProperty
- 只允许拦截属性的获取、设置、枚举和配置。(只对对象属性进行数据劫持,无法对整个对象进行劫持)
Proxy
- 拦截的范围更广,包括属性查找、赋值、枚举、函数调用、原型链修改等几乎对象的所有操作。
灵活性
Object.defineProperty
- 灵活性较低,因为一旦定义了属性的描述符,后续很难再动态地改变它(除非再次使用
Object.defineProperty)。
Proxy
- 灵活性更高,因为处理器对象中的陷阱函数可以动态地根据需求进行更改,从而实现对目标对象行为的动态控制。
性能
Object.defineProperty
- 在性能上可能稍微优于Proxy,因为它直接修改对象的属性,没有中间代理层的开销。
Proxy
- 由于在目标对象之前设置了一层拦截,每次访问对象属性时都需要通过这层拦截,因此可能会带来一定的性能开销。
兼容性
Object.defineProperty
- 在现代浏览器中都有很好的支持,包括较老的IE版本。
Proxy
- 在一些较老的浏览器版本中可能不受支持,但在现代浏览器中的支持良好。
适用场景
Object.defineProperty
- 适用于需要对单个对象的属性进行精确控制的场景,如Vue 2.x中的响应式系统。
Proxy
- 适用于需要对对象进行全局、动态控制的场景,如实现API的权限控制、数据验证、日志记录等。
28.vue 中使用了哪些设计模式
-
MVVM(Model-View-ViewModel)设计模式:
- Vue.js 的核心设计思想就是 MVVM。Model 表示数据模型层,View 表示视图层,而 ViewModel 是连接 Model 和 View 的桥梁。ViewModel 负责监听 Model 的变化并更新 View,同时也负责接收 View 的事件并更新 Model。
-
观察者模式(Observer Pattern) :
- Vue.js 的响应式系统使用了观察者模式。每个 Vue 实例都是一个观察者,数据对象则是被观察的目标。当数据发生变化时,会通知所有依赖该数据的观察者,从而触发视图的更新。
-
发布-订阅模式(Publish-Subscribe Pattern) :
- Vue.js 的事件系统基于发布-订阅模式。组件可以发布事件,其他组件或实例可以订阅这些事件,并在事件发生时执行相应的操作。
-
工厂模式(Factory Pattern) :
- 在 Vue.js 中,工厂模式用于创建组件的实例。通过传入不同的参数或配置,可以创建出具有不同功能或状态的组件实例。
-
单例模式(Singleton Pattern) :
- Vuex 的 store 就是单例模式的一个应用。在一个应用中,只存在一个 store 实例,所有的组件都共享这个 store 实例。
-
装饰器模式(Decorator Pattern) :
- Vue.js 的指令系统采用了装饰器模式。指令可以在不改变原有元素的基础上,通过添加新的功能或行为来扩展元素的能力。
-
模板方法模式(Template Method Pattern) :
- Vue.js 的生命周期钩子函数是模板方法模式的一个应用。生命周期钩子定义了一个操作的算法骨架,而具体的实现步骤则由子类(即 Vue 组件)来完成。
-
策略模式(Strategy Pattern) :
- Vue.js 的渲染函数和编译器使用了策略模式。根据不同的输入或条件,选择不同的策略(渲染逻辑或编译逻辑)来执行。
29.Vue3升级了哪些重要功能?
- 性能提升:Vue 3通过模板编译时的静态分析和优化,以及使用更高效的响应式系统,提升了运行时的性能,使得数据更新性能更高。
- Composition API:Vue 3引入了Composition API,使开发者能够更灵活地组织和复用逻辑代码。通过setup方法来定义组件逻辑,可以使用单个setup函数来处理组件的状态、计算属性、方法等。这种新的API与原有的Options API共存,甚至可以互相嵌套使用,提供了良好的兼容性。
- 更好的TypeScript支持:Vue 3对TypeScript的支持更加友好,包括更准确的类型推导和类型声明。使用TypeScript开发Vue 3,可以获得更好的开发体验和代码健壮性。
- 响应式原理的改变:Vue 3的响应式原理从Vue 2的Object.defineProperty升级为了Proxy。Proxy提供了更全面的属性拦截,包括属性的读取、赋值、枚举、删除等,从而实现了更强大的响应式功能。
- 新增Fragment和Suspense:Vue 3新增了Fragment(片段)和Suspense功能。Fragment允许组件有多个根节点,提高了组件的灵活性。Suspense则用于处理异步组件的加载状态,提供了更好的用户体验。
- 移除.sync修饰符和filter:Vue 3移除了.sync修饰符和filter功能,这些变化可能会对一些老项目产生影响,但在新的项目中,开发者可以使用其他方式来替代这些功能。
- 新增Teleport和Custom Renderer API:Vue 3新增了Teleport(传送门)和Custom Renderer API。Teleport用于将子组件渲染到DOM树中的任意位置,提供了更大的布局灵活性。Custom Renderer API则允许开发者暴露自定义渲染API,进一步扩展了Vue的应用场景。
30.vue2和vue3 核心 diff 算法区别?
- 实现原理:
- Vue 2使用的是基于递归的双指针diff算法。这种算法在比较新旧节点时,通过递归的方式对节点进行深度优先遍历,并比较节点的差异。
- Vue 3则采用了基于数组的动态规划diff算法。这种算法利用动态规划的思想,通过建立将要更新的节点中的key和对应位置index的映射关系,然后遍历节点,通过key值取出对应数值进行比较。
- 效率:
- Vue 3的diff算法在效率上相较于Vue 2有了显著的提升。这主要得益于Vue 3采用了一些优化技巧,如按需更新和静态标记等。Vue 3能够跳过静态子树的比较,只对动态节点进行更新,从而减少了不必要的比较操作,提高了性能。
- 处理逻辑:
- Vue 2的diff算法会对整个组件树进行完整的遍历和比较。无论是新节点还是旧节点,都会进行详细的对比和更新。
- Vue 3的diff算法则更加智能和高效。它首先处理最侧边对比,即处理新旧节点在列表头尾部的更新情况。然后,对于中间节点的更新,Vue 3通过建立key和index的映射关系,快速定位需要更新的节点,并进行相应的操作。这种处理方式减少了不必要的比较,提高了效率。
此外,Vue 3的diff算法还引入了“最长递增子序列”的概念,用于确定更新区域的位置调换和新节点的插入。这是Vue 3 diff算法的核心部分,也是其在性能上优于Vue 2的重要原因之一。
31.watch和watchEffect的区别?
- 使用方式:
- watch是一个选项API,主要在组件的选项中使用,需要显式指定要监听的数据和回调函数。它允许你监听特定的响应式数据变化,并在数据变化时执行回调函数。
- watchEffect则是一个函数API,通常在组件的setup函数或生命周期函数中使用。它不需要显式指定要监听的数据,而是自动追踪在副作用函数中访问到的所有响应式属性,并在这些属性变化时重新执行该函数。
- 监听范围:
- watch针对具体的数据进行监视,你可以精确地控制要监听哪些数据源,并且只有在这些数据源发生变化时才会触发回调函数。
- watchEffect则更加灵活,它会自动追踪在副作用函数中能访问到的所有响应式属性,这意味着它可能会监听比预期更多的数据变化。
- 初始化执行:
- watch只有在监听的数据源实际发生变化时才会触发回调函数,初始化时不会执行。
- watchEffect在初始化时会立即执行一次,以捕获当前的响应式状态。
- 控制选项:
- watch提供了更多的控制选项,如深度监听(deep)、立即执行回调函数(immediate)以及手动停止监听等。
- watchEffect则较为简单,主要关注自动追踪和响应式执行。
32.scope是怎么做的样式隔离的?讲讲原理?
- 添加唯一属性:
当我们在 Vue 组件的<style>标签中添加scoped属性时,Vue 的模板编译器会为组件的每个元素添加一个唯一的属性。这个属性通常是形如data-v-[hash]的形式,其中[hash]是一个基于组件内容的哈希值,确保每个组件的唯一性。 - 重写选择器:
在编译过程中,Vue 会遍历scoped样式中的所有选择器,并将它们重写为包含这个唯一属性的形式。例如,一个普通的类选择器.my-class会被重写为.my-class[data-v-[hash]]。这样,重写后的选择器就只会匹配到带有这个唯一属性的元素,即当前组件的元素。 - 注入唯一属性:
当组件被渲染为 DOM 时,这个唯一的属性会被注入到组件的根元素上。如果组件有子组件,那么这个属性只会传递到子组件的根元素,而不会影响到子组件内部的元素。这样,重写后的选择器就能准确地选中当前组件内的元素。
需要注意的是,虽然
scoped样式提供了很好的样式隔离效果,但它也有一些限制和注意事项。例如,它无法穿透子组件的样式隔离(除非使用深度选择器),也无法直接修改第三方组件的样式。
33.如何打破scope的限制?
1. 使用深度选择器
深度选择器允许父组件的样式穿透到子组件中。在 Vue 2 中,你可以使用 >>> 或 /deep/ 来实现这一点;在 Vue 3 中,推荐使用 ::v-deep。
2. 使用全局样式
在全局样式中定义的规则不会受到 scoped 的限制,因此可以直接应用到任何组件上。你可以在项目的入口文件(如 main.js 或 main.ts)中引入全局样式文件。
3. 使用 CSS Modules
CSS Modules 提供了一种更细粒度的样式管理方式,它允许你为每个类名生成唯一的标识符,从而实现样式的局部化。这样,即使在不同的组件中使用了相同的类名,也不会造成冲突。
4. 动态绑定样式
通过动态绑定样式(使用 :style 或 v-bind:style),你可以在组件内部直接控制元素的样式,而不受 scoped 的限制。
注意事项:
- 尽量避免过度使用全局样式,因为这可能会导致样式冲突和难以维护。
- 使用深度选择器时要谨慎,因为它会破坏组件的封装性,并可能导致样式难以预测和控制。
- 动态绑定样式虽然灵活,但也要注意不要过度使用内联样式,以免影响性能和维护性。
34.为什么vue3中组件能支持多个根节点?讲讲原理
在Vue 3中,组件能够支持多个根节点的原理主要基于其编译器层面的改进和优化,特别是引入了基于标记片段(Fragment)的编译机制。
标记片段是一种特殊的数据结构,能够容纳多个子节点,但并没有实际的DOM元素。在处理这样的模板时,Vue 3的编译器会将模板中的每个根级标签(包括自定义组件)都编译为一个单独的标记片段。然后,这些标记片段会作为一个数组,一起创建实际的渲染函数。 35.