精华内容
下载资源
问答
  • 主要介绍了Vue使用虚拟dom进行渲染view的方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
  • 今天小编就为大家分享一篇解决vue中虚拟dom,无法实时更新的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • 为什么需要虚拟DOM

    千次阅读 多人点赞 2020-08-30 17:46:20
    它的作用是以js的形式在内存中描述真实的DOM结构,这样当页面内容需要发生变动时,React可以通过对前后虚拟DOM的比对,计算出如何以最小的代价操作真实DOM。 1. 真实DOM操作的性能问题 在虚拟DOM出现之前,前端...


    我们知道,虚拟DOM的概念是由Facebook的React团队最早提出来的,也是React框架的核心概念之一。它的作用是以js的形式在内存中描述真实的DOM结构,这样当页面内容需要发生变动时,React可以通过对前后虚拟DOM的比对,计算出如何以最小的代价操作真实DOM。

    1. 真实DOM操作的性能问题

    在虚拟DOM出现之前,前端开发者最常用的方式是用jQuery直接操作真实DOM,像下面这样:

    <body>
      <div id="root"></div>
      <script>
        $('#root').text('这是一段文本');
        // 或者这种原生的方式
        // var root = document.querySelector('#root');
        // root.textContent = '这是一段文本';
      </script>
    </body>
    

    在本例中,jQuery只是提供了一层对原生DOM操作的封装。我们可以简单手写一个$实现:

    function $ (selector) {
      this.nodeList = document.querySelectorAll(selector);
      return this;
    }
    
    $.prototype.text = function (textContent) {
      this.nodeList.forEach(node => {
        node.textContent = textContent;
      });
      return this;
    }
    

    (本例只是用于解释jQuery的封装原理,并非jQuery的真实实现)

    通过这个简单的封装我们可以发现,jQuery的主要工作是提供对原生DOM操作的统一实现(另外还有对原生js的增强,如ajax、动画等)。换句话说,使用jQuery,本质上还是在直接操作真实DOM。

    那么直接操作真实DOM有什么问题呢?

    这就要从浏览器内核的构成说起了。我们以webkit内核为例,它大致包含以下模块(图片来自网络):
    在这里插入图片描述
    首先来看左侧的深色区域,它是webkit内核的WebCore层。该区域左下角的HTML模块代表HTML引擎,作用是解析HTML文档。当浏览器下载了一个HTML文档后,它负责将该文档解析成DOM树,也就是由一个个标签节点构成的文档树。最终解析出来的DOM树将交由它右侧的DOM模块负责管理,而这个DOM树就是我们平常所说的真实DOM。

    在深色区域的右侧紧邻着的是JavaScriptCore,即webkit默认的JavaScript引擎(在Chrome和chromium中它被替换为了V8),它负责执行JavaScript代码。

    我们知道,JavaScript具备操作DOM树的能力。但是从浏览器内核的结构可以看到,DOM树由DOM模块负责管理,在浏览器内核中单独占有一块内存,而这块内存与JavaScript引擎所管理的内存并无直接关系。换句话说,JavaScript引擎不能直接操作真实DOM树。

    为了给JavaScript提供操作DOM树的能力,浏览器在全局对象window上为JavaScript封装了一个document对象,然后在该对象上提供了大量的DOM操作接口,这些接口都是用C++实现的。如:
    在这里插入图片描述
    在浏览器控制台查看document.getElementById,可以看到它的函数体是{ [native code] },这表示它是一个用C++编写的函数,因此无法查看具体实现。当我们在调用这个函数时,JavaScript引擎并没有直接与DOM模块交互,而是由浏览器来操作DOM模块,随后再把操作结果返回给JavaScript引擎。这种借助父级模块实现两个同级模块交互的通信方式非常常见。

    正是由于JavaScript需要借助浏览器提供的DOM接口才能操作真实DOM,所以操作真实DOM的代价往往是比较大的(这其中还涉及C++与JavaScript数据结构的转换问题)。再加上修改DOM经常导致页面重绘,所以一般来说,DOM操作越多,网页的性能就越差。我们以一个简单的图例来理解这个过程:
    在这里插入图片描述
    有人可能会问,既然DOM操作的代价如此之大,为什么不由JavaScript引擎来管理DOM呢?不要忘了,世界上第一款浏览器诞生于1990年,那时候就已经出现了DOM的原始概念,而JavaScript则直到1995年才诞生。换句话说,DOM的出现早于JavaScript。因此,如果要由JavaScript来管理DOM,那就意味着浏览器内核必须重构。而让浏览器开发者为了一个早期被称为“玩具语言”的JavaScript重构浏览器内核显然是不可能的。不过随着JavaScript的发展,由JavaScript引擎直接管理DOM的构想已经被提上了chromium的计划列表(目前只是定为长期计划,要真正实现还需要相当长的时间)。

    所以,截止到目前,如何有效地减少对真实DOM的操作,仍然是前端性能优化的一个关键点。虚拟DOM就是目前较为流行的一个解决方案。

    2. 虚拟DOM的作用

    显然,JavaScript无法直接操作DOM是带来上述性能问题的根源之一(其他原因包括,真实DOM树的体积非常庞大,而且操作它会导致页面重绘)。那么能不能在JavaScript内存中,以js对象的形式也描述一棵DOM树呢?当然可以,这就是我们所说的虚拟DOM树。
    在这里插入图片描述
    可以看到,虚拟DOM并不能消除原生的DOM操作,你仍然需要通过浏览器提供的DOM接口来操作真实DOM树,才能使页面发生改变。虚拟DOM的设计似乎是多此一举。

    但是虚拟DOM带来了一个重要的优势,那就是我们可以在完全不访问真实DOM的情况下,掌握DOM的结构,这为框架自动优化DOM操作提供了可能。举例来说,如果我们本打算手动进行三次真实DOM操作,而框架在分析了虚拟DOM的结构后,把这三次DOM操作简化成了一次,这不就带来了性能上的提升吗?再甚者,如果连这一次DOM操作都可以由框架自动完成(自动更新的前提是我们要定义视图和数据的绑定关系),而我们只需要负责处理数据,那么虚拟DOM的价值体现得就更明显了。

    React就是这么使用虚拟DOM的。

    当我们使用jsx语法定义一个模板时,React会用它生成一个由JavaScript描述的虚拟DOM树,并将其保存在JavaScript内存中。这个虚拟DOM树还保留了我们在模板中定义的数据和视图的绑定关系,这为React自动根据数据变化更新视图提供了可能。随后当数据发生变化时,React重新生成一个虚拟DOM树,通过对比两个虚拟DOM树的差异,React就可以知道该如何高效地更新视图。接着它就会调用原生的DOM接口,去更新真实DOM。过程大致如下:
    在这里插入图片描述
    对于开发者而言,虚拟DOM的实现是透明的,它只是框架自动高效更新DOM的一种内部解决方案。开发者需要按照框架给定的语法定义数据和视图的绑定关系,随后就只需要关心数据变化(即业务逻辑)即可。

    当然了,虚拟DOM并不是解决DOM操作性能问题的唯一解决方案,Vue的响应式系统也是一种重要的解决方案。从某种程度上来说,Vue依靠响应式系统可以实现“精确定点更新”,即直接定位到哪个DOM节点需要更新,而不需要经过虚拟DOM的比对,不过“精确定点更新”的内存代价偏大,因此目前Vue采用了响应式系统和虚拟DOM结合的方式,本文暂不详述。

    最后我们来看一下Vue中虚拟DOM树的结构,实际上它就是一个js对象而已,我们以下面的模板为例:

    <template>
      <div id="app">
        <ul>
          <li v-for=“item in items”>
              itemid: {{ item.id }}
           </li>
        </ul>
      </div>
    </template>
    

    对应的虚拟DOM:
    在这里插入图片描述

    总结

    要理解为什么需要虚拟DOM,必须弄清楚JavaScript引擎和DOM模块之间的关系,并体会由这种关系导致的DOM操作的性能问题。虚拟DOM设计的核心就是用高效的js操作,来减少低性能的DOM操作,以此来提升网页性能。

    从一定程度上来说,是浏览器的架构问题催生了虚拟DOM,而这个架构问题几乎需要重构浏览器内核才能解决,所以目前虚拟DOM仍广为流行。如果未来的某一天,真实DOM被迁移到JavaScript内存中,虚拟DOM的价值实际上也就不存在了。

    展开全文
  • React虚拟DOM好处

    2018-12-14 15:17:44
    关于React提供的虚拟DOM好处有一些困惑和误解需要阐明。 我们总是或多或少的听说过直接操作DOM是低效和缓慢的。然而,我们几乎没有可用的数据来支持这个观点。关于React虚拟DOM的令人愉悦的地方在于web开发过程中...

    关于React提供的虚拟DOM的好处有一些困惑和误解需要阐明。

    我们总是或多或少的听说过直接操作DOM是低效和缓慢的。然而,我们几乎没有可用的数据来支持这个观点。关于React虚拟DOM的令人愉悦的地方在于web开发过程中,它采用了更加高效的方式来更新view层。

    我们把使用React的其他好处姑且放到一边,例如单项绑定和组件化。本节我讲详细的讨论一下虚拟DOM,随后公正的抉择为什么使用React而不是其他UI库(或者任何UI库都不使用)。

    为什么需要UI库

    在反应式的系统中两个最重要的idea应该是事件驱动和对数据变化的响应。

    DOM用户接口组件有内部状态,当无论何时有情况发生变化的时候,更新浏览器并不是像简单的重新生成DOM那么简单。举个例子,如果Gmail曾经这么做的话,因为浏览器窗口为了显示新的信息,需要不断的刷新整个页面,如果你正在写邮件的话,所有的都会被清除,你一定会经常恼怒不已。

    DOM的这种状态方式决定了为什么我们需要一个用户界面库,因为这些库提供了一些解决方案,例如键/值 观察(在Ember库中使用了此方法),或者脏值检测(Angular使用了此方法)。UI库可以观察数据模型的改变,然后在改变发生的时候只改变对应的部分DOM,或者观察DOM改变,然后更新数据模型。

    这种观察和更新的模式叫做双向绑定,这种方式常常让与用户界面的工作更加复杂和让人困惑。

    React的不同之处

    React的虚拟DOM与其他库的不同之处在于从程序员的角度看,这种模式更加的简单。你只需要写纯javascript,然后更新React组件,React将为你更新DOM。数据绑定并没有和应用程序搅和在一起。

    React使用单向数据绑定使得事情变得更加简单,每一次在ReactUI的input域中输入内容,React并不直接改变状态。相反,React更新数据模型,随后引起UI更新,你输入的文字将在域中出现。

    DOM真的慢吗?

    关于虚拟DOM的很多演讲和文章都指出,尽管现在jiavascript的引擎已经很快了,但是读取和写入浏览器的DOM还是很慢,

    这不是特别的准确。DOM是很快的。增加和去除DOM节点就是设置一些javascript对象的属性,这才是简单的操作,这并不是需要做很多的工作。

    真正慢的地方自在于,每次DOM改变的时候,都需要在浏览器中进行渲染。每一次DOM改变的时候,浏览器都需要重新计算CSS,进行布局处理,然后重新渲染页面。这都需要时间。

    浏览器的开发者持续不断的工作来缩短渲染页面的时间。最关键的需要完成的事情是最小化DOM改变,然后批处理DOM变化,在必要的时候才重新渲染页面。(目前原生浏览器还是无法做到)

    这种批处理DOM改变的策略,把我们提升到了一个更加抽象的层次,这也是React虚拟DOM背后的idea。

    虚拟DOM是怎么工作的?

    与真实DOM相似,虚拟DOM也是节点树,以对象的形式列出内容,属性。React的render方法从React组建中创建节点树,因为动作改变等引起数据模型变化的时候,React更新节点树。

    每次React app内部的数据改变,用户界面的虚拟DOM都会构建。

    这里就是最有趣的地方,在React中更新浏览器DOM需要三步: 1. 每次数据模型变化的时候,虚拟DOM节点树都会重新构建。 2. React依赖某个算法(称之为diff算法)来与上一个虚拟DOM节点数进行比较,只有在不同的情况下才重新进行计算。 3.所有的变化要经过批处理,完成之后,真实DOM才进行更新。

    虚拟DOM慢吗?

    有人认为只要有一点改变就重新构造整个虚拟DOM节点树有点浪费,但是他没有提及一个事实,React在内存中存储有两个虚拟DOM树。

    但是,真实情况是渲染虚拟DOM总是比直接渲染浏览器DOM快。这种论断与你使用的浏览器也没多大的关系。

    我们看一些数据

    我不会做一些基准测试,因为已经有很多开发者创建了很多测试来搞清楚React虚拟DOM方法是否比原生的DOM渲染更加快速。但是频繁的结论显示似乎不是这样,但是因为很多测试都是不实际的,所以这些测试结果也无关紧要。

    简单来说,虚拟DOM就是把所有浏览器为了最小化DOM操作,这些对于开发者来说透明的操作进行了抽象。这层额外的抽象的缺点就是增加了CPU的开销。

    这里有一个"hello,world"的例子,使用了原生的DOM操作:

    随后我们在React中做相同的事情。在这里请注意我们需要包含React,React DOM 和babel,其中babel负责把在render方法中的类XML的代码进行转换成原生的javascript。

    结果显示,本地方法更加的快速。这并不是开玩笑,让我们看一下证据。

    这里是加载和渲染真实DOM的性能分析图。(来源于Chrome)

    这里是在同样的浏览器中加载和展示React的"hello,world"应用时间线。

    请注意本质上来说,事情是相同的。React比直接操作DOM慢,而且慢了很多。那么,与jQuery比又如何呢?

    从分析图中,我们可以看到,jQuery进行展示最简单的hello,world的应用比原生的纯javascript满了50毫秒,但是jQuery版本和原声版本都是React的3倍。

    所以,很清晰,如果要说哪一个更快,原生的javascript和jQuery无疑略胜一筹。

    但是,使用库总是要比不使用库慢一些,这显然是再正常不过了,在内存里进行构建DOM,然后再进行真实的DOM操作更慢,也是很符合逻辑的情况。

    废话说多了,现在让我们准确思考一下,怎么使得虚拟DOM加载更快。

    怎么使用虚拟DOM

    我的"hello world"项目对于React来说是不公平的,原因在于他们仅仅处理的是初次渲染页面的性能比较。React设计的目的是用来更新网页。

    因为虚拟DOM的存在,每一次数据模型的改变都会触发虚拟用户接口的刷新。这与其他库进行直接的DOM更新是不一样的。虚拟DOM实际上使用了更少的内训,因为它不需要在内存中设置观察者。

    然而,每一次动作发生的时候,在内存中比较虚拟DOM也是低效的。这需要大量的CPU运算。

    因为这个愿意,React开发者在决定需要渲染什么的时候并不是完全被动的。如果你知道特定的动作不会影响特定的组件,你可以告诉React不要去分析组件差别,这无疑可以大大节省资源,加快应用程序的性能。示范操作中的React程序是可以进行性能提升的。

    真实情况是,没办法准确的知道虚拟DOM是否比直接进行DOM操作更快。因为这依赖于很多变量,尤其是和你怎么优化程序密切相关。

    这并不是革命性或者令人惊讶的事情,每个人使用的工具都是挺好的。React和虚拟DOM给予我们的,是用一种简单的方式进行UI渲染,它提供给我们一种更加简便的方式来更新浏览器。这种简化可以大大释放我们的脑力负担,使得优化用户界面更加的容易。这才是React真正好处所在的地方,如果处理得到,使用React,兼具性能和生产力,何乐而不为呢?

    原文地址:www.accelebrate.com/blog/the-re…

    作者: Chris Minnick

    说明:原文有一处举了一个100000个墨西哥玉米卷的例子,只是想说明不用每一次拿一个玉米卷,而是等待想拿的玉米卷确定之后然后在进行运输到自己国家进行消费。本质上还是映射React的工作方式,原文闲的稍加啰嗦,有删节。

    总结: 本文核心有两点:1.稍微介绍一下React的虚拟DOM和原生DOM的区别和联系。2.文末点出,虚拟DOM如果处理得到,是可以处理好性能问题的,但是React的真正好处在于,它的模块化思想,释放了生产力。

    颠覆:文中有几个观点需要重新审视,我们也经常听说直接操作DOM是低效的,但是这种低效在什么地方,似乎也没有国内的开发者进行准确的数据论证比较,作者给出了它的解释,直接操作DOM并不低效,低效的地方在于渲染页面的过程繁杂浪费时间,相比于React批处理之后只渲染一遍无疑是高效了很多。

    转载于:https://juejin.im/post/5c13b169e51d4556a446276f

    展开全文
  • 杰特 在JQuery环境下用MVVM开发复杂组件,仅几kb超小体积。 定义一个开关组件,挂载到jquery后, $('#el').switch()就可以将#el变成一个开关组件,和$('#el').val()可以获取值( js也能取),放进form也能直接提交,...
  • 文章目录一、什么是虚拟DOM二、为什么需要虚拟DOM三、如何实现虚拟DOM小结参考文献 一、什么是虚拟DOM 虚拟 DOM (Virtual DOM )这个概念相信大家都不陌生,从 React 到 Vue ,虚拟 DOM 为这两个框架都带来了跨...

    故心故心故心故心小故冲啊



    在这里插入图片描述

    一、什么是虚拟DOM

    虚拟 DOM (Virtual DOM )这个概念相信大家都不陌生,从 React 到 Vue ,虚拟 DOM 为这两个框架都带来了跨平台的能力(React-Native 和 Weex)

    实际上它只是一层对真实DOM的抽象,以JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上

    在Javascript对象中,虚拟DOM 表现为一个 Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对这三个属性的名命可能会有差别

    创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应

    在vue中同样使用到了虚拟DOM技术

    定义真实DOM

    <div id="app">
        <p class="p">节点内容</p>
        <h3>{{ foo }}</h3>
    </div>
    

    实例化vue

    const app = new Vue({
        el:"#app",
        data:{
            foo:"foo"
        }
    })
    

    观察render的render,我们能得到虚拟DOM

    (function anonymous() {
     with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',{staticClass:"p"},
           [_v("节点内容")]),_v(" "),_c('h3',[_v(_s(foo))])])}})
    

    通过VNode,vue可以对这颗抽象树进行创建节点,删除节点以及修改节点的操作, 经过diff算法得出一些需要修改的最小单位,再更新视图,减少了dom操作,提高了性能

    二、为什么需要虚拟DOM

    DOM是很慢的,其元素非常庞大,页面的性能问题,大部分都是由DOM操作引起的

    真实的DOM节点,哪怕一个最简单的div也包含着很多属性,可以打印出来直观感受一下:

    在这里插入图片描述
    由此可见,操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验

    举个例子:

    你用传统的原生api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程

    当你在一次操作时,需要更新10个DOM节点,浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程

    而通过VNode,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,避免大量的无谓计算

    很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI

    三、如何实现虚拟DOM

    首先可以看看vue中VNode的结构

    源码位置:src/core/vdom/vnode.js

    export default class VNode {
      tag: string | void;
      data: VNodeData | void;
      children: ?Array<VNode>;
      text: string | void;
      elm: Node | void;
      ns: string | void;
      context: Component | void; // rendered in this component's scope
      functionalContext: Component | void; // only for functional component root nodes
      key: string | number | void;
      componentOptions: VNodeComponentOptions | void;
      componentInstance: Component | void; // component instance
      parent: VNode | void; // component placeholder node
      raw: boolean; // contains raw HTML? (server only)
      isStatic: boolean; // hoisted static node
      isRootInsert: boolean; // necessary for enter transition check
      isComment: boolean; // empty comment placeholder?
      isCloned: boolean; // is a cloned node?
      isOnce: boolean; // is a v-once node?
    
      constructor (
        tag?: string,
        data?: VNodeData,
        children?: ?Array<VNode>,
        text?: string,
        elm?: Node,
        context?: Component,
        componentOptions?: VNodeComponentOptions
      ) {
        /*当前节点的标签名*/
        this.tag = tag
        /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
        this.data = data
        /*当前节点的子节点,是一个数组*/
        this.children = children
        /*当前节点的文本*/
        this.text = text
        /*当前虚拟节点对应的真实dom节点*/
        this.elm = elm
        /*当前节点的名字空间*/
        this.ns = undefined
        /*编译作用域*/
        this.context = context
        /*函数化组件作用域*/
        this.functionalContext = undefined
        /*节点的key属性,被当作节点的标志,用以优化*/
        this.key = data && data.key
        /*组件的option选项*/
        this.componentOptions = componentOptions
        /*当前节点对应的组件的实例*/
        this.componentInstance = undefined
        /*当前节点的父节点*/
        this.parent = undefined
        /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
        this.raw = false
        /*静态节点标志*/
        this.isStatic = false
        /*是否作为跟节点插入*/
        this.isRootInsert = true
        /*是否为注释节点*/
        this.isComment = false
        /*是否为克隆节点*/
        this.isCloned = false
        /*是否有v-once指令*/
        this.isOnce = false
      }
    
      // DEPRECATED: alias for componentInstance for backwards compat.
      /* istanbul ignore next https://github.com/answershuto/learnVue*/
      get child (): Component | void {
        return this.componentInstance
      }
    }
    

    这里对VNode进行稍微的说明:

    所有对象的 context 选项都指向了 Vue 实例
    elm 属性则指向了其相对应的真实 DOM 节点
    vue是通过createElement生成VNode

    源码位置:src/core/vdom/create-element.js

    export function createElement (
      context: Component,
      tag: any,
      data: any,
      children: any,
      normalizationType: any,
      alwaysNormalize: boolean
    ): VNode | Array<VNode> {
      if (Array.isArray(data) || isPrimitive(data)) {
        normalizationType = children
        children = data
        data = undefined
      }
      if (isTrue(alwaysNormalize)) {
        normalizationType = ALWAYS_NORMALIZE
      }
      return _createElement(context, tag, data, children, normalizationType)
    }
    

    上面可以看到createElement 方法实际上是对 _createElement 方法的封装,对参数的传入进行了判断

    export function _createElement(
        context: Component,
        tag?: string | Class<Component> | Function | Object,
        data?: VNodeData,
        children?: any,
        normalizationType?: number
    ): VNode | Array<VNode> {
        if (isDef(data) && isDef((data: any).__ob__)) {
            process.env.NODE_ENV !== 'production' && warn(
                `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
                'Always create fresh vnode data objects in each render!',
                context`
            )
            return createEmptyVNode()
        }
        // object syntax in v-bind
        if (isDef(data) && isDef(data.is)) {
            tag = data.is
        }
        if (!tag) {
            // in case of component :is set to falsy value
            return createEmptyVNode()
        }
        ... 
        // support single function children as default scoped slot
        if (Array.isArray(children) &&
            typeof children[0] === 'function'
        ) {
            data = data || {}
            data.scopedSlots = { default: children[0] }
            children.length = 0
        }
        if (normalizationType === ALWAYS_NORMALIZE) {
            children = normalizeChildren(children)
        } else if ( === SIMPLE_NORMALIZE) {
            children = simpleNormalizeChildren(children)
        }
     // 创建VNode
        ...
    }
    

    可以看到_createElement接收5个参数:

    context 表示 VNode 的上下文环境,是 Component 类型

    tag 表示标签,它可以是一个字符串,也可以是一个 Component

    data 表示 VNode 的数据,它是一个 VNodeData 类型

    children 表示当前 VNode的子节点,它是任意类型的

    normalizationType 表示子节点规范的类型,类型不同规范的方法也就不一样,主要是参考 render 函数是编译生成的还是用户手写的

    根据normalizationType 的类型,children会有不同的定义

    if (normalizationType === ALWAYS_NORMALIZE) {
        children = normalizeChildren(children)
    } else if ( === SIMPLE_NORMALIZE) {
        children = simpleNormalizeChildren(children)
    }
    

    simpleNormalizeChildren方法调用场景是 render 函数是编译生成的

    normalizeChildren方法调用场景分为下面两种:

    render 函数是用户手写的
    编译 slot、v-for 的时候会产生嵌套数组
    无论是simpleNormalizeChildren还是normalizeChildren都是对children进行规范(使children 变成了一个类型为 VNode 的 Array),这里就不展开说了

    规范化children的源码位置在:src/core/vdom/helpers/normalzie-children.js

    在规范化children后,就去创建VNode

    let vnode, ns
    // 对tag进行判断
    if (typeof tag === 'string') {
      let Ctor
      ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
      if (config.isReservedTag(tag)) {
        // 如果是内置的节点,则直接创建一个普通VNode
        vnode = new VNode(
          config.parsePlatformTagName(tag), data, children,
          undefined, undefined, context
        )
      } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
        // component
        // 如果是component类型,则会通过createComponent创建VNode节点
        vnode = createComponent(Ctor, data, context, children, tag)
      } else {
        vnode = new VNode(
          tag, data, children,
          undefined, undefined, context
        )
      }
    } else {
      // direct component options / constructor
      vnode = createComponent(tag, data, context, children)
    }
    

    createComponent同样是创建VNode

    源码位置:src/core/vdom/create-component.js

    export function createComponent (
      Ctor: Class<Component> | Function | Object | void,
      data: ?VNodeData,
      context: Component,
      children: ?Array<VNode>,
      tag?: string
    ): VNode | Array<VNode> | void {
      if (isUndef(Ctor)) {
        return
      }
     // 构建子类构造函数 
      const baseCtor = context.$options._base
    
      // plain options object: turn it into a constructor
      if (isObject(Ctor)) {
        Ctor = baseCtor.extend(Ctor)
      }
    
      // if at this stage it's not a constructor or an async component factory,
      // reject.
      if (typeof Ctor !== 'function') {
        if (process.env.NODE_ENV !== 'production') {
          warn(`Invalid Component definition: ${String(Ctor)}`, context)
        }
        return
      }
    
      // async component
      let asyncFactory
      if (isUndef(Ctor.cid)) {
        asyncFactory = Ctor
        Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
        if (Ctor === undefined) {
          return createAsyncPlaceholder(
            asyncFactory,
            data,
            context,
            children,
            tag
          )
        }
      }
    
      data = data || {}
    
      // resolve constructor options in case global mixins are applied after
      // component constructor creation
      resolveConstructorOptions(Ctor)
    
      // transform component v-model data into props & events
      if (isDef(data.model)) {
        transformModel(Ctor.options, data)
      }
    
      // extract props
      const propsData = extractPropsFromVNodeData(data, Ctor, tag)
    
      // functional component
      if (isTrue(Ctor.options.functional)) {
        return createFunctionalComponent(Ctor, propsData, data, context, children)
      }
    
      // extract listeners, since these needs to be treated as
      // child component listeners instead of DOM listeners
      const listeners = data.on
      // replace with listeners with .native modifier
      // so it gets processed during parent component patch.
      data.on = data.nativeOn
    
      if (isTrue(Ctor.options.abstract)) {
        const slot = data.slot
        data = {}
        if (slot) {
          data.slot = slot
        }
      }
    
      // 安装组件钩子函数,把钩子函数合并到data.hook中
      installComponentHooks(data)
    
      //实例化一个VNode返回。组件的VNode是没有children的
      const name = Ctor.options.name || tag
      const vnode = new VNode(
        `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
        data, undefined, undefined, undefined, context,
        { Ctor, propsData, listeners, tag, children },
        asyncFactory
      )
      if (__WEEX__ && isRecyclableComponent(vnode)) {
        return renderRecyclableComponentTemplate(vnode)
      }
    
      return vnode
    }
    

    稍微提下createComponent生成VNode的三个关键流程:

    • 构造子类构造函数Ctor
    • installComponentHooks安装组件钩子函数
    • 实例化 vnode

    小结

    createElement 创建 VNode 的过程,每个 VNode 有 children,children 每个元素也是一个VNode,这样就形成了一个虚拟树结构,用于描述真实的DOM树结构

    参考文献

    https://ustbhuangyi.github.io/vue-analysis/v2/data-driven/create-element.html#children-%E7%9A%84%E8%A7%84%E8%8C%83%E5%8C%96
    https://juejin.cn/post/6876711874050818061

    展开全文
  • vue 虚拟DOM的原理

    2020-10-14 17:29:51
    主要介绍了vue 虚拟DOM的原理,帮助大家更好的理解和学习vue,感兴趣的朋友可以了解下
  • 虚拟DOM大家都不陌生,研究了一下略带收获,在这里简单记录下,做个备忘 1、什么是虚拟DOM以及虚拟DOM的表达 虚拟DOM简而言之就是,用JS去按照DOM结构来实现的树形结构对象,也就是一个JS对象而已。那么什么样的对象...
  • 一个玩具级的 HTML 转虚拟 DOM 编译器
  • 主要介绍了浅谈React的最大亮点之虚拟DOM,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 虚拟dom添加虚拟domby Sindre Osen Aarsaether 通过Sindre Osen Aarsaether 虚拟DOM缓慢。 符合已记忆的DOM。 (The Virtual DOM is slow. Meet the Memoized DOM.) 超越虚拟DOM和状态管理 (Moving beyond the ...

    虚拟dom添加虚拟dom

    by Sindre Osen Aarsaether

    通过Sindre Osen Aarsaether

    虚拟DOM缓慢。 符合已记忆的DOM。 (The Virtual DOM is slow. Meet the Memoized DOM.)

    超越虚拟DOM和状态管理 (Moving beyond the Virtual DOM and State Management)

    The virtual DOM was a fantastic innovation. It brought about a much more productive way of writing web applications by allowing us to write our views in a declarative manner.

    虚拟DOM是一项了不起的创新。 通过允许我们以声明的方式编写视图,它带来了一种更加高效的Web应用程序编写方式。

    This big benefit has little to do with performance of the initial render. Instead, it is the process of updating the DOM to reflect changes in your state has become much faster.

    这个很大的好处与初始渲染的性能无关。 相反,它是更新DOM以反映您状态变化的过程,变得更快。

    This process of bringing the DOM in sync with the state is often referred to as DOM reconciliation.

    使DOM与状态同步的过程通常称为DOM协调

    If we had an infinitely fast reconciler, we could vastly simplify our applications by rendering everything on every single frame. The state layer would never need to know about views at all — much less send out events and track which views need to react when certain parts of the state change. The view would always be in sync with the data, no matter what you threw at it.

    如果我们有一个无限快的协调器,则可以通过在每个帧上渲染所有内容来极大地简化我们的应用程序。 状态层根本不需要了解视图,更不用说发出事件并跟踪在状态的某些部分发生变化时需要响应的视图。 无论您对视图进行什么操作,视图都将始终与数据保持同步。

    Sadly, virtual DOM implementations are not infinitely fast. They are, in fact, surprisingly slow. Thankfully, many have jumped on the Immutability™ bandwagon, in which case the virtual DOM thanks you! Others wrap all state in observables (e.g. mobx), and keep track of which view depends on what state. This allows you to reconcile only parts of your view, but comes with its own set of drawbacks.

    令人遗憾的是,虚拟DOM实现并非无限快。 实际上,它们出奇地慢。 值得庆幸的是,许多人跃跃欲试,在这种情况下,虚拟DOM谢谢您! 其他人则将所有状态都包裹在可观察对象(例如mobx)中,并跟踪哪个视图取决于哪个状态。 这使您只能协调部分视图,但有其自身的缺点。

    The biggest issue is that we tend to decide how to manage our application state based on our view layer. What if we could get better performance in a world where the data layer and view layer don’t really know or care about each other?

    最大的问题是,我们倾向于根据视图层决定如何管理应用程序状态。 如果在一个数据层和视图层并不真正了解或关心彼此的世界中,我们可以获得更好的性能怎么办?

    认识记忆化的DOM (Meet the Memoized DOM)

    Imba is a programming language for the web. It powers the interactive screencasting platform scrimba.com, of which I am the lead developer. Imba was born to make developing web applications fun again. It features a clean and readable syntax inspired by Ruby. It compiles to readable and performant JavaScript, and works inside the existing ecosystem.

    Imba是一种网络编程语言。 它为交互式截屏平台scrimba.com提供了动力 ,我是其中的首席开发人员。 Imba的诞生再次使开发Web应用程序变得有趣。 它具有受Ruby启发的简洁易读的语法。 它可以编译为可读和高性能JavaScript,并可以在现有的生态系统中运行。

    Besides a clean and readable syntax, the biggest benefit of Imba is that it truly treats DOM elements as first-class citizens, on a much deeper level than JSX. It allows you to write views declaratively, yet it does not use a virtual DOM. Instead, Imba compiles views to a memoized DOM, which turns out to be an order of magnitude faster.

    除了简洁易懂的语法外,Imba的最大好处是,它在比JSX更深的层次上真正将DOM元素视为一流的公民。 它允许您声明性地编写视图,但不使用虚拟DOM。 取而代之的是,Imba将视图编译为已记忆的DOM,事实证明它快了一个数量级

    这个怎么运作 (How it works)

    The general idea is that we create lightweight wrappers around DOM elements, and compile declarative views to chains of setters, each modifying the underlying DOM directly.

    通常的想法是,我们围绕DOM元素创建轻量级包装,并将声明式视图编译为设置器链,每个视图都直接修改基础DOM。

    tag AppView    def render        <self>            <h1.title> "Welcome"            <p.desc .red=(Math.random > 0.5)> "Roulette"

    The Imba view above will roughly compile into the following javascript:

    上方的Imba视图将大致编译为以下javascript:

    class AppView extends Imba.Tag {  render() {    var $ = this.$; // inline cache for tag    return this.setChildren($.$ = $.$ || [      Imba.tag('h1',$).flag('title').setText("Welcome"),      Imba.tag('p',$).flag('desc').setText("Roulette")    ]).synced(      $[1].flagIf('red',Math.random() > 0.5)    );  }}

    This is a very simple example to illustrate the basic concept. During compilation we split creation and updates into separate branches. The first time render is called for an <AppView> the children will be created and static attributes will be set. On all subsequent calls the only real work we do is flip the className of our <p>. Albeit much more complex, the same concept is used for conditionals, loops, and everything else inside tag trees.

    这是一个非常简单的示例,用于说明基本概念。 在编译过程中,我们将创建和更新分为单独的分支。 第一次为<AppVi ew>调用渲染时,将创建子代并设置静态属性。 在所有后续调用,我们要做的唯一的真正的工作是翻转的classNameØ fo乌尔<P>。 尽管非常复杂,但相同的概念用于条件树,循环以及标签树中的所有其他内容。

    If you’re interested in how it really works I recommend reading this intro.

    如果您对它的实际工作方式感兴趣,建议阅读此介绍

    基准测试 (Benchmark)

    React is fast, they said. React is fast enough, they said. React Fiber will be fast enough, they said.
    他们说,React很快。 他们说,React足够快。 他们说,React Fiber将足够快。

    Most benchmarks test things like “insert/shuffle/remove 1000 rows”. This gives little indication about real-world performance. When there are hundres of changes, most of the difference is eaten up by actual DOM mutations, repainting, etc. It fails to measure the most important metric.

    大多数基准测试都会测试“插入/随机播放/删除1000行”之类的内容。 这几乎没有提供有关实际性能的指示。 当存在数百种变化时,大多数差异会被实际的DOM突变,重绘等吞噬掉。它无法衡量最重要的指标。

    If you truly want to test the performance of DOM reconciliation, you need to look at how quickly the implementation brings the DOM in sync with the state, especially when there are few/no changes.

    如果您确实想测试DOM协调的性能,则需要查看实现使DOM与状态同步的速度, 尤其是在更改很少/没有更改的情况下

    So, to capture a realistic view of the reconciler performance, we could change a small part of the application state in each iteration, and then measure the time it takes to forcefully bring the view in sync with this changed state. The view should not be listening to any part of the state, and the state should not need to notify anyone whether it has changed.

    因此,为了捕获协调器性能的真实视图,我们可以在每次迭代中更改应用程序状态的一小部分,然后测量将视图强制与此更改状态同步所需的时间。 该视图不应监听状态的任何部分,并且该状态不需要通知任何人它是否已更改。

    This benchmark steps through a deterministic sequence of state alterations, doing at most one change per iteration. We are measuring the time it takes to reconcile the whole application view after:

    该基准测试逐步执行确定性的状态更改序列, 每次迭代最多进行一次更改 。 在以下情况下,我们正在测量协调整个应用程序视图所需的时间:

    1. Toggling the completion of a task

      切换任务的完成
    2. Removing a task

      删除任务
    3. Inserting a task

      插入任务
    4. Renaming a task

      重命名任务
    5. Doing nothing

      什么也不做

    结果 (Results)

    Running the benchmark on an iMac (4GHz i7) yields the following results:

    在iMac(4GHz i7)上运行基准测试会产生以下结果:

    Safari 11 (Safari 11)

    • Imba 1.3: 360458 ops / sec

      Imba 1.3: 每秒360458次操作

    • React 16.2: 9752 ops / sec — 36.96x slower

      React 16.2: 9752操作/秒- 慢36.96倍

    • Vue 2.5: 8719 ops / sec — 41.34x slower

      Vue 2.5:每秒8719次操作- 慢41.34倍

    Chrome65 (Chrome 65)

    • Imba 1.3: 282484 ops / sec

      Imba 1.3: 282484次操作/秒

    • React 16.2: 8882 ops / sec — 31.81x slower

      React 16.2: 8882次操作/秒- 慢31.81倍

    • Vue 2.5: 8103 ops / sec — 34.86x slower

      Vue 2.5:每秒8103次操作- 慢34.86倍

    Firefox 58 (Firefox 58)

    • Imba 1.3: 234334 ops / sec

      Imba 1.3: 每秒234334次操作

    • React 16.2: 5075 ops / sec — 46.17x slower

      React 16.2: 5075次操作/秒- 慢46.17倍

    • Vue 2.5: 3119 ops / sec — 75.13x slower

      Vue 2.5:每秒3119次操作- 慢75.13倍

    This seems outrageous right? Surely, it cannot be right.

    这似乎太离谱了吧? 当然,这是不对的。

    • All implementations are really reconciling on every step.

      所有实现真正和解的每一步。

    • All implementations are blocking, synchronous, and deterministic.

      所有实现都是阻塞,同步和确定性的。
    • All implementations are performing the same amount of DOM mutations.

      所有实现都执行相同数量的DOM突变。
    • Yes, we are using the minified production build of React. The development version is 200x slower than Imba on the same test.

      是的,我们使用的是最小化的React生产版本。 在同一测试中,开发版本比Imba 慢200倍。

    • The memoized DOM creates practically no garbage during an iteration, uses less memory overall, and is conceptually very simple.

      备注DOM在迭代过程中几乎不产生垃圾,总体上使用较少的内存,并且从概念上讲非常简单。

    All the implementations can probably be optimized more. I’m very happy to accept pull-requests at GitHub. To be clear, I have tremendous respect for what React has achieved, and I truly love Vue. Imba has taken a lot of inspiration from it. I suspect it should be possible to compile Vue templates using a similar approach, and would love for someone to give it a go!

    所有实现可能都可以进行更多优化。 我很高兴在GitHub接受请求请求。 明确地说,我对React所取得的成就深表敬意,并且我真的很喜欢Vue。 Imba从中汲取了很多灵感。 我怀疑应该可以使用类似的方法来编译Vue模板,并且希望有人可以尝试一下!

    剖析 (Profiling)

    Let’s test the raw reconciler performance when there aren’t even any changes. This removes the time spent doing actual DOM mutations from the equation, and gives us a good picture about how much work is going on during reconciliation. The charted CPU profile from Chrome gives a visual indication of how much less work is done with the memoized DOM technique.

    当没有任何变化时,让我们测试原始调节器性能。 这消除了从等式中进行实际DOM突变所花费的时间,并为我们提供了有关协调期间正在进行的工作量的清晰图片。 Chrome的图表CPU配置文件直观显示了使用DOM技术的工作量。

    英巴1.3 (Imba 1.3)

    React16.2 (React 16.2)

    Vue 2.5 (Vue 2.5)

    可以缩放吗? (Does it scale?)

    “There are A LOT, and I mean, A LOT of small little projects that claim more speed, easier development, but on closer inspection usually lack very important features (such as module life cycle hooks) and, of course without them the performance is higher, but the flexibility to use those libraries beyond a todo list application is limited.”
    “有很多,我的意思是,很多小的项目要求更快,更轻松的开发,但是仔细检查通常会缺少非常重要的功能(例如模块生命周期挂钩),当然,如果没有它们,性能是更高,但是在待办事项列表应用程序之外使用这些库的灵活性受到限制。”

    This is a quote from someone who read through an early draft of this article, and I would like to tackle it head on. The performance difference is not limited to a simple test, quite the contrary. Imba has been used in production for several years at scrimba.com, but it is still not for the faint of heart. For most developers the massive ecosystems for Vue and React will be hard (and probably unwise) to leave behind. The Imba documentation still leaves a lot to be desired, but we are improving it every day.

    这是从阅读本文初稿的人那里引用的,我想直接解决。 相反,性能差异不仅限于简单的测试。 Imba已在scrimba.com上用于生产多年,但仍不是出于胆小。 对于大多数开发人员而言,Vue和React的庞大生态系统将很难(可能不明智)落在后面。 Imba文档仍然有很多不足之处,但是我们每天都在改进它。

    有关系吗? (Does it matter?)

    I’m sure you’ve heard that React is fast enough. But fast enough for what? It doesn’t really matter if React was 15% faster, but with an order of magnitude improvement we can start to explore simpler ways to build applications.

    我确定您已经听说过React足够快。 但是足够快吗? React是否快15%并不重要,但是随着数量级的提高,我们可以开始探索构建应用程序的更简单方法。

    It’s not about the perceived speed, but about what it lets you do. At scrimba.com we don’t worry about keeping the view in sync with the state. We don’t worry about tracking when state has changed. Our data models are not observable. We just render. Whenever. And it’s liberating.

    这与感知的速度无关,而与它可以做什么相关。 在scrimba.com,我们不必担心视图与状态保持同步。 我们不必担心状态更改的时间。 我们的数据模型是不可观察的。 我们只是渲染。 每当。 它正在解放。

    翻译自: https://www.freecodecamp.org/news/the-virtual-dom-is-slow-meet-the-memoized-dom-bb19f546cc52/

    虚拟dom添加虚拟dom

    展开全文
  • vdom-to-html, 将虚拟DOM节点转换为 HTML vdom-to-html 将虚拟dom节点转换为 HTML安装npm install --save vdom-to-html用法var VNode = require('virtual-dom
  • 一个主要的令人兴奋的新特性就是更新页面的”虚拟DOM”的加入。 虚拟 DOM 可以做什么? React 和 Ember 都使用了虚拟DOM来提升页面的刷新速度。为了理解其如何工作,让我们先讨论一下几个概念: 更新DOM的花费时间...
  • 虚拟DOM的作用和定义 什么是虚拟DOM 虚拟DOM就是普通的js对象。是一个用来描述真实dom结构的js对象,因为它不是真实的dom,所以才叫做虚拟dom虚拟dom的作用 我们都知道传统的dom数据发生变化的时候,我们都...
  • 虚拟DOM

    2021-08-19 23:04:03
    虚拟DOM相关知识
  • 虚拟DOM的实现 使用虚拟DOM的原因: 减少回流与重绘 将DOM结构转换成对象保存到内存中 <img /> => { tag: 'img'} 文本节点 => { tag: undefined, value: '文本节点' } <img title="1" class="c" />...
  • 虚拟DOM和实际的DOM有何不同?

    千次阅读 2019-10-27 09:00:00
    在本文我们会先聊聊 DOM 的一些缺陷,然后在此基础上介绍虚拟 DOM 是如何解决这些缺陷的,最后再站在双缓存和 MVC 的视角来聊聊虚拟 DOM。理解了这些会让你对目前的前端框架有一个更加底层的认识,这也有助于你更好...
  • 主要介绍了详解操作虚拟dom模拟react视图渲染,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 虚拟DOM中的key

    2021-08-03 17:31:50
    详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下: a.旧虚拟DOM中找到了与新虚拟DOM相同的key: (1).若虚拟...
  • 虚拟 DOM 的优缺点?

    千次阅读 2019-09-12 14:29:53
    保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在...
  • [react] react的虚拟DOM和vue的虚拟DOM有什么区别? React 是数据先生成 vdom,然后对比 vdom(实现上并非先后顺序,两棵树边遍历边生成新的树),最后通过新的 DOM 树渲染。 Vue 是数据先进性对比,先通过数据的...
  • 虚拟DOM的实现

    2017-05-20 17:13:42
    这是虚拟DOM简单实现的代码,理解时可以参考http://blog.csdn.net/dear_mr/article/details/72566716
  • React虚拟DOM及创建虚拟DOM的两种方式

    千次阅读 2018-10-30 14:55:37
    1.虚拟DOM是什么? 一个虚拟DOM(元素)是一个一般的js对象,准确的说是一个对象树(倒立的);虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应;如果只是更新虚拟DOM,页面是不会重绘的。 Virtual ...
  • 一一篇篇文文章章带带你你搞搞懂懂Vue虚虚拟拟Dom与与diff算算法法 这篇文章主要给大家介绍了关于Vue虚拟Dom与diff算法的相关资料文中通过示例代码介绍的非常详细对大家 的学习或者工作 有一定的参考学习价值需要的...
  • 真实DOM和虚拟DOM

    2020-07-30 20:30:18
    文章目录如何高效操作DOM什么是DOM浏览器真实解析DOM的流程为什么说操作DOM耗时如何高效操作DOM虚拟DOM什么是虚拟DOM为什么要有虚拟DOM虚拟DOM的作用Vue中的虚拟DOM是如何实现的diff算法创建节点删除节点更新节点 ...
  • vnode2canvas - 基于Vue插件将虚拟DOM渲染到canvas中
  • Virtual Dom(虚拟DOM

    2019-09-29 10:17:07
    virtual dom (虚拟DOM)简称 vdom 是Vue和React的核心部分。vdom是比较独立的部分,它是用JS来模拟dom结构,dom变化的对比放在js层来做(图灵完备语言),这样可以提高重绘性能。 图灵完备语言要求能实现各种复杂...
  • vue中的虚拟DOM原理

    千次阅读 2020-06-06 14:02:51
    虚拟DOM其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点, 实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。 相当于在js与DOM之间做了一个...
  • 适用于嵌入式项目的微型(512字节)虚拟DOM模板引擎 IE浏览器/边缘 火狐浏览器 Chrome 苹果浏览器 歌剧 iOS Safari 适用于Android的Chrome 优势14+ 45岁以上 49+ 10+ 37岁以上 10.2+ 55岁以上 .dom借鉴了...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 63,912
精华内容 25,564
关键字:

虚拟dom的好处