【Vue原理】VNode - 源码版

写文章不容易,点个赞呗兄弟 专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧 研究基于 Vue版本 【2.5.17】javascript

若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧html

【Vue原理】VNode - 源码版 vue

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

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

一、vnode 是什么及其做用

二、vnode 何时生成

三、vnode 怎么生成

四、vnode 存放什么信息

五、vnode 存放在哪里

文章很长,看以前值作好准备浏览器

公众号


VNode是什么及做用

首先,第一个问题已经很烂了,网上有不少相关的内容,为了内容的完整性,因此也放上来哈哈。函数

VNode 表示 虚拟节点 Virtual DOM,为何叫虚拟节点呢,由于不是真的 DOM 节点。性能

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

就像用 JavaScript 对象描述一我的同样this

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

过程就是,把你的 template 模板 描述成 VNode,而后一系列操做以后经过 VNode 造成真实DOM进行挂载

是什么?

JavaScript 对象

什么用?

一、兼容性强,不受执行环境的影响。VNode 由于是 JS 对象,无论 Node 仍是 浏览器,均可以统一操做, 从而得到了服务端渲染、原生渲染、手写渲染函数等能力

二、减小操做 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 的时候,包含了很是多的属性,每一个属性都是节点的描述的一部分

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

普通属性

一、data

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

二、存储绑定的事件

三、....其余

公众号

公众号

二、elm

真实DOM 节点

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

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

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

公众号

三、context

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

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

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

4 isStatic

是不是静态节点

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

组件相关属性

一、parent

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

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

一、存在这样一个组件 test

公众号

二、页面中使用这个组件

公众号

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

公众号

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

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

公众号

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

2 componentInstance

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

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

公众号

3 componentOptions

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

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

公众号

公众号

其中 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 、正常标签

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

公众号

解析成渲染函数以下

function (){    

    with(this){  

        return _c('div',{

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

            ["1111"]

        )

    }
}

看上面_c 源码,能够知道通过 _c 把参数传导,这样去构建 VNode

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

公众号

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

二、组件

好比页面使用了test组件

公众号

解析成渲染函数以下

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

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

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

公众号

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 实例一级属性上的

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

公众号

下面来讲说这两个东西

一、_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并挂载 }

}

二、$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;
    }
}

最后

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

公众号