[TOC]

2 / 8 原则

20% 的知识覆盖 80% 的功能

注意点

watch

watch的是引用类型,是拿不到 oldVal

v-for

key是什么作用?

利于 Diff 算法,性能优化

事件

事件被绑定到哪里?(此处可以与react的事件对比)

  1. event 对象是原生的
  2. 事件被挂载到当前对象 ```vue // prower by vite

<a name="PxYhr"></a>
#### 生命周期

- 挂载阶段
- 更新阶段
- 销毁阶段


重点:父子组件的生命周期:<br />**父 beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted**

- **parent created -> child created -> child mounted -> parent mounted**
- **parent beforeUpdate -> child beforeUpdate -> child updated -> parent updated **
- **parent beforeDestory -> child beforeDestory -> child destroyed -> parent destoryed**

**重点:**

- **beforeCreate 阶段,vue 实例还未初始化,data observer 和 event/watcher 未调用**,所以对 data/methods/文档节点的调用无法得到正确数据
- **created 阶段,渲染 HTML 文档,dom 节点和 css 规则树和 js 文件被解析后,没有进入到浏览器的 render 过程(也就是说实例已被初始化,还未挂载到 $el 上,无法获取到对应的节点,但是可以获取到 data/methods 中的数据)**
- **beforeMount 与 created 类似**
- **mounted 阶段,浏览器完成了 dom 树和 css 规则树的渲染,完成对 render tree 的布局,浏览器调用渲染器 paint 在屏幕上显示(在 vue 中,vue 的 template 成功挂载到 $el 中,可以调用节点)**

<a name="QqjBS"></a>
#### vue-router
动态路由:`/:id`<br />懒加载:`() =>import([文件名])`

路由模式:hash,history

hash:

- hash变化会触发网页跳转,浏览器前进与后退
- hash变化不会刷新页面
- hash不会提交到server端
```javascript
window.onhashchange = function(event) {
  console.log(location.hash)
}

history(需要server配合)

  • url规范的路由,跳转不刷新页面
  • history.pushState/history.replaceState
  • window.onpopstate
    // 监听浏览器的前进/后退
    const state = {name: 'page1'}
    history.pushState(state, '', 'page1')
    window.onpopstate = funtion(event) {
    console.log(event.state, location.pathname)
    }
    

问题

Vue2

Vue对象的变化
Vue无法检测property的添加或删除。
因为Vue在初始化实例时会对property执行getter/setter转化,所以property必须在data对象上存在才能让Vue将它转为响应式的。

可以使用Vue.set(object, propertyName, value)向嵌套对象添加响应式。

如果你需要为已有对象赋值多个新的property,可以使用Object.assign。但是这样添加的新的property不会触发更新。这时需要创建原对象混合的新对象。

// 代替 Object.assign(this.someObject, { a: 1, b: 2 })
this.someObject = Object.assign({}, this.someObject, {a: 1, b: 2})

Vue数组的变化
Vue无法检测2种数组的变化:

  • 利用索引直接设置一个数组项vm.items[indexOfItem] = newValue
  • 修改数组的长度,vm.items.length = newLength

第一种有2种方法解决:

  • Vue.set(vm.items, indexOfItem, newValue)
  • vm.items.splice(indexOfItem, 1, newValue)

第二种:

  • vm.items.splice(newLength)

Vue异步更新队列
Vue在更新DOM时时异步执行的。只要数据变化,Vue将开启一个队列,并缓存在同一事件循环所发生的数据变更。
如果同一watcher被多次触发,只会被推入到队列中一次。
重要:在缓存中去除重复数据可以避免造成不必要的计算和DOM操作。
在下一个事件循环’tick’,Vue刷新队列并执行。

Vue内部对异步队列尝试使用原生的Promise.then,MutationObserver,setImmediate,如果执行环境不支持,则会采用setTimeout(fn,0)代替。

Vue 有了数据劫持为什么还要 DOM diff?

现在前端框架有2种数据变动侦测方式,一种是 pull,一种是 push。
React是 pull,在进行 setState 操作后显示更新数据,使用 diff 算法一层层找出差异,然后 patch 到 DOM 树上,React 一开始不知道哪里变化了,只是知道变化了,然后暴力进行查找那个变化。
Vue 的响应式系统是 push 的代表,Vue 初始化的时候就对 data 的数据进行依赖收集,因此 Vue 能实时知道哪里发生了变化,一般绑定的细粒度过高,会生成大量的 Watcher 实例,则会造成过大的内存和依赖追踪的开销,而细粒度过低无法侦测到变化,因此,Vue 采用的是中等细粒度的方案,只针对组件级别进行响应式监听也就是 Push,这样就可以知道哪个组件发生了变化,再对组件进行 diff 算法找到具体变化的位置,这是 pull 操作,Vue 是 pull + push 结合进行变化侦测的。

https://zhuanlan.zhihu.com/p/101330697

https://juejin.cn/post/6844904031983239181#heading-19

为什么要引入虚拟 DOM,而不是直接把组件转换为真实的 DOM 元素?

更高效:当组件发生变化,利用虚拟 DOM 树可以对新旧 DOM 进行比较,找出区别,只更新发生变化的 DOM,让渲染更加高效。
更灵活:将原来直接渲染变成先构建虚拟 DOM,在进行渲染。相当于抽离出中间层,对应的渲染层可以针对浏览器的渲染,也可以针对小程序等其他平台的渲染。

响应式原理

Object.defineProperty问题:

  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听新增属性和删除属性(Vue.set,Vue.delete)

当创建 Vue 实例的时候,vue 会遍历 data 选项的属性,并利用 Object.defineProperty 方法为每个属性添加 getter 和 setter 方法进行劫持(getter用来收集依赖,setter用来派发更新),并在内部追踪依赖,在属性被访问和修改时通知变化
每个组件实例都有相对应的 watcher 实例,会在组件渲染过程中记录依赖的所有属性(依赖收集),当改动后,会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。

总结:
vue 采用数据劫持结合发布-订阅模式,通过 Object.defineProperty 方法来劫持各个属性的 getter / setter,在数据变动时发布消息给订阅者,触发响应的监听回调

computed 实现原理

本质是一个惰性求值的观察者
内部实现了一个惰性的watcher,也就是 computed watch,它不会立刻求值,持有一个 dep 实例
通过this.dirty属性标记计算属性是否需要重新求值
当 computed 的依赖状态发生变化。就会通知整个惰性的 watcher,通过 this.dep.subs.length 判断有无订阅者,

  • 有会重新计算,然后对比新旧值,如果变化会重新渲染。
  • 没有把 this.dirty = true

懒计算特性
缓存特性

computed 和 watch 有什么区别及应用场景?

区别:
computed 计算属性

  • 依赖其他属性值
  • computed属性值有缓存
  • 只有它依赖的属性值发生了变化,下一次获取 computed 的值时才会重新计算computed值

watch:

  • “观察”的作用,没有缓存性,类似于某些数据的监听回调,每当监听的数据发生变化都会执行相应回调进行后续操作

应用场景:

  • computed:进行数值计算时,依赖其他数据
  • watch 需要进行异步操作

    为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

    proxy 是一个包含另一个对象或函数,并允许你对其进行拦截的对象

Object.defineProperty 只能劫持对象的属性,所以需要遍历对象所有的属性,Vue2 里通过递归+遍历 data 对象来实现对数据的监控,如果属性的值也是个对象,需要深度遍历,显然劫持一个完整对象才是更好的选择。
Proxy 可以劫持一个完整的对象并返回一个新的对象。Proxy 不仅可以代理对象,也可以代理数组,还可以代理动态增加的属性。

为什么在 Vue2 中不能检测数组的变化?

Object.defineProperty本身有一定的监控数组下标变化的能力,但是从性能/体验的性价比考虑,尤大就弃用了这个特性
因为处于性能和用户体验性价比,性能代价和获得用户体验不成正比

Vue2中只对pop,push,shift,unshift,splice,sort,reverse做了hack处理,其他数组的属性检测不到

Vue 中的 key 到底有什么作用?

key 给每个 vnode 的唯一 id,用于 diff 操作更加准确和快速

diff 算法:先进行新旧节点的首位交叉对比,当无法匹配时会用新节点的 key,与旧节点进行比对,从而找出相应的旧节点。(通过 tag 和 key 判断,是否是 sameNode)

更准确:带上 key 后不是简单的就地复用,而是通过 sameNode 函数判断 a.key=b.key,可以避免就地复用的情况。如果不加 key,会导致之前节点的状态被保留下来,产生一系列的 bug
更快速:key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度为 O(1)

减少渲染次数,提升渲染性能

nextTick 的原理

JS时单线程的,是基于事件循环的
事件循环的步骤:

  1. 所有同步任务在主线程上执行,形成一个执行栈

主线程之外,还存在一个任务队列,只要异步任务有了结果,就在任务队列上添加一个事件
一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件,那些对应的异步任务,结束等待状态,进入执行栈,开始执行

  1. 主线程不断重复第三步操作

主线程执行过程就是一个tick,所有异步结果都是通过“任务队列”来调度。
消息队列中存放着一个个的任务(task),任务分为2类:宏任务(macro task)和微任务(micro task),每个宏任务结束后,都要清空微任务。

常见的宏任务:setTimeout,MessageChannel,postMessage,setImmidate
常见的微任务:MutationObserver,Promise.then

再Vue2.5,宏任务的降级方法依次为:setImmidate,MessageChannel,setTimeout

vue 的 nextTick 方法的实现原理:

  • vue使用异步队列的方式来控制DOM更新和nextTick回调先后执行
  • micro task因为其高优先级特性,能确保队列中的微任务在事件循环前被执行完毕
  • 考虑兼容问题,vue做了micro task 向 macro task的降级方案。

vue 是如何对数组方法进行变异的?

vue 的 data 为什么必须是函数?

.vue 文件实际上是一个类,在使用的过程中,其实是实例化的过程
因为组件时可以复用的,js 对象时引用关系,如果 data 是对象,那么子组件的 data 属性值就会相互污染,产生副作用。
而 new Vue 的实例不会被复用

Vue的事件机制,$on,$emit,$off,$once

vue事件机制本身就是一个发布-订阅模式的实现。

Vue的渲染过程?

Keep-alive的实现原理和缓存策略

vm.$set 实现原理

响应式的好处?

数据双向绑定,无需操作 DOM,数据修改自动更新视图

解释一下 jsx

是一个 js 的语法扩展。
在 js 里面写 DOM 元素。

vue的 template 和 jsx 的区别

模板优势

  • 模板是一种更静态更具有约束的表现形式,任何可以解析HTML的引擎都能使用,迁移成本较低。
  • 静态模板在编译时进行了比较多的优化。

jsx 的优势:

  • 更加灵活,任何js代码都可以在jsx中执行
  • 但是导致它在编译阶段优化比较困难,需要开发者自己优化

Vue可以提供2种渲染模式,默认为template,也可以通过render function实现更灵活的应用

虚拟 DOM 的好处

vue 虚拟 DOM 的优缺点

优点:

  • 保证性能的下限,框架的虚拟DOM需要适配上层API可能产生的操作,它的一些DOM操作是普适的,所以它的性能不是最优的
  • 无需手动操作DOM(双向数据绑定)
  • 跨平台

缺点:

  • 无法进行极致的性能优化

Vue-router 的 hash 模式和 history 模式的区别

  • 显示上,hash模式带#号,history没有
  • 底层实现上,hash模式利用了onhashchange事件(监听location.hash的改变),而history模式利用HTML5 history新增的2个方法(pushStatereplaceState),pushState()可以改变url地址且不会发送请求,replaceState()可以读取历史记录栈,并且修改浏览器记录
  • 当需要通过URL向后端发送HTTP请求时(如用户手动输入URL后回车或者刷新和重启浏览器),这时候history模式需要后端支持。因为history模式,前端的URL需要与后端发送请求的URL一致,如果后端没有处理的话,就会返回一个404错误
    window.onhashchange = function() {
    // 需要截取一下
      console.log(location.hash.slice(1))
    }
    

    使用history模式,后端不在返回404错误页面,需要前端覆盖所有路由情况,然后给出一个404页面。 服务器处理:所有路径都返回index.html文件

{path: '*', component: NoFoundComponent}

Vue2中Object.defineProperty

此方法不是一个可以shim的属性,所以 Vue 不支持 ie8 及更低版本


Vue3

Vue3 中的 Proxy

这是 es6 的新特性,但是 Vue3 版本也使用了 Object.defineProperty 来兼容ie版本

将对象作为数据传递给组件实例时,Vue 会将其转换为 proxy,使 Vue 在 property 被访问和修改时执行依赖跟踪和更改通知。每个 property 将视为一个依赖项。
首次渲染组件会跟踪一组依赖项列表。反过来组件就成了每个 property 的订阅者,当 proxy 拦截到 set 操作时,该 property 将通知其所有的订阅的组件重新渲染

vuex 和 pinia 区别

https://juejin.cn/post/6986540472986501150
vuex优点:

  • 支持调试功能,如时间旅行,和编辑
  • 适用于大型/高复杂项目
    缺点:
  • vue3开始,getter的结果不会像计算属性那样缓存
  • vuex4有类型安全相关问题

pinia优点:

  • 完整的ts支持
  • 轻巧,1k
  • store的action作为常规函数调用,不需要dispatch或者mapActions辅助函数
  • 支持多个store
  • 支持 Vue devtools、SSR 和 webpack 代码拆分
    缺点:
  • 不支持时间旅行和编辑等调试功能