VUE源码解析 -- Vue初始化流程解析

262 阅读8分钟

在讲new Vue()干了啥之前,我们先看一下在引入Vue对象的时候发生了什么。

我们知道import执行的时候 对应的文件代码会执行,在这个过程中给Vue绑定了很多我们后面会用到的方法和属性,所有在这里有必要说明一下:
我们先看一下引用关系:
通过这个关系我们能够看到我们实际使用的Vue对象是经过了层层包装导出的,下面我们在分别看一下每个文件中都对Vue对象做了什么封装

  1. core/instace/index.js
    定义了Vue的构造函数以及给Vue.prototype对象添加了一些方法:
  2. core/index.js
    给Vue对象增加多个属性和方法,对应官方文档中的全局配置和全局API:
  3. plaforms/web/runtime/index.js
    增加全局指令和内置全局组件,定义$mount(不包含编译器)和__patch__(diff算法函数):
  4. platfroms/web/entry-runtime-with-complier.js
    重新定义$mount(包含编译器)和Vue.compile(将一个模板字符串编译成 render 函数)

    上面我们主要说了在我们平时项目中import Vue对象的时候会发生的东西,下面我们来进入正文,看一下new Vue会执行什么操作:

new操作符相信大家经常使用,new操作符实际执行的是 创建一个对象,然后设置该对象的__proto__指向后面的Vue.prototype
new Vue()实际执行的顺序是:
创建一个新对象
设置该对象的__proto__指向Vue.prototype
执行Vue构造函数,并把设置构造函数中的this为该对象
最后返回this
其实主要的逻辑就是第三步,下面我们来看一下执行Vue构造函数的逻辑
主要就是执行_init方法:

// instance/index.js
function Vue (options) {
  // 直接调用Vue构造函数 给出warning提示
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 执行_init方法
  this._init(options)
}
// instance/init.js
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    /*
      性能监控
    */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }
    // a flag to avoid this being observed
    vm._isVue = true
    // merge options 合并参数(options)
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      // 根据不同的策略合并不同的属性 props data ... 
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),// vm.constructor 返回的是构造函数的引用 Vue对象
        options || {},
        vm
      )
    }
    /*
      设置render函数执行作用域
    */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm)
    initState(vm)
    initProvide(vm) 
    callHook(vm, 'created')

    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

从上面的代码我们可以看到 _init方法主要执行的是下面的流程:

  • 性能监控(vue初始化过程)
  • 合并参数
  • 初始化生命周期参数
  • 初始化事件相关参数
  • 初始化render相关参数
  • 触发beforeCreated生命周期
  • 初始化inject参数
  • 初始化options中的props、methods、data、computed、watch参数
  • 初始化provide
  • 执行$mount方法
    下面我们来详细看一下每个函数都具体做了什么:
  1. 性能监控(vue初始化过程)
    对应代码如下:
  let startTag, endTag
  // config.performance 对应全局属性 Vue.config.performance的值
  // mark方法:window.performance.mark方法(根据给出 name 值,在浏览器的性能输入缓冲区中创建一个相关的timestamp(是一个double类型,用于存储毫秒级的时间值。))
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    startTag = `vue-perf-start:${vm._uid}`
    endTag = `vue-perf-end:${vm._uid}`
    mark(startTag)
  }

  ... 
  // formatComponentName 得到当前vm的组件名称 
  // measure:window.performance.measure方法(在浏览器的指定 start mark 和 end mark 间的性能输入缓冲区中创建一个指定的 timestamp)就是获取两个mark标记之间的时间值
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(`vue ${vm._name} init`, startTag, endTag)
  }

总结一下上面的代码就是监控 每个组件init所耗费的时间

  1. 合并参数
  // merge options 合并参数(options)
  // options._isComponent: 当vm对象当做组件被渲染时
  if (options && options._isComponent) {
    // 优化内部组件实例化,因为动态选项合并非常慢,并且没有任何内部组件选项需要特殊处理。
    initInternalComponent(vm, options)
  } else {
    // 根据不同的策略合并不同的属性 props data ... 
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),// vm.constructor 返回的是构造函数,也就是Vue对象
      options || {},
      vm
    )
  }

合并参数的时候分为两个分支:组件、非组件,举个简单例子来看一下: ComponentA就是当做组件,执行上面的分支,newVue为根组件,进入下面的分支

  const ComponentA = {
    template:`<div class="contentA">
      <h2>我是子组件A</h2>
      </div>`,
    data:function () {
        return {
            count:0,
        }
    },
}

var newVue  = new Vue({
    el:"#root",
    template:`<div>
        <h1>我是父组件</h1>
        <ComponentA/>
    </div>
    `,
    data:{
        msg:"hello"
    },
    components:{ComponentA},
})

然后思考一下:为什么组件(ComponentA)的options不需要合并呢?
这是因为在创建组件的时候就已经把他的参数处理过了,这个地方就不需要再继续处理了。 这个过程后面的文章中会详细讲解,这里我们需要知道组件的options已经被处理过了就可以了

下面是mergeOptions函数,总结一下它的作用是合并options参数
先看一下resolveConstructorOptions 这个函数就是返回Vue.options(这个在前面我们可以看到options参数里面存储的就是内置组件、内置指令等) mergeOptions具体实现我们来结合代码来看一下:

// util/options.js
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  // 检查组件名称是否合法(options.components)
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }
  
  if (typeof child === 'function') {
    child = child.options
  }
  // 解析格式为对应的格式,对应代码都比较简单 可以自行去查看
  // 解析props属性 props:[name,age] --> {name:{type:null},age:{type:null}} 如果props是对象的话 直接使用该对象
  normalizeProps(child, vm)
  // 和props类似 都转换成对象的格式
  normalizeInject(child, vm)
  // 格式化自定义指令
  normalizeDirectives(child)
  // 合并extends和mixins选项
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }
  // parent 和child 的数据都已经处理完成
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  // 处理child中有,但是parent中没有的属性
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  // 不同的属性有不同的合并策略 key--> data/methods/props/mounted等等
  // data --> strats.data
  // props --> 
  // methods-->
  // components --> 
  // computed--> 
  function mergeField (key) {
    //根据不同的属性 使用不同的策略来合并选项
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
// vue对象不同的属性有不同的合并策略,这里我们只列举一两个,感兴趣的同学可以自行去查看不同属性的合并策略
// 这里我们看一下生命周期的合并函数
// 生命周期 对应的合并函数是mergeHook 
// 可以看到 生命周期合并的时候是生成一个数组,当需要调用生命周期钩子函数的时候循环执行数组中的函数
LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}
// 去重复
function dedupeHooks (hooks) {
  const res = []
  for (let i = 0; i < hooks.length; i++) {
    if (res.indexOf(hooks[i]) === -1) {
      res.push(hooks[i])
    }
  }
  return res
}
  1. 初始化生命周期参数
 // 这个方法比较简单 复杂点的代码已经加上了注释
export function initLifecycle (vm: Component) {
  const options = vm.$options
  // locate first non-abstract parent
  // 找到第一个非抽象父类
  let parent = options.parent
  if (parent && !options.abstract) {
    // parent.$options.abstract === true 是keep-alive/transtion 等内置组件
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }
  // 设置父组件
  vm.$parent = parent
  // 设置根组件 为父类根组件 
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}
  1. 初始化事件相关参数
export function initEvents (vm: Component) {
  // 存储父组件中绑定的事件
  vm._events = Object.create(null)
  // 
  vm._hasHookEvent = false
  // 初始化父组件中传过来的函数 
  /*
    例如:<ComponentA @changeFoo="changeMsg"/> 
    vm.$options._parentListeners = {
      changeFoo:function(){...}
    }
  */
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}
export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  // 更新组件上面绑定的事件,遍历listeners和oldlisteners对象 如果listeners有old里面没有 就add,如果old有,new里面没有 就remove; 具体实现后面会详细讲
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}
  1. 初始化render相关参数
export function initRender (vm: Component) {
  // 存储虚拟dom对象
  vm._vnode = null
  vm._staticTrees = null 
  const options = vm.$options
  // options._parentVnode存储的是 在父组件中的虚拟dom树中的自己
  const parentVnode = vm.$vnode = options._parentVnode 
  // context 指的是
  const renderContext = parentVnode && parentVnode.context
  // options._renderChildren 存储的组件插槽对应的vnode对象数组
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // createElement('h1', '标题') 用在render函数中 返回应该渲染一个什么样的节点给render函数使用
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // Vnode 中的data属性
  const parentData = parentVnode && parentVnode.data
  // 定义this.$attrs(只有在props中没有声明的属性才会出现在parentData.attrs中)和 this.$listeners属性
  defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
  defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
  1. 触发beforeCreated生命周期
// callHook(vm, 'beforeCreate')
export function callHook (vm: Component, hook: string) {
  
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    // 前面已经说过每个生命周期可以有多个handle函数
    for (let i = 0, j = handlers.length; i < j; i++) {
      // 就是执行vm.handlers[i],但是增加异常场景报错提示
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  // 触发类似这种绑定的生命周期函数 this.$on("hook:updated",function(){...}) 
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
}

  1. 初始化inject参数
export function initInjections (vm: Component) {
  // resolveInject: vm.$parent逐级向上寻找inject的from对应的provide值
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    // 不需要响应式
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      // vm[key]设置get/set方法
      defineReactive(vm, key, result[key])
    })
    toggleObserving(true)
  }
}
// 下面在来看一下resolveInject的实现

export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    const result = Object.create(null)
    // 支持symbol类型的key
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      // 防止重复监听该对象
      if (key === '__ob__') continue
      const provideKey = inject[key].from
      let source = vm
      // 逐级向上查找inject中from对应的值
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
      // 如果都没有定义,使用default的值,否则warning提示
      if (!source) {

        if ('default' in inject[key]) {
          const provideDefault = inject[key].default
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault
        } else if (process.env.NODE_ENV !== 'production') {
          warn(`Injection "${key}" not found`, vm)
        }
      }
    }
    return result
  }
}

  1. 初始化options中的props、methods、data、computed、watch参数
/*
  initState 方法结构很简单 就是初始化对应的参数
  但是请注意下 初始化的顺序是有说法的 ,比如 计算属性可能用到data或者props中的值 watch也可能监听到data、props、computed中的值
*/
export function initState (vm: Component) {
  // 会把这个vm实例上的所有的依赖(watch实例)放到这个对象中
  vm._watchers = []
  const opts = vm.$options
  // 初始化props属性
  if (opts.props) initProps(vm, opts.props)
  // 初始化methods方法
  if (opts.methods) initMethods(vm, opts.methods)
  // 初始化data
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // 初始化 计算属性
  if (opts.computed) initComputed(vm, opts.computed)
  // 初始化 watch 
  // nativeWatch:这个是因为在个别浏览中会给对象增加一个watch属性,这个地方判断是排除这种情况
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
// 下面我们来依次介绍一下几个初始化的方法:
// initProps方法
function initProps (vm: Component, propsOptions: Object) {
  // vm.$options.propsData 存储着 父组件传过来的props对象 
  // 那是在什么时候赋值的呢?  updateChildComponent方法中赋值,这个后面我们会讲到 
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // vm.$options._propKeys 缓存props的key值,当后面更新的时候可以直接从数组中遍历拿到
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // 如果不是根组件的话 就不需要把数据变成响应式的 ,因为再父组件中已经是响应式的了 不需要重复定义
  if (!isRoot) {
    toggleObserving(false)
  }
  // 遍历props对象,前面已经格式化过了
  for (const key in propsOptions) {
    // push到 $options的_propKeys选项中
    keys.push(key)
    // 拿到props的key对应的value值
    // validateProp 函数下面会讲
    const value = validateProp(key, propsOptions, propsData, vm)
    // 给vm._props赋值
    defineReactive(props, key, value)
    // 判断一下key 是否在vm对象上,如果不在的话 代理 vm的key属性 get/set的时候去修改vm._props上的值
    /*
     可以看一下proxy函数定义:
     const sharedPropertyDefinition = {
        enumerable: true,
        configurable: true,
        get: noop,
        set: noop
      }
     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)
      }
    */ 
    if (!(key in vm)) {
      // vm.key --> vm._props.key
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}
// initProps --- validateProp方法
export function validateProp (
  key: string, // props的key
  propOptions: Object, // props对象vm.$options.props
  propsData: Object, // vm.$options.propsData  -- 父组件中传过来的值
  vm?: Component 
): any {
  const prop = propOptions[key]
  const absent = !hasOwn(propsData, key)
  let value = propsData[key]
  // getTypeIndex:判断props.type是否是Boolean类型 是的话返回类型数组中的下标,不是返回-1
  const booleanIndex = getTypeIndex(Boolean, prop.type)
  if (booleanIndex > -1) {
    // 如果父组件没有传该props 并且没有default选项
    if (absent && !hasOwn(prop, 'default')) {
      value = false
      // <Child name></Child> 这种场景的时候的用法 
      // hyphenate: userName --> user-name
    } else if (value === '' || value === hyphenate(key)) {
      // booleanIndex < stringIndex: type:[Boolean, String]  Boolean优先级更高
      const stringIndex = getTypeIndex(String, prop.type)
      if (stringIndex < 0 || booleanIndex < stringIndex) {
        value = true
      }
    }
  }
  // 说明没有传value值,就使用default中的值 
  if (value === undefined) {
    // 获取default属性的值
    value = getPropDefaultValue(vm, prop, key)
    const prevShouldObserve = shouldObserve
    toggleObserving(true)
    // 监听props的value值
    observe(value)
    toggleObserving(prevShouldObserve)
  }
  return value
}
// initMethods方法:遍历,查看名称是否重复,然后调用bind修改methods方法的作用域
function initMethods (vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      // 是否名字已经在props中
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      //isReserved函数是用来判断字符串是否以_或$开头。
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    // 设置methods的作用域为vm,也就是this对象指向当前Vue对象
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}
// initData方法
function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)// 执行data函数
    : data || {}
  // data的构造函数不是Object的话 warning提示
  /*
    export function isPlainObject (obj: any): boolean {
      return _toString.call(obj) === '[object Object]'
    }
  */
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 命名是否重复校验
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      // vm.key --> vm._data.key
      proxy(vm, `_data`, key)
    }
  }
  // 观察data对象,实现双向绑定功能
  observe(data, true /* asRootData */)
}
// initComputed方法
function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  // vm._computedWatchers 存储计算属性的Watcher对象,当计算属性中的数据发生改变是调用该watcher来重新计算值
  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]
    // 计算属性的值可以是对象也可以是函数,函数的话直接当做该属性的getter方法,对象的话是获取该对象的getter属性
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    // 没有get方法,warning提示
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }
    // 添加Watcher依赖,当计算属性中用到的值改变的时候会调用执行该对象的update方法,然后在执行getter函数,从而实现只有计算属性依赖的值发生改变时才会重新求值
    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // 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)) {
      // 把计算属性绑定到vm上面
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        // 重复命名warning
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        // 重复命名warning
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}
// initWatch方法
function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    // 多个handle
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  // 监听该对象,vm.$watch的具体实现后面会讲,这个先知道一个是干啥的就好了,
  return vm.$watch(expOrFn, handler, options)
}

到这里我们就知道了initState方法做了什么事情了:就是把我们定义的Vue对象中的props/data/computed/methods/watch属性和vm绑定起来

  1. 初始化provide

理解一下为什么现在才初始化provide?

因为provide中可能会依赖到前面initState中初始化的数据

// 这个就是给vm._provide赋值而已,方便后面的inject使用
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}
  1. 执行$mount方法

这个方法就是把我们定义的template模板中的字符串转换成render函数,然后生成DOM节点,渲染到el对应的DOM树中, 因为这个方法比较复杂,后面会单独出一篇对应的文章,这里就先理解一下好了。

到这个地方我们就应该已经知道了new Vue() 到底干了什么,后面的话会通过另一个方面来看Vue源码,比如说 跟着data数据的变化去探索数据变化时是如何反应到真实页面中、编译的流程等等。

总结

一定要坚持,你会发现不一样的风景