vue知识总结(1)

260 阅读8分钟

1. v-if和v-for哪个优先级更高,如果2个同时出现,应该怎么优化得到更好的性能?

  • 源码中找答案compiler/codegen/index.js
  • 两者同级时,即v-for里面写了v-if的时候_l的return中包含了渲染条件
  • 两者不同级先判断了条件再看是否执行_l
  • 结论
    1. 显然v-for优先于v-if被解析。
    2. 如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都无法避免,浪费了性能。
    3. 可以在外层嵌套template进行v-if的判断,然后在内部进行v-for循环。
    4. 如果if条件出现在循环数据内部,可通过计算属性提前过滤不需要显示的项。

2. Vue组件data为什么必须是个函数而Vue的根实例则没有此限制?

  • 源码中找答案:src\core\instance\state.js - initData()
  • 函数每次执行都会返回全新data对象实例
  • 如果data传对象,程序甚至无法通过vue检测,会提示the 'data' option should be a function。
  • 结论
    1. vue组件可能存在多个实例,如果使用对象形式定义data,则会导致他们公用一个data对象,那么状态变更将会影响所有组件实例,这是不合理的。
    2. 采用函数形式定义,在initData的时候,会将其作为工厂函数,返回全新data对象,有效规避多实例之间状态污染问题。
    3. vue在根实例创建的时候不存在这个问题,是因为根实例只有一个,不需要担心这个问题。

3. 你知道vue中key的作用和工作原理吗?说说你对它的理解。

  • 源码中找答案:src\core\vdom\patch.js - updateChildren()

  • 用插入一个dom作演示

  • 不使用key,在对比到c的时候就直接执行的创建->替换操作,在e的时候执行创建->追加

  • 使用key

// 首次循环patch A A (头头)
A B C D E 
A B F C D E 

// 第2次循环patch B B (头头)
B C D E 
B F C D E 

// 第3次循环patch E E (尾尾)
C D E 
F C D E 

// 第4次循环patch D D (尾尾)
C D 
F C D 

// 第5次循环patch C C  (尾尾)
C
F C
// oldCh全部处理结束,newCh中剩下的F,创建F并插入到C前面 
  • 结论
    1. key的主要作用是为了高效的更新虚拟dom,原理是vue在patch过程中通过key,可以精准的判断两个节点是不是同一个,从而避免频繁的更新不同元素,使得patch过程更高效,减少dom操作量,提高性能。
    2. 如果不设置key,可能会在列表更新的时候引发一些隐藏的bug.
    3. vue中在使用相同标签名元素过度时,也会使用到key属性,其目的也是为了让vue可以区分他们,否则vue只会替换其内部属性,而不会触发过度效果。

4. 怎么理解vue中的diff算法。

  • 必要性,lifecycle.js - mountComponent() ,每个组件的挂载,都要执行mount,每次mount,都会new一个watcher实例,组件中可以有多个地方使用了data中的key,所以必须通过diff知道到底是哪个key发生了改变(因为diff里面有updateComponent)
  • 执行方式,patch.js - patchVnode(),patchVnode是diff发生的地方,整体策略是深度优先,同层比较。
  • 高效性,patch.js - updateChildren(),diff通过相同节点的猜测判断,降低算法复杂度,复用相同的组件。
  • 结论:
    1. diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比(即diff),将变化的地方更新在真 实DOM上;另外,也需要diff高效的执行对比过程,从而降低时间复杂度为O(n)。
    2. vue 2.x中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,只有引入diff才能精确找到发生变化的地方。(主要是数据更新的时候,执行了setter,setter里面会通知更新,就执行了componengtUpdata,而update必定是把改变的虚拟dom,渲染为真实的dom,这里就会进行patch)
    3. vue中diff执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果oldVnode和新的渲染结果newVnode,此过程称为patch。
    4. diff过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文 本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同做4次比对尝试,如果 没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点;借助key通常可以非 常精确找到相同节点,因此整个patch过程非常高效。

5.谈一谈对vue组件化的理解?

  • 组件定义的2种方式
// 全局,可以写template或者直接写render
Vue.component('comp', { 
    template: '<div>this is a component</div>' 
    }
  ) 
// 单文件组件的形式(平时项目中用的更多)
<template> 
  <div> 
    this is a component     
  </div> 
</template>
// vue-loader会编译template为render函数,最终导出的依然是组件配置对象
  • 源码中的组件定义:src\core\global-api\assets.js
// 组件构造函数生成
// definition就是传入的组件构造函数
// 通过extend创建组件构造函数
definition = this.options._base.extend(definition)
// 全局注册 {components:{comp: Ctor}}即在当前的vue的构造函数的选项中加上一个componengts的选项
this.options[type + 's'][id] = definition
return definition
  • 源码组件化实现:src\core\global-api\extend.js 如果是一个全局component这样的方式定义,它是立刻执行,甚至在当前的实例话之前就执行了
// 传入的是当前组件的配置选项(全局组件),它是立刻执行,甚至在当前的实例化之前就执行了
// 创建一个VueComponent类,这个类就是所有组件的基类
const Sub = function VueComponent (options) {
  this._init(options)
}
// 它本身还是继承于vue这个类,因为这里的Super来自vue,所以这里可以说Sub原型是来自vue原型的一个拷贝
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
// 做一些选项的合并
Sub.options = mergeOptions(
  Super.options,
  extendOptions
)
  • 源码实例化及挂载:src\core\vdom\patch.js - createElm() 如果是一个局部组件,声明在一个单文件里面,在后续的运行时的某个时刻(patch的时候,父节点没有,就需要执行创建,如果是根节点,必须要执行createElm,因为要挂载啊)
// 在createElm方法中(即将vdom => dom的时候)
// 判断当前vnode是否是一个自定义组件的vnode
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  return
}
// 如果是一个组件,就会执行createComponent方法,创建自定义组件dom结构
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  // 获取钩子hook
  let i = vnode.data
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      // 实例化和挂载
      i(vnode, false /* hydrating */)
    }
    if (isDef(vnode.componentInstance)) {
      // 属性回调执行
      initComponent(vnode, insertedVnodeQueue)
      // 追加到父组件
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
  • 执行
// 1. src\instance\render--render()会执行createElement()
// 2. src\core\vdom\create-element会执行createComponent()
// 自定义组件,前面条件获取components选项中对应的组件构造函数
vnode = createComponent(Ctor, data, context, children, tag)
// 或者传入的是组件选项
vnode = createComponent(tag, data, context, children)
// 3.  src\core\vdom\create-component
// 在里面会创建组件未来一些关键生命周期钩子
componentVNodeHooks.prepatch(mountedNode, mountedNode)
// 创建自定义组件实例
const child = vnode.componentInstance = createComponentInstanceForVnode(
  vnode,
  activeInstance
)
// 挂载组件
child.$mount(hydrating ? vnode.elm : undefined, hydrating)

// 结论:实例化的过程是自上而下的,从根往叶子节点去创建,但是由于子节点会先执行$mount,所以挂载是自下而上的。
  • 源码组件化性能上的优点:src/core/instance/lifecycle.js---mountComponent()
// 这里主要针对组件,watcher,渲染函数和更新函数之间的关系
// 组件的实例在执行$mount的时候会调用生命周期的--mountComponent
// 在这里会给每一个组件new一个watcher实例
new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'beforeUpdate')
    }
  }
}, true /* isRenderWatcher */)
// 将来如果组件的数据发上变化了,只会调用这个组件的渲染函数和更新函数,所以我们要合理的切割组件的粒度。
updateComponent = () => {
  vm._update(vm._render(), hydrating)
}
  • 总结:
    1. 组件是独立和可复用的代码组织单元。组件系统是 Vue 核心特性之一,它使开发者使用小型、独立和通常可复用的组件构建大型应。
    2. 组件化开发能大幅提高应用开发效率、测试性、复用性等。
    3. 组件使用按分类有:页面组件(路由导航一些页面组件,它的复用性或许不那么高,但是它是组织我们页面之间来回切换的,必备的组件)、业务组件(如登录的组件)、通用组件(所有的项目基本都要用,按钮,表单,输入框)。
    4. vue的组件很特别,它是基于配置的,我们平常编写的是配置不是组件,框架最后会生成这些构造函数。它主要基于vueComponent这个类,它扩展自vue,在扩展的过程中,会继承vue中已经有的选项。
    5. vue中常见组件化技术有:属性prop,自定义事件,插槽等,它们主要用于组件通信、扩展等;
    6. 合理的划分组件,有助于提升应用性能。
    7. 组件应该是高内聚、低耦合的。
    8. 组件应该是高内聚、低耦合的