• 理解虚拟DOM机制和key属性的作用


    什么是Vue的虚拟DMO?

    虚拟DOM是区别于真实的DOM提出的。

    在js事件直接操作DOM的时代(包括Jquery的时代),我们通过JS直接对真实的DOM树进行增删改查。
    image
    但是JS事件直接操作DOM会随着项目规模的扩大、事件的增加导致事件的管理以及事件和DOM之间的关系的维护变得日益复杂。

    image
    为了解决这个问题,以Vue为代表的新型前端框架(包括react等)提出了引入数据中间层来避免直接操作DOM的思路:让前端框架底层代替用户去操作DOM,用户不再关注DOM元素,而聚焦于数据(state)。使用Vue等框架,我们只需要修改数据,数据变化后Vue帮助我们来更新DOM。
    image
    但是DOM的变化是非常消耗计算机资源的,如何尽量的减少DOM的更新,是Vue需要考虑的。于是虚拟DOM的概念随之被提出。所谓的虚拟DOM并不是一个真正的DOM树,它是Vue底层在检测到数据修改后,不立刻直接去修改真实的DOM,而是结合数据和模板,生成一个临时的由js对象模拟的DOM树。
    image

    Vue的实际DOM树与虚拟DOM树比较算法

    获得虚拟DOM之后,Vue底层会通过算法计算真实DOM树与虚拟DOM树的区别,并得到需要更新的节点,尽可能的复用现成的DOM节点,而不是去更新全部的DOM树,从而达到减少计算资源消耗的目的。

    在实际项目中,DOM树可能极其复杂。为了提升真实DOM树和虚拟DOM树之间的比较效率,Vue提出了同层级比较的算法。即每次只比较处于同一个层级的DOM元素的变化情况。不同层级的不予比较。如下图所示就是只比较相同颜色区域是否发生变化。
    image
    那么Vue的同层比较算法具体是什么判断和处理逻辑呢?

    它的基本逻辑是:当同一层的DOM节点中,如果能够判断节点的唯一性,那么尽可能的采用移动,插入等逻辑保持复用。如果不能保证唯一性,那么则采用更新,删除等操作实现目的。

    Vue算法实现举例:
    1. 如下图这种情况(同一层级,不同的元素——元素类型唯一)
      image
      Vue能够根据元素类型判断BCD为三个不同的唯一的元素,故而采取了性能高消耗少的移动逻辑。即将BCD的顺序直接调整为CDB。全部复用了所用的DOM。

    2. 如下图这种情况(不同层级,不同元素)
      image
      Vue虽然能够根据元素类型判断BCD为三个不同的唯一元素,但是Vue的算法是同层比较,当Vue扫描时发现C节点不存在了,于是直接对C节点进行了删除,包括c节点下原有的EF。当递归到下一个层级时再创建新的c节点和ef节点。即BCD变成BD,Vue并不是把C移动到B下面,而是删除原有的C,重新在B下面新建了C及其下级的EF。没有复用C,E,F

    3. 如下图这种情况(DOM的同层级,元素类型内容变化)
      image
      Vue检测到同一层不再存在C而是存在G,于是算法删除了C包括其下属的EF,新建了G;当递归到下一层级时,再为G创建了EF。没有复用EF。

    4. 如下图这种情况(DOM同层级,相同的元素——无法判断元素的唯一性)
      image
      这种情况下Vue无法通过元素类型判断元素的唯一,也没有key属性帮助其判断元素唯一,故而Vue认为元素不是唯一的。此时它会首先将B1更新成B2,再将B2更新成B1,并删除原B2下的EF。当递归到下一层级时,为新的B2新建EF。此种情况Vue无法复用EF。

    5. 如下图这种情况(DOM同类型节点的同层级顺序变化,有Key属性的情况下)。
      image
      当有key存在时,Vue底层能够判断节点的唯一性,故而Vue是采取的将B2节点包括下属的EF节点移动到B1之前的逻辑。完全复用了B2,E,F

    6. 如下图这种情况。(同层级,新增元素,无key)
      image
      因为vue无法确定元素的唯一性,故而vue认为用户是想要更新节点。因此它会先将B2更新成B4,将B3更新成B2,最后新增一个B3.无法复用B2,B3

    7. 如下图这种情况。(同层级,新增元素,有key)
      image
      Vue通过key确定唯一性,会将b4直接插入到b1和b2之间,达到复用B2,B3的目的。

    App.vue的唯一性改造

    通过上述7种情况的描述,我们发现如果没有为Vue指定key属性,那么Vue在操作DOM时的效率,不会达到最优。

    回到我们的todoitem组件,我们之前在组件中绑定了Key属性,但是我们的key属性绑定的是title,现在看来很不严谨,因为title极有可能出现重复,而一旦出现重复,vue在操作DOM的时候就有可能会采用消耗较大的处理逻辑。

    <template>
      <div id="app">
        <input type="text" v-model="message"/>
        <input type="text" :value="message" @input="handleChange"/>
        {{message}}
        <todolist>
          <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del">
            <template v-slot:pretext="{val}">
              <label>前置文字{{val}}</label>
            </template>
          </todoitem>
        </todolist>
      </div>
    </template>
    

    我们可以考虑将此处的key绑定为v-for的循环下标

    <template>
      <div id="app">
        <input type="text" v-model="message"/>
        <input type="text" :value="message" @input="handleChange"/>
        {{message}}
        <todolist>
          <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="(item,index) in list" :key="index" data-wen="wen" :title="item.title" :del="item.del">
            <template v-slot:pretext="{val}">
              <label>前置文字{{val}}</label>
            </template>
          </todoitem>
        </todolist>
      </div>
    </template>
    

    当然如果进一步考虑,假设list是一个一直在不断的被增删改查,排除的列表使用list下标也是不严谨的,最佳办法则是为组件的数据增加id返回值。

      data(){
        return{
          message:"hello world",
          Checkedmsg:false,
          list: [
                  {
                    title: "新课程1",
                    del: false,
                    id:1
                  },
                  {
                    title: "新课程2",
                    del: true,
                    id:2
                  },
                  {
                    title: "新课程3",
                    del: false,
                    id:3
                  }
                ]
        };
      },
    

    模板中使用id绑定key

    <template>
      <div id="app">
        <input type="text" v-model="message"/>
        <input type="text" :value="message" @input="handleChange"/>
        {{message}}
        <todolist>
          <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="item in list" :key="item.id" data-wen="wen" :title="item.title" :del="item.del">
            <template v-slot:pretext="{val}">
              <label>前置文字{{val}}</label>
            </template>
          </todoitem>
        </todolist>
      </div>
    </template>
    
  • 相关阅读:
    codevs 1766 装果子
    codevs 1415 比那名居天子
    codevs 1388 砍树
    codevs 1373 射命丸文
    codevs 2867 天平系统3
    codevs 2866 天平系统2
    codevs 2865 天平系统1
    codevs 2832 6个朋友
    广搜优化题目总结
    Codeforces Round #578 (Div. 2)
  • 原文地址:https://www.cnblogs.com/wenpeng/p/12291433.html
Copyright © 2020-2023  润新知