一文梳理 vue 面试题知识点
Vue3.0 和 2.0 的响应式原理区别
Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
相关代码如下
mixin 和 mixins 区别
mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。
虽然文档不建议在应用中直接使用 mixin,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 ajax 或者一些工具函数等等。
mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。
delete 和 Vue.delete 删除数组的区别
delete只是被删除的元素变成了empty/undefined其他的元素的键值还是不变。Vue.delete直接删除了数组 改变了数组的键值。
Vue 子组件和父组件执行顺序
加载渲染过程:
父组件 beforeCreate
父组件 created
父组件 beforeMount
子组件 beforeCreate
子组件 created
子组件 beforeMount
子组件 mounted
父组件 mounted
更新过程:
父组件 beforeUpdate
子组件 beforeUpdate
子组件 updated
父组件 updated
销毁过程:
父组件 beforeDestroy
子组件 beforeDestroy
子组件 destroyed
父组件 destoryed
Vue 模版编译原理知道吗,能简单说一下吗?
简单说,Vue 的编译过程就是将template转化为render函数的过程。会经历以下阶段:
生成 AST 树
优化
codegen
首先解析模版,生成AST语法树(一种用 JavaScript 对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的 DOM 也不会变化。那么优化过程就是深度遍历 AST 树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的AST树转换为可执行的代码。
Vue 组件 data 为什么必须是个函数?
根实例对象
data可以是对象也可以是函数 (根实例是单例),不会产生数据污染情况组件实例对象
data必须为函数 一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数,
简版理解
相关源码
Vue 模版编译原理
vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。
解析阶段:使用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为抽象语法树 AST。
优化阶段:遍历 AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行 diff 比较时,直接跳过这一些静态节点,优化 runtime 的性能。
生成阶段:将最终的 AST 转化为 render 函数字符串。
什么是 mixin ?
Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。
如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。
然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。
双向数据绑定的原理
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化
compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个 update()方法 ③待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退。
MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。
参考:前端vue面试题详细解答
Vue 中的 key 到底有什么用?
key是为 Vue 中的 vnode 标记的唯一 id,通过这个 key,我们的 diff 操作可以更准确、更快速
diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异.
diff 程可以概括为:oldCh 和 newCh 各有两个头尾的变量 StartIdx 和 EndIdx,它们的 2 个变量相互比较,一共有 4 种比较方式。如果 4 种比较都没匹配,如果设置了 key,就会用 key 进行比较,在比较的过程中,变量会往中间靠,一旦 StartIdx>EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.
准确: 如果不加
key,那么 vue 会选择复用节点(Vue 的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的 bug.快速: key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1).
使用 Object.defineProperty() 来进行数据劫持有什么缺点?
在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。
在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用 Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。
vue-router 路由钩子函数是什么 执行顺序是什么
路由钩子的执行流程, 钩子函数种类有:全局守卫、路由守卫、组件守卫
完整的导航解析流程:
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
用过 pinia 吗?有什么优点?
1. pinia 是什么?
在
Vue3中,可以使用传统的Vuex来实现状态管理,也可以使用最新的pinia来实现状态管理,我们来看看官网如何解释pinia的:Pinia是Vue的存储库,它允许您跨组件/页面共享状态。实际上,
pinia就是Vuex的升级版,官网也说过,为了尊重原作者,所以取名pinia,而没有取名Vuex,所以大家可以直接将pinia比作为Vue3的Vuex
2. 为什么要使用 pinia?
Vue2和Vue3都支持,这让我们同时使用Vue2和Vue3的小伙伴都能很快上手。pinia中只有state、getter、action,抛弃了Vuex中的Mutation,Vuex中mutation一直都不太受小伙伴们的待见,pinia直接抛弃它了,这无疑减少了我们工作量。pinia中action支持同步和异步,Vuex不支持良好的
Typescript支持,毕竟我们Vue3都推荐使用TS来编写,这个时候使用pinia就非常合适了无需再创建各个模块嵌套了,
Vuex中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而pinia中每个store都是独立的,互相不影响。体积非常小,只有
1KB左右。pinia支持插件来扩展自身功能。支持服务端渲染
3. pinna 使用
准备工作
我们这里搭建一个最新的Vue3 + TS + Vite项目
pinia基础使用
2.1 创建store
创建store很简单,调用 pinia中的defineStore函数即可,该函数接收两个参数:
name:一个字符串,必传项,该store的唯一id。options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。
我们可以定义任意数量的store,因为我们其实一个store就是一个函数,这也是pinia的好处之一,让我们的代码扁平化了,这和Vue3的实现思想是一样的
2.2 使用store
2.3 添加state
2.4 读取state数据
上段代码中我们直接通过store.age等方式获取到了store存储的值,但是大家有没有发现,这样比较繁琐,我们其实可以用解构的方式来获取值,使得代码更简洁一点
2.5 修改state数据
2.6 重置state
有时候我们修改了
state数据,想要将它还原,这个时候该怎么做呢?就比如用户填写了一部分表单,突然想重置为最初始的状态。此时,我们直接调用
store的$reset()方法即可,继续使用我们的例子,添加一个重置按钮
当我们点击重置按钮时,store中的数据会变为初始状态,页面也会更新
2.7 批量更改state数据
如果我们一次性需要修改很多条数据的话,有更加简便的方法,使用store的$patch方法,修改app.vue代码,添加一个批量更改数据的方法
有经验的小伙伴可能发现了,我们采用这种批量更改的方式似乎代价有一点大,假如我们
state中有些字段无需更改,但是按照上段代码的写法,我们必须要将 state 中的所有字段例举出了。为了解决该问题,
pinia提供的$patch方法还可以接收一个回调函数,它的用法有点像我们的数组循环回调函数了。
2.8 直接替换整个state
pinia提供了方法让我们直接替换整个state对象,使用store的$state方法
上段代码会将我们提前声明的state替换为新的对象,可能这种场景用得比较少
getters属性
getters是defineStore参数配置项里面的另一个属性可以把
getter想象成Vue中的计算属性,它的作用就是返回一个新的结果,既然它和Vue中的计算属性类似,那么它肯定也是会被缓存的,就和computed一样
3.1 添加getter
上段代码中我们在配置项参数中添加了getter属性,该属性对象中定义了一个getAddAge方法,该方法会默认接收一个state参数,也就是state对象,然后该方法返回的是一个新的数据
3.2 使用getter
上段代码中我们直接在标签上使用了store.gettAddAge方法,这样可以保证响应式,其实我们state中的name等属性也可以以此种方式直接在标签上使用,也可以保持响应式
3.3 getter中调用其它getter
3.3 getter传参
actions属性
前面我们提到的
state和getters 属性都主要是数据层面的,并没有具体的业务逻辑代码,它们两个就和我们组件代码中的data数据和computed计算属性一样。那么,如果我们有业务代码的话,最好就是卸载
actions属性里面,该属性就和我们组件代码中的methods相似,用来放置一些处理业务逻辑的方法。actions属性值同样是一个对象,该对象里面也是存储的各种各样的方法,包括同步方法和异步方法
4.1 添加actions
4.2 使用actions
使用actions中的方法也非常简单,比如我们在App.vue中想要调用该方法
总结
pinia的知识点很少,如果你有 Vuex 基础,那么学起来更是易如反掌
pinia 无非就是以下 3 个大点:
stategettersactions
Vue 性能优化
编码优化:
事件代理
keep-alive拆分组件
key保证唯一性路由懒加载、异步组件
防抖节流
Vue 加载性能优化
第三方模块按需导入(
babel-plugin-component)图片懒加载
用户体验
app-skeleton骨架屏shellapp 壳pwa
SEO 优化
预渲染
谈谈对 keep-alive 的了解
keep-alive 可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。常用的2个属性 include/exclude ,2个生命周期 activated , deactivated
了解 nextTick 吗?
异步方法,异步渲染最后一步,与 JS 事件循环联系紧密。主要使用了宏任务微任务(setTimeout、promise那些),定义了一个异步方法,多次调用nextTick会将方法存入队列,通过异步方法清空当前队列。
Vue 的优点
轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十
kb;简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
双向数据绑定:保留了
angular的特点,在数据操作方面更为简单;组件化:保留了
react的优点,实现了html的封装和重用,在构建单页面应用方面有着独特的优势;视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
虚拟 DOM:
dom操作是非常耗费性能的,不再使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种方式;运行速度更快:相比较于
react而言,同样是操作虚拟dom,就性能而言,vue存在很大的优势。
如何从真实 DOM 到虚拟 DOM
涉及到 Vue 中的模板编译原理,主要过程:
将模板转换成
ast树,ast用对象来描述真实的 JS 语法(将真实 DOM 转换成虚拟 DOM)优化树
将
ast树生成代码
Vue3.0 有什么更新
(1)监测机制的改变
3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。
消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:
(2)只能监测属性,不能监测对象
检测属性的添加和删除;
检测数组索引和长度的变更;
支持 Map、Set、WeakMap 和 WeakSet。
(3)模板
作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。
同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。
(4)对象式的组件声明方式
vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。
3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易
(5)其它方面的更改
支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。
基于 tree shaking 优化,提供了更多的内置功能。
v-on 可以监听多个方法吗?
可以监听多个方法
v-on 常用修饰符
.stop该修饰符将阻止事件向上冒泡。同理于调用event.stopPropagation()方法.prevent该修饰符会阻止当前事件的默认行为。同理于调用event.preventDefault()方法.self该指令只当事件是从事件绑定的元素本身触发时才触发回调.once该修饰符表示绑定的事件只会被触发一次










评论