1. MVVM
- Model-View-ViewModel
- Model --- 模型
- View --- 视图
- ViewModel --- 视图模型
- ViewModel连接View和Model的桥梁
- ViewModel作用
- 将model转换为视图,将后端传递过来的数据转换为页面,通过数据绑定
- 将视图转换为模型,将页面转换为后端的数据,通过DOM事件的监听
2. SPA
SPA仅仅在Web页面初始化时加载相应的HTML、JavaScript和CSS。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现HTML内容的变换,UI与用户的交互,避免页面的重新加载
- 优点:
- 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染
- 基于上面一点,SPA相对对服务器压力小
- 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理
- 缺点:
- 初次加载耗时多:为实现单页Web应用功能及显示效果,需要在加载页面的时候将JavaScript、CSS统一加载,部分页面按需加载
- 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理
- SEO难度较大:由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势
3. v-show和v-if的区别
- 用法上
- v-show是不支持template
- v-show不可以和v-else一起使用
- 本质上
- v-show元素无论是否需要显示到浏览器上,它的DOM实际上都是有存在的,只是通过CSS的display属性来进行切换
- v-if当条件为false时,其对应的原生压根不会被渲染到DOM中
- 使用上
- 如果我们的原生需要在显示和隐藏之间频繁的切换,那么使用v-show
- 如果不会频繁的发生切换,那么使用v-if
4. v-model的本质
双向绑定:当数据发生变化的时候,视图也就会发生变化,当视图发生变化的时候,数据也会跟着同步变化
- 表单元素使用v-model的本质
- v-bind绑定value属性的值
- v-on绑定input事件监听到函数,函数会获取最新的值赋值到绑定的属性上
- <input :value="message" @input="message = $event.target.value"/>
- 组件使用v-model的本质
- 将其 value attribute绑定到一个名为modelValue的prop上
- 在其 input 事件被触发时,将新的值通过自定义的update:modelValue事件抛出
- <counter v-bind:modelValue=“appCounter” @update:modelValue=“appCounter = $event” />
5. computed和method
- 相同
- 都可以通过this进行访问
- 都可以对一些数据进行处理和计算
- 区别
- computed底层会缓存,性能更高
- 如果数据不发生变化,计算属性是不需要重新计算的;如果依赖的数据发生改变,计算属性会重新计算
6. data选项为什么是一个函数
- 避免冲突,防止多个实例共用一个data
- 因为对象返回的是引用类型,每次函数都会返回一个新的对象
7. v-if和v-for
- vue2中,v-for比v-if的优先级高
- vue3中,v-if比v-for的优先级高
- 注意事项
- 不要把v-if和v-for同时用在同一个元素上,带来性能方面的浪费
- 如果避免出现这种情况,则在外层嵌套template,并在这一层进行v-if判断,然后在内部进行v-for循环
- 如果条件出现在循环内部,可通过计算属性computed提前过滤掉不需要显示的选项
8. vue2的生命周期钩子函数
- beforeCreate — 组件实例被创建之初
- created — 组件实例已经完全创建
- beforeMount — 模版解析之后,挂载结束之前
- mounted — 模版挂载结束之后
- beforeUpdate — 数据改变后,虚拟DOM重新渲染为真实DOM之前
- updated — 虚拟DOM重新渲染之后
- activated — 组件第一次被创建的时候会进入,当缓存组件第二次进入,只会触发activated
- deactived — 退出缓存组件 $forceUpdate强制重新渲染组件;nextTick;set
- beforeDestroy - 组件实例销毁之前
- destroyed - 组件实例销毁之后
- nextTick
9. 父子组件的生命周期顺序
- 加载渲染过程
- 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
- 子组件更新过程
- 父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
- 副组件更新过程
- 父beforeUpdate -> 父updated
- 销毁过程
- 父beforeDestroy -> 子beforeDestory -> 子destroyed -> 父destroyed
10. vue2组件间的通信
- 父传子:子组件通过props来接收父组件的传值
- 子传父:子组件通过emit来触发事件传递,父组件通过监听对应事件来接收数据
- Provide/Inject:父组件通过Provide提供内容,子/孙组件通过Inject注入父组件提供的内容
- 事件总线:第三方组件库/手写EventBus
- 组件实例:通过ref获取组件实例,调用组件实例的属性或方法进行传值
- vuex/pinia:可以使用全局状态管理来进行全局共享数据
11. vue的模板编译过程
- 编译过程
- vue模板编译是指将Vue模板字符串转换为可执行的渲染函数的过程。Vue模板编译器会将模板字符串解析为抽象语法树(AST),然后将AST转换为可执行的渲染函数,最终生成Virtual DOM,进行页面渲染。
- 具体步骤
- 在解析阶段:编译器会将模板解析成一个抽象语法树(AST),对模板进行逐个字符的解析,识别模板中的各种语法,然后将其转化为AST节点
- 在优化阶段:对生成的AST节点进行静态优化,包括静态节点提升、缓存节点、diff算法优化等
- 在生成代码阶段:将优化后的AST生成可执行的JavaScript代码,这个过程是通过对AST进行遍历,将AST中的每个节点都转化为相应的JavaScript代码,并将其添加到最终的渲染函数中
12. vuex/pinia
- 状态管理
- vuex
- state:基础数据
- getters:计算属性
- mutations:同步修改状态的方法
- actions:异步修改状态的方法
- modules:模块化
- 数据流程:当组件需要修改状态时,会触发一个Action。Action通过Commit发送一个Mutation,Mutation更新State,并将变化通知给所有的组件。组件可以通过Getter获取状态中的值,并对其进行渲染
- pinia
- state:基础数据
- getters:计算属性
- actions:可以使用同步任务,可以使用异步任务
- 利用storeToRefs将state中的数据变为响应式;通过 reset 对数据进行渲染
- pinia相比vuex的优点
- 更加轻量级:相比 Vuex,Pinia 更加轻量级,因为它不需要使用 Vuex 的一些复杂的概念,如模块和 getter
- 更加简单易用:Pinia 的 API 设计更加简单易用,因为它使用了 Vue.js 3 的新特性,如 Composition API
- 更加灵活:Pinia 提供了更加灵活的状态管理方式,因为它支持多个 store 实例,而 Vuex 只支持一个 store 实例
- pinia相比vuex的缺点
- 相对较新:Pinia 是一个相对较新的状态管理库,因此它可能存在一些未知的问题和限制
- 生态系统不够完善:由于 Pinia 是一个相对较新的库,它的生态系统可能不够完善,因此可能需要花费更多的时间来解决问题
13. VueRouter
- 路由的两种模式
- hash模式
- URL有#号
- 通过监听location的haschange事件判断URL的更新
- 改变hash,浏览器本身不会有任何请求服务器的动作
- history模式
- URL无#号
- 通过history API的pushState和popState方法切换地址,页面不会刷新
- 页面刷新是会真的发送请求,但是没有这个路径就会报错。但是hash值不会随请求发送到服务器
- hash模式
- 路由的参数传递
- 动态路由
- path:/user/:id
- 获取动态路由的值的方式
- 在template中,直接通过$route.params获取值
- 在created中,通过this.$route.params获取值
- 在setup中,使用vue-router库提供的一个hook useRoute
- query
- 通过query的方式来传递参数
- 在界面中通过$route.query来获取参数
- 在created中,通过this.$route.query获取值
- 在setup中,使用vue-router库提供的一个hook useRoute
- 动态路由
- 路由守卫
- 全局
- router.beforeEach(to, from, next)(全局守卫,进入路由之前执行)
- router.afterEach(to, from, next)(全局后置守卫,在路由进入之后被触发,没有next方法)
- 组件内
- beforeRouteEnter(to, from, next)
- beforeRouteLeave(to, from, next)
- beforeRouteUpdate(to, from, next)
- 单独路由独享组件
- beforeEnter(to, from, next)(路由独享守卫,不想全局配置守卫,可以对某些页面进行单独设置守卫)
- afterEach(to, from, next)
- 全局
- 路由守卫有什么作用
- 可以在进入路由之前进行某些判断,比如,检查token是否存在来判断用户是否已经登录
- 可以在路由守卫中进行页面的权限判断,比如,判断某个用户是否拥有该页面的权限
- 也可以用来记录页面的某些信息,比如,记录页面的滚动信息等
- route和router的区别
- route是路由信息对象,在vue3中通过useRoute来获取
- 包括了path,params,hash,query,fullPath,matched,name等路由信息参数
- router是路由实例对象,在vue3中通过useRouter来获取
- 包括了路由跳转方法、钩子函数等,比如:push、go、back、addRouter、beforeEnter等
14. keep-alive
是 vue 提供的一个抽象组件,用于在组件之间切换时缓存组件的状态或DOM
- 作用
- 性能优化:通过缓存组件实例,避免了每次切换组件时都重新创建和销毁组件的开销,减少了DOM操作
- 状态保留:缓存的组件实例在切换回来时,状态会被保留,提高用户体验
- 实现机制
- 利用了 vue 的抽象组件能力,在内部维护一个缓存对象,用于存储被缓存组件的状态和 DOM 结构
- 当需要缓存的组件被激活时,从缓存对象中检索相应的组件,如果存在,则直接复用之前的状态和 DOM,而不需要重新创建
- 具体缓存的内容
- 缓存的是组件的实例及其状态、数据以及渲染出的 DOM 结构
- 这样,在组件被再次激活时,可以直接从缓存中获取到这些信息,而不需要重新初始化组件及其状态,从而提高了性能
15. $nextTick
- 当vue中的数据对象某属性值改变时,数据是同步更新的,视图是异步更新的
- 通过将数据更新操作放入异步队列,实现了性能优化和避免重复更新的目的
- 可以使用$nextTick方法在实例更新操作完成后以获取最新的数据状态或执行相关的操作
- $nextTick是 vue 框架中的一个函数,用于在 DOM 更新完成后执行回调函数
- 原理
- $nextTick的原理是利用了JavaScript的异步回调任务队列来实现Vue框架中自己的异步回调队列
- 具体来说,$nextTick会判断当前的执行环境是否支持Promise、MutationObserver、setImmediate和setTimeout,如果支持,则创建对应的异步方法
- 其中,MutationObserver并不是监听 DOM,而是利用其微任务特性
- $nextTick将回调函数放到微任务或者宏任务当中以延迟它的执行顺序。这种机制可以确保在 DOM 更新完成后执行回调函数,从而避免了因 DOM 未及时更新而导致的问题
- 用途
- 解决在 Vue 中修改数据后,DOM 不会立即更新的问题
- 如果在created()钩子进行DOM操作,也一定要放在nextTick()的回调函数中
16. vue2和vue3的区别
- 双向绑定原理不同
- 通过Object.defineProperty()的get和set做数据劫持,结合发布者订阅者模式实现。Object.defineProperty()会遍历每个属性,加上get和set方法,从而达到双向绑定
- $set/$forceUpdate
- ES6的proxy代理对象,并通过reflect劫持对象,对代理对象进行操作
- 通过Object.defineProperty()的get和set做数据劫持,结合发布者订阅者模式实现。Object.defineProperty()会遍历每个属性,加上get和set方法,从而达到双向绑定
- 是否支持碎片
- 不可以有多个根标签
- 可以有多个根标签,默认将多个根标签包裹在fragment虚拟标签中
- API类型
- options API data/props/components/methods/computed/watch/created/mixins
- Composition API
- ref/reactive定义变量/方法直接放在setup中
- 生命周期函数
- 11个 mounted
- onMounted
- 去除了beforeCreated/created,用setup函数替代
- diff算法不同
- 遍历每一个节点,与虚拟节点对比,返回patch对象,存储两个节点不同的地方,用patch记录的消息更新dom
- 给每一个虚拟节点增加patchFlag,只会比较标识发生变化的节点,进行视图更新
- props/emit
- props/this.$emit()
- defineProps/defineEmits
- route/router
- $route/$router
- useRoute/useRouter
17. vue2和vue3双向绑定
- 为什么vue3选择使用proxy
- Object.defineProperty只能监听某个属性,不能对全对象监听
- Proxy可以监听数组,不再单独的对数组进行特异化处理,可以检测到数组内部数据的变化
- vue2通过Object.defineProperty对数据进行劫持,通过发布者订阅者模式对数据进行更新
- vue2通过Object.defineProperty将数据设置为setter和getter的访问方式,每当数据被访问时触发getter,数据被修改时触发setter
- 在getter时,会收集依赖。每一个组件实例都对应一个watcher,当组件被渲染时,会将关联的数据属性收集到watcher的依赖中
- 在setter进行修改数据时,watcher会通知依赖里的每个组件修改对应的值
- vue3通过proxy创建一个代理对象,handler中Reflect拦截对数据的访问和修改的操作,执行的get和set
18. setup的作用
- 在vue3中,setup函数充当了组件编写Composition API的入口点
- setup函数要有两个参数
- 第一参数:props,父组件传递过来的属性会被放到props对象中
- 第二个参数:context,包含三个属性
- attrs:所有的非prop的attribute
- slots:父组件传递过来的插槽
- emit:当组件内部需要发出事件时会用到emit
- 可以在setup中可以定义响应式数据、方法、计算属性、侦听器等等
- 可以通过setup的返回值来替代data选项,让数据直接在template中使用
19. ref和reactive
- ref
- 通常是创建单个值的响应式应用
- 通常用户基本数据类型,但也可以传入复杂数据类型
- 使用ref创建的对象需要通过.value属性访问内部的值
- reactive
- 创建包含多个属性的响应式对象,并且这些属性也是响应式的
- 通常用于传入对象或数组
- 使用reactive创建的响应式对象可以直接访问。
- 应用场景
- reactive应用于本地数据,或多个数据之间是有联系的
- 其他情况用ref
20. watch和watchEffect的区别
- 使用方式
- watch的使用方式是通过在组件选择中定义一个watch对象,然后在其中定义需要监听的响应式数据和回调函数
- watchEffect则是在组件内部使用,它会自动最终组件中使用的响应式数据,并在数据变化时执行回调函数
watch: { count(newValue, oldValue) { console.log(newValue, oldValue) } } watchEffect(() => { console.log(“change”) }) - 监听reactive定义的响应式数据
- watch可以监听reactive定义的响应式数据,但需要使用toRefs函数将reactive转换为ref对象
- watchEffect可以监听reactive定义的响应式数据,无需额外处理
watch(toRefs(state), (newValue, oldValue) => { console.log(newValue, oldValue) }) - 监听多个响应式数据
- watch可以同时监听多个响应式数据,通过将需要监听的数据以数组的形式传递给watch函数
- watchEffect则无法直接监听多个响应式数据,需要通过在回调函数内部使用多个响应式数据来实现
watch([count, name], ([countValue, nameValue], [countOldValue, nameOldValue]) => { console.log(countValue, nameValue, countOldValue, nameOldValue) }) - 监听对象中某个属性的变化
- watch可以监听对象中某个属性的变化,通过在对象属性名前加上字符串插值 $ 来指定要监听的属性
- watchEffect则无法直接监听对象中某个属性的变化,需要通过在回调函数内部使用对象属性来实现
watch({ ‘$user.name’: (newValue, oldValue) => { console.log(newValue, oldValue) } }) - 深度监听
- watch可以通过设置deep选项来进行深度监听
- watchEffect会默认进行深度监听,无需额外设置
watch(obj, (newValue, oldValue) => { console.log(newValue, oldValue) }, { deep: true }) - 默认执行
- watch可以通过设置immediate选项来在初始渲染时立即执行回调函数
- watchEffect默认会在初始渲染时立即执行回调函数你,无需额外设置
watch(count, (newValue, oldValue) => { console.log(newValue, oldValue) }, { immediate: true }) - 副作用
- watchEffect在DOM挂载和更新前会触发,需要放在onMounted中手动清除副作用
- 使用场景
- watch用于需要知道哪个数据项发生变化以及需要比较旧值和新值的情况,因为它提供了旧值和新值的参数
- watchEffect更适合在不关心旧值和具体数据项变化的情况下使用,因为它只关注副作用函数的执行,不提供旧值和新值的参数
21. vue3在编译过程中进行了哪些优化
- 静态树提升(Static Tree Hoisting)
- 在 vue 3.0 中,引入了静态树提升优化,这是一项重大的改进
- 它通过分析模板并检测其中的静态部分,将静态节点提升为常量,从而减小渲染时的开销
- 这一优化可以显著降低渲染函数的复杂性,减少不必要的运行时开销,从而提高应用的性能和响应速度。
- 源码优化
- vue 3.0 在编译器的源码生成方面进行了优化,生成的代码更加精简和高效
- 这种优化有助于减小构建后的包体积,提高运行时性能
- 通过对源码进行优化,vue 3.0 在性能方面有了显著的提升。
- Patch Flag
- 引入了 Patch Flag,它允许 Vue 在渲染时跳过不需要更新的节点,进一步提高性能
- Patch Flag 为 Vue 提供了一种方法来跟踪哪些节点需要重新渲染,以及哪些节点可以被跳过
- 这一机制可以有效地减少不必要的 DOM 操作,提高页面渲染的效率。
- Diff 算法优化
- vue 3.0 使用了更高效的 Virtual DOM diff 算法,相较于 vue 2.x,减少了不必要的虚拟节点创建和比对,提高了渲染性能
- 通过优化 Diff 算法,vue 3.0 能够更快速地检测到数据变化并更新视图,从而提升了整体的页面性能。
- 模板嵌套内联
- vue 3.0 允许在模板中内联子组件的模板,从而避免了运行时编译
- 这一优化可以帮助减小构建后的包大小,提高初始化性能
- 通过内联模板,vue 3.0 能够更高效地处理模板,并在渲染时节省时间。
- 模板块提取
- vue 3.0 允许在编译时将模板块提取到独立的模块中,这有助于代码分割和按需加载,减小初始化时需要加载的代码量
- 模板块提取使得 vue 3.0 更易于进行代码管理和维护,同时也能提升应用的性能和加载速度。
- 更好的类型支持
- vue 3.0 支持更好的类型推断,借助 TypeScript 等类型检查工具,可以提供更好的开发体验和更强的类型安全性
- 通过更好的类型支持,vue 3.0 能够更早地发现代码中的潜在问题,并提供更准确的代码提示,从而提高开发效率和代码质量。
- 综上所述,vue 3.0 在编译方面进行了多项优化,从而提升了应用的性能、减小了包体积,并改善了开发体验。这些优化使得 vue 3.0 在大型应用和性能要求较高的场景中
22. 虚拟DOM
- 什么是虚拟DOM
- 虚拟DOM是一个轻量级的内存中的树形结构,它是实际DOM的一种抽象表示
- 当应用状态发生改变时,框架会重新构建虚拟DOM,然后通过对比前后两个虚拟DOM树的差异,计算出最小的DOM的操作集合,并将这些操作应用到实际的DOM中,从而实现页面的更新
- 优点
- 性能优化:通过最小化实际DOM操作的次数,虚拟DOM可以显著提高页面渲染的性能,尤其是在大型应用中
- 跨平台兼容:虚拟DOM可以独立于底层平台,使得前端框架可以在不同的浏览器和环境中工作,而不必担心平台差异
- 开发效率:虚拟DOM可以简化开发流程,开发者只需关注数据的变化,而不必手动管理DOM更新,从而提高开发效率和代码可维护性
- 虚拟DOM的解析过程
- 构建初始的虚拟DOM树
- 在组件渲染的过程中,会创建一个初始的虚拟DOM树,来描述当前组件的状态和结构。
- 生成新的虚拟DOM树
- 随着组件状态的改变,会触发数据更新,从而生成一个新的虚拟DOM树,以表示更新后的组件状态和结构。
- 比较新旧虚拟DOM树
- 通过比较 新、旧虚拟DOM树的差异,找出需要进行更新的部分。
- 生成更新补丁(patch)
- 根据差异的比较结果,生成一系列的更新补丁,用于描述需要对真实DOM进行的操作,如添加、删除、修改节点等。
- 应用更新补丁
- 将生成的更新补丁应用到真实DOM上,进行实际的DOM操作,以达到将真实DOM更新为新的组件状态和结构的目的
- 构建初始的虚拟DOM树
23. webpack的loader和plugins
- webpack只能加载JS文件
- loader:加载器,可以让webpack加载和解析非JS文件
- css-loader
- babel-loader ES6解析为ES5
- loader:加载器,可以让webpack加载和解析非JS文件
- plugin:插件,扩展webpack的功能
- webpack-merge 提取公共配置,减少重复配置代码
24. webpack和vite
- webpack构建速度相对较慢,分析整个依赖,进行多次文件的扫描和转义
- HMR,热模块替换,来实现快速开发,配置很复杂
- 通过各种loader、plugins处理各种不同类型的资源
- 打包原理:将所有资源打包成一个或多个bundle文件
- vite轻量级,vite依据ES模块的特性,构建已经或者正在编译的文件,而不是整个项目
- 支持HMR,无需配置
- 使用原生浏览器倒入,不需要大规模编译和打包
- 打包原理:保持开发时模块化结构
25. TS的特性
- 类型
- any/unknow/void/never/tuple
- 类型注解
- 联合类型 |
- 交叉类型 &
- type/interface
- type --- 类型别名,定义非对象类型,不能重复
- interface --- 接口,定义对象类型,可以重复
- 类型断言
- as --- 类型断言
- ! --- 非空类型断言,跳过ts的检测
- 枚举类型
- enum --- 将有可能的值列举出来
- 泛型
- function foo(args: Type)
26. 前端性能优化
- 在页面开发中,尽量使用虚拟DOM开发,减少页面的重排(可以通过performance进行性能分析)
- 防抖和节流
- 防抖:搜索框的搜索时间、页面弹窗的点击事件
- 节流:滚动条滚动事件、dom的拖拽事件
- SSR
- server side render
- vue在客户端将标签渲染成的整个html片段的工作在服务端完成,服务端形成的html片段直接返回给客户端的这个过程就叫做服务端渲染
- 优点:首屏加载更快;更好的SEO
- 缺点:更多的开发限制;更多的服务器负载
- webpack的性能优化
- 通过alias、extensions 缩小查找文件的范围 提升查找效率
- alias 可以设置文件别名
- extensions 可以导入文件的时候不写文件别名
- noparse 减少需要解析的文件
- 避免重复编译第三方库,把element-ui、echarts单独的打包到独立的文件,不会因为业务的更新重新打包
- 通过alias、extensions 缩小查找文件的范围 提升查找效率
- web缓存
- 浏览器缓存通过localStorage/sessionStorage设置token等信息用于自动登录
- http缓存通过强缓存和协商缓存减少不必要的网络传输,节约带宽,加快响应速度