天天看点

Vue2.x 源码阅读思路笔记Virtual DOMHTML parser构建一个最简单的数据绑定的VueVNode 的属性 attrs 和 props控制语句响应式原理深度追踪依赖变化事件处理生命周期

目前公司主要技术栈是Vue,为了更好的使用,完成的了解Vue的原理是很有必要的。刚开始直接阅读Vue源码时,发现自己阅读的效率很低。偶然间(其实不偶然)

在Github中发现了这份笔记 如何学习Vue2源码,完整的记录了实现Vue框架的完整过程。

我fork了作者的项目,跟着作者的思路如何学习Vue2源码,完整走了一遍如何实现一个2.X版本的Vue。阅读过程中我拉了一个新分支如何学习Vue2源码(带注释),添加了详细的注释,方便自己记忆。通过阅读作者的github,节省自己大量时间~

下面内容为自己的笔记,自己整理思路用。

建议阅读这位大神的github:如何学习Vue2源码。

我的阅读记录: 如何学习Vue2源码(带注释)

Virtual DOM

参考:https://mp.weixin.qq.com/s/QXQAPEXojB9Zvri6GdXcJg
  • 生成virtual DOM
    • 用js对象模拟DOM树(html语法树?)
  • render真实DOM
    • virtual对象的render函数
  • 生成新的virtual DOM
  • render新的真实DOM
  • diff新旧真实DOM,保存变更的patches
    • diff算法复杂度问题
    • 深度优先遍历,每遍历一个node同时跟新的树比较,记录差异
    • 差异类型,不同类型处理方式不一致
    • 列表对比算法,调换顺序最优解?
  • 在旧的真实DOM上应该patches
    • 不同的patch类型,不同的处理方式

HTML parser

  • Token解析
    • HTML字符串 --> Token数组
  • 语法树解析
    • Token数组 --> HTML AST
  • VNode树
  • Dom树

首先有2个重要的概念要留意,将会贯穿后续的文章:静态、动态(runtime)。

我们给一下以下简单的定义:

静态: 任何时刻运行转换函数,同一个输入得到的数据结构都是一致的

动态: 存在某一时刻运行转换函数,同一个输入得到的数据结构是不一致的

所以上边所有涉及到的数据结构可以归为:

字符串 是 静态 的

HTMLElment Token 流 是 静态 的

AST 树 是 静态 的

VNode 树 是 动态 的

DOM 树 是 动态 的

构建一个最简单的数据绑定的Vue

AST树、VNode render、VNode三者之间的渐进关系

Vue2.x 源码阅读思路笔记Virtual DOMHTML parser构建一个最简单的数据绑定的VueVNode 的属性 attrs 和 props控制语句响应式原理深度追踪依赖变化事件处理生命周期

VNode 的属性 attrs 和 props

Vue2.x 源码阅读思路笔记Virtual DOMHTML parser构建一个最简单的数据绑定的VueVNode 的属性 attrs 和 props控制语句响应式原理深度追踪依赖变化事件处理生命周期

控制语句

v-if v-else-if v-else v-for

新增语法步骤同上及几章:

  • parse阶段增加解析v-for v-if部分的代码,在AST上添加这部分语法的属性
  • 生成render函数阶段,同样要增加将AST转换成render函数的fn,
  • 生成Vnode阶段,在单用render函数的时候,需要将v-if语法转换为实际的VNode对象
  • path阶段 将VNode树转换成DOM树

响应式原理

基于Object.defineProperty实现data的数据监听,set 写操作的时候,我们要调用 vm._update() 进行视图更新。

深入理解Vue的computed实现原理及其实现方式

深度追踪依赖变化

obsever、dep、watch之间的关系:https://github.com/coderzzp/vue-come-true/tree/master/vue-come-true-First

深入浅出 - vue变化侦测原理

Vue2.x 源码阅读思路笔记Virtual DOMHTML parser构建一个最简单的数据绑定的VueVNode 的属性 attrs 和 props控制语句响应式原理深度追踪依赖变化事件处理生命周期

详细源码分析:https://segmentfault.com/a/1190000014360080

  • 收集依赖流程:
observe -> 
walk -> 
defineReactive -> 
get -> 
dep.depend() -> 
watcher.addDep(new Dep()) -> 
watcher.newDeps.push(dep) -> 
dep.addSub(new Watcher()) -> 
dep.subs.push(watcher)
           
  • 视图更新流程:
set -> 
dep.notify() -> 
subs[i].update() -> 
watcher.run() || queueWatcher(this) -> 
watcher.get() || watcher.cb -> 
watcher.getter() -> 
vm._update() -> 
vm.__patch__()
           

事件处理

时间的处理与之前属性的处理基本一致:

a. 以下 HTML:

b. 解析后得到的 AST 节点:

evtAstElm = {
  type: 1,
  tag: 'button',
  events: {
    'click': { value: 'clickme' }
  },
  children: [ /* blabla.. */ ]
}
           

c. 生成的 render code:

_c('button', { 
  on: { "click": clickme } 
}, [ _v("click me")] )
           

d. 得到一个带属性的 VNode 节点:

VNode {
  tag: 'button',
  data: {
    on: { "click": clickme } 
  },
  children: [ /* blabla.. */ ]
}
           

c. 生成的 render code ( clickme 函数需要代理到当前的 vm 对象上,同时绑上 vm 这个运行时 context):

_c('button', { 
  on: { "click": clickme } 
}, [ _v("click me")] )
           

e. 最后渲染在 dom 上的时候:

源码的事例 https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.4.1.md

生命周期

Vue2.x 源码阅读思路笔记Virtual DOMHTML parser构建一个最简单的数据绑定的VueVNode 的属性 attrs 和 props控制语句响应式原理深度追踪依赖变化事件处理生命周期
  1. new Vue()
  2. 初始化生命周期、往vue实例上绑事件、方法

    这里只的事件、方法,并不是用户自己绑定的method、data之类的数据,而是Vue实例本身的一些内部方法。

  3. 执行钩子:

    beforeCreate()

    beforeCreate()

    钩子中无法访问到,data、method、$el等数据

    $el挂载点,也就是vue生成的dom挂载的地方

  4. 初始化状态

    简单来说就是初始化method、data,监听数据之类的操作

  5. 执行钩子:

    created()

    data、method方法已经能访问了,但是还没有挂载点信息
  6. 是否有挂载点?

    有:获取模板信息(有无template,两种情况),

    编译 -> vnode -> render函数 -> Dom -> diff -> patch

    无:暂停执行,知道使用

    .$mount()

    手动挂载后,继续执行
  7. 执行钩子:

    beforeMount()

    .$el

    中已经有挂载点了,但是dom资源还没有渲染
  8. 将VNode渲染成Dom,并挂载到挂载点上。

    $el

    得到更新。
  9. 执行钩子

    mounted()

    挂载完成。
  10. data改变时,触发监听。
  11. 执行

    beforeUpdate()

    此时仅监听到了data改变,还未做出操作
  12. diff VNode -> patch

    对比VNode变化,更新dom
  13. 检测到页面销毁(

    .$destroy()

    被执行)
  14. 执行钩子

    beforeDestroy()

  15. 移除data、mothod、watcher、components、event listener等
  16. 执行钩子

    destroyed()

继续阅读