vuejs篇

142 阅读3分钟

响应式原理

官网图

源码图

源码标注图

白话

  1. 从 new Vue 开始,首先通过 get、set 监听 Data 中的数据变化,同时创建 Dep 用来搜集使用该 Data 的 Watcher。Object.defineProperty()Dep\color{#fd7741}{Object.defineProperty()和Dep}
  2. 编译模板,创建 Watcher,并将 Dep.target 标识为当前 Watcher。readerwatcher\color{#fd7741}{reader和watcher}
  3. 编译模板时,如果使用到了 Data 中的数据,就会触发 Data 的 get 方法,然后调用 Dep.addSub 将 Watcher 搜集起来。订阅\color{#fd7741}{订阅}
  4. 数据更新时,会触发 Data 的 set 方法,然后调用 Dep.notify 通知所有使用到该 Data 的 Watcher 去更新 DOM。发布\color{#fd7741}{发布}

参考

深入响应式原理 — Vue.js

图解 Vue 响应式原理 - 掘金

Object.defineProperty() - JavaScript | MDN

Vue数据双向绑定

为什么Vue3.0使用Proxy实现数据监听(defineProperty表示不背这个锅)

Vue生命周期函数

beforeCreate

在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。

created

已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。

data 和 methods都已经被初始化好了。

beforeMount

在挂载开始之前被调用:相关的 render 函数首次被调用。(遇到子组件,进入子组件开始初始化

执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的

mounted

实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。 执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。如果我们想要通过插件操作页面上的DOM节点,最早可以在这个阶段中进行

beforeUpdate

当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步

updated

页面显示的数据和data中的数据已经保持同步了,都是最新的

beforeDestroy

Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods,指令,过滤器……都是处于可用状态,还没有真正被销毁

destroyed

这个时候上所有的 data 和 methods,指令,过滤器……都是处于不可用状态,组件已经被销毁了。

activated

被 keep-alive 缓存的组件激活时调用。

deactivated

被 keep-alive 缓存的组件停用时调用

Vue中父子组件生命周期执行顺序

父beforeCreate
↓
父created
↓
父beforeMount(子beforeCreate->子created->子beforeMount->子mounted)
↓
父mounted

异步更新队列

原理

  1. Vue 在更新 DOM 时是异步执行的。
  2. 只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
  3. 如果同一个 watcher 被多次触发,只会被推入到队列中一次
  4. 当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新

使用

为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)

写法

因为 $nextTick() 返回一个 Promise 对象,所里以下3种都可以

this.$nextTick(function () {
  console.log(this.$el.textContent) // => '已更新'
})
this.$nextTick().then(f1,f2)
await this.$nextTick()

程序化的侦听器

事件:hook:+vue生命周期钩子

this.$once('hook:beforeDestroy', function () { picker.destroy(); timer=null })

解惑

vue为何无法监听对象的属性的改变?

Object.defineProperty()

拦截对象属性的值的变化

删除或者添加新的属性的 ×

vue为什么无法监听数组索引的变化,但是数组方法却可以删除或者添加新的属性

  1. Object.defineProperty()是可以对Array的索引进行拦截的
  2. 但是Vue考虑到性能与成本不值得,所有放弃到了这个特性
  3. Vue对shift,pop,unshift,push,splice,sort,reverse方法进行了“重写”(只针对data里配置的数组生效),达到数据响应式的效果

Vue3.0使用Proxy实现响应式,有什么优点和缺点

优点:

  1. Proxy可劫持属性的删除和新增,有13种针对目标对象的捕捉器方法
  2. Proxy不用层层遍历属性,就可实现深度劫持
  3. 可代理所有类型的对象
  4. 不修改源对象,返回一个新的代理对象

缺点:

  1. 兼容性不好,babel的polyfill库也只能实现一部分捕捉方法

为什么 vue 组件中的 data 必须是函数

因为vue组件可能被复用,当复用的时候,就相当于创建了一个组件的实例,如果data是一个对象,组件实例间会共享同一data对象,如果是data函数的话,data获取就是独立的数据对象。 !!data 中数据可能会被复用,要保证不同组件调用的时候数据是相同的

vue中key属性的作用

  1. 在vue data变化之后,通过watcher的update方法,执行reader函数,生成vdom,
  2. 进行vdom diff,递归的进行同级节点比较,
  3. 同级节点进行diff children
  4. 如果以上逻辑都匹配不到,再把所有旧子节点的 key 做一个映射表,然后用新 vnode 的 key 去找出在旧节点中可以复用的位置。
  5. 如果key命中,就使用这个vnode和旧vnode进行patchVnode,就可以直接复用。不用重新创建新的dom,减少开销(原生dom创建会有很多事件并且会触发回流)
// 节点判断
function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      )
    )
  )

vue中key为什么不能用index和随机数

  • 因为数据顺序变化后,一样的index确是不一样的值,进行patchVnode时还是不能复用,失去了key本该有的意义
  • 随机数在diff的时候会认为都是全新的vnode,那样就造成,本来可以复用的都要重写渲染,无意义