vue 源码进修(二) 实例初始化和挂载历程

vue 进口

从vue的构建历程能够晓得,web环境下,进口文件在 src/platforms/web/entry-runtime-with-compiler.js(以Runtime + Compiler形式构建,vue直接运行在浏览器举行编译事情)

import Vue from './runtime/index'

下一步,找到./runtime/index,发明:

import Vue from 'core/index'

下一步,找到core/index,发明:

import Vue from './instance/index'

根据这个思绪找,末了发明:Vue是在’core/index’下定义的

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

引入要领,用function定义了Vue类,再以Vue为参数,挪用了5个要领,末了导出了vue

能够进入这5个文件检察相干要领,重要就是在Vue原型上挂载要领,能够看到,Vue 是把这5个要领按功用放入差别的模块中,这很利于代码的保护和治理

initGlobalAPI

回到core/index.js, 看到除了引入已经在原型上挂载要领后的 Vue 外,还导入initGlobalAPI 、 isServerRendering、FunctionalRenderContext,实行initGlobalAPI(Vue),在vue.prototype上挂载$isServer、$ssrContext、FunctionalRenderContext,在vue 上挂载 version 属性,

看到initGlobalAPI的定义,重如果往vue.config、vue.util等上挂载全局静态属性和静态要领(可直接经由过程Vue挪用,而不是实例挪用),再把builtInComponents 内置组件扩展到Vue.options.components下。此处大抵相识下它是做什么的即可,背面用到再做具体剖析。

new Vue()

平常我们用vue都采纳模板语法来声明:

<div id="app">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

new Vue()时,vue做了哪些处置惩罚?

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

看到vue只能经由过程new实例化,不然报错。实例化vue后,实行了this._init(),该要领在经由过程initMixin(Vue)挂载在Vue原型上的,找到定义文件core/instance/init.js 检察该要领。

_init()

一开始在this对象上定义_uid、_isVue,推断options._isComponent,此次先不斟酌options._isComponenttrue的状况,走else,兼并options,接着装置proxy, 初始化生命周期,初始化事宜、初始化衬着、初始化data、钩子函数等,末了推断有vm.$options.el则实行vm.$mount(),等于把el衬着成终究的DOM

初始化data 数据绑定

_init()中经由过程initState()来绑定数据到vm上,看下initState的定义:

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

猎取options,初始化propsmethodsdata、盘算属性、watch绑定到vm上,先来看下initData()是怎样把绑定data的:

  • 先推断data是否是function范例,是则挪用getData,返回data的自挪用,不是则直接返回data,并将data赋值到vm._data
  • data、props、methods,作个校验,防备涌现反复的key,由于它们终究都邑挂载到vm上,都是经由过程vm.key来挪用
  • 经由过程proxy(vm, `_data`, key)把每一个key都挂载在vm

    export function proxy (target: Object, sourceKey: string, key: string) {
      sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
      }
      sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }

    proxy() 定义了一个get/set函数,再经由过程Object.defineProperty定义修正属性(不相识Object.defineProperty()的同砚能够先看下文档,经由过程Object.defineProperty()定义的属性,经由过程描述符的设置能够举行更精准的掌握对象属性),将对target的key接见加了一层get/set,即当接见vm.key时,实际上是挪用了sharedPropertyDefinition.get,返回this._data.key,如许就完成了经由过程vm.key来挪用vm._data上的属性

  • 末了,observe(data, true /* asRootData */) 观察者,对数据作相应式处置惩罚,这也是vue的中心之一,此处先不剖析

$mount() 实例挂载

Vue的中心头脑之一是数据驱动,在vue下,我们不会直接操纵DOM,而是经由过程js修正数据,一切逻辑只须要斟酌对数据的修正,末了再把数据衬着成DOM。个中,$mount()就是担任把数据挂载到vm,再衬着成终究DOM

接下来将会剖析下 vue 是怎样把javaScript对象衬着成dom元素的,和之前一样,重要剖析主线代码

预处置惩罚

照样从src/platform/web/entry-runtime-with-compiler.js 文件入手,

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  ···
}

首先将本来原型上的$mount要领缓存起来,再从新定义$mount

  • 先推断 elel 不能是 body, html ,由于衬着出来的 DOM 末了是会替换掉el
  • 推断render要领, 有的话直接挪用mount.call(this, el, hydrating)
  • 没有render要领时:
  1. 推断有无template ,有则用compileToFunctions将其编译成render要领
  2. 没有template时,则检察有无el,有转换成template,再用compileToFunctions将其编译成render要领
  3. render挂载到options下
  4. 末了挪用 mount.call(this, el, hydrating),等于挪用本来原型上的mount要领

我们发明这一系列挪用都是为了天生render函数,申明在vue中,一切的组件衬着终究都须要render要领(不管是单文件.vue照样el/template),vue 文档里也提到:

Vue 选项中的 render 函数若存在,则
Vue 组织函数不会从
template 选项或经由过程 el 选项指定的挂载元素中提掏出的
HTML 模板编译衬着函数。

本来原型上的mount要领

找到本来原型上的mount要领,在src/platform/web/runtime/index.js中:

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

这个是公用的$mount要领,这么设想使得这个要领能够被 runtime onlyruntime+compiler 版本配合运用

$mount 第一个参数el, 示意挂载的元素,在浏览器环境会经由过程query(el)猎取到dom对象,第二个参数和服务端衬着相干,不举行深入剖析,此处不传。接着挪用mountComponent()

看下query(),比较简朴,当el string时,找到该选择器返回dom对象,不然新创建个div dom对象,eldom对象直接返回el.

mountComponent

mountComponent定义在src/core/instance/lifecycle.js中,传入vm,el,

  • el缓存在vm.$el
  • 推断有无render要领,没有则直接把createEmptyVNode作为render函数
  • 开辟环境正告(没有Render但有el/template不能运用runtime-only版本、rendertemplate必需要有一个)
  • 挂载beforeMount钩子
  • 定义 updateComponent , 衬着相干

    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  • new Watcher() 实例化一个衬着watcher,简朴看下定义,
    this.getter = expOrFn
    updateComponent挂载到this.getter
    this.value = this.lazy ? undefined : this.get()

    get () {
      pushTarget(this)
      let value
      const vm = this.vm
      try {
        value = this.getter.call(vm, vm)
      } catch (e) {...}
      return value
    }

    实行this.get(),则实行了this.getter,即updateComponent,所以new Watcher()时会实行updateComponent,也就会实行到vm._update、vm._render要领。

    由于以后不止初始化时须要衬着页面,数据发生变化时也是要更新到dom上的,实例watcher能够完成对数据举行监听以及随后的更新dom处置惩罚,watcher会在初始化实行回调,也会在数据变化时实行回调,此处先简朴引见为何要运用watcher,不深入剖析watcher完成道理。

  • 末了推断有无根节点,无则示意初次挂载,增加mounted钩子函数 ,返回vm

总结

实例初始化:new Vue()->挂载要领属性->this._init->初始化data->$mount

挂载历程:(在complier版本,天生render函数)对el作处置惩罚,实行mountComponent,mountComponent中定义了updateComponent,经由过程实例化watcher的回调实行updateComponent,实行updateComponent,即挪用了vm._update、vm._render实在衬着成dom对象。

    原文作者:huangnuoen
    原文地址: https://segmentfault.com/a/1190000017822643
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞