- 首次渲染之前,首先进行 Vue 的初始化,即初始化实例成员和静态成员,初始化结束后,要调用 new Vue()——Vue 的构造函数,在构造函数中调用了 this._init() 方法,该方法相当于 Vue 的入口,在 this._init() 方法中最终调用了 vm.$mount(),即入口的 $mount(src/platforms/web/entry-runtime-with-compiler.js),该方法的核心作用是将模板编译成 render 函数,方法中会首先判断是否有传入 render 选项,如果没有传入,会获取 template 选项,如 template 中也没有内容,会将 el 中的内容作为模板,然后把模板编译成 render 函数,该方法是通过 compileToFunctions() 方法生成 render() 渲染函数,当 render 函数编译好之后,会把 render 函数存入 options.render 中,接着调用 runtime/index.js 中 vm.$mount() 方法,在该方法中会重新获取 el,如果是运行时版本,不会执行 entry-runtime-with-compiler.js入口文件的,所以在运行时版本要在runtime/index.js文件中的 $mount() 中重新获取 el,再调用 mountComponent(),该方法是在 src/core/instance/lifecycle.js 文件中定义的,,首先会判断是否有 render 选项,如果没有 set render 选项,但是传入了模板,并且当前是开发环境的话会发送一个警告,如果当前使用的是运行的 Vue 而且 传入 render,会发送警告,告诉运行版本不支持编译器,触发 beforeMount 挂载之前钩子函数,定义 updateComponent,在该函数中调用了 vm._update(vm._render(), ...), vm._render() 两个方法,render 方法的作用是生成虚拟 DOM,update方法是将虚拟 DOM 转换为真实 DOM,并挂载到页面中,接着创建 Watcher 对象,并传入了 updateComponent 函数,在 Watcher 内部会调用 get() 方法,在 get() 方法内部会调用 updateComponent(),在该方法中调用了 render和update 方法,在render 方法中调用了用户传入的render或者编译生成的 render,当 render 执行完毕,会把 VNode虚拟 DOM返回,在update() 方法中调用了vm.patch(vm.$el, vnode)方法,patch方法就是将虚拟 DOM 转换成真实Dom 并挂载到界面上,patch 会将生成的真实 DOM 记录到 vm.$el中, Watcher 实例创建完之后,触发 mounted 钩子函数,挂载结束,最后返回 Vue 实例。
- (1)、Vue 响应式是从 Vue 实例的 init 方法开始的,在 init 方法中调用了 initState() 方法,初始化 Vue 实例的状态,再调用 initData() 方法,把 data 属性注入到 Vue 实例中,并调用 observe() 方法把 data 对象转换成响应式的对象,即响应式的入口。
- (2)、observe(value) 接收一个参数,该参数就是响应式要处理的对象,首先需判断 value 是否是对象,如果不是对象直接返回,再判断 value 对象是否有 _ob_属性,如果有说明该对象已做过响应化处理,则直接返回,如果没有 ob 属性,则需要为 value 对象创建 observer 对象,并返回 observer 对象。
- (3)、在 observer 类的构造函数中,首先会给 value 对象定义一个不可枚举的 ob 属性,并把当前 observer 对象记录到 ob 属性上,再进行数组的响应式处理和对象的响应式处理,数据的响应式处理就是设置数组的那几个特殊的方法,如:push()、pop()、sort()等,这些方法会改变原数组,所以当上述方法被调用时,需要发送通知,发送通知是找到数组对象中对应的 ob,即 observer 对象,找到 observer 中的 Dep,调用 Dep 的 notify 方法,更改完数组中的特殊方法后,遍历数组中的每个成员,对每个成员再调用 observe,如果某个成员是对象,也会把这个对象转换成响应式对象;
- (4)、如果当前 value 是对象,会调用 walk 方法,遍历对象的所有属性,对每一个属性调用 defineReactive,在 defineReatctive 中会为每一个属性创建对应的 dep 对象,让 dep 收集依赖,如果当前属性值是对象,会调用 observe (也就是如果当前属性是对象,也要把该对象转换为响应式对象),defineReactive 核心就是定义 getter\setter,在 getter 中收集依赖,收集依赖时要为每一个属性收集依赖,如果这个属性的值是对象,也要为子对象收集依赖,在 getter 中返回属性的值,在 setter 中需要把新值保真起来,如新值是对象的话,要调用 observe,把新设置的对象,也转换为响应式对象,在 setter 中数据发生变化,需要发送通知,即调用 dep.notify() 方法。
- (5)、依赖收集的过程(为属性和子对象收集依赖),首先执行 watcher 对象的 get 方法,在 get 方法中调用 pushTarget,该方法会把 watcher 对象记录到 Dep.target 属性中,访问 data 中的成员时收集依赖,访问属性值时,会触发 defineReactive 的getter,在 getter 中会收集依赖,会把属性对应的 watcher 对象添加到 dep 的 subs 数组中,也就是为属性收集依赖,如果这个属性值也是一个对象,此时需创建一个 childOb 对象,要为子对象收集依赖,目的是子对象发生变化时可以发送通知
- (6)、Watcher,当数据变化时会调用 dep.notify() 发送通知,它会调用 Watcher 对象的 update() 方法,在 update() 方法中调用 queueWatcher(),在该方法中会判断 watcher 是否被处理,如没有处理的话会被添加到 queue 队列中,并调用 flashSchedulerQueue(),即刷新任务队列函数,在 flashSchedulerQueue 函数中会触发 beforeUpdate 钩子函数,然后调用 watcher.run() 方法,在run() 方法中继续调用 watcher 的 get() 方法去调用 getter(), getter 中存储的就是 updateComonent,即针对的是渲染 watcher,run() 方法执行完毕之后,就把数据更新到视图上,在页面中可看到最新数据,接着需要做一些清理工作,会清空上一次的依赖,重置 watcher 中的状态,触发 actived 钩子函数,最后触发 updated 函数。
- DOM 操作只有一次,因为比较时设置了 key,如遇到不相等的值时,会从后往前找,后面几项都是相等的,如内容相等不会更新 DOM,会重用上一次的结果,只有新增的选项,界面上没有,才会执行一次插入的操作,设置 key可减少 Dom 操作。
- 模板编译的入口函数 compileToFunction(template, ...),其内部会先从缓存中加载编译好的 render 函数,如缓存中没有,调用 compile(template, options) 开始编译,在 compile 函数中,需要先合并选项 options,然后调用 baseCompile(template.trim(), finalOptions)编译模板,compile 函数的核心是合并选项,真正的处理是在 baseCompile中完成的,把模板和合并好的选项传递给 baseCompile,它里面完成模板编译的三件事情,首先调用 parse() 方法,把模板 template 转换成 AST tree 对象,也就是抽象语法树,然后对 AST 进行优化,调用 optimize(),标记 AST tree 中的所有静态根节点 sub trees,静态根节点不需要每次被重绘,patch阶段会跳过静态根节点,最后调用 generate(),把优化之后的 AST tree 转换成字符串形式的代码,当 compile 执行完毕,会回到入口函数 compileToFunctions(template, ...),它里面会继续把字符串形式的代码转换成函数的形式,通过调用 createFunction(),当 render 和 statticRenderFns 创建完毕,最终会被挂载到 Vue 实例的 options 选项对应的属性上。