vue2源码解析-diff算法及响应式原理

274 阅读3分钟

vue2源码解析

这里主要记录下diff算法和响应式原理

Diff算法

vue的源码借鉴了 snabbdom(虚拟dom库)

diff算法节点比较准则

  • diff算法是对新的虚拟dom与老的虚拟dom进行精细化比较最后将比较的结果重新渲染反映到真实的dom上

  • diff算法只对同一个虚拟dom才进行精细化比较,否则只会进行暴力插入和插入

  • diff算法只会进行同层比较不会进行跨层比较

diff算法的优化策略

四指针:

  • oldStartIdx(旧前指针)
  • newStartIdx(新前指针)
  • oldEndIdx(旧后指针)
  • newEndIdx(新后指针)

diff算法指针的命中查找规则:

4种命中查找规则:按照顺序依次命中且只命中一个,若四个都未命中,则重新执行循环

  • 1、新前与旧前(新增节点,新前与新后所包含的节点 [新前, 新后] 就是新增节点,在旧前指针之前插入新增节点)
  • 2、新后与旧后(删除节点,将旧前与旧后所包含的节点 [旧前, 旧后] 删除)
  • 3、新后与旧前(移动位置,旧前插入到旧后之后)
  • 4、新前与旧后(移动位置,旧后插入到旧前之前)

命中失败的出口:

oldStartIdx>oldEndIdx && newStartIdx>newEndIdx

1、新前与旧前

旧前-> A        A <-新前
       B        B 
旧后-> C        M
                N 
                C <-新后

当新前与旧前命中之后,新前旧前指针都会继续向下移动

             A        A 
             B        B 
旧前-> 旧后-> C        M <-新前
                      N 
                      C <-新后

当新前与旧前指针没有命中的时候,则判断新后与旧后指针是否命中,若命中,则两个指针向上移动

             A        A 
      旧后-> B        B 
     旧前->  C        M <-新前
                      N <-新后
                      C 

此时由于oldStartIdx>oldEndIdx所以循环结束,此时发现新前与新后两个指针所囊括的元素[M,N]是我们需要新增的元素

新增准则:我们要在旧前指针之前插入需要新增的元素

2、新后与旧后

旧前-> A        C <-新前
       B        D <-新后
       C       
旧后-> D            

当旧前与新前指针未命中的时候,判断新后与旧后是否命中,命中则上移指针

旧前-> A        C <-新前 <-新后
      B        D 
旧后-> C       
      D            

当旧前与新前指针未命中的时候,判断新后与旧后是否命中,命中则上移指针

                  <-新后              
旧前-> A        C <-新前 
旧后-> B        D 
      C       
      D            

此时newStartIdx>newEndIdx循环结束,此时发现旧前与旧后指针囊括的元素[A, B]就是我们需要删除的元素,于是我们遍历指针删除这两个元素

3、新后与旧前

旧前-> A        C <-新前 
       B        B 
旧后-> C        A <-新后

当旧前与新前以及旧后与新后都不匹配的时候,我们尝试命中旧前与新后,命中后我们将旧前插入到旧后之后,并移动指针

             C <-新前 
旧前-> B     B <-新后
旧后-> C     A 
      A                                
                   C <-新前 <-新后
                   B 
旧前-> 旧后-> C     A 
             B
             A                                

旧前与新前命中移动指针两边都结束了循环

4、新前与旧后

旧前-> A        C <-新前 
       B        A 
旧后-> C        B <-新后                           

当前三种命中方式都没命中,但是命中了新前和旧后的第四种命中方式,我们将旧后节点插入到旧前之前,并移动相应的指针。

      C
旧前-> A        C 
旧后-> B        A <-新前 
               B <-新后                           

然后后续都是命中新前与旧前的第一种命中方式结束,两边都结束循环。

5、四种方式都没命中

当四种方式都未命中的情况下需要进行一下操作,为了性能的优化我们利用Map缓存所有旧节点的索引。

如果新节点是全新的节点则我们找不到其在旧节点中的对应的索引说明需要插入该新节点。

如果新节点可以在旧节点中找到对应的索引则需要移动该节点,并且都需要将新前指针向后移动,结束循环并删除多余的元素

旧前-> A        D <-新前 新后
       B  
旧后-> C                            

我们可以看到这种情况四种都命中不了,我们需要将D插入在旧前的前面,并移动新前指针结束循环,并删除旧前指针到旧后指针囊括的ABC元素

旧前-> A        B <-新前 新后
       B  
旧后-> C                            

这种情况也是四种都不命中,我们需要将新节点中对应旧节点中的那个元素插入到旧前的前面,并新前移动指针结束循环,并删除旧前指针与旧后指针囊括的元素ABC。

响应式原理

Object.defineProperty(obj, key, {}): 直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

let obj = {}
Object.defineProperty(obj, 'a', {
  value: 2
})


// 数据劫持
Object.defineProperty(obj, 'a', {
  get() {

  }
})

检测对象

检测数组

vue 底层改写了 array 的7个方法

  • 1、push
  • 2、pop
  • 3、unshift
  • 4、shift
  • 5、splice
  • 6、sort
  • 7、reverse

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null

const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)

Object.setPrototypeOf(o, arrayMethods)
// o.__proto__ = arrayMethods