神族九帝's blog 神族九帝's blog
首页
  • 神卡套餐 (opens new window)
  • 神族九帝 (opens new window)
  • 网盘资源 (opens new window)
  • 今日热点 (opens new window)
  • 在线PS (opens new window)
  • IT工具 (opens new window)
  • FC游戏 (opens new window)
  • 在线壁纸 (opens new window)
  • 面试突击
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 工作笔记
  • 前端基础建设与架构 30 讲
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • 2周刷完100道前端优质面试真题
  • 思维导图
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • React
  • 更多
  • 未来要做的事
  • Stirling-PDF
  • ComfyUI
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 收藏夹
  • 更多
GitHub (opens new window)

神族九帝,永不言弃

首页
  • 神卡套餐 (opens new window)
  • 神族九帝 (opens new window)
  • 网盘资源 (opens new window)
  • 今日热点 (opens new window)
  • 在线PS (opens new window)
  • IT工具 (opens new window)
  • FC游戏 (opens new window)
  • 在线壁纸 (opens new window)
  • 面试突击
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 工作笔记
  • 前端基础建设与架构 30 讲
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • 2周刷完100道前端优质面试真题
  • 思维导图
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • React
  • 更多
  • 未来要做的事
  • Stirling-PDF
  • ComfyUI
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 收藏夹
  • 更多
GitHub (opens new window)
  • 工作笔记

  • 前端基础建设与架构 30 讲

  • vue2源码学习

    • vue源码系列
    • vue源码面试题
      • 谈一下你对 MVVM 的理解
      • 说一下响应式数据的理解
      • vue 中是如何检测数组变化的
      • vue 为何采用异步渲染
      • nextTick 实现原理
      • vue 中 computed 的特点
      • watch 中的 deep:true 是如何实现的
      • vue 生命周期
      • ajax 请求放在哪个生命周期中
      • 何时需要使用 beforeDestroy
      • vue 模板编译原理
      • 用 vnode 来描述一个 dom 结构
      • diff 算法的时间复杂度
      • 简述 diff 算法原理
      • v-for 中为什么要用 key?
      • 描述组件渲染和更新过程
      • 组件中的 data 为什么是个函数?
      • Vue 中事件绑定的原理
      • v-model 的实现原理及如何自定义 v-model?
      • vue 中的 v-html 会导致哪些问题
      • 父子组件生命周期顺序
      • vue 中相同逻辑如何抽离
      • 为什么要使用异步组件?
      • 插槽和作用域插槽
        • 插槽
        • 作用域插槽
      • 谈谈你对 keep-alive 的理解(一个组件)
      • vue 中常见的性能优化
        • 编码优化
        • Vue 加载性能优化
        • 用户体验
        • SEO 优化
        • 打包优化
        • 缓存压缩
        • Vue3.0 的改进
      • 实现 hash 路由和 history 路由
      • 路由守卫解析流程
      • action 和 mutation 区别
      • 像 vue-router,vuex 他们都是作为 vue 插件,请说一下他们分别都是如何在 vue 中生效的?
      • 请你说一下 vue 的设计架构
  • 剖析vuejs内部运行机制

  • TypeScript 入门实战笔记

  • vue3源码学习

  • 2周刷完100道前端优质面试真题

  • 思维导图

  • npm发包

  • 重学node

  • 前端性能优化方法与实战

  • webpack原理与实战

  • webGl

  • 前端优化

  • Web3

  • React

  • 更多

  • 笔记
  • vue2源码学习
wu529778790
2021-11-11

vue源码面试题

# 谈一下你对 MVVM 的理解

20211111222832

传统的 MVC 指的是,用户操作会请求服务端路由,路由拦截分发请求,调用对应的控制器来处理。控制器会获取数据,然后数据与模板结合,将结果返回给前端,页面重新渲染。 数据流是单向的,view——>model——>view MVVM:传统的前端会将数据手动渲染到页面上,MVVM 模式不需要用户手动操作 DOM 元素,将数据绑定到 viewModel 层上,会自动将数据渲染到页面中。视图变化会通知 viewModel 层更新数据,viewModel 就是 MVVM 模式的桥梁。数据驱动 数据流动时双向的,model——>viewModel<——>view

# 说一下响应式数据的理解

vue2 核心 Object.defineProperty

默认 Vue 在初始化数据时,会给 data 中的属性使用 Object.defineProperty,在 getter 和 setter 的进行拦截,重新定义所有属性。当页面取到对应属性时,会进行依赖收集(收集当前组件的 watcher)。如果属性发生变化会通知相关依赖进行更新操作。

依赖收集、派发更新的作用:如果没有这项操作,每个数据更新就会去渲染页面,极大的消耗性能。加了这项操作,去监听相关数据的改变,添加到队列里,当所有改变完事儿之后,一起进行渲染。

vue3 核心 proxy

解决了 vue2 中的处理对象递归、处理数组麻烦的问题

原理:

20211111223247

响应式数据原理

20211111223321

export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep();

  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  let childOb = !shallow && observe(val); //递归处理子
  //每一个对象属性添加get、set方法,变为响应式对象
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend(); //依赖收集
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return;
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify(); //派发更新
    },
  });
}

# vue 中是如何检测数组变化的

使用了函数劫持的方式,重写了数组方法,利用切片式方法进行了原型链重写

vue 酱 data 中的数组,进行了原型链重写,指向了自己定义的数组的原型方法。这样当调用数组 api 式,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次进行监控

Object.created() 保存原有原型

20211111223906

/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from "../util/index";

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto); //es6语法,相当于继承一个对象,添加的属性是在原型下

const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse",
];

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function(method) {
  // cache original method
  const original = arrayProto[method]; //将原生方法存下来
  def(arrayMethods, method, function mutator(...args) {
    //重写的方法
    const result = original.apply(this, args); //原生的方法
    const ob = this.__ob__;
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
        break;
    }
    if (inserted) ob.observeArray(inserted); //数组中新操作的对象进行响应式处理
    // notify change
    ob.dep.notify(); //派发更新,渲染页面
    return result;
  });
});

# vue 为何采用异步渲染

vue 是组件级更新,如果不采用异步更新,那么每次更新数据都会对当前组件重新渲染。为了新能考虑,vue 会在本轮数据更新后,再去异步更新视图

20211111224114

export function queueWatcher(watcher: Watcher) {
  const id = watcher.id; //判断watcher的id是否存在
  if (has[id] == null) {
    has[id] = true;
    if (!flushing) {
      queue.push(watcher);
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1;
      while (i > index && queue[i].id > watcher.id) {
        i--;
      }
      queue.splice(i + 1, 0, watcher);
    }
    // queue the flush
    if (!waiting) {
      //wating默认为false
      waiting = true;

      if (process.env.NODE_ENV !== "production" && !config.async) {
        flushSchedulerQueue();
        return;
      }
      nextTick(flushSchedulerQueue); //调用nextTick方法,批量更新
    }
  }
}

# nextTick 实现原理

nextTick 主要是使用了宏任务和微任务,定义了一个异步方法。多次调用 nextTick 会将方法存入队列中,通知这个异步方法清空当前队列,所以 nextTick 就是异步方法

原理:

20211111224447

源码:

export function nextTick(cb?: Function, ctx?: Object) {
  let _resolve;
  callbacks.push(() => {
    //callbacks是一个数组
    if (cb) {
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, "nextTick");
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  if (!pending) {
    //pengding默认为false
    pending = true;
    timerFunc(); //调用异步方法
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== "undefined") {
    return new Promise((resolve) => {
      _resolve = resolve;
    });
  }
}

# vue 中 computed 的特点

默认 computed 也是一个 watcher,具备缓存,只有当依赖的属性发生变化才会更新视图

原理:

20211111224809

源码:

function initComputed(vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = (vm._computedWatchers = Object.create(null));
  // computed properties are just getters during SSR
  const isSSR = isServerRendering();

  for (const key in computed) {
    const userDef = computed[key]; //获取用户定义
    const getter = typeof userDef === "function" ? userDef : userDef.get;
    if (process.env.NODE_ENV !== "production" && getter == null) {
      warn(`Getter is missing for computed property "${key}".`, vm);
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop, //将用户定义传到watcher中
        noop,
        computedWatcherOptions //lazy:true懒watcher
      );
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef);
    } else if (process.env.NODE_ENV !== "production") {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm);
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(
          `The computed property "${key}" is already defined as a prop.`,
          vm
        );
      }
    }
  }
}

export function defineComputed(
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering();
  if (typeof userDef === "function") {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key) //创建计算属性的getter,不是用用户传的
      : createGetterInvoker(userDef);
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop;
    sharedPropertyDefinition.set = userDef.set || noop;
  }
  if (
    process.env.NODE_ENV !== "production" &&
    sharedPropertyDefinition.set === noop
  ) {
    sharedPropertyDefinition.set = function() {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      );
    };
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

function createComputedGetter(key) {
  return function computedGetter() {
    //用户取值的时候会调用此方法
    const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        //dirty为true会去进行求值,这儿的dirty起到了缓存的作用
        watcher.evaluate();
      }
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value;
    }
  };
}

# watch 中的 deep:true 是如何实现的

内部原理就是递归,耗费性能

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])//每一项创建一个watcher
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
/**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)//将watcher放到全局上
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)//取值,会进行依赖收集
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {//深度监听
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
  export function traverse (val: any) {
  _traverse(val, seenObjects)
  seenObjects.clear()
}

function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }
  if (isA) {//递归处理
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

# vue 生命周期

  • beforeCreate 在实例初始化 new Vue()之后,数据观测(data observer)响应式处理之前被调用
  • created 实例已经创建完成之后被调用,实例已完成以下的配置:数据观测(data observer)、属性和方法的运算。watch/event 事件回调。数据可以拿到,但是没有$el。
  • beforeMount 再挂开始之前被调用,相关的 render 函数首次被调用//template
  • mounted el 被新创建的$el 替换,并挂载到实例上去之后被调用。页面渲染完毕
  • beforeUpdate 数据更新时调用,发生在虚拟 Dom 重新渲染和打补丁之前
  • updted 由于数据更改导致的虚拟 Dom 重新渲染和打补丁,在这之后会被调用该钩子
  • beforeDestroy 实例销毁之前调用,在这一步,实例仍然完全可用
  • destoryed Vue 实例销毁后调用。调用后,vue 实例指示的所有东西都会解除绑定,所有的事件监听都会被移除,所有的子实例也都会被销毁。该钩子在服务端渲染期间不被调用

每个生命周期内部可以做什么事

  • created 实例已经创建完成,因为他是最早触发的,可以进行一些数据资源的请求

  • mounted 实例已经挂载完成,可以进行一些 Dom 操作

  • beforeUpdate 可以在这个钩子中进一步更改状态,不会触发附加的冲渲染过程

  • updated 可以执行依赖于 Dom 的操作,尽量避免这个时候更改状态,因为可能会导致无限循环。该钩子服务器渲染期间不可用

  • destroyed 可以执行一些优化操作,清空定时器,解除绑定事件

    20211111232647

原理

20211111235408

20211111235432

# ajax 请求放在哪个生命周期中

在 created 的时候,视图中的 DOM 并没有渲染出来,此时直接去操作 DOM 节点,无法找到相关元素。 在 mounted 中,此时 DOM 已经渲染出来,可以直接操作 DOM 节点。 一般情况下都放到 mounted 中,保证逻辑的统一性,因为生命周期是同步执行的,ajax 是异步执行的。 服务器端渲染因为没有 DOM,不支持 mounted 方法,所以在服务器端渲染的情况下统一放到 created 中。

# 何时需要使用 beforeDestroy

  • 可能在当前组件使用了$on 方法,需要在组件销毁前解绑
  • 清除自己定义的定时器
  • 解除事件的原生绑定 scroll、mousemove…

# vue 模板编译原理

template ==》 ast 抽象语法树 ==》 codegen 方法 ==》 render 函数 ==》 createElement 方法 ==》 VirtualDom 虚拟 Dom

20211112000520

模板结合数据,生成抽象语法树,描述 html、js 语法

20211112000539

语法树生成 render 函数

20211112000656

render 函数

20211112000725

生成 Virtual Dom(虚拟 dom),描述真实的 dom 节点

渲染成真实 dom

20211112000748

# 用 vnode 来描述一个 dom 结构

20211112001434

20211112001411

# diff 算法的时间复杂度

两个树完全 diff 算法的时间复杂度为 O(n3),Vue 进行了优化,只考虑同级不考虑跨级,将时间复杂度降为 O(n)

前端当中,很少会跨层级的移动 Dom 元素,所以 Virtual Dom 只会对同一个层级的元素进行对比

# 简述 diff 算法原理

1、先同级比较,再比较儿子节点 2、先判断一方有儿子一方没儿子的情况 3、比较都有儿子的情况 4、递归比较子节点

vue3 中做了优化,只比较动态节点,略过静态节点,极大的提高了效率

双指针去确定位置

diff 算法原理图

20211112001622

20211112001706

# v-for 中为什么要用 key?

解决 vue 中 diff 算法结构相同 key 相同,内容复用的问题,通过 key(最好自定义 id,不要用索引),明确 dom 元素,防止复用

20211112001831

# 描述组件渲染和更新过程

渲染组件时,会通过 Vue.extend 方法构建子组件的构造函数,并进行实例化,最终手动调用$mount 进行挂载。更新组件时会进行 patchVnode 流程,核心就是 diff 算法

20211112001912

# 组件中的 data 为什么是个函数?

同一个组件被复用多次,会创建多个实例。这些实例用的是同一个构造函数,如果 data 是一个对象的话,所有组件共享了同一个对象。为了保证组件的数据独立性,要求每个组件都必须通过 data 函数返回一个对象作为组件的状态

# Vue 中事件绑定的原理

Vue 的事件绑定分为两种:一种是原生的事件绑定,一种是组件的事件绑定

原生 dom 事件绑定采用的是 addEventListener

组件的事件绑定采用的是$on 方法

20211112002031

20211112002045

# v-model 的实现原理及如何自定义 v-model?

v-model 可以看成是 value+input 方法的语法糖

20211112002113

20211112002127

20211112002144

20211112002159

不同的标签去触发不同的方法

20211112002217

# vue 中的 v-html 会导致哪些问题

可能会导致 XXS 攻击 v-html 会替换掉标签内的子元素 原理:

20211112002251

# 父子组件生命周期顺序

加载渲染过程

父 beforeCreate ==> 父 created ==> 父 beforeMount ==> 子 beforeCreat ==>子 created ==> 子 beforeMount ==> 子 mounted ==> 父 mounted

子组件更新过程

父 beforeUpdate ==> 子 beforeUpdate ==> 子 updated ==> 父 updated

父组件更新过程

父 beforeUpdate ==> 父 updated

销毁过程

父 beforeDestroy ==> 子 beforeDestroy ==> 子 destroyed ==> 父 destroyed

理解

组件的调用顺序都是先父后子,渲染完成的顺序是先子后父 组件的销毁操作是先父后子,销毁完成的顺序是先子后父

原理图

20211112002437

  • 父子间通信 父 ==> 子通过 props ,子 ==> 父通过$on、$emit(发布订阅)
  • 通过获取父子组件实例的方式$parent、$children
  • 在父组件中提供数据,子组件进行消费 Provide、Inject(插件必备)
  • Ref 获取实例的方式调用组件的属性和方法
  • Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue,全局就可以使用$bus
  • Vuex 状态管理实现通信

# vue 中相同逻辑如何抽离

Vue.mixin 用法给组件每个生命周期、函数都混入一些公共逻辑

Vue.mixin({
  beforeCreate() {}, //这儿定义的生命周期和方法会在每个组件里面拿到
});

源码

import { mergeOptions } from "../util/index";
export function initMixin(Vue: GlobalAPI) {
  Vue.mixin = function(mixin: Object) {
    this.options = mergeOptions(this.options, mixin); //将当前定义的属性合并到每个组件中
    return this;
  };
}

export function mergeOptions(
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== "production") {
    checkComponents(child);
  }

  if (typeof child === "function") {
    child = child.options;
  }

  normalizeProps(child, vm);
  normalizeInject(child, vm);
  normalizeDirectives(child);

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    if (child.extends) {
      //递归合并extends
      parent = mergeOptions(parent, child.extends, vm);
    }
    if (child.mixins) {
      //递归合并mixin
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm);
      }
    }
  }

  const options = {}; //属性及生命周期的合并
  let key;
  for (key in parent) {
    mergeField(key);
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
  function mergeField(key) {
    const strat = strats[key] || defaultStrat;
    options[key] = strat(parent[key], child[key], vm, key);
  }
  return options;
}

# 为什么要使用异步组件?

如果组件功能多,打包出的结果会变大,可以采用异步组件的方式来加载组件。主要依赖 import()这个语法,可以实现文件的分割加载

20211112002734

# 插槽和作用域插槽

渲染的作用域不同,普通插槽是父组件,作用域插槽是子组件

# 插槽

创建组件虚拟节点时,会将组件的儿子的虚拟节点保存起来。当初始化组件时,通过插槽属性将儿子进行分类,{a:[vnode],b:[vnode]}

渲染组件时,会拿对应的 slot 属性的节点进行替换操作。(插槽的作用域为父组件)

20211112002853

20211112002917

# 作用域插槽

作用域插槽在解析的时候,不会作为组件的儿子节点,会解析成函数。

当子组件渲染时,调用此函数进行渲染。(插槽的作用域为父组件)

20211112003031

20211112003044

原理

20211112003119

# 谈谈你对 keep-alive 的理解(一个组件)

keep-alive 可以实现组件的缓存,当组件切换时,不会对当前组件卸载

常用的 2 个属性 include、exclude

常用的 2 个生命周期 activated、deactivated

源码:

export default {
  name: "keep-alive",
  abstract: true, //抽象组件

  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number],
  },

  created() {
    this.cache = Object.create(null); //创建缓存列表
    this.keys = []; //创建缓存组件的key列表
  },

  destroyed() {
    //keep-alive销毁时,会清空所有的缓存和key
    for (const key in this.cache) {
      //循环销毁
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },

  mounted() {
    //会监控include和exclude属性,进行组件的缓存处理
    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); //只缓存第一个组件
    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;
      }

      const { cache, keys } = this;
      const key: ?string =
        vnode.key == null
          ? // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : "")
          : vnode.key; //如果组件没key,就自己通过组件的标签和key和cid拼接一个key
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance; //直接拿到组件实例
        // make current key freshest
        remove(keys, key); //删除当前的[b,c,d,e,a] //LRU最近最久未使用法
        keys.push(key); //将key放到后面[b,a]
      } else {
        cache[key] = vnode; //缓存vnode
        keys.push(key); //将key存入
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) {
          //缓存的太多,超过了max就需要删除掉
          pruneCacheEntry(cache, keys[0], keys, this._vnode); //要删除第0个,但是渲染的就是第0个
        }
      }

      vnode.data.keepAlive = true; //标准keep-alive下的组件是一个缓存组件
    }
    return vnode || (slot && slot[0]); //返回当前的虚拟节点
  },
};

# vue 中常见的性能优化

一个优秀的 Vue 团队代码规范是什么样子的? (opens new window)

# 编码优化

  • 不要将所有的数据都放到 data 中,data 中的数据都会增加 getter、setter,会收集对应的 watcher
  • vue 在 v-for 时给每项元素绑定事件需要用事件代理
  • SPA 页面采用 keep-alive 缓存组件
  • 拆分组件(提高复用性、增加代码的可维护性,减少不必要的渲染)
  • v-if 当值为 false 时,内部指令不会执行,具有阻断功能。很多情况下使用 v-if 替换 v-show
  • key 保证唯一性(默认 vue 会采用就地复用策略)
  • Object.freeze 冻结数据
  • 合理使用路由懒加载、异步组件
  • 数据持久化的问题,防抖、节流

# Vue 加载性能优化

  • 第三方模块按需导入(babel-plugin-component)
  • 滚动到可视区域动态加载(https://tangbc.github.io/vue-virtual-scroll-list)
  • 图片懒加载(https://github.com/hilongjw/vue-lazyload.git)

# 用户体验

  • app-skeleton 骨架屏
  • app shell app 壳
  • pwa

# SEO 优化

  • 预渲染插件 prerender-spa-plugin
  • 服务端渲染 ssr

# 打包优化

  • 使用 cdn 的方式加载第三方模块
  • 多线程打包 happypack
  • splitChunks 抽离公共文件
  • sourceMap 生成

# 缓存压缩

  • 客户端缓存、服务端缓存
  • 服务端 gzip 压缩

# Vue3.0 的改进

  • 采用了 TS 来编写
  • 支持 composition API
  • 响应式数据原理改成了 proxy
  • diff 对比算法更新,只更新 vdom 绑定了动态数据的部分

# 实现 hash 路由和 history 路由

  • onhashchange #
  • history.pushState h5 api

# 路由守卫解析流程

20211112003846

# action 和 mutation 区别

mutation 是同步更新数据(内部会进行是否为异步方式更新的数据检测)

action 异步操作,可以获取数据后调用 mutation 提交最终数据

# 像 vue-router,vuex 他们都是作为 vue 插件,请说一下他们分别都是如何在 vue 中生效的?

通过 vue 的插件系统,用 vue.mixin 混入到全局,在每个组件的生命周期的某个阶段注入组件实例

# 请你说一下 vue 的设计架构

vue2 采用的是典型的混入式架构,类似于 express 和 jquery,各部分分模块开发,再通过一个 mixin 去混入到最终暴露到全局的类上

编辑 (opens new window)
上次更新: 2025/03/17, 12:21:00
vue源码系列
Vuejs 运行机制全局概览

← vue源码系列 Vuejs 运行机制全局概览→

最近更新
01
Code Review
10-14
02
ComfyUI
10-11
03
vscode插件开发
08-24
更多文章>
Power by vuepress | Copyright © 2015-2025 神族九帝
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×