Vue大致构建流程

iOS开发
• 阅读 1268

构建流程大致框图

Vue大致构建流程

构建与数据更新要点

该顺序并不代表真正的构建流程,只是对重要内容的拆解

初始化与挂载

在使用 new Vue() 后 Vue 会调用 _init 函数进行初始化,其会

  • 初始化 options参数
  • 初始化生命周期
  • 初始化 vm 状态,其中包含props/methods/data/computed与watch等
  • 初始化完成后挂载实例

数据初始化过程中还会递归使用 Object.defineProperty (Vue2.X) / Proxy (Vue3) 设置 setter 与 getter 函数实现响应式与依赖收集

挂载后编译

挂载完成后为生成 render function ,vue 会将 template 进行编译,该过程大抵分为三阶段

parse

【阶段目标】生成 AST 语法树

parse 会使用正则表达式解析 template 模板中的指令、class、style 等数据,最终生成 AST 语法树

optimize

【阶段目标】增添优化标记

optimize 主要作用为标记 static 静态节点,该阶段主要是对后序页面更新后比对过程的优化

generate

【阶段目标】生成 render function

generate 是将 AST 语法树转换为 render function 字符串的过程

构建vDom

vDom 相当于一个缓冲层,其能够实现对真实 Dom 的最少操作从而优化性能,而且由于其本身是 JS 对象,在不同环境中拥有良好的跨平台性

转换过程

在获取到 render function 后其会被转换为 VNode 节点,虚拟 Dom 实质上就是由 VNode 组合成的树,在逻辑上是对真实 Dom的抽象

VNode

其归根结底是一个 JS 对象,使用对象的属性来描述当前节点的一些状态,最终使用 VNode 节点的形式模拟 Dom 树

// 简单范例
class VNode {
    constructor (tag, data, children, text, elm) {
        /*当前节点的标签名*/
        this.tag = tag;
        /*当前节点的一些数据信息,比如 props、attrs 等数据*/
        this.data = data;
        /*当前节点的子节点,是一个数组*/
        this.children = children;
        /*当前节点的文本*/
        this.text = text;
        /*当前虚拟节点对应的真实 dom 节点*/
        this.elm = elm;
    }
}

发布订阅者模式

发布者

发布者维护一个订阅者列表,当该发布者被引用时则将引用的对象置入订阅者列表中,每当该发布者改变时就会通知订阅者更新视图

class Dep {
    constructor () {
        /* 用来存放订阅者对象的数组 */
        this.subs = [];
    }


    /* 在订阅者数组中添加一个订阅对象 */
    addSub (sub) {
        this.subs.push(sub);
    }


    /* 通知所有订阅对象更新视图 */
    notify () {
        this.subs.forEach((sub) => {
            sub.update();
        })
    }
}

订阅者(观察者)

订阅者在引用发布对象时会被收集进入发布对象的订阅者列表中

class Watcher {
    constructor () {
        /* 在new一个订阅者对象时将该对象赋值给Dep.target,在get中会用到 */
        Dep.target = this;
    }


    /* 更新视图的方法 */
    update () {
        console.log("视图更新啦~");
    }
}
Dep.target = null;

依赖收集

在完成上述逻辑后需要明确发布者如何得知订阅者引用了自己,这时就需要使用 getter 逻辑,使发布者在自身被引用时完成捕获逻辑

具体体现在应用方法上 Vue2.X 使用的是 Object.defineProperty ,Vue3使用的是 Proxy

function defineReactive (obj, key, val) {
    /* new一个发布者对象对象 */
    const dep = new Dep();
    
    Object.defineProperty(obj, key, {   // 此处以 Vue2.X 举例
        enumerable: true,
        configurable: true,
        /* 数据被读取时触发get内逻辑 */
        get: function reactiveGetter () {
            /* 将Dep.target(即当前的订阅对象存入发布者的订阅者列表中) */
            dep.addSub(Dep.target);
            return val;         
        },
        /* 数据被操作时触发set内逻辑 */
        set: function reactiveSetter (newVal) {
            if (newVal === val) return;
            /* 在set时触发发布者的通知逻辑来通知所有的订阅者对象更新视图 */
            dep.notify();
        }
    });
}


class Vue {
    constructor(options) {
        this._data = options.data;
        /* 此处完成数据的响应式处理 */
        observer(this._data);
        /* 新建一个订阅者对象,这时候 Dep.target 会指向这个订阅者对象 */
        new Watcher();
        /* 在这里模拟数据渲染的过程,其会触发 test 属性的 get 逻辑 */
        console.log('render~', this._data.test);
    }
}

注:该阶段体现在 Vue 构建过程中其实可以分成两部分,在数据初始化(init)时对参数完成发布订阅者逻辑的创建,在渲染(render)时收集部分依赖,最终完成响应式设计

数据更新比对

在页面中对数据进行操作时会触发发布者的通知逻辑从而使订阅者执行相应逻辑并生成新的 VNode ,新老 VNode 会进行一个 patch 过程比对得出差异,最终将差异更新至 Dom 完成视图的更新

patch

比对的核心为 diff 算法,diff 算法是通过同层的树节点进行比对,其时间复杂度只有 O(n)
图中相同色块的节点会进行比对,得出的差异最终会更新至视图
需要注意的是 Vue 与 React 得到 diff 算法虽说都是忽略跨级比较,只进行同级比较,但是

  1. Vue 对比节点时,当节点元素类型相同但是 className 不同则认定它们为不同类型元素并删除重建,而 React 则会认为其为同类型节点只修改节点属性

    1. Vue 同级对比时采用从两端至中心的对比方式,而 React 采用从左向右依次对比,这样的区别是当最右节点移动至最左端时,React 会将前面的节点依次移动而 Vue 只会移动一个节点
    2. Vue 组件数据变动时只更新自己,React 组件数据变动会更新自己及所有子组件

碍于篇幅具体对比流程请自行搜索,这里不多赘述

更新渲染

在更新渲染阶段,Vue 使用了异步更新策略

为什么需要异步更新

Vue 的异步更新策略更多的是出于性能优化的考量

举一个栗子,页面某值循环执行1000次自增(模拟多 effect 场景),此时若是遵循同步更新逻辑则会触发1000次 setter=>Dep=>watcher=>update 流程,这就导致会有大量的性能损耗

而 Vue 使用的异步更新策略会创建一个更新栈,然后将更新压入栈中并进行去重处理(去重操作的目的就在于优化本例出现的多次执行情况),在当轮同步代码执行完成后开始执行更新栈中的更新工作,最终将改变反馈至视图中

异步更新流程

Vue 的异步更新机制借用了事件循环机制内容,其将更新标明 id 并放入微任务队列中,待当轮同步任务执行完成后就会开始执行微任务队列中所有更新任务,最终将更新渲染至视图

当微任务队列中被置入相同 id 的更新任务时,Vue 异步更新策略只会保留最后的更新任务,实现性能优化效果

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
梦
4年前
微信小程序new Date()转换时间异常问题
微信小程序苹果手机页面上显示时间异常,安卓机正常问题image(https://imghelloworld.osscnbeijing.aliyuncs.com/imgs/b691e1230e2f15efbd81fe11ef734d4f.png)错误代码vardate'2021030617:00:00'vardateT
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Easter79 Easter79
3年前
SpringBoot写后端接口,看这一篇就够了!
摘要:本文演示如何构建起一个优秀的后端接口体系,体系构建好了自然就有了规范,同时再构建新的后端接口也会十分轻松。一个后端接口大致分为四个部分组成:接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响应数据(response)。如何构建这几个部分每个公司要求都不同,没有什么“一定是最好的”标准,但一个优秀的后端
Stella981 Stella981
3年前
SpringBoot写后端接口,看这一篇就够了!
摘要:本文演示如何构建起一个优秀的后端接口体系,体系构建好了自然就有了规范,同时再构建新的后端接口也会十分轻松。一个后端接口大致分为四个部分组成:接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响应数据(response)。如何构建这几个部分每个公司要求都不同,没有什么“一定是最好的”标准,但一个优秀的后端
Stella981 Stella981
3年前
H5游戏性能优化整理(cocos
近期在一家公司负责H5游戏加载速度优化,这里把近期做的项目优化项做一个整理分享:(若文中有错误的地方,还请指出。)   分享流程:了解html渲染流程html相关优化http相关优化项目结构和游戏流程及优化游戏渲染相关优化代码编写优化html渲染流程HTML解析过程:构建DOM树、构
Stella981 Stella981
3年前
Python调用Ant构建时根据构建状态来决定命令行退出状态
在使用python执行Ant构建时遇到的问题:使用os.system()调用Ant构建时,不论构建成功还是失败(BUILDSUCCESSFUL/BUILDFAILED),命令行的总是正常退出要解决问题:首先想到的是获取ant命令的返回值,根据返回值来决定命令行的退出状态(0或非0,0代表正常退出)查阅相关资料,得知python调用系
Wesley13 Wesley13
3年前
InfoQ 趋势报告:架构和设计领域技术演变详解
https://www.infoq.cn/article/R7lWXd0R4VFf3E0bB\38本文概述了我们对当前“架构和设计”领域的看法,这个领域侧重于基础设施模式、技术框架模式的实现,以及软件架构师必须掌握的设计流程和技能。关键要点:我们看到了“演化式架构”设计需求的增长,这种架构建立在可替换性设计和关注“胶水”