【Vue原理】VNode - 源码版

C_Sharp
• 阅读 3667
写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧

【Vue原理】VNode - 源码版

今天就来探索 VNode 的源码,VNode 是 Vue2 渲染机制中很重要的一部分,是深入Vue 必须了解的部分

我们以4个问题来开始我们的探索

1、vnode 是什么及其作用

2、vnode 什么时候生成

3、vnode 怎么生成

4、vnode 存放什么信息

5、vnode 存放在哪里

文章很长,看之前值做好准备

【Vue原理】VNode - 源码版


VNode是什么及作用

首先,第一个问题已经很烂了,网上有很多相关的内容,为了内容的完整性,所以也放上来哈哈。

VNode 表示 虚拟节点 Virtual DOM,为什么叫虚拟节点呢,因为不是真的 DOM 节点。

他只是用 javascript 对象来描述真实 DOM,这么描述,把DOM标签,属性,内容都变成 对象的属性

就像用 JavaScript 对象描述一个人一样

{sex:'男', name:'神仙朱', salary:5000,children:null}

过程就是,把你的 template 模板 描述成 VNode,然后一系列操作之后通过 VNode 形成真实DOM进行挂载

是什么?

JavaScript 对象

什么用?

1、兼容性强,不受执行环境的影响。VNode 因为是 JS 对象,不管 Node 还是 浏览器,都可以统一操作, 从而获得了服务端渲染、原生渲染、手写渲染函数等能力

2、减少操作 DOM。任何页面的变化,都只使用 VNode 进行操作对比,只需要在最后一步挂载更新DOM,不需要频繁操作DOM,从而提高页面性能


VNode怎么生成

在 Vue 源码中,vnode 是通过一个构造函数生成的,构造函数看起来挺简单的

本来以为很多内容,带着沉重的心情去探索,然后看到之后就放松了下来,看了一会,心情再次沉重了起来

其中涉及的内容还是挺多的....不然哪里来开篇的那么多问题

行了,看下 VNode 的构造函数

function VNode(
    tag, data, children, 
    text, elm, context, 
    componentOptions

) {    

    this.tag = tag; // 标签名

    this.data = data;    

    this.children = children; // 子元素

    this.text = text; // 文本内容

    this.elm = elm; // Dom 节点



    this.context = context;    

    this.componentOptions = componentOptions;    

    this.componentInstance = undefined;    

    this.parent = undefined;    

    this.isStatic = false; // 是否静态节点

    this.isComment = false; // 是否是注释节点

    this.isCloned = false; // 是否克隆节点

};

看完上面,先不要纠结都是什么东西,先来走一遍

比如我们使用 vnode 去描述这样一个template

<div class="parent" style="height:0" href="2222">
    111111

</div>

使用 VNode 构造函数就可以生成下面的 VNode

{    

    tag: 'div',    

    data: {        

        attrs:{href:"2222"}

        staticClass: "parent",        

        staticStyle: {            

            height: "0"

        }
    },    

    children: [{        

        tag: undefined,        

        text: "111111"

    }]
}

这个 JS 对象,就已经囊括了整个模板的所有信息,完全可以根据这个对象来构造真实DOM了

至于其中都是什么意思,请看下个问题


VNode存放什么信息

新建一个 vnode 的时候,包含了非常多的属性,每个属性都是节点的描述的一部分

我们只捡一些属性来探索一下,了解主体即可

普通属性

1、data

1、存储节点的属性,class,style 等

2、存储绑定的事件

3、....其他

【Vue原理】VNode - 源码版

【Vue原理】VNode - 源码版

2、elm

真实DOM 节点

生成VNode 的时候,并不存在真实 DOM

elm 会在需要创建DOM 时完成赋值,具体函数在 createElm 中

赋值语句就是一句(简化了源码)

【Vue原理】VNode - 源码版

3、context

渲染这个模板的上下文对象

意思就是,template 里面的动态数据要从这个 context 中获取,而 context 就是 Vue 实例

如果是页面,那么context 就是本页面的实例,如果是组件,context则是组件的实例

4 isStatic

是否是静态节点

当一个节点被标记为静态节点的时候,说明这个节点可以不用去更新它了,当数据变化的时候,可以忽略去比对他,以提高比对效率

组件相关属性

1、parent

这个parent 表示是组件的外壳节点

额,什么是外壳节点,举个栗子先吧

1、存在这样一个组件 test

【Vue原理】VNode - 源码版

2、页面中使用这个组件

【Vue原理】VNode - 源码版

诶,到这里就有意思了,组件其实应有两种 VNode

【Vue原理】VNode - 源码版

这两种VNode 名义上都是对的,都有理,谁是正牌不好说

最后尤大判定第一个 VNode 是 第二个 VNode 的爸爸,也就是外壳节点

【Vue原理】VNode - 源码版

外壳节点通常是 父组件和 子组件的 关联,用于保存一些父组件传给子组件的数据 等

2 componentInstance

这个顾名思义,就是组件生成的实例,保存在这里

上面 test 组件的外壳节点中的 componentInstance

【Vue原理】VNode - 源码版

3 componentOptions

这个就存储一些 父子组件 PY 交易的证据

比如 props,事件,slot 什么的,打印看下

【Vue原理】VNode - 源码版

【Vue原理】VNode - 源码版

其中 children 保存的就是 slot,listeners 保存 事件,propsData 保存 props


VNode怎么生成

在初始化完选项,解析完模板之后,就需要挂载 DOM了。此时就需要生成 VNode,才能根据 VNode 生成 DOM 然后挂载

挂载 DOM 第一步,就是先执行渲染函数,得到整个模板的 VNode

比如有以下渲染函数,执行会返回 VNode,就是 _c 返回的VNode

function (){ 
    with(this){  
        return _c('div',{attrs:{"href":"xxxx"}},["1111"]).
    } 
}

渲染函数会绑定上下文对象,加上 with 的作用,_c 其实就是 vm._c

现在就来看 vm._c 是什么东西

vm._c = function(a, b, c, d) {    

    return createElement(vm, a, b, c, d, false);

};
function createElement(

    context, tag, data, 

    children, normalizationType

) {    

    var vnode;    

    if (tag是正常html标签) {

        vnode = new VNode(

            tag, data, children, undefined, 

            undefined, context

        );
    } 
    else if (tag 是组件) {
        vnode = createComponent(

            Ctor, data, context, 

            children, tag

        );

    }    

    return vnode

}

我们可以看到,正常标签 和 组件会走不同流程

1 、正常标签

比如有这样一个正常标签模板

【Vue原理】VNode - 源码版

解析成渲染函数如下

function (){    

    with(this){  

        return _c('div',{

            attrs:{"href":"xxxx"}},

            ["1111"]

        )

    }
}

看上面_c 源码,可以知道经过 _c 把参数传导,这样去构建 VNode

new VNode(tag, data, children, undefined, undefined, context);

【Vue原理】VNode - 源码版

这样就保存了 tag,data,children 和 context

2、组件

比如页面使用了test组件

【Vue原理】VNode - 源码版

解析成渲染函数如下

with(this){  
    return _c('div',[
        _c('test',
            {attrs:{"name":name}},
            ["1111"]
        )
    ],1)
}

看上面 _c 代码知道 ,_c 会先调用 createComponent

createComponent(Ctor, data, context, children, tag);}

【Vue原理】VNode - 源码版

createComponent 中也会调用 VNode 构造函数,生成VNode 并返回

function createComponent(

    Ctor, data, context, 

    children, tag

) {    

    // extractPropsFromVNodeData 作用是把传入data的 attr 中属于 props的筛选出来
    var propsData = extractPropsFromVNodeData(data, Ctor, tag);    


    var vnode = new VNode(

        ("vue-component-" + (Ctor.cid) + tag),
        data, undefined, undefined, undefined,

        context, {            

            Ctor: Ctor, 

            // 父组件给子组件绑定的props
            propsData: propsData, 

            // 父组件给子组件绑定的事件
            listeners: listeners, 

            tag: tag,            

            children: children

        });    

    return vnode

}

VNode存放在哪里

那么创建出来的 VNode 是否有被存起来,毫无疑问,肯定是要的啊

主要是三个位置存了 vnode,分别是

parent ,_vnode ,$vnode

parent 上面已经说过,就先不提了,剩下两个全部是挂在 Vue 实例一级属性上的

打印一下组件的实例,可以很清楚看到这两个属性

【Vue原理】VNode - 源码版

下面来说说这两个东西

1、_vnode

_vnode 存放表示当前节点的 VNode

什么叫当前,也就是可以通过这个VNode 直接映射成 当前真实DOM

他的作用是什么呢?

用来比对更新,比如你的数据变化了,此时会生成一个新的 VNode,然后再拿到保存的_vnode 对比,就可以得到最小区域,从而只用更新这部分

所以, _vnode 存放的可以说是当前节点,也可以说是旧节点

另外,_vnode 中保存有一个 parent,这个parent 就是外壳节点,上面说 vnode 的时候已经说过了

在哪里赋值?

我们来完整地走一遍流程,涉及源码很多,但是我已经非常精简了,大概了解个流程

function Vue() {
    ...初始化组件选项等
    mountComponent()

}

function mountComponent() {

    ....解析模板,生成渲染函数
   
    // 用于生成VNode,生成DOM,挂载DOM
    updateComponent = function() {
        vm._update(vm._render());
    };    

    // 新建 watcher,保存updateComponent为更新函数,新建的时候会立即执行一遍
    new Watcher(vm, updateComponent)
}

function Watcher(vm, expOrFn) {    

    this.getter = expOrFn ;    

    this.getter()

}

// 执行前面解析得到的渲染函数,返回生成的 VNode
Vue.prototype._render = () {}

// 根据vnode,生成DOM 挂载
Vue.prototype._update = function(vnode) {    

    var prevVnode = vm._vnode;

    vm._vnode = vnode;    

    if (不存在旧节点) { ...使用vnode创建DOM并直接挂载 }    

    else { ...存在旧节点,开始比对旧节点和新节点,然后创建DOM并挂载 }

}

2、$vnode

$vnode 只有组件实例才有,因为 $vnode 存放的是外壳节点,页面实例中是不存在 $vnode 的

本来也想走下流程的,无奈兜兜转转太多,涉及源码更多

在哪里进行赋值?

我就放最后一步 updateChildComponent

updateChildComponent 会在上个 _vnode 提到的 vm._update 执行流程中调用

function updateChildComponent(
    vm, parentVnode

) {

    vm.$options._parentVnode = parentVnode;
    vm.$vnode = parentVnode; 
    if (vm._vnode) {
        vm._vnode.parent = parentVnode;
    }
}

最后

鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵,如果有任何描述不当的地方,欢迎后台联系本人,有重谢

【Vue原理】VNode - 源码版

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(