3、vue如何监听数组变化
要想让 Object.defineProperty() 这个 API 拥有监听数组的能力,我们可以这么做。具体代码如下:
// 触发更新视图 function updateView() { console.log('视图更新') } // 重新定义数组原型 const oldArrayProperty = Array.prototype // 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型 const arrProto = Object.create(oldArrayProperty); ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => { arrProto[methodName] = function () { updateView() // 触发视图更新 oldArrayProperty[methodName].call(this, ...arguments) // Array.prototype.push.call(this, ...arguments) } }) // 重新定义属性,监听起来 function defineReactive(target, key, value) { // 深度监听 observer(value) // 核心 API Object.defineProperty(target, key, { get() { return value }, set(newValue) { if (newValue !== value) { // 深度监听 observer(newValue) // 设置新值 // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值 value = newValue // 触发更新视图 updateView() } } }) } // 监听对象属性 function observer(target) { if (typeof target !== 'object' || target === null) { // 不是对象或数组 return target } // 污染全局的 Array 原型(如果直接定义在这里面,会直接污染全局) // Array.prototype.push = function () { // updateView() // ... // } if (Array.isArray(target)) { target.__proto__ = arrProto } // 重新定义各个属性(for in 也可以遍历数组) for (let key in target) { defineReactive(target, key, target[key]) } } // 准备数据 const data = { name: 'monday', age: 20, info: { address: '深圳' // 需要深度监听 }, nums: ['打篮球', '出来玩', '打乒乓球'] } // 监听数据 observer(data) // 测试 data.info.address = '上海' // 深度监听 data.nums.push('神游') // 监听数组 复制代码 复制代码
此时浏览器的打印效果如下:
我们可以看到,两个数据对应的视图都更新了。通过对数组原型的重新定义,我们就让 Object.defineProperty() 实现了监听数组的能力。
4、请描述响应式原理
响应式原理概述:
- 任何一个
Vue组件都会生成一个render函数。 - 之后
render函数会生成一个vnode。 - 同时,在执行
render函数的时候会触发data里面的getter,触发后则会生成依赖。 - 所谓依赖,就是在
data触发到哪个变量,就会将哪一个变量观察起来。 - 之后,需要查看触发到的这个变量是否是之前作为依赖被观察起来的,如果是,则触发
setter进行数据修改;如果不是,则直接进行监听操作; - 最后,如果确定是之前作为依赖被重新观察起来的,那就执行
re-render重新渲染操作,并且进行patch(vnode, newVnode)。
5、请用vnode描述一个DOM结构
根据下方的 html 代码,用 v-node 模拟出该 html 代码的 DOM 结构。
html代码:
<div id="div1" class="container"> <p> vdom </p> <ul style="font-size:20px;"> <li>a</li> </ul> </div> 复制代码 复制代码
用JS模拟出以上代码的DOM结构:
{ tag: 'div', props:{ className: 'container', id: 'div1' }, children: [ { tag: 'p', chindren: 'vdom' }, { tag: 'ul', props:{ style: 'font-size: 20px' }, children: [ { tag: 'li', children: 'a' } // .... ] } ] } 复制代码
6、diff算法的时间复杂度
- 树的时间复杂度是 O(n3) ,因此,我们就想办法,优化其时间复杂度从O(n3)到O(n),以达到操作
vdom节点,那这个优化过程其实我们所说的diff算法。 - 所以,
diff算法的时间复杂度为O(n)。
7、简述diff算法过程
- 首先,对比节点本身,要先判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换;
- 如果为相同节点时,进行
patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,则将旧的子节点移除); - 比较如果都有子节点,则进行
updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)。 - 匹配时,找到相同的子节点,递归比较子节点。
注意: 在 diff 中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从 O(n^3) 降低值 O(n) ,也就是说,只有当新旧 children 都为多个子节点时才需要用核心的 diff 算法进行同层级比较。
8、vue模板编译的原理是什么
vue在进行模板编译之后,会先转化成一个render函数,之后继续执行render函数,执行完成之后返回一个vnode;- 在得到
vnode之后,基于vnode的基础上,再执行patch和diff。
9、vue为何是异步渲染,$nextTick有何用?
vue是组件级更新,一旦当前组件里的数据变了,那么它就会去更新这个组件。- 但是试想一下,如果当数据更改一次,组件就要去重新渲染一次,这样对性能来说都是不太友好的。
- 因此,为了防止数据一更新就更新组件,所以需要异步渲染来处理。
- 而异步渲染的核心的方法就是
nextTick,$nextTick可以在DOM更新完之后,再触发回调。
10、SPA单页面应用是什么?
SPA,即单页面应用(Single Page Application)。所谓单页 Web 应用,就是只有一张 Web 页面的应用。单页应用程序 (SPA) 是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的 Web 应用程序。浏览器一开始会加载必需的 HTML 、 CSS 和 JavaScript ,所有的操作都在这张页面上完成,都由 JavaScript 来控制。
现如今,为了配合单页面 Web 应用快速发展的节奏,各类前端组件化技术栈层出不穷。近几年来,通过不断的版本迭代, vue 和 react 两大技术栈脱颖而出,成为当下最受欢迎的两大技术栈。
11、hash和history的区别是什么?
(1)hash
- hash变化会触发网页跳转,即浏览器的前进和后退。
hash可以改变url,但是不会触发页面重新加载(hash的改变是记录在window.history中),即不会刷新页面。也就是说,所有页面的跳转都是在客户端进行操作。因此,这并不算是一次http请求,所以这种模式不利于SEO优化。hash只能修改#后面的部分,所以只能跳转到与当前url同文档的url。hash通过window.onhashchange的方式,来监听hash的改变,借此实现无刷新跳转的功能。hash永远不会提交到server端(可以理解为只在前端自生自灭)。
(2)history
- 新的
url可以是与当前url同源的任意url,也可以是与当前url一样的地址,但是这样会导致的一个问题是,会把重复的这一次操作记录到栈当中。 - 通过
history.state,添加任意类型的数据到记录中。 - 可以额外设置
title属性,以便后续使用。 - 通过
pushState、replaceState来实现无刷新跳转的功能。 - 使用
history模式时,在对当前的页面进行刷新时,此时浏览器会重新发起请求。如果nginx没有匹配得到当前的url,就会出现404的页面。 - 而对于
hash模式来说, 它虽然看着是改变了url,但不会被包括在http请求中。所以,它算是被用来指导浏览器的动作,并不影响服务器端。因此,改变hash并没有真正地改变url,所以页面路径还是之前的路径,nginx也就不会拦截。 - 因此,在使用
history模式时,需要通过服务端来允许地址可访问,如果没有设置,就很容易导致出现404的局面。
12、hash和history两者的选择
to B的系统推荐用hash,相对简单且容易使用,且因为hash对url规范不敏感;to C的系统,可以考虑选择H5 history,但是需要服务端支持;- 能先用简单的,就别用复杂的,要考虑成本和收益。
🖨️五、vue3.x知识预备
关于 vue3 模块,我将把基础知识和原理的内容结合在一起进行整理。详细见下图👇
- 关于以上内容,已整理成博文,戳下方链接进入学习👇
- 原文1:一文了解vue3基础新特性
- 链接1:juejin.cn/post/697640…
- 原文2:敲黑板!vue3重点!一文了解Composition API新特性:ref、toRef、toRefs
- 链接2:juejin.cn/post/697667…
- 原文3:一文get一波 vue3.x 进阶新特性
- 链接3:juejin.cn/post/697604…
- 原文4:vue2的响应式原理学“废”了吗?继续观摩vue3响应式原理Proxy
- 链接4:juejin.cn/post/697936…
⌨️六、vue3.x常见面试题
基于以上知识点,我们将其细分为面试中的常考题。详细见下图👇
接下来对这些题进行一一解答。
1、vue3和vue2有什么优势?
vue3比vue2来说,性能上更好,代码体积更小,并且有更好的ts支持。- 同时,更为突出的特点是,
vue3有更好的代码组织能力,有更好的逻辑抽离能力,并且还有更多各式各样的新功能。 - 其中尤为突出的就是大家平常耳熟能详的
Composition API和Options API。
2、描述vue3生命周期
以下给出 Vue2 与 Vue3 生命周期的对比。
| Vue2生命周期(Options API) | Vue3生命周期(Composition API) | 含义 |
| beforeCreate | setup | 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用 |
| created | setup | 页面还没有渲染,但是vue的实例已经初始化结束。 |
| beforeMount | onBeforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用。 |
| mounted | onMounted | 页面已经渲染完毕。 |
| beforeUpdate | onBeforeUpdate | 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。 |
| updated | onUpdated | 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。 |
| beforeDestory | onBeforeUnmount | 实例销毁之前调用。在这一步,实例仍然完全可用。 |
| destroy | onUnmounted | Vue 实例销毁后调用。 |
3、如何看待Composition API和Options API
对于 Composition API 和 Options API 的使用,主要有以下几点建议:
- 两者不建议共用,不然很容易引起混乱;
- 对于小型项目、或者业务逻辑比较简单的项目,建议使用
Options API; - 对于中大型项目、或者逻辑比较复杂的项目,建议使用
Composition API,相较于Options API来说,Composition API对大型项目更好一些,逻辑的抽离,代码的复用,使得大型项目得以更好的维护。
4、如何理解ref、toRef和toRefs
(1)ref是什么
ref是可以生成值类型(即基本数据类型) 的响应式数据;ref可以用于模板和reactive;ref通过.value来修改值(一定要记得加上.value);ref不仅可以用于响应式,还可以用于模板的DOM元素。
(2)toRef是什么
toRef可以响应对象Object,其针对的是某一个响应式对象(reactive封装)的属性prop。toRef和对象Object两者保持引用关系,即一个改完另外一个也跟着改。toRef如果用于普通对象(非响应式对象),产出的结果不具备响应式。如下代码所示:
//普通对象 const state = { age: 20, name: 'monday' } //响应式对象 const state = reactive({ age: 20, name: 'monday' }) 复制代码
(3)toRefs是什么
- 与
toRef不一样的是,toRefs是针对整个对象的所有属性,目标在于将响应式对象(reactive封装)转换为普通对象。 - 普通对象里的每一个属性
prop都对应一个ref。 toRefs和对象Object两者保持引用关系,即一个改完另外一个也跟着改。
5、vue3升级了哪些重要的功能?
- createApp
- emits(父子组件间的通信)
- 多事件处理
- Fragment
- 移除
.sync - 异步组件
- 移除filter
- Teleport
- Suspense
6、Composition API如何实现代码的逻辑复用?
composition API通过把代码的逻辑抽离出来进行封装,并把封装的内容直接引用到生命周期里面,已达到代码的逻辑复用效果。
7、Vue3如何实现响应式?
- 利用
reactive注册响应式对象,对函数返回值进行操作。 - 利用
Proxy劫持数据的get,set,deleteProperty,has,own。 - 利用
WeakMap,Map,Set来实现依赖收集。 - 缺点: 使用大量
ES6新增特性,旧版本浏览器兼容性差。
8、Watch和watchEffect的区别是什么?
- 两者都可以监听
data属性变化; watch需要明确监听哪个属性;- 而
watchEffect会根据其中的属性,自动监听其变化。
9、setup中如何获取组件实例?
在 vue2 中, Options API 可以使用 this 来获取组件的实例,但是到现在的 vue3 ,已经被摒弃掉了。在 setup 和其他 Composition API 中没有 this ,但是它提供了一个 getCurrentInstance 来获取当前的实例。
10、vue3为何比vue2快?
- Proxy响应式
- PatchFlag
- hoistStatic
- cacheHandler
- SSR优化
- tree-shaking
11、vite是什么?
vite是一个前端的打包工具,是vue作者发起的一个项目;vite借助vue的影响力,发展较快,和webpack有着一定的竞争关系;- 优势:
vite使得程序在开发环境下无需打包,且启动非常快速。
12、Composition API和React hooks的对比
- 前者
setup只会被调用一次,而后者函数会被多次调用。 - 前者无需
useMemo和useCallback(即缓存数据和缓存函数),因为setup只调用一次。 - 前者无需顾虑调用顺序,而后者需要保证
hooks的顺序一致。 - 前者
reactive+ref比后者的useState,要难理解。
📸七、结束语
从 vue2.x 的基础知识,再到 vue2.x 的原理知识,最后到 vue3.x 的新特性和原理知识学习,全文贯穿着 vue 的知识要点及相关知识点所涉及到的一些面试题。
关于本文的介绍到这里就结束啦!希望对大家有帮助~



