构建过程总框图及数据更新重点这个顺序并不代表真正的构建过程,而是重要内容的拆解、初始化和挂载。使用newVue()后,Vue会调用_init函数进行初始化,初始化options参数初始化生命周期初始化vm状态,包括props/methods/data/computed和watch等,初始化完成后挂载实例data并递归使用Object.defineProperty(Vue2.X)/Proxy(Vue3)设置setter和getter函数实现响应式和依赖收集。mount编译挂载后,生成render函数,vue会编译模板。这个过程大致分为三个阶段。解析template模板中的指令、类、样式等数据,最终生成AST语法树后续页面更新后generate[phasegoal]generaterenderfunctiongenerate是将AST语法树转化为render函数字符串构建vDom的过程vDom相当于一个缓冲层,可以做到对真实Dom操作最少优化性能,并且由于是JS对象,在不同环境下都有很好的跨平台转换过程。获取到render函数后,会转化为VNode节点。虚拟Dom本质上是一棵由VNode组成的树。从逻辑上讲,它是真实Dom的抽象VNode。是一个JS对象,利用对象的属性来描述当前节点的一些状态,最后利用VNode节点来模拟Dom树//简单示例classVNode{constructor(tag,data,children,text,elm){/*当前节点标签名*/this.tag=tag;/*当前节点的一些数据信息,如props、attrs等数据*/this.data=data;/*当前节点的子节点是一个数组*/this.children=children;/*当前节点的文本*/this.text=text;/*当前虚拟节点对应的真实dom节点*/this.elm=elm;}}PublisherSubscriberModePublisher发布者维护一个订阅者列表。当发布者被引用时,被引用的对象被放置在订阅者列表中。每当发布者发生变化时,都会通知订阅者更新视图}/*在订阅者数组中添加一个订阅对象*/addSub(sub){this.subs.push(sub);}/*通知所有订阅的对象更新视图*/notify(){this.subs.forEach((sub)=>{sub.update();})}}订阅者(observers)当对象被引用时订阅者发布后,会被收集到发布对象的订阅者列表中}/*更新视图的方法*/update(){console.log("视图更新了~");}}Dep.target=null;依赖收集在完成上述逻辑后需要明确发布者是如何知道订阅者引用自己的。这时候他们就需要使用getter逻辑来让发布者在被引用的时候完成捕获逻辑。具体体现在应用方法上。Vue2.X使用Object.defineProperty,Vue3使用ProxyfunctiondefineReactive(obj,key,val){/*新建一个发布者对象*/constdep=newDep();Object.defineProperty(obj,key,{//这里以Vue2.X为例enumerable:true,configurable:true,/*读取数据时触发get中的逻辑*/get:functionreactiveGetter(){/*Dep.target(即当前订阅对象存储在发布者的订阅者列表中)*/dep.addSub(Dep.target);returnval;},/*操作数据时触发set中的逻辑*/set:functionreactiveSetter(newVal){if(newVal===val)return;/*set时触发发布者的通知逻辑,通知所有订阅者对象更新视图*/dep.notify();}});}classVue{构造函数(选项){this._data=options.data;/*数据的响应式处理到这里就完成了*/observer(this._data);/*创建一个新的订阅者对象,此时Dep.target会指向这个订阅者对象*/newWatcher();/*这里模拟数据渲染过程,会触发test属性的get逻辑*/console.log('render~',this._data.test);}}注:这个阶段反映了其实Vue的构建过程可以分为两部分。在数据初始化(init)时完成参数的发布订阅逻辑创建,在渲染(render)时收集部分依赖,最后在页面完成响应式设计数据更新对比。当对数据进行操作时,会触发发布者的通知逻辑,让订阅者执行相应的逻辑并生成新的VNode,新旧VNode会执行一次patch过程比较差异,最后将差异更新到Dom中,完成视图的更新。补丁比较的核心是diff算法。diff算法是通过同一层的树节点进行比较,其时间复杂度仅为O(n)graph会比较相同颜色块的节点,并将差异更新到视图中。需要注意的是,虽然Vue和React得到的diff算法忽略了跨层比较,只进行了对等比较,但是Vue在比较节点时,当节点元素类型相同但className不同时,它们会被认为是不同类型的元素而被删除和重建,而React会认为它们是同一类型的节点,只会修改节点属性。Vue在比较同行时采用的是从两端向中心比较的方式,而React则是从左向右进行比较。不同的是,当最右边的节点向最左边移动时,React会将前面的节点一个一个地移动,而Vue只会移动一个节点。Vue组件的数据发生变化时,只会更新自己,而React组件的数据发生变化。它将更新自身和所有子组件。由于具体比较过程较长,请自行搜索。我不会重复更新渲染。在更新渲染阶段,Vue采用异步更新策略。为什么它更需要更新Vue的异步更新策略来提高性能?优化注意事项举个栗子,页面上某个值执行1000次自增循环(模拟多效场景),此时如果按照同步更新逻辑,setter=>Dep=>watcher=>updateprocess会被触发1000次,导致会有很大的性能损失,而Vue使用的异步更新策略会创建一个更新栈,然后将更新压入栈中,进行去重处理(去重操作的目的是为了优化本例中的多次执行),当本轮同步代码执行完成后,会执行更新栈中的更新工作,并将变化反馈给视图中的异步更新过程。Vue的异步更新机制借鉴了事件循环机制的内容,用id标记更新,放入微任务队列中,当本轮同步任务执行完毕后,微任务队列中的所有更新任务都会被执行,更新将呈现给视图。当同id的更新任务放入microtask队列时,Vue异步更新策略只会保留最后一个更新任务,达到性能优化效果
