Vue 原理总结

667 阅读5分钟
vue数据绑定的不足点

因为Vue内部使用Object.defineProperty进行的数据劫持,而这个API无法探测到对象根属性的添加和删除,以及直接给数组下标进行赋值,所以不会通知渲染watcher进行视图更新,而理论上这个API也无法探测到数组的一系列方法(push,splice,pop),但是Vue框架修改了数组的原型,使得在调用这些方法修改数据后会执行视图更新的操作。

vue更改数组的规范

1.不能通过数组的索引(下标)去更改数组的值,这样不会渲染页面。

eg: vm.arr[0] = 10; 虽然数组的数据修改成功,但是并不会渲染到页面.

2.不能通过更改数组长度的方式去更改数组,这样也不会渲染页面。 eg: vm.arr.length = 1; 同上,长度修改为1,页面依然未渲染.

3.解决方法 Vue提供了常见的七个数组变异方法来帮助我们修改数组且渲染 pop,shift,unshift,splice,sort,reverse,push 通过这些方法能达到我们想要的结果

vue更改对象的规范

1.向对象内添加或者删除属性,不会渲染页面。

eg:vm.obj.c = 999; delete vm.obj.a;

2.解决方法

对象没有数组那样的变异方法解决,而是通过Vue创建的$.set 来解决

一个完整的代码

  • 1.先创建一个data对象

  • 2.对Object.defineProperty方法封装,对data对象中的属性监听是否修改,并对它们修改做操作和读取做操作,即 set() 和 get(),设置属性值时,判断是否修改,没修改则不做操作. observer(value):是对data对象属性的属性 例如 a,b 同样进行监听 render():是对数据是否渲染的判断 输出语句

  • 3.observer函数用来引入需要执行监听的数据,并判断数据类型是否为对象, 是则对对象的属性进行遍历操作,再通过上面封装的方法defineRetive进行监听。

  • 4.设置一个函数来判断是否渲染了页面

  • 5.这样,对对象属性的修改就实现了监听渲染的效果了 渲染了页面 对对象里的属性或者属性的属性进行修改 必然得到渲染

未渲染页面 通过添加属性,删除属性,数组索引的方式是不会得到效果的 前面提过,是不会渲染的 因为Object.defineProperty没有对这些新增和变化的数据监听 数组则是不采用逐个监听的方式,每次变化都需要重新监听太耗费性能,遂采用数组方法来修改数据。 而push 和 reserve 方法则是原生的数组方法,并没有经过Vue内部对其进行修改,所以也达不到效果。

  • 6.为了让我们对象能添加属性,可以简单封装一个set()方法实现set()方法实现 set()既能对对象数据处理也能对数组处理 数组用splice方法进行修改 对象调用之前的封装方法

  • 7.数组方法的实现 需要通过封装处理才可以达到渲染页面的效果 将原先的数组对象的原型拿过来自己再创建一个具有这个原型的对象 然后遍历这七个常用方法以数组的方式,在里面调用原型上的原生方法再改变它们的this指向,接受传参值,为对象加上这七个方法,后面数组要用时将他的原型改变为此对象;

原型改变为我们创建的对象,并且判断数据类型为数组时的操作

这样就可用数组方法渲染到页面了

v-module的原理

可以通过model属性的prop和event属性来进行自定义。

Vue事件绑定原理

原生事件绑定是通过addEventListener绑定给真实元素的,组件事件绑定是通过Vue自定义的$on实现的

Vue模版编译原理

Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:

  • 生成AST树
  • 优化
  • codegen

Vue 中是如何检测数组变化

核心点:

  • 首先,使用函数劫持的方式,重写了数组的方法。
  • Vue 将 data 中的数组,进行了原型链重写。指向了自己定义的数组原型方法,这样当调用数组 api 时,就可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次进行观测。

  • 第一步同样是初始化用户传入的 data 数据。对应源码src/core/instance/state.js的 112 行的initData函数。
  • 第二步是对数据进行观测。对应源码src/core/observer/index.js的 124 行。
  • 第三步是将数组的原型方法指向重写的原型。对应源码src/core/observer/index.js的 49 行。protoAugment方法
  • 第四步进行了两步操作。首先是对数组的原型方法进行重写,methodsToPatch

为什么Vue采用异步渲染

问题:如果Vue不采用异步更新,那么每次数据更新时是不是都会对当前组件进行重写渲染呢?

答案:为了性能考虑,会在本轮数据更新后,再去异步更新视图。

  • 第一步调用dep.notify()通知watcher进行更新操作。
  • 第二步其实就是在第一步的notify方法中,遍历subs,执行subs[i].update()方法,也就是依次调用watcher的update方法。
  • 第三步是执行update函数中的queueWatcher方法。
  • 第四步就是执行nextTick(flushSchedulerQueue)方法,在下一个tick中刷新watcher队列

vue2.0的diff算法

简单来说,diff算法有以下过程

  • 同级比较,再比较子节点
  • 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
  • 比较都有子节点的情况(核心diff)
  • 递归比较子节点 正常Diff两个树的时间复杂度是O(n^3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。 Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。

Vue3.x借鉴了 ivi算法和 inferno算法

在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,

数据发生变化时,vue 是怎么更新节点的

先根据真实DOM生成一颗virtual DOM,当virtual DOM某个节点的数据改变后会生成一个新的Vnode,然后Vnode与oldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode.

diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。

virtual DOM和真实DOM的区别?

virtual DOM是将真实的DOM的数据抽取出来,以对象的形式模拟树形结构。 Vnode和oldVnode都是对象。

diff的比较方式

采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。

diff流程图

当数据发生改变时,set方法会让调用Dep.notify通知所有订阅者Watcher,订阅者会调用patch给真实的DOM打补丁,更新相应的视图。

patch函数接收两个参数oldVnode和Vnode分别代表新的节点和之前的旧节点

  • 判断两节点是否值得比较,值得比较则执行patchVnode
vue-router原理
  • 第一步: install方法

通过Vue.mixin()方法,全局注册一个混合,影响注册之后所有创建的每个 Vue 实例,该混合在beforeCreate钩子中通过Vue.util.defineReactive()定义了响应式的_route属性。所谓响应式属性,即当_route值改变时,会自动调用Vue实例的render()方法,更新视图。

  • 第二步: crateMap转换路由格式

将路由信息转换格式,利用createmap转换路由信息格式。

  • 第三步: 创建VueRouter类,初始化mode routes routesMap对象

在路由里面new了,说明 vue-router中是一个类,此时 ,将new中的对象options 传到VueRouter类中。将mode routes routesMap history挂载到VueRouter上,并使用了init方法。

  • 第四步: hash 和history

在constructor中使用this.init()方法,在init方法里面可以判断路由的mode是hash还是 history。