zero-blog icon indicating copy to clipboard operation
zero-blog copied to clipboard

Vue 部分源码简析(未完)

Open PerseveranceZ opened this issue 5 years ago • 0 comments

Vue - 双绑原理

  1. 代理 data
  2. 遍历 data,如果属性值是对象则递归遍历,如果属性值是数组则先进行原型劫持或者重写原型的方式,挂载自定的 splice, push方法等操作数组的方法,然后遍历数组每一项
  3. Object.defineProperty 对每个属性值重写其访问器属性 3.1 getter 中收集 Dep.target,创建一个 Watcher 到 Dep 队列中 3.2 setter 中则执行 Dep.notify(),通知所有订阅该数据的 watcher,执行watcheComponentUpdate() ,触发当前实例配置上的 render 方法,生成虚拟 DOM,调用当前实例的 _update() 方法,触发 re-render 重新渲染 3.3 词法分析生成 AST 3.4 语法分析解析 AST 生成 render function code 代码 3.5 通过 RFC 生成虚拟 DOM,开始进行 diff 过程,反映到真实 DOM 中。

实现一个简单的 Vue https://juejin.im/post/5acc17cb51882555745a03f8

Vue - computed

  1. 调用 defineComputed 在当前组件实例中创建同名属性,computed 赋予的方法即他的 getter
  2. 执行 computed 的方法,触发 getter 将计算属性放入 Dep 队列,同时方法中依赖的 data 中的属性也会触发 getter 放到当前 Dep 队列,当依赖的属性变更之后,触发 setter 更新依赖

Vue - $mount

$mount 函数要做的核心事情就是编译模板(template)字符串为渲染函数,并将渲染函数赋值给 vm.$options.render 选项,这个选项将会在真正挂载组件的 mountComponent 函数中,大致分为三个阶段

1. 生成 AST

递归处理字符串

  • 起始标签
  • 闭合标签
  • 节点生成(标签元素,普通文本,文本元素)

其中,AST 是借助栈来进行构建,每当解析到起始标签时,创建一个AST标签元素,生成添加到当前父级节点 children 属性中,并对其压栈,没有父级节点则为根节点,父级节即当前栈顶元素,当解析到标签内元素时,判断是文本元素还是插值元素(带有{{xxx}}的文本元素,把{{}} slice 成 _s()),生成对应节点后 push 到当前栈顶元素 children 属性中(文本节点不压栈),当解析到闭合节点时,判断当前栈顶元素是和当前闭合标签一致,如果不一致,弹出未发现闭合标签警告,如果符合一致,进行弹栈,以此往复。

2. 优化 AST(标记静态节点,优化整体性能,避免不必要的计算)

  • 标记静态节点:文本节点,或者只包含元素节点和静态节点的元素节点,均为静态节点,在AST中标识对应节点 static 属性为 true
  • 标记静态子树:有children 节点并且孩子不是静态节点的节点,有且嵌套一层静态元素节点的节点,全部将其标记为静态子树。

3. 生成 render 和 staticRenderFns

根据 AST 节点拼接,根据节点的属性如 v-for, v-if, v-else 转化成对应 js 逻辑(for 循环,三目运算)等,根据静态节点和静态子树均创建静态渲染函数并缓存,首次渲染过后下次 Patch 阶段直接调用返回的结果,将差值节点,元素节点转化为对应函数

Vue - dom diff 过程

  1. 通过 setter 执行 Dep.notify 通知所有订阅 watcher ,执行 vue.prototype._update 方法,最终调用到 patch,diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。
  2. 打补丁的过程中,首先判断是不是相同 Vnode,会通过 key,标签名,是否为注释节点,data 对象属性,input 类型时的 type 等维度来判断。
  3. 如果不是相同节点,则直接根据 Vnode 创建真实隔离节点,找到当前存在节点的父级,插入新的隔离节点,并删除老节点。
  4. 如果是新老节点为同一节点,真正开始 patchVnode: 4.1 如果新老节点内容一致,直接 return 不做任何处理,节省性能 4.2 如果新老节点同时为文本节点,切文本内容不一致,则直接替换真实 dom 上的文本节点 4.3 如果存在新节点的孩子,老节点没有孩子,则直接根据 Vnode.children 创建真实节点插入 4.4 如果oldVnode.children 存在,但是Vnode.children不存在,直接删除 oldVnode.children 4.5 如果 oldVnode.children 和 Vnode.children 都存在切不相等,进行 updateChildren 阶段

updateChildren 阶段就是通过在 oldVnode.children 和 Vnode.children 的首尾分别放置两个指针,两两对比,匹配上就把对应指针向中介进行位移继续匹配,直到指针重叠交叉,根据指针位置来判断是否进行增加还是删除操作,updateChildren 就是一个不断匹配同级节点不断反向递归调用 patchVnode 的一个过程。

粉红色的部分为oldCh和vCh

我们将它们取出来并分别用s和e指针指向它们的头child和尾child

现在分别对oldS、oldE、S、E两两做sameVnode比较,有四种比较方式,当其中两个能匹配上那么真实dom中的相应节点会移到Vnode相应的位置,这句话有点绕,打个比方

  • 如果是oldS和E匹配上了,那么真实dom中的第一个节点会移到最后
  • 如果是oldE和S匹配上了,那么真实dom中的最后一个节点会移到最前,匹配上的两个指针向中间移动
  • 如果四种匹配没有一对是成功的,分为两种情况
    • 如果新旧子节点都存在key,那么会根据oldChild的key生成一张hash表,用S的key与hash表做匹配,匹配成功就判断S和匹配节点是否为sameNode,如果是,就在真实dom中将成功的节点移到最前面,否则,将S生成对应的节点插入到dom中对应的oldS位置,oldS和S指针向中间移动。
    • 如果没有key,则直接将S生成新的节点插入真实DOM(ps:这下可以解释为什么v-for的时候需要设置key了,如果没有key那么就只会做四种匹配,就算指针中间有可复用的节点都不能被复用了)

PerseveranceZ avatar Aug 09 '18 09:08 PerseveranceZ