想深入理解Vue的原理,和组织结构,这本
HuangYi
写的电子书是非常好的资料。
以备不时之需,特将主要脉络记录下来
作者经常说: xx入口和 这里关键的代码是, 所以我们分析源码时,也要学会找重点
数据驱动
-
组装Vue这个构造函数的过程 platforms下某个平台的入口文件比如web下的entry-runtime-with-compiler.js,这个文件再去runtime目录下的index.js 这个文件再去code/index.js文件中找Vue,从平台目录跳出到core目录了。core是Vue的核心代码目录。然后再去code/instance/index.js文件,这个文件才正式组装Vue构造函数
-
code/instance/index.js中Vue构造函数通过prototype拓展功能被分散到多个模块中去实现,非常便于代码的维护,直接的结果就是每个js文件代码函数都不会很多。
-
然后是code/global-api.js文件向Vue添加一些全局静态方法比如Vue.set。
-
Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,调用beforeCreate钩子,初始化injections, 初始化 data、props、computed、watcher,初始化provide,调用created钩子。最后如果有el属性,调用$mount渲染成最终的DOM。
-
runtime的$mount方法最终会调用core/instance/lifecycle.js中的mountComponent, mountComponent会创建一个
Watcher
实例,用来监听vm上的数据变化,初始化Watcher
或者数据变化时,就会调用_update
Component也就是先_render
再_update
,更新DOM。 -
_render
返回的是VNode。_render
其实是调用vdom/create-element.js 中的creatElement创建的VNode -
然后通过core/instance/lifecycle.js中的
_update
方法,将VNode转换为DOM。 -
_update
用到的patch方法是由core/vdom/patch.js中的createPatchFunction创建,createPatchFunction返回的patch函数缓存了modules(平台的一些模块,会在整个patch
过程的不同阶段执行相应的钩子函数)和nodeOps (一些dom节点的操作方法)。 -
patch
函数,如果接收第一个参数oldVnode是个dom元素(vue实例的一次path时候就是传入一个dom元素),会执行createElm方法创建一个dom元素并插入到他的父元素中。 -
作者把初始化vue的过程画了一张图:
new Vue -> init -> $mount -> compile -> render -> vnode -> patch -> DOM
组件化
-
core/vdom/create-element.js 中的_createElement会判断
tag
参数, 如果不是html字符串标签,就会调用createComponent创建组建类型的VNode。 -
createComponent在core/vdom/create-component.js中,他主要有3 个关键步骤:
-
构造子类构造函数。通过Vue.extend方法。
-
安装组件钩子函数:installComponentHooks(data)。组件VNode在path过程中会执行对于钩子函数。
-
实例化组件VNode,不用传children参数。patch时候组件型VNode会特殊对待。
-
自己关于vm.$vnode和vm._vnode的一些补充:
vm.$vnode 组件占位节点。 new Vue()创建的根组件,$vnode是null 。 <child> <div></div> </child> 定义的子组件childVm对应一个childVm.$vnode。 这个childVm.$vnode会是他的父vnode(也就是vm._node)的一个孩子。 但是这个这个childVm.$vnode本身不会产生dom元素。我们实际调试页面时候也没有见过页面里有和组件名一样的元素标签。 那这个vnode对应的dom元素从哪来呢?其实是来源于他的孩子<div></div>会生成一个childVm._vnode。这个childVm._vnode会渲染出dom元素,并且把这个dom元素赋值给childVm.$vnode的dom元素变量。所以childVm.$vnode既是子组件最终渲染的childVm._vnode的父vnode,也是父组件vm的一个占位vnode(父组件某个孩子vnode)。 childVm._vnode.parent === childVm.$vnode childVm.$vnode.parent为 null vnode.js里清晰的注释了 parent // component placeholder node 也就是说parent只是个占位node,如果一个vnode不需要占位vnode,那parent自然就是null
深入响应式原理
-
src/core/instance/state.js中initState方法中initData和initProps方法对vue实例的$options属性的上data和props属性做初始化。
-
defineReactive方法把每个属性对应的值变成响应式的。
-
observe(data, true) 会把data上的属性变成响应式的,同时开始观察每个属性。
-
defineReactive时候,发现对象属性会递归调用observe方法,发现数组会调用对每个元素递归调用observe。
-
defineReactive通过Object.defineProperty重写属性的getter和setter。getter用来依赖收集,setter用来派发更新。
-
vue初始化时候会创建Watcher, Watcher构造函数会调用传递给构造函数的getter函数,这个getter函数会触发对vue data的访问,就会触发data上的每个属性的getter,每个data属性也对应一个Dep对象,触发属性的getter,这个Dep对象就把这个watcher实例添加到自己的watcher数组里。data属性访问就完成了对watcher的收集。
-
为data属性设置值时,就会触发属性的setter,属性对应的Dep对象就会通知他的watcher数组里的每个watcher,每个需要渲染的watcher就会被添加到更新队列里,nexttick时候执行watcher.run,最终执行虚拟节点的patch。data属性设置就完成了更新的派发。
image.png -
data以及data下的所有Object(包括多层次的,包括数组里通过指定方式添加的对象),都会有个
__ob__
属性,是一个Observer实例,在defineReactive时候,如果当前要变成响应式的属性的值是个Object,如果没有__ob__
属性 就会添加__ob__
属性,__obj__
属性会有个Dep对象,这个Dep对象也把当前watcher添加到watcher数组里。这个就是为Vue.set 做好准备的。只需要调用每个Object上的__ob__.dep.notify()
,就可通知相关watcher。 -
core/observer/index.js 中Vue.set 方法的函数体set接收 3个参数,
target
可能是数组或者是普通对象,key
代表的是数组的下标或者是对象的键值,val
代表添加的值。target不能是data本身。必须是data的下一层或者下几层属性对象(或数组)。通过defineReactive让这个新加的属性变成响应式的。然后调用targe对应的observer ob.dep.notify()方法,通知watcher重新收集依赖,同时渲染DOM -
数组,这几个方法是可以让新加的元素(如果有的话)变成响应式的,同时会触发watcher更新,进而渲染相关DOM
const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]
-
computed和用户添加的watch也都是用Watcher实现的,个别逻辑有点区别
编译
- 记得个ast(抽象语法树)
扩展
略
Vue Router
略
Vuex
略
网友评论