vue 组件缓存清除实践

写在前面
  1. 关于 vue 组件缓存的用法很简单,官网教程 讲解的很详细,关于 vue 组件缓存的带来的弊端网上也有很多探坑的文章,最明显的就是缓存下来的组件如果不做处理,激活的时候就会命中缓存,如果你这个时候希望有新的数据获取,可能你需要在 activated 钩子函数中做一些处理,当然网上有一些做法是通过路由的元信息来做一些处理,如果对组件缓存原理深入了解就知道那些方法可能不能彻底解决问题;
  2. 很繁琐,因为我也做过,所以我不希望在每个缓存组件中都做处理,我更希望的是,我想随意销毁某个缓存组件,我想进行的是向下缓存而不是向上缓存或者都缓存,举个例子,现在有一个列表页,详情页,详情页子页面,我希望,我离开子页面的时候,子页面销毁,离开详情页的时候,详情页销毁;
  3. 现在这些都成为可能了,不是很难理解,但是需要你知道 vue 组件缓存 实现的过程,如果不理解,可以参考 vue 技术揭秘之 keep-alive,因为实现过程是对缓存的逆操作,本文只会介绍组件销毁的实现,不会拓展缓存相关内容。
demo 场景描述
  1. 组件注册
    全局注册四个路由级别非嵌套的组件,包含 nametemplate 选项,部分组件包含 beforeRouteLeave 选项, 分别为 列表 1、2、3、4
    components.png
  2. 路由配置
    额外添加的就是路由元信息 meta,里面包含了两个关键字段 levelcompName 前者后面会说,后者是对应的组件名称,即取的是组件的 name 字段
    routes.png
  3. 全部配置信息,这里采用的是 vue 混入
    mixins.png
  4. 页面结构,顶部固定导航条,可以导航到对应的列表


    view.png
  5. 现在点击导航栏 1、2、3、4 之后查看 vue-devtools 可以看到,列表 1、2、3 都被缓存下来了
    unhandler-cache-result.png
需求描述

假设上述是一个层层嵌套逻辑,列表1 > 列表2 > 列表3 > 列表4 ,现在需要在返回的时候,依次销毁低层级的组件,所谓低层级指的是相对嵌套较深的,例如列表4相对于列表1、2、3都是低层级。我们先来简单实现这样的一种需求

初级缓存组件清除实现
  • demo 场景描述之路由配置里面,我在元信息里面添加了一个 level 字段,这个字段是用来描述当前组件的级别,level 越高代表是深层嵌套的组件,从 1 起步;
    component-level.png
  • 下面是具体去缓存的实现,封装的去缓存方法
// util.js
function inArray(ele, array) {
  let i = array.indexOf(ele)
  let o = {
    include: i !== -1,
    index: i
  }
  return o
}
/**
 * @param {Obejct} to 目标路由
 * @param {Obejct} from 当前路由
 * @param {Function} next next 管道函数
 * @param {VNode} vm 当前组件实例
 * @param {Boolean} manualDelete 是否要手动移除缓存组件,弥补当路由缺少 level 时,清空组件缓存的不足
 */
function destroyComponent (to, from, next, vm, manualDelete = false) {
  // 禁止向上缓存
  if (
      (
        from &&
        from.meta.level &&
        to.meta.level &&
        from.meta.level > to.meta.level
      ) ||
      manualDelete
    ) {
    const { data, parent, componentOptions, key } = vm.$vnode
    if (vm.$vnode && data.keepAlive) {
      if (parent && parent.componentInstance && parent.componentInstance.cache) {
        if (componentOptions) {
          const cacheCompKey = !key ?
                      componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
                      :
                      key
          const cache = parent.componentInstance.cache
          const keys = parent.componentInstance.keys
          const { include, index } = inArray(cacheCompKey, keys)
          // 清除缓存 component'key
          if (include && cache[cacheCompKey]) {
            keys.splice(index, 1)
            delete cache[cacheCompKey]
          }
        }
      }
    }
    // 销毁缓存组件
    vm.$destroy()
  }
  next()
}
// 你可以把它挂载到 vue 原型上
Vue.prototype.$dc = destroyComponent
  • 然后你在全局混入的 beforeRouteLeave 钩子函数里面执行该方法了, 最后一个参数允许你在组件内的 beforeRouteLeave 里面执行该方法来直接销毁当前组件
    remove-cache-method-1.png
  • 上述方法通过对比两个组件之间级别(level),符合条件就会从缓存列表(cache, keys)中删除缓存组件,并且会调用 $destroy 方法彻底销毁缓存。
  • 虽然该方法能够实现上面的简单逻辑,也能实现手动控制销毁,但是有一些问题存在:
    1. 手动销毁的时候,只能销毁当前组件,不能销毁指定的某个缓存组件或者某些缓存组件
    2. 只会判断目标组件和当前组件的级别关系,不能判断在两者之间缓存的组件是否要移除,例如,列表1、2、3 均缓存了,如果直接从列表3跳到列表1,那么列表2是没有处理的,还是处于缓存状态的;
    3. 边界情况,即如果目标组件和当前组件以及一样,当前组件也不会销毁,虽然你可以修正为 from.meta.level >= to.meta.level 但是有时候可能需要这样的信息是可配置的
清除缓存的进阶
  • 为了解决上面的问题,下面是一个新的方案:既支持路由级别组件缓存的清除,又支持能定向清除某个或者一组缓存组件,且允许你调整整个项目清除缓存的逻辑;
  • 创建一个包含缓存存储、配置以及清空方法的对象
// util.js
function inArray(ele, array) {
  let i = array.indexOf(ele)
  let o = {
    include: i !== -1,
    index: i
  }
  return o
}

function isArray (array) {
  return Array.isArray(array)
}

const hasOwnProperty = Object.prototype.hasOwnProperty
function hasOwn (key, obj) {
  return hasOwnProperty.call(obj, key)
}
// 创建管理缓存的对象
class manageCachedComponents {

  constructor () {
    this.mc_keepAliveKeys = []
    this.mc_keepAliveCache = {}
    this.mc_cachedParentComponent = {}
    this.mc_cachedCompnentsInfo = {}
    this.mc_removeCacheRule = {
      // 默认为 true,即代表会移除低于目标组件路由级别的所有缓存组件,
      // 否则如果当前组件路由级别低于目标组件路由级别,只会移除当前缓存组件
      removeAllLowLevelCacheComp: true,
      // 边界情况,默认是 true, 如果当前组件和目标组件路由级别一样,是否清除当前缓存组件
      removeSameLevelCacheComp: true
    }
  }

  /**
   * 添加缓存组件到缓存列表
   * @param {Object} Vnode 当前组件实例
   */
  mc_addCacheComponentToCacheList (Vnode) {
    const { mc_cachedCompnentsInfo } = this
    const { $vnode, $route, includes } = Vnode
    const { componentOptions, parent } = $vnode
    const componentName = componentOptions.Ctor.options.name
    const compName = `cache-com::${componentName}`
    const { include } = inArray(componentName, includes)
    if (parent && include && !hasOwn(compName, mc_cachedCompnentsInfo)) {
      const { keys, cache } = parent.componentInstance
      const key = !$vnode.key
                  ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
                  : $vnode.key
      const routeLevel = $route.meta.level
      mc_cachedCompnentsInfo[compName] = {
        // 组件名称
        componentName,
        // 缓存组件的 key
        key,
        // 组件路由级别
        routeLevel
      }
      // 所有缓存组件 key 的列表
      this.mc_keepAliveKeys = keys
      // 所有缓存组件 key-value 集合
      this.mc_keepAliveCache = cache
      // 所有缓存组件的父实例
      this.mc_cachedParentComponent = parent
    }
  }

  // 移除缓存 key
  mc_removeCacheKey (key, keys) {
    const { include, index } = inArray(key, keys)
    if (include) {
      return keys.splice(index, 1)
    }
  }

  /**
   * 从 keep-alive 实例的 cache 移除缓存组件并移除缓存 key
   * @param {String} key 缓存组件的 key
   * @param {String} componentName 要清除的缓存组件名称
   */
  mc_removeCachedComponent (key, componentName) {
    const { mc_keepAliveKeys, mc_cachedParentComponent, mc_cachedCompnentsInfo } = this
    const { componentInstance } = mc_cachedParentComponent
    // 缓存组件 keep-alive 的 cache 和  keys
    const cacheList = componentInstance.cache
    const keysList = componentInstance.keys
    const { include } = inArray(key, keysList)
    if (include && cacheList[key]) {
      this.mc_removeCacheKey(key, keysList)
      this.mc_removeCacheKey(key, mc_keepAliveKeys)
      cacheList[key].componentInstance.$destroy()
      delete cacheList[key]
      delete mc_cachedCompnentsInfo[componentName]
    }
  }

  /**
   * 根据组件名称移除指定的组件
   * @param {String|Array} componentName 要移除的组件名称或者名称列表
   */
  mc_removeCachedByComponentName (componentName) {
    if (!isArray(componentName) && typeof componentName !== 'string') {
      throw new TypeError(`移除的组件可以是 array 或者 string,当前类型为: ${typeof componentName}`)
    }
    const { mc_cachedCompnentsInfo } = this
    if (isArray(componentName)) {
      const unKnowComponents = []
      for (const name of componentName) {
        const compName = `cache-com::${name}`
        if (hasOwn(compName, mc_cachedCompnentsInfo)) {
          const { key } = mc_cachedCompnentsInfo[compName]
          this.mc_removeCachedComponent(key, compName)
        } else {
          unKnowComponents.push(name)
        }
      }
      // 提示存在非缓存组件
      if (unKnowComponents.length) {
        let tips = unKnowComponents.join(` && `)
        console.warn(`${tips} 组件非缓存组件,请在移除缓存列表中删除以上组件名`)
      }
      return
    }

    const compName = `cache-com::${componentName}`
    if (hasOwn(compName, mc_cachedCompnentsInfo)) {
      const { key } = mc_cachedCompnentsInfo[compName]
      this.mc_removeCachedComponent(key, compName)
    } else {
      console.warn(`${componentName} 组件非缓存组件,请添加正确的缓存组件名`)
    }
  }

  /**
   * 移除路由级别的缓存组件
   * @param {Object} toRoute 跳转路由记录
   * @param {Object} Vnode 当前组件实例
   */
  mc_removeCachedByComponentLevel (toRoute, Vnode) {
    const { level, compName } = toRoute.meta
    const { mc_cachedCompnentsInfo, mc_removeCacheRule } = this
    const componentName = Vnode.$vnode.componentOptions.Ctor.options.name
    // exp-1-目标组件非缓存组件,不做处理,但可以根据业务逻辑结合 removeCachedByComponentName 函数来处理
    // exp-2-目标组件是缓存组件,但是未添加 level,会默认你一直缓存,不做处理
    // exp-3-当前组件非缓存组件,目标组件为缓存组件,不做处理,参考 exp-1 的做法
    // 以下逻辑只确保是两个缓存组件之间的跳转
    if (
        level &&
        compName &&
        mc_cachedCompnentsInfo['cache-com::' + compName] &&
        mc_cachedCompnentsInfo['cache-com::' + componentName]
      ) {
      const { removeAllLowLevelCacheComp, removeSameLevelCacheComp } = mc_removeCacheRule
      if (removeAllLowLevelCacheComp) {
        const cachedCompList = []
        // 查找所有不小于当前组件路由级别的缓存组件,即代表要销毁的组件
        for (const cacheItem in mc_cachedCompnentsInfo) {
          const { componentName, routeLevel } = mc_cachedCompnentsInfo[cacheItem]
          if (
              // 排除目标缓存组件,不希望目标组件也被删除
              // 虽然会在 activated 钩子函数里面重新添加到缓存列表
              componentName !== compName &&
              Number(routeLevel) >= level &&
              // 边界处理
              removeSameLevelCacheComp
            ) {
              cachedCompList.push(mc_cachedCompnentsInfo[cacheItem])
          }
        }

        if (cachedCompList.length) {
          cachedCompList.forEach(cacheItem => {
            const { key, componentName } = cacheItem
            const compName = 'cache-com::' + componentName
            this.mc_removeCachedComponent(key, compName)
          })
        }
        return
      }
      // 只移除当前缓存组件
      const { routeLevel } = mc_cachedCompnentsInfo['cache-com::' + componentName]
      if (Number(routeLevel) >= level && removeSameLevelCacheComp) {
        this.mc_removeCachedByComponentName(componentName)
      }
    }
  }
}
// 你可以把它挂载到 vue 原型上
Vue.prototype.$mc = new manageCachedComponents()
  • 使用起来非常简单,只需要你在全局的 activated 函数里面执行添加缓存方法,在全局 beforeRouteLeave 里面执行移除方法方法即可
    remove-cache-method-2.png

    你还可以在组件内的 beforeRouteLeave 钩子函数里面执行移除某些组件的逻辑
    remove-custom-cache.png
  • 使用上述方法需要注意的事项是
    1. 给缓存组件添加组件名称;
    2. 需要在路由记录里面配置好 compName 选项,并且组织好你的 level,因为在实际业务比 demo 复杂很多;
    3. 缓存组件会激活 activated 钩子,你需要在该函数里面执行添加缓存的方法,不然整个清缓存是不起作用的;
    4. 默认的清除规则是移除所有低层级的缓存组件(即缓存组件列表1、2、3,从列表3跳到列表1,列表2、3均会清除);
    5. 边界情况的也会清除(即如果列表2、3 的 level 相同,从列表3跳到列表2,会清除列表3的缓存);
  • 你可能注意到了一个问题,在整个项目中配置不支持动态修改的,即在整个项目中缓存移除的规则是不同时支持两种模式的,不想麻烦做是因为 vue 混入的缘故,全局的 beforeRouteLeave 会在组件内 beforeRouteLeave 之前执行,所以你懂得...不过你无需担心有死角的清除问题,因为你可以通过 mc_removeCachedByComponentName 该方法来清除任意你想要销毁的组件。
2019/05/04 - 新增对 TS 支持
  • 如果你是 vue + ts 的开发方式,可以采用下面的方式,由于当前 vue (或者 <2.6.10)的版本对 ts 支持不是很好,所以大部分是采用 vue-shims.d.ts 的方式来进行模块拓展,更多的使用细节可参考 vue 官网对 Typescript 的支持 以及 Typescript 模块拓展
  • 下面是文件相对位置关系


    file.png
  • vue-shims.d.ts 文件内容
/*
 * @description: 模块拓展类型定义文件
 */
import Vue, { VNode } from 'vue'
import { Route } from 'vue-router'
import ManageCachedComponents from './clear-cache'

export type ElementType = string | number

export interface KeepAliveCachedComponent {
  [key: string]: VNode
}

interface CtorOptions {
  name: string
  [key: string]: any
}

declare module 'vue/types/vue' {
  interface Vue {
    $route: Route
    $mc: ManageCachedComponents
    includes: string[]
    keys?: ElementType[]
    cache?: KeepAliveCachedComponent
  }
  interface VueConstructor {
    cid: number
    options: CtorOptions
  }
}
  • cache-clear.ts 文件
/*
 * @description: TS 版本的缓存移除
 */

import Vue, { VNode } from 'vue'
import { Route } from 'vue-router'
import { ElementType } from './vue-shim'

interface CachedComponentList {
  componentName: string,
  key: string,
  routeLevel: number
}

interface RemoveCachedRules {
  removeAllLowLevelCacheComp: boolean
  removeSameLevelCacheComp: boolean
}

const hasOwnProperty = Object.prototype.hasOwnProperty

const inArray = (ele: ElementType, array: ElementType[]) => {
  const i = array.indexOf(ele)
  const o = {
    include: i !== -1,
    index: i
  }
  return o
}

const isArray = (array: any) => {
  return Array.isArray(array)
}

const hasOwn = (key: ElementType, obj: object) => {
  return hasOwnProperty.call(obj, key)
}

export default class ManageCachedComponents {
  private mc_keepAliveKeys: ElementType[] = []
  private mc_cachedParentComponent: VNode = <VNode>{}
  private mc_cachedComponentsInfo: CachedComponentList = <CachedComponentList>{}
  public mc_removeCacheRule: RemoveCachedRules = {
    removeAllLowLevelCacheComp: true,
    removeSameLevelCacheComp: true
  }

  /**
   * 从缓存列表中移除 key
   */
  private mc_removeCacheKey (key: ElementType, keys: ElementType[]) {
    const { include, index } = inArray(key, keys)
    include && keys.splice(index, 1)
  }

  /**
   * 从 keep-alive 实例的 cache 移除缓存组件并移除缓存 key
   * @param key 缓存组件的 key
   * @param componentName 要清除的缓存组件名称
   */
  private mc_removeCachedComponent (key: string, componentName: string) {
    const { mc_keepAliveKeys, mc_cachedParentComponent, mc_cachedComponentsInfo } = this
    const { componentInstance } = mc_cachedParentComponent
    const cacheList = componentInstance.cache
    const keysList = componentInstance.keys
    const { include } = inArray(key, keysList)
    if (include && cacheList[key]) {
      this.mc_removeCacheKey(key, keysList)
      this.mc_removeCacheKey(key, mc_keepAliveKeys)
      cacheList[key].componentInstance.$destroy()
      delete cacheList[key]
      delete mc_cachedComponentsInfo[componentName]
    }
  }

  /**
   * 添加缓存组件到缓存列表
   * @param Vue 当前组件实例
   */
  mc_addCacheComponentToCacheList (Vue: Vue) {
    const { mc_cachedComponentsInfo } = this
    const { $vnode, $route, includes } = Vue
    const { componentOptions, parent } = $vnode
    const componentName = componentOptions.Ctor.options.name
    const compName = `cache-com::${componentName}`
    const { include } = inArray(componentName, includes)
    if (parent && include && !hasOwn(compName, mc_cachedComponentsInfo)) {
      const { keys, cache } = parent.componentInstance
      const key = !$vnode.key
                  ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
                  : $vnode.key
      const routeLevel = $route.meta.level
      mc_cachedComponentsInfo[compName] = {
        componentName,
        key,
        routeLevel
      }
      this.mc_keepAliveKeys = keys
      this.mc_cachedParentComponent = parent
    }
  }

  /**
   * 根据组件名称移除指定的组件
   * @param componentName 要移除的组件名称或者名称列表
   */
  mc_removeCachedByComponentName (componentName: string | string[]) {
    if (!isArray(componentName) && typeof componentName !== 'string') {
      throw new TypeError(`移除的组件可以是 array 或者 string,当前类型为: ${typeof componentName}`)
    }
    const { mc_cachedComponentsInfo } = this
    if (isArray(componentName)) {
      const unKnowComponents = []
      for (const name of componentName) {
        const compName = `cache-com::${name}`
        if (hasOwn(compName, mc_cachedComponentsInfo)) {
          const { key } = mc_cachedComponentsInfo[compName]
          this.mc_removeCachedComponent(key, compName)
        } else {
          unKnowComponents.push(name)
        }
      }
      // 提示存在非缓存组件
      if (unKnowComponents.length) {
        let tips = unKnowComponents.join(` && `)
        console.warn(`${tips} 组件非缓存组件,请在移除缓存列表中删除以上组件名`)
      }
      return
    }

    const compName = `cache-com::${componentName}`
    if (hasOwn(compName, mc_cachedComponentsInfo)) {
      const { key } = mc_cachedComponentsInfo[compName]
      this.mc_removeCachedComponent(key, compName)
    } else {
      console.warn(`${componentName} 组件非缓存组件,请添加正确的缓存组件名`)
    }
  }

  /**
   * 移除路由级别的缓存组件
   * @param toRoute 跳转路由记录
   * @param Vue 当前组件实例
   */
  mc_removeCachedByComponentLevel (toRoute: Route, Vue: Vue) {
    const { level, compName } = toRoute.meta
    const { mc_cachedComponentsInfo, mc_removeCacheRule } = this
    const componentName = Vue.$vnode.componentOptions.Ctor.options.name
    if (
        level &&
        compName &&
        mc_cachedComponentsInfo['cache-com::' + compName] &&
        mc_cachedComponentsInfo['cache-com::' + componentName]
      ) {
      const { removeAllLowLevelCacheComp, removeSameLevelCacheComp } = mc_removeCacheRule
      if (removeAllLowLevelCacheComp) {
        const cachedCompList = []
        for (const cacheItem in mc_cachedComponentsInfo) {
          const { componentName, routeLevel } = mc_cachedComponentsInfo[cacheItem]
          if (
              componentName !== compName &&
              Number(routeLevel) >= level &&
              removeSameLevelCacheComp
            ) {
              cachedCompList.push(mc_cachedComponentsInfo[cacheItem])
          }
        }

        if (cachedCompList.length) {
          cachedCompList.forEach(cacheItem => {
            const { key, componentName } = cacheItem
            const compName = 'cache-com::' + componentName
            this.mc_removeCachedComponent(key, compName)
          })
        }
        return
      }
      const { routeLevel } = mc_cachedComponentsInfo['cache-com::' + componentName]
      if (Number(routeLevel) >= level && removeSameLevelCacheComp) {
        this.mc_removeCachedByComponentName(componentName)
      }
    }
  }
}
  • 如果 vue3.0 出来以后,就不需要 vue-shims.d.ts 文件了,到时候使用 ts 会更加方便,当然更希望尤大能够增加缓存操作的 api,这样就不再为了缓存而造各种轮子了。
写在最后
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • # 传智播客vue 学习## 1. 什么是 Vue.js* Vue 开发手机 APP 需要借助于 Weex* Vu...
    再见天才阅读 3,480评论 0 6
  • vue笔记 一.vue实例 vue的生命周期 beforeCreate(创建前), created(创建后), b...
    秋殇1002阅读 1,033评论 0 1
  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 4,989评论 0 29
  • VUE Vue :数据驱动的M V Vm框架 m :model(后台提供数据),v :view(页面),vM(模板...
    wudongyu阅读 5,298评论 0 11
  • 一:什么是闭包?闭包的用处? (1)闭包就是能够读取其他函数内部变量的函数。在本质上,闭包就 是将函数内部和函数外...
    xuguibin阅读 9,193评论 1 52