我写了三篇关于vue源码分析、响应式、虚拟dom、模板编译组件化的文章。这三篇文章比较详细。这是一个摘要。三篇文章应该更好。下面是一般过程和前面例子的总结。一、第一个渲染过程。首先,我们在导入vue的时候,会初始化实例成员,静态成员是全局静态的,比如config、options、内部工具方法,还有一些静态方法。例如set、nextTick、component、instruction、filter方法,原型方法如:mount(内部调用mountComponent挂载)、init、_render(方法中默认调用options中的render,vm.$createElement默认传给用户,传入的render作为h函数生成虚拟dom,模板编译render内部使用的vm._c不需要传入this),_update等,初始化init中的实例成员,如options、_isVue、uid记录、Vue.extend()初始化组件的构造函数,继承自vue的所有原型方法,合并配置选项。实例化newVue(),会调用原型上定义的init方法;this._init()在这里合并options配置,并初始化生命周期变量,events监听自定义事件。执行initRender函数(生成vm._c处理编译和生成render,生成vm.$createElement处理用户传入的render)执行hook回调,对传入的data数据做响应式处理并劫持属性,生成每个的dep对象属性节点,dep对象用于通知watcher更新,劫持数组原型方法。如果有计算属性生成计算观察者,有监听器,生成监听观察者生成观察者时,会根据传入的方法决定是否去获取对应数据中的值。如果在传入的方法中获取到了值,就会触发我们之前数据劫持对应的get方法,从而将我们当前的watcher添加到对应属性的depsubs数组中。如果当前属性是一个子对象,对应的子对象dep也需要添加watcher(在set和array中会用到)。然后触发created创建的钩子函数。最后执行$mount挂载。vm.$挂载();这个方法会先寻找options.render函数,看用户是否传入,如果没有,使用传入的模板,调用compileToFunctions模板被转换为渲染函数。这个render函数内部调用vm._c处理模板编译,生成vNode。生成的渲染被分配给options.render。当随后调用_render()时,渲染将从选项中获取并调用。这需要Compiler版本的vue。如果用户传入render,则后续调用_render会直接调用用户传入的render,从options.render中获取执行。这会使用传入的vm.$createElement作为h函数生成虚拟dom,最后调用mountComponent进行挂载。mountComponent的主要功能是定义updateComponent。该方法的作用是更新render中编译的接口_update(_render())//_c或者用户传入_$createElement生成虚拟dom_render()生成虚拟dom,_update()内部调用patchVnode进行比较旧的和新的vNode来更新dom。在_render中会调用对应编译好的vm._c或vm._createElement生成虚拟dom。这个过程中会判断A自定义组件是否会调用createComponent,createComponent会调用extend()返回组件构造函数,并创建组件vnode,然后注册插件inithook,在init中实例化组件hook,然后调用继承自Vue的init初始化方法,最后调用mount()生成一个renderingwatcher,将组件挂载到页面上(vnode.elm,这里验证一个组件对应一个renderingwatcher)创建一个渲染观察者实例,并通过updateComponent创建一个观察者实例。会传入updateComponent方法,这里初始化会调用传入函数,即updateComponent来更新界面。在这个过程中,会获取到我们之前进行属性劫持的数据中的属性,然后触发相应的get,将renderingwatcher添加到对应属性的dep中,在属性dep的subs数组中,相互之间的依赖关系属性dep和渲染观察者形成。(这样就形成了一个观察关系,这里可能会在多个属性dep的subs数组中放置一个renderingwatcher,因为一个renderingwatcher对应一个component.dep中的一个attributesubs的subs数组中也可以放置多个不同的watcher,比如一个兼具渲染和计算||监听属性的watcher)这里的语句下,一个组件对应一个renderingwatcher.mounted,最后执行这个hook,以及整体渲染完成到此为止,完成了vue的第一次渲染。二、前面提到的响应式原理,我们在newVue()时调用init来劫持data数据生成属性对应的deppublisher和对应的get、set方法。watcher实例化时,会将自己赋值给Dep.target,然后在获取到属性值时触发相应的get,通过dep.depend()和childOb.dep将当前watcher添加到自己和子对象中。depend()在dep的subs数组中。同时watcher也会记录dep.id,防止后面触发get时重复添加。那么当数据中的属性赋值发生变化时,就会触发对应的集合,集合会判断值是否发生变化。如果它发生变化,它将被分配给val。然后set会判断新赋值的值是否是一个对象。如果是,继续观察数据劫持,然后调用dep的notify方法调用dep的subs数组中watcher的update方法。queueWatcher方法将在更新方法中被调用,这里会用watcher的id作为一个object的key来判断是否重复,如果不重复则将当前watcher放入queue队列中。然后调用nextTick方法,将flushSchedulerQueue方法作为参数传入。flushSchedulerQueue方法的作用watcher是按照watcher.id排序的,也就是创建的顺序(计算,监听,渲染),然后清空之前用来重复添加objectkey的id,然后执行watcher.run()在watche.run中执行此操作。get()也是传入的函数,如果watcher渲染了,就是updateComponent调用内部的_update(_render())生成Vnode,比较更新。如果是计算或者监听watcher,执行完get()pass方法后,会执行cb传入的callbackwatcher排序的作用是:首先将组件从父组件更新到子组件。也就是说如果有多个renderingwatcher,先更新父renderingwatcher再执行子renderingwatcher,然后组件的用户监控程序在渲染监控程序之前。运行是因为用户观察者是在渲染观察者之前创建的,也就是说每一级组件的计算和监听观察者都是在渲染观察者之前执行的,因为在渲染观察者中可能会用到计算属性。最后,如果一个组件在运行过程中销毁了父组件的监听程序,这里可以跳过它的观察者。nextTick接收到传入函数后,生成一个匿名函数(当前传入函数在匿名函数中执行,加上trycatch的错误处理)到一个callbacks数组中,现在不会立即执行其中的函数callbacks数组,然后判断pending属性为false,默认为false,如果为false,将其改为true,标记本次tick的task,然后使用Promise.resolve()生成一个promisemicrotaskthen(flushCallbacks),在这个tick事件循环结束时挂起,并在这个tick事件循环结束时执行微任务flushCallbacks回调。这个flushCallbacks回调的主要作用是将pending状态改为false,标志这一轮tick结束生成callbacks数组的副本,然后依次执行callbacks中的函数。如果浏览器不支持,异步promsie将降级为setTimeout。这也体现了vue中的更新是异步的。这里我们用一段伪代码来推理一下它的更新过程
{{title}}