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直接删除即可), 对比元素节点的子节点, 如果子节点不一样 那么,就需要进行改动(创建, 删除, 移动, 更新)
- old有, new 没有 => removeElement
- old有, new 有 => 看看内容, 不一样, 更新.=> 可能移动位置.
- old没有, new有 => createElemnt
- 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. 所以, 这是所有的前提.
- 将v-if、v-for等非html内容改为原生的html代码. render函数的作用.
用户模版 => 模版编译 => render函数 => VNode => patch(DOM-Diff) => 视图展示
- 抽象语法树AST.
以树状形式来表现编程语言的语法结构.- 模版解析阶段:将模版字符串 => 抽象语法树AST.
- 优化阶段:遍历AST, 找到静态节点, 打上标记, 之后是不需要进行更新的.
- 代码生成阶段: 将AST转换成渲染函数.
- 解析器不只有一个, 包含了html、文本、过滤器等解析器, 但是主要的还是, html解析器, 然后, 遇到文本、过滤器解析器, 再调用对应的解析器.
parser
||
paeseHtml <== parseText、parseFilters
-
html解析器, 在遇见文本时,触发chars钩子函数, 在遇见注释时, 触发comment钩子函数.
-
解析HTML不同格式的内容.
4.1 解析注释 <!-- 我是注释-->
利用正则表达式,如果监测到<!--
那么, 再向后找, 如果仍然可以找到-->
, 就是注释,保留就不动, 不保留,那么render
函数, 往commentEnd+3
, 从结束点开始往后render, 跳过
4.2 解析条件注释 <!-- 我是注释 -->
利用正则表达式, 如果监测到<!--
, 那么再向后查找, 如果找到了条件注释, 那么就使用render来跳过render.
4.3 解析DOCTYPE <!DOCTPE>
正则表达式=> 跳过, 使用advance(length)方法对render的位置进行修改.
4.4 解析开始标签
正则表达式 =>
- 先找到
<div
, 后面再查找class = 'a', style = ''
, 这样的字眼, 将所有的这些属性都提取完就找到了标签. - 如果是自闭合标签,就是监测,在
/>
或者></div>
, 就是是否闭合的了.
4.5 解析结束标签
正则表达式 => 如果符合条件, 那么直接将remove往后移动.
然后, 调用end() 钩子函数, 直接结束就行.
4.6 解析文本
- 先找
<
, 如果是开头的,那么建看用其他类型的判断, 如果不是开头, 那么就是解析文本.
- 保证AST的节点层级关系:
- AST怎么保证的层级关系: 使用stack栈, 将解析的节点元素,放进去, 就可以将对应关系联系上了.
- stack的使用,js里面的数组包含了stack的很多方法, 就是pop和push, 那么直接使用数组就可以达到stack的作用了.
3. 优化阶段
1) 标记静态节点
1.1 先查看是否是根节点, 如果是根节点就进行标记,
1.2 如果不是查看是否是元素节点, 如果是就递归,递归过程中,如果是就进行标记.
1.3 type的分类和意思是, 1: 就是静态节点, 2: 含有变量的文本节点. 3: 不包含变量的文本节点.
1.4 如果是元素节点,还需要继续遍历.
2) 标记静态根节点
2.1 就是从AST的根向下找, 找根节点, 如果是, 就进行标记, 标记完后判断, 是元素节点递归, 否则, 跳过.