小白学习Vue2.x源码

vue的数据动态响应的原理。

1. 将所有的数据,都改为Object.defineProperty, 这样就会监听到所有的数据变换了.

1)监测的是两个类型: object的和array的. 分为对象和数组类型.
2)在getter里面进行收集, 将数据变为可监测的. (defineProperty => js自带的方法)
3) 对象的监测, 是可以直接使用set和get来监测的,但是, 数组就没办法了, 无法监测数组的变动。
4) 原生的数组的方法不会有其他作用,如果监测, 那么在原生的js上, 封装自己的方法。

 Array.defineProtype.newPush = function (obj,value) {
    
    
    //这里可以通知变化。 
    console.log('obj新增了数据对象。')
    obj.push(value)
}

5) 其实使用的不是新增方法, 而是进行了方法的拦截, 将数组中的方法进行拦截.

(1)数组方法拦截器.

    实现原理: 将原生的数组里的方法都进行拦截重写, 这也是vue的真实做法。 
    实现局限: 这样做的只能监测到, 通过数组的pop, push。。。等方法, 如果通过下标进行修改就不行了。 
              所以, vue针对这样的情况, 做了vue.$set和vue.$delete方法来弥补。 

2. 依赖收集, 将每一个数据需要依赖的数据,建一个数组, 相关的数据都放进去, 如果数据修改了, 那么可以根据这个数据知道需要修改的数据.

3. 怎么收集, 怎么检测.

4. 通知依赖.

2. 虚拟DOM

1) 虚拟DOM就是, 使用js来代替真正的DOM树. 这样就可以监测到DOM树进行的变化了。
2) 真正的DOM树,很复杂,所以在真实操作DOM的时候,会很伤, 如果先用虚拟DOM来进行监测,看变化的那一些, 这样就会减少性能输出。
3) 虚拟DOM就是利用js的计算能力换取渲染DOM的方法。
4) diff-Dom算法, 就是针对js代表的DOM进行DOM树的修改操作, 监听看哪些被修改了, 就去真正的修改DOM。
5) VNode, 就是用来进行描述虚拟DOM的数据格式。
- 1. 包含的数据有 tag、data、children、text、elm、context等, 标签带有的数据和属性。
- 2. 通过属性的搭配, 可以表示各种不同的节点对象了。
6) VNode的种类: *注释、*文本、*元素、*组件、*函数式组件、*克隆 六种VNode的形式。
- 1. 注释Node: text、isComment两个属性。
- 2. 文本节点: text属性。
- 3. 克隆节点: 将所有的节点属性都绑定进来。
- 4. 元素节点: // VNode节点
{ tag:'div', data:{}, children:[ { tag:'span', text:'文字内容' } ] }
- 5. 组件节点: 除了和上面一样的, 还有两个特有属性: componentOptions(组件的props) 和componentInstance(组件的vue)。
- 6. 函数式组件节点: 比组件节点, 还多两个属性: fnContext和fnOption.
7) VNode的作用: 在template加载的时候, 将DOM => 虚拟DOM,然后等数据进来, 如果监听到有数据改变了,那么就会自动的将视图进行更新。
3. DOM-DIff算法:
- vue中的DOM-Diff算法叫做patch, 就是打补丁的意思。
- 新的node, 加上去, 没有的node,删掉, 一样的node,以新的为主。
- 以新的node为基准,改造旧的node。

创建节点、删除节点、更新节点

- 创建节点: 先判断是  元素=> 注释 => 文本。 
- 删除节点: 找到父亲节点, 然后删掉子节点(removeChild方法)。 
- 更新节点: (只包含静态节点、文本节点、元素节点)
    1. 如果是静态节点:```javascript <div>你好, 哈哈哈</div>```, 直接跳过。 
    1. 如果是纯文本的: 那就比对, 需要删,就删, 需要更新就更新。 
    2. 如果是元素节点: 1) 如果子为空, 那直接清空
                      2) 如果子不为空, 那么新旧 递归进行对比。 
    3. 如果是克隆节点: 1) 创建一个clone节点,
                      2) 将所有的节点信息放进来, 然后对其更新(包含的所有的节点信息). 

更新节点的算法:

  • 通过两层循环, 外层和内层的循环(外层循环new, 内层循环old, 这样多的old直接删除即可), 对比元素节点的子节点, 如果子节点不一样 那么,就需要进行改动(创建, 删除, 移动, 更新)

    1. old有, new 没有 => removeElement
    2. old有, new 有 => 看看内容, 不一样, 更新.=> 可能移动位置.
    3. old没有, new有 => createElemnt
    4. old没有, new 没有 => 不做任何处理.
  • 新建的node,放入(old 即DOM树)的位置是, 未处理的节点之前, 而不是已处理的节点之后.

删除子节点算法:

  • 循环结束后, 发现在old里面还没有被处理的节点就是待删除的子节点.

更新子节点算法:

  • 只要在old里面找到了new中的节点, 而且位置也一样, 那么就直接更新(这里不管他是不是内容改变, 可能直接更新会比看内容是否更新性能要高)

移动子节点算法:

  • old和new中都有, 但是, 其中的位置不对, 比如new是DOM的第二个子节点, old是DOM的第三个子节点, 那么就将old的第三个移动到第二个里面.

  • 这里, 移动的位置同样是old中, 未处理的节点之前, 而不是已处理的之后, 否则在插入的时候, 又会遇见同样的问题的.

双层循环的弊端与改进:

  • 如果是节点数量很多的时候, 双层循环会使得速度很慢, 那么, 在算法上怎么实现速度的提升呢.
  • vue中使用的方式是
    新前 => 旧后, 如果相同, 新前的下标 +1 , 旧后的下标 -1. 旧后移动位置到未处理之前.
    新前 => 旧前, 如果相同, 新前、旧前的下标都 +1. 旧前位置不动.
    新后 => 旧前, 如果相同, 新后 -1, 旧前 +1. 旧前移动到未处理之后.
    新后 => 旧后, 如果相同, 新后、旧后 -1. 旧后, 移动到未处理之前.
    如果还不行 => 老样子双层循环.
  • 每次更新的下标为前后都向进一位. 然后标记为已处理的节点.
  • 如果, oldStartIdx > oldEndIdx就是, 剩下的所有的新的都需要插入到DOM(就是old里面)
  • 如果, newStartIdx > newEndIdx , 剩下的都是需要删除的了;

模版编译

  • 模版编译就是将用户输入的html代码=> VNode虚拟DOM的过程, 因为DOM-Diff的对象就是VNode. 所以, 这是所有的前提.
  1. 将v-if、v-for等非html内容改为原生的html代码. render函数的作用.

用户模版 => 模版编译 => render函数 => VNode => patch(DOM-Diff) => 视图展示

  • 抽象语法树AST.
    以树状形式来表现编程语言的语法结构.
    1. 模版解析阶段:将模版字符串 => 抽象语法树AST.
    2. 优化阶段:遍历AST, 找到静态节点, 打上标记, 之后是不需要进行更新的.
    3. 代码生成阶段: 将AST转换成渲染函数.
  1. 解析器不只有一个, 包含了html、文本、过滤器等解析器, 但是主要的还是, html解析器, 然后, 遇到文本、过滤器解析器, 再调用对应的解析器.

parser

||

paeseHtml <== parseText、parseFilters

  1. html解析器, 在遇见文本时,触发chars钩子函数, 在遇见注释时, 触发comment钩子函数.

  2. 解析HTML不同格式的内容.

4.1 解析注释 <!-- 我是注释-->
利用正则表达式,如果监测到<!-- 那么, 再向后找, 如果仍然可以找到-->, 就是注释,保留就不动, 不保留,那么render函数, 往commentEnd+3, 从结束点开始往后render, 跳过

4.2 解析条件注释 <!-- 我是注释 -->
利用正则表达式, 如果监测到<!--, 那么再向后查找, 如果找到了条件注释, 那么就使用render来跳过render.

4.3 解析DOCTYPE <!DOCTPE>
正则表达式=> 跳过, 使用advance(length)方法对render的位置进行修改.

4.4 解析开始标签
正则表达式 =>

  1. 先找到<div, 后面再查找class = 'a', style = '', 这样的字眼, 将所有的这些属性都提取完就找到了标签.
  2. 如果是自闭合标签,就是监测,在/>或者 ></div>, 就是是否闭合的了.

4.5 解析结束标签
正则表达式 => 如果符合条件, 那么直接将remove往后移动.
然后, 调用end() 钩子函数, 直接结束就行.

4.6 解析文本

  1. 先找 <, 如果是开头的,那么建看用其他类型的判断, 如果不是开头, 那么就是解析文本.
  1. 保证AST的节点层级关系:
  1. AST怎么保证的层级关系: 使用stack栈, 将解析的节点元素,放进去, 就可以将对应关系联系上了.
  2. stack的使用,js里面的数组包含了stack的很多方法, 就是pop和push, 那么直接使用数组就可以达到stack的作用了.

3. 优化阶段

1) 标记静态节点

1.1 先查看是否是根节点, 如果是根节点就进行标记, 
1.2 如果不是查看是否是元素节点, 如果是就递归,递归过程中,如果是就进行标记.
1.3 type的分类和意思是, 1: 就是静态节点, 2: 含有变量的文本节点. 3: 不包含变量的文本节点. 
1.4 如果是元素节点,还需要继续遍历.  

2) 标记静态根节点

2.1 就是从AST的根向下找, 找根节点, 如果是, 就进行标记, 标记完后判断, 是元素节点递归, 否则, 跳过. 

总结: 在AST生成阶段, 将所有的静态节点标记上, 那么, 在patch阶段就可以直接跳过, 减少了性能消耗.

猜你喜欢

转载自blog.csdn.net/weixin_40944062/article/details/107962508