- initMixin 初始化:_init 实例化对象调用
- stateMixin 数据:$data,$props,$set,$delete,$watch
- eventsMixin 事件:$on,$off,$once,$emit
- lifecycleMixin 生命周期:_update,$forceUpdate,$destroy
- renderMixin 渲染:$nextTick,_render
- 在一万多行又添加了 patch 和 $mount 方法
initGlobalAPI
1. config 为Vue.config做层代理
2. Vue.util 工具类(提示警告、对象合并)</br>
3. 添加静态方法:Vue.set()更新视图、Vue.delete()删除数据、Vue.nextTick()用于更新视图后回调递归
4. 添加静态属性:添加 components,directives,filters 静态对象,记录静态组件
5. initUse - Vue.use() 安装插件、initMixin$1 - Vue.mixin() 合并参数、initExtend - Vue.extend() 继承
6. initAssetRegisters - 添加 component,directive,filter 静态方法 定义组件、指令、过滤器
- mergeOptions 合并选项
mergeOptions合并options参数(resolveConstructorOptions合并vm.constructor构造函数的属性options)。集合在 vm.options 上。
- 选项检查:
- components(checkComponents(child))1.正则判断非法的标签 2.不能使用Vue自身自定义的组件名,html的保留标签。
- prop(normalizeProps(child, vm))形式:数组、对象形式;两种形式最终都会转换成对象的形式。规则:1.数组形式保证是字符串 2.非数组,非对象则判定props选项传递非法
- inject(normalizeInject(child, vm))inject 选项有两种写法,数组的方式以及对象的方式,和 props 的校验规则一致,最终 inject 都会转换成对象的形式存在。
- directive(normalizeDirectives(child))。针对函数的写法会将行为赋予 bind,update 钩子。
- 合并选项:
- 常规选项合并(el,data);只允许vue实例才拥有el属性,其他子类构造器不允许有el属性。mergeDataOrFn 将父类的数据整合到子类的数据选项中,如若父类数据和子类数据冲突时,保留子类数据,如果对象深层嵌套,则需要递归调用 mergeData 进行数据合并。
- 默认资源合并(component、directive、filter)父类选项将以原型链的形式被处理。
- 生命周期钩子函数合并;mergeHook策略,对于生命周期钩子选项,子类和父类相同的选项将合并成数组,这样在执行子类钩子函数时,父类钩子选项也会执行,并且父会优先于子执行。
- watch 选项合并;最终和父类选项合并成数组,并且数组的选项成员,可以是回调函数,选项对象,或者函数名。
- props,methods,inject,computed 类似选项合并;如果父类不存在选项,则返回子类选项,子类父类都存在时,用子类选项去覆盖父类选项。
- 选项检查:
- initProxy 数据代理
为 vm 实例化对象添加了 _renderProxy 方法。拦截 with 语句下的作用对象,对非法没有定义的变量进行筛选判断。 - initLifecycle 初始化生命周期
为 vm 添加了$root、$parent、$children、$refs等属性。 - initEvents 初始化组件事件
如果 vm.$options._parentListeners 事件存在,updateComponentListenets更新组件事件,添加新的事件,删除旧的事件。
updateListeners更新事件。add添加事件(1.once标志真调用$once 执行一次函数就解绑2.once标志为假,$on添加事件,把事件推进队列去vm._events[event]) - initRender 初始化渲染
为vm添加 _vnode、$slots、$scopedSlots等属性。添加了 _c 和 createElement 两个渲染方法。
并且把 $attrs 属性和 $listener 事件属性添加到 defineReactive 观察者中。 - initState 构建响应式
- initProps 将props属性设置为响应式数据
- initMethods 必须为函数;命名不能props重复;不能_ $命名;挂载到根实例上。
- initComputed 对象时要有getter 方法;对每个属性创建个监听依赖;设计响应式数据;命名防止和data、props冲突。
- initWatch 对象。循环 watch 执行 createWatcher -> vm.$watch() 处理。
- initData 命名不能和props、methods重复;数据代理(vm._data); 为data绑定一个观察者observe。
nitDate -> new Observer(options.data) -> this.walk(data) -> defineReactive(obj, keys[i]) -> new Dep(); Object.defineProperty(obj, key, {get() {// 做依赖的收集 dep.depend()},set(nval) {// 派发更新 dep.notify();})
依赖收集的过程(每个数据都是一个依赖管理器,每个使用的数据就是一个依赖,当访问到数据时会将当前访问的场景作为一个依赖收集到依赖管理器中。)
派发更新的过程 1.判断数据更改前后一致情况;2.新值为对象,对属性做依赖收集;3.通知该数据收集watcher依赖,遍历每个watcher,进行更新,调用dep.notify();4.更新时将每个watcher推到队列中,下个tick进行run操作。5.执行run方法,执行get方法,重新计算新值,依赖的清除。
确认挂载节点 -》编译模板为render函数 -》render函数转换为virtualDom -》virtualDom创建真实节点
$mount -> mountComponent -> 实例化watcher -> updateComponent -> vm._render() -> vm._update()
_render() 定义在renderMixin中,vnode = render.call(vm._renderProxy, vm.$createElement)。
模板编译:
compileToFunctions 最终会返回另外两个包装过的属性 render,staticRenderFns,将with语法封装成执行函数(createCompiler 1.parse(把模板解析为AST抽象语法树) 2.optimize(优化AST,标记静态节点) 3.generate(根据不同平台将AST转为渲染函数))。
_c 和 $createElement 在initRender 函数中定义。_c 是 template 内部模板编译成 render 函数时调用的方法。$createElement 是手写render函数调用的方法。
通过模板生成的render函数 可以保证子节点都是Vnode;手写的render 需要数据规范校验和规范子节点children。通过 new Vnode() 生成完整的Vnode树。遇到子组件优先处理子组件的初始化。
updateComponent(){ vm._update(vm._render(), hydrating); } vm._update(定义在lifecycleMixin) 1.初始化渲染阶段 2.数据更新阶段。通过判断 vm._vnode 是否有旧节点来是初始渲染还是数据更新。
vm._update 核心是 patch ,patch = createPatchFunction 的返回值。核心是通过调用 createElm 方法进行 dom 操作,创建节点,插入子节点,递归创建一个完整的DOM树并插入到Body中。
原则:只进行同层节点的比较,节点不一致(_sameVnode),直接用新节点及其子节点替换旧节点。
- patchVnode 是新旧 Vnode 对比的核心方法,对比的逻辑如下。
- 节点相同,且节点除了拥有文本节点外没有其他子节点。这种情况下直接替换文本内容。
- 新节点没有子节点,旧节点有子节点,则删除旧节点所有子节点。
- 旧节点没有子节点,新节点有子节点,则用心的所有子节点去更新旧节点。
- 新旧都存在子节点,则对比子节点内容做操作(updateChildren)。
- updateChildren
- 旧节点的起始位置为 oldStartIndex,截止位置 oldEndIndex,新节点的起始位置为 newStartIndex,截止位置为 newEndIndex。
- 新旧 children 的起始位置的元素两两对比,顺序是 newStartVnode,oldStartVnode; newEndVnode,oldEndVnode; newEndVnode,oldStartVnode; newStartVnode,oldEndVnode
- newStartVnode,oldStartVnode 节点相同,执行一次 patchVnode 过程,递归对比相应子节点,并替换节点。oldStartIndex,newStartIndex 都向右移动一位。
- newEndVnode,oldEndVnode 节点相同,执行一次 patchVnode 过程,递归对比相应子节点,并替换节点。oldEndIndex,newEndIndex 都向左移动一位。
- newEndVnode,oldStartVnode 节点相同,执行一次 patchVnode 过程,并将旧的 oldStartVnode 移动到尾部,oldStartIndex 右移一位,newEndIndex 左移一位。
- newStartVnode,oldEndVnode 节点相同,执行一次 patchVnode 过程,并将旧的 oldEndVnode 移动到头部,oldEndIndex 左移一位,newStartIndex 右移一位。
- 四种组合都不同,则会搜索旧节点所有子节点,找到将这个旧节点和 newStartVnode 执行 patchVnode 过程。
- 不断的对比的过程使得 oldStartIndex 不断逼近 oldEndIndex , newStartIndex 不断逼近 newEndIndex 。当 oldEndIndex <= oldStartIndex 说明旧节点已经遍历完了,此时只要批量增加新节点即可。当 newEndIndex <= newStartIndex 说明旧节点还有剩下,此时只要批量删除旧节点即可。
当四种比较节点多找不到匹配时,会调用 findIdxInOld 找到旧节点中和新的比较节点一致的节点。
唯一属性 key,有了这个唯一的标志位,我们可以对旧节点建立简单的字典查询,只要有 key 值便可以方便的搜索到符合要求的旧节点。