返回

Vue.js: 运行机制解密

前端

Vue.js 是一款流行的前端框架,在构建用户界面和单页面应用时受到广泛应用。Vue.js 的运行机制一直备受开发者关注,本文将深入剖析 Vue.js 的运行机制,从初始化到响应式系统、组件树、更新机制,逐层解析其工作原理,帮助你深入理解 Vue.js 的运作方式。

初始化及挂载

new Vue() 之后,Vue 会调用 _init 函数进行初始化,也就是这里的 init 方法。

constructor() {
  this._init(options)
}

_init 函数中,Vue 会完成以下工作:

  • 初始化属性
  • 初始化数据
  • 初始化 computed 和 watch
  • 初始化 methods
  • 初始化 lifecycle hooks
  • 调用 $mount 方法挂载实例
_init(options) {
  const vm = this
  // a) 初始化 options
  vm.$options = options
  // b) 初始化 data
  callHook(vm, 'beforeCreate')
  observe(vm, '_data', vm._data)
  callHook(vm, 'created')
  // c) 初始化 computed 和 watch
  if (vm.$options.computed) {
    initComputed(vm)
  }
  if (vm.$options.watch) {
    for (const key in vm.$options.watch) {
      const handler = vm.$options.watch[key]
      if (typeof handler === 'string') {
        handler = vm[handler]
      }
      watch(vm, key, handler)
    }
  }
  // d) 初始化 methods
  if (vm.$options.methods) {
    for (const key in vm.$options.methods) {
      vm[key] = vm.$options.methods[key].bind(vm)
    }
  }
  // e) 初始化 lifecycle hooks
  if (vm.$options.LIFECYCLE_HOOKS) {
    for (const key in vm.$options.LIFECYCLE_HOOKS) {
      vm.$options.LIFECYCLE_HOOKS[key].forEach(hook => {
        vm.$on('hook:' + key, hook)
      })
    }
  }
  // f) 调用 $mount 方法挂载实例
  callHook(vm, 'beforeMount')

  if (vm.$mount) {
    vm._isAttached = true
    mountComponent(vm, vm.$el)
  } else if (vm._renderProxy) {
    vm._renderProxy._update(vm._vnode, {})
  } else if (vm.$options.template) {
    mountTemplate(vm, vm.$options.template)
  }
  callHook(vm, 'mounted')

  // vm._watcherList.forEach(w => w.teardown())
}

响应式系统

Vue.js 的响应式系统是其核心之一。它允许你声明式地定义数据依赖关系,当数据发生变化时,系统会自动更新相关视图。

Vue.js 的响应式系统基于数据劫持。当 Vue 实例被创建时,Vue 会遍历实例的数据对象,使用 Object.defineProperty() 将每个属性转换为 getter/setter。当属性被访问或修改时,getter/setter 会触发相应的更新。

observe(value, key, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  const ob = observe(value, '__ob__', asRootData)
  if (isArray(value)) {
    for (let i = 0, l = value.length; i < l; i++) {
      observe(value[i], key + '[' + i + ']', asRootData)
    }
  } else {
    for (const key in value) {
      observe(value[key], key, asRootData)
    }
  }
  return ob
}

组件树

Vue.js 采用组件化的设计,每个组件都是一个独立的可复用单元。组件树是 Vue 实例中组件的层次结构表示。组件树的根节点是 Vue 实例本身,叶子节点是组件实例。

组件树是 Vue.js 更新机制的核心。当数据发生变化时,Vue 会从组件树的根节点开始,逐层向下遍历每个组件,检查每个组件的数据是否发生了变化。如果组件的数据发生了变化,Vue 会更新该组件及其所有子组件的视图。

mountComponent(vm, el) {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  if (process.env.NODE_ENV !== 'production') {
    updateComponent = () => {
      callHook(vm, 'beforeUpdate')
      const prevVnode = vm._vnode
      const prevActiveInstance = vm._activeInstance
      const cloned = createComponentInstanceForVnode(
        vm,
        vm._vnode,
        vm._vnode
      )
      vm._update(cloned._vnode, prevVnode)
      callHook(vm, 'updated')
    }
  } else {
    updateComponent = () => {
      vm._update(vm._vnode, vm._vnode)
    }
  }

  vm._update(vm._render(), updateComponent)
  callHook(vm, 'mounted')

  if (vm.$el.dataset.ົMounting === 'true') {
    vm.$el.removeAttribute('data-mounting')
  }
}

更新机制

Vue.js 的更新机制是基于虚拟 DOM 的。虚拟 DOM 是一个轻量级的 DOM 表示,它只包含 DOM 节点的必要信息。当数据发生变化时,Vue 会先更新虚拟 DOM,然后再将虚拟 DOM 更新应用到实际的 DOM 上。

Vue.js 的更新机制非常高效,因为它只更新那些发生变化的组件及其子组件。这使得 Vue.js 非常适合构建大型和复杂的单页面应用。

updateComponent(prevVnode, nextVnode) {
  let componentInstance = vm._component
  if (!componentInstance) {
    // cached component
    // install component instance
    componentInstance = vm._component = createComponentInstanceForVnode(
      vm,
      nextVnode,
      prevVnode
    )
  } else {
    componentInstance.update(nextVnode)
  }
  if (process.env.NODE_ENV !== 'production') {
    prevVnode.componentInstance = componentInstance
  }
}

总结

Vue.js 的运行机制非常复杂,但其核心原理并不难理解。通过初始化、响应式系统、组件树和更新机制,Vue.js 能够高效地构建和更新用户界面。