最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • keep-alive实现原理分析

    正文概述 掘金(After_school)   2021-01-12   996

    keep-alive

    keep-alive 是什么

    1.keep-alive是vue框架自身的组件

    2.keep-alive组件自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

    keep-alive 的用法

    1.在动态组件中应用

    <keep-alive :include="whiteList" :exclude="blackList" :max="amount">
      <component :is="currentComponent"></component>
    </keep-alive>
    

    2.在 vue-Router 中应用

    <keep-alive :include="whiteList" :exclude="blackList" :max="amount">
      <router-view></router-view>
    </keep-alive>
    

    keep-alive 组件提供的 include 属性和 exculde 属性可以定义缓存名单,确定被 keep-alive 包围的组件哪些被缓存,哪些不被缓存,其中 include 定义缓存白名单,exclude 定义缓存黑名单。

    vue基础知识铺垫

    在学习vue keep-alive的缓存原理之前,我们需要了解vue组件的渲染原理,这样才能知道keep-alive的缓存时机和原理,我们首先要搞清楚两个问题。

    1.什么是虚拟节点Virtual Vnode?它的作用?

    2.vue组件的渲染过程

    接下来,让我们先来了解一下Virtual Vnode。

    1.什么是虚拟节点Virtual Vnode?它的作用?

    Virtual DOM的诞生是建立在浏览器DOM操作是很“昂贵”的基础之上的,下面我们来直观看一下DOM元素(下图),这仅仅是一个最简单的DOM元素。当我们通过JavaScript操作页面中DOM节点时,浏览器会从构建DOM树开始从头到尾执行一遍渲染流程。 假如我们要更新10个DOM节点, 浏览器接收到第一个更新请求时会马上执行, 连续执行10次,而事实上最后一次执行的结果才是我们需要的。前九次运算都是在浪费性能,最终导致页面卡顿,内存占用过高,用户体验差。 keep-alive实现原理分析 我们都知道,在做前端项目时,需要根据用户的选择,不断的更新页面视图,以响应用户的操作,但是DOM操作是十分浪费性能的,虚拟DOM就是为了解决浏览器性能问题而被设计出来的。 假如一次操作中有10个更新DOM的动作,虚拟DOM不会立即执行,而是将这10次更新的diff内容保存到本地的JS对象中,最终将这个JS对象一次性更新到DOM树上避免大量无效计算。这样页面的更新可以全部反应在JS对象(虚拟DOM上),操作内存中的JS对象速度当然要更快,等更新完后再将JS对象映射成真实的DOM,交给浏览器去绘制。

    总结一下,Virtual DOM就是用一个原生的JS对象去描述DOM节点,修改它比直接修改一个DOM的代价要小很多。在Vue中,Virtual DOM是用VNode(一个Class)去描述的,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是用来扩展 VNode的灵活性以及实现一些特殊feature的。由于VNode只是用来映射到真实DOM的渲染,不需要包含操作DOM的方法,因此它是非常轻量和简单的。

    Virtual DOM除了它的数据结构的定义,映射到真实的DOM实际上要经历VNode的create、diff、patch等过程,有了Virtual DOM,就可以通过diff算法,对照哪些vue节点更新了,然后统一去更新有变化的节点,减少定位时间和重绘过程,提升性能!

    2.vue组件的渲染过程

    vue就是基于Virtual DOM实现页面渲染和更新的,基于Virtual DOM机制的页面渲染机制--首先将数据渲染为Virtual DOM,然后将Virtual DOM生成DOM,有数据更新时,先对照Virtual DOM的不同,然后统一对DOM元素做修改,这就是总体思想。 接下来,我们来看下大概的流程:

    1.整体的渲染过程

    keep-alive实现原理分析 整体的渲染过程,其实跟上面总结的总体思路是一一对应的,大概分为三步:

    1.$mount过程--根据template生成render()函数。

    2.render过程--生成虚拟的Vnode。

    3.patch过程--将虚拟节点转换为真实的DOM。

    4.整个1、2、3三个步骤,是根据template模版结构,深度优先遍历循环的,父节点先于子节点渲染,子节点先于父节点插入DOM。

    下面我们来详细了解下三个过程

    2.$mount过程

    keep-alive实现原理分析 整个$mount过程就是把template模版转换为能生成Vnode节点的renger()函数,分为三步:

    1.parse--解析模版字符串生成AST

    parse的目标是把template模板字符串转换成AST树,它是一种用JavaScript对象的形式来描述整个模板。那么整个parse的过程是利用正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的回调函数,来达到构造AST树的目的。个人理解就是把template(模板)解析成一个对象,该对象是包含这个模板所以信息的一种数据,而这种数据浏览器是不支持的,为Vue后面的处理template提供基础数据。本实例中会生成如下AST树。

    2.optimize--优化AST语法树 为什么此处会有优化过程?我们知道Vue是数据驱动,是响应式的,但是template模版中并不是所有的数据都是响应式的,也有许多数据是初始化渲染之后就不会有变化的,那么这部分数据对应的DOM也不会发生变化。后面有一个update更新界面的过程,在这当中会有一个patch的过程,diff算法会直接跳过静态节点,从而减少了比较的过程,优化了patch的性能。

    3.codegen--将优化后的AST树转换成可执行的代码,生成render函数。

    3.生成真实DOM的过程(patch)

    下面我们来了解一下patch的过程,keep-alive组件的缓存实现就是在patch的过程当中生效的。 patch过程的主要逻辑就是要把vnode节点转化为真实的DOM元素并插入到html中,其主要逻辑是在createComponent()函数中开始的:

    function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
          var i = vnode.data;
          if (isDef(i)) { // 判断init()钩子函数是否存在
            var isReactivated = isDef(vnode.componentInstance) && i.keepAlive; // isReactivated用来判断组件是否被keep-alive缓存
            if (isDef(i = i.hook) && isDef(i = i.init)) {
              i(vnode, false /* hydrating */); // 执行组件初始化的内部钩子init(),开启深度优先遍历
            }
            if (isDef(vnode.componentInstance)) { // 所有组件的深度优先遍历完成以后,开始执行将DOM插入到父组件的过程,从最底层的DOM开始,向父级元素传递
              initComponent(vnode, insertedVnodeQueue);
              insert(parentElm, vnode.elm, refElm); // 将真实节点添加到父节点中
              if (isTrue(isReactivated)) { 
                reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm); // 如果是keep-alive缓存的组件,则直接将DOM元素插入父级DOM,不需要在重新渲染
              }
              return true
          }
       }
    }
    

    可以看到createComponent会判断当前标签是不是组件,如果是就会执行组件的init()钩子函数,我们在来看一下init()钩子函数,先不考虑keepAlive,在init()函数中,会先判断当前组件是否存在子组件,如果存在,就会调用child.mount()函数,执行子组件的渲染和转换过程,所以说整个mount()函数,执行子组件的渲染和转换过程,所以说整个mount()函数,执行子组件的渲染和转换过程,所以说整个mount和patch过程是深度优先遍历的。

    // 组件内部钩子,createComponent的i就是componentVNodeHooks对象,可以看到i(vnode, false /* hydrating */);调用就是执行组件的实例话
    var componentVNodeHooks = {
       init: function init (vnode, hydrating) {
         if (
           vnode.componentInstance &&
           !vnode.componentInstance._isDestroyed &&
           vnode.data.keepAlive
         ) { // 判断是否已被keep-alive缓存
           var mountedNode = vnode; // work around flow
           componentVNodeHooks.prepatch(mountedNode, mountedNode); // 执行一些预操作
         } else { // 如果没keep-alive缓存,被就执行组件的实例化,深度优先遍历
           var child = vnode.componentInstance = createComponentInstanceForVnode(
             vnode,
             activeInstance
           );
           child.$mount(hydrating ? vnode.elm : undefined, hydrating);
         }
       }
    }
    

    大家如果想要了解更多关于vue渲染的原理,可以参考这个网站:caibaojian.com/vue-analysi…。 整个patch的过程可以分为三个步骤: 1.执行当前组件的createComponent函数,并调用当前组件的init()函数,判断当前组件是否存在子组件,如果存在,则执行第二步。 2.执行子组件的$mount--render()--patch()--createComponent()过程,并判断是否存在子组件,如果存在,则反复执行第二步,直到遍历完所有子组件,开始执行第三步。 3.执行createComponent函数中的initComponent(vnode, insertedVnodeQueue)和insert(parentElm, vnode.elm, refElm)函数,生成并将DOM插入真实html中。

    在了解了vue组件的渲染过程以后,我们再来看keep-alive的缓存原理就会简单一些。

    keep-alive源码+举例分析

    1.keep-alive源码

    keep-alive是vue框架内部自己实现的组件,通过上个章节对vue组件渲染过程的简单总结,我们知道组件的渲染过程(render)是先父后子的关系,所以被keep-alive组件(父组件)包裹的子组件的渲染过程是在keep-alive(父组件)组件render之后的进行的,下面我们结合keep-alive的源码,看一下keep-alive组件的render过程

    export default {
      name: 'keep-alive', // 组件名称
      abstract: true, // 抽象组件,不会真正的渲染成DOM
    
      props: {
        include: patternTypes, // 需要缓存的组件
        exclude: patternTypes, // 不需要缓存的组件
        max: [String, Number] // 缓存的最大个数
      },
    
      created () {
        this.cache = Object.create(null) // 存储被缓存的组件
        this.keys = []
      },
    
      destroyed () {
        for (const key in this.cache) {
          pruneCacheEntry(this.cache, key, this.keys) // 修剪缓存
        }
      },
    
      mounted () {
        this.$watch('include', val => {
          pruneCache(this, name => matches(val, name))
        })
        this.$watch('exclude', val => {
          pruneCache(this, name => !matches(val, name))
        })
      },
    
      render () {
        const slot = this.$slots.default 
        const vnode: VNode = getFirstComponentChild(slot) // 通过slot获取缓存组件
        const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
        if (componentOptions) {
          // check pattern
          const name: ?string = getComponentName(componentOptions)
          const { include, exclude } = this
          if (
            // not included
            (include && (!name || !matches(include, name))) ||
            // excluded
            (exclude && name && matches(exclude, name))
          ) {
            return vnode // 据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode)
          }
    
          const { cache, keys } = this
          const key: ?string = vnode.key == null
            ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
            : vnode.key
          if (cache[key]) { // 如果cache[key]有值则表示有缓存,非首次渲染
            vnode.componentInstance = cache[key].componentInstance // 如果命中缓存,则将缓存组件的实例赋值给keep-alive组件的componentInstance属性,后续有用
            // make current key freshest
            remove(keys, key)
            keys.push(key)
          } else { // 如果cache[key]没有值则表示首次渲染,并记录缓存的组件
            cache[key] = vnode
            keys.push(key)
            // prune oldest entry
            if (this.max && keys.length > parseInt(this.max)) {
              pruneCacheEntry(cache, keys[0], keys, this._vnode)
            }
          }
    
          vnode.data.keepAlive = true
        }
        return vnode || (slot && slot[0]) // 最终返回keep-alive组件
      }
    }
    

    keep-alive组件有自己的渲染函数,下面我们来看一下keep-alive的渲染函数render都做了哪些操作:

    第一步:获取keep-alive包裹着的第一个子组件对象及其组件名;

    第二步:根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步;

    第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步;

    第四步:在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max的设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。

    第五步:最后并且很重要,将该组件实例的keepAlive属性值设置为true。

    第六步:完成render过程以后,就该执行组件的patch过程,将组件变成实际的dom,下面我们结合例子来分析一下,keep-alive组件的patch过程。

    1.例子

    let A = {
        template: '<div class="a">' +
            '<p>A组件</p>' +
                '</div>',
            name: 'A'
        }
    
    let B = {
        template: '<div class="b">' +
            '<p>B组件</p>' +
                '</div>',
            name: 'B'
        }
    
    new Vue({
        el: '#app',
        template: '<div class="content">' +
            '<keep-alive>' +
            '<component :is="currentComp">' +
            '</component>' +
            '</keep-alive>' +
            '<button @click="change">switch</button>' +
            '</div>',
        data: {
            currentComp: 'A'
        },
        methods: {
            change() {
                this.currentComp = this.currentComp === 'A' ? 'B' : 'A'
            }
        },
        components: {
            A,
            B
        }
    })
    
    

    以A两个组件为例,A组件被keep-alive缓存,结合源码,我们对比看一下A组件在首次显示(首次渲染)和切换再次显示(缓存渲染),keep-alive是怎么实现组件缓存的。

    首次渲染

    第一步,keep-alive组件执行render()函数,可以看到首次执行render()函数的时候,没有缓存任何子组件,keep-alive缓存的子组件的vnode.componentInstance为null且vnode.data.keepAlive为true。

    keep-alive实现原理分析

    第二步,keep-alive组件的patch()过程,实际上执行的是init()函数

    keep-alive实现原理分析

    第三步,我们来看下init()函数,在init()函数里面会对vnode.componentInstance和vnode.data.keepAlive的值做判断,由第一步的值可知,首次渲染的时候会执行createComponentInstanceForVnode()创建新的子组件,接下来会去执行子组件的$mount过程,去创建子组件的虚拟Vnode。

    keep-alive实现原理分析

    第四步,可以看到$mount过程生成了,接下来回去循环挂载子组件。

    keep-alive实现原理分析

    非首次渲染

    当我们点击切换按钮,从A组件重新切回B组件的时候,就会触发keep-alive缓存机制,我们来看一下整个流程。

    第一步,触发keep-alive的render()函数,此时会命中缓存,所以keep-alive缓存的子组件的vnode.componentInstance为组件A且vnode.data.keepAlive为true。

    keep-alive实现原理分析

    第二步,执行keep-alive组件的patch()过程,实际上执行的是init()函数,这时vnode.componentInstance和vnode.data.keepAlive都为true,所以不会执行createComponentInstanceForVnode()创建新的子组件,而是直接去缓存中取子组件。

    keep-alive实现原理分析

    第三步,当我们的代码重新执行到createComponent时,此时isReactivated为true,会执行reactivateComponent()方法

    keep-alive实现原理分析

    第四步,执行reactivateComponent()函数,最后通过执行insert(parentElm, vnode.elm, refElm)就把缓存的DOM对象直接插入到目标元素中,这样就完成了在数据更新的情况下的渲染过程。

    keep-alive实现原理分析

    这就是整个keep-alive实现缓存的原理。

    参考文档:

    1.https://juejin.cn/…

    2.https://juejin.cn/…

    3.http://caibaojian…


    起源地下载网 » keep-alive实现原理分析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元