VUE源码阅读笔记3:vue初始化都发生了哪些故事

蛰伏已久 蛰伏已久 2019-02-13

接上节,我们看一下vue的初始化都做了哪些事情

所在文件:src/instance/init.js

import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'

let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    //给每个vue实例指定一个uid
    vm._uid = uid++

    ....
     
    // 合并 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 {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    
    
    // expose real self
    vm._self = vm
    
    //初始化生命周期
    initLifecycle(vm)
    
    //初始化事件
    initEvents(vm)
    
    //初始化render
    initRender(vm)
    
    //调用钩子函数beforeCreate,此时还没有初始化state,data/props还不能调用
    callHook(vm, 'beforeCreate')
    
    //初始化注入
    initInjections(vm) // resolve injections before data/props
    
    //初始化状态
    initState(vm)
    
    
    initProvide(vm) // resolve provide after data/props
    
    //调用钩子函数created,此时可以调用data/props了
    callHook(vm, 'created')

    ...
    
    //如果实例化Vue时指明了el,则进行挂载
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

初始化分成这几步

  • 合并参数options

  • 初始化声明周期、事件、render

  • 调用钩子函数 beforeCreate

  • 初始化注入、状态

  • 调用钩子函数created

  • 挂载

接下来我们一个一个细看都发生了什么,首先看一下初始化声明周期initLifecycle

export function initLifecycle (vm: Component) {
  const options = vm.$options

  //找到第一个非抽象父组件
  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    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 = {}

  
  //初始化声明周期的标识flag值
  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

声明周期初始化比较简单,接下来看看初始化事件initEvents

export function initEvents (vm: Component) {

  //创建一个空对象,用来存储事件
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  //初始化父组件捕获的事件,_parentListeners其实是父组件模板中写的v-on
  const listeners = vm.$options._parentListeners
  if (listeners) {
    //将父组件模板中注册的事件,放到当前组件实例的listeners里面
    updateComponentListeners(vm, listeners)
  }
}

继续深入看updateComponentListeners

export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, vm)
  target = undefined
}

继续updateListeners,不是十分明白这块,先暂时忽略吧,哈哈

export function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  vm: Component
) {
  let name, def, cur, old, event
  for (name in on) {
    def = cur = on[name]
    old = oldOn[name]
    
    //事件name、once、capture、passive
    event = normalizeEvent(name)
    /* istanbul ignore if */
    if (__WEEX__ && isPlainObject(def)) {
      cur = def.handler
      event.params = def.params
    }
    if (isUndef(cur)) {
      process.env.NODE_ENV !== 'production' && warn(
        `Invalid handler for event "${event.name}": got ` + String(cur),
        vm
      )
    } else if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur)
      }
      add(event.name, cur, event.once, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      old.fns = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}


接下来查看initRender

export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  
  //处理组件slot,返回slot插槽对象
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
  
    //定义响应式属性$attrs,其值为父组件的属性,猜想是不是当父组件的属性变化后,子组件也要跟着更新
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    
    //同上
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

这里defineReactive就是利用观察者模式进行数据绑定的,下一节我们仔细来看

接下来查看调用钩子函数callHook

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  if (handlers) {
    //同名钩子函数可能有多个,数组遍历调用
    for (let i = 0, j = handlers.length; i < j; i++) {
      try {
        //钩子函数中的this,执行组件实例
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}

初始化注入initInjections,不太明白,跳过

接下来看非常重要的initState

export function initState (vm: Component) {
  //应该是用来存储监听
  vm._watchers = []
  const opts = vm.$options
  
  
  if (opts.props) initProps(vm, opts.props)  //如果有属性props,则初始化props
  if (opts.methods) initMethods(vm, opts.methods) //如果有methods,初始化methods
  if (opts.data) {
    initData(vm)   //有data,初始化data
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)  //初始化计算属性
  
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)  //初始化watch
  }
}

可以看到初始化state主要包含这几块

  • 初始化props

  • 初始化methods

  • 初始化data

  • 初始化computed

  • 初始化watch

初始化props

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false)
  }
  
  //遍历props配置对象
  for (const key in propsOptions) {
    keys.push(key)
    
    //获取属性值
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      
      //响应式处理
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      //响应式处理
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      //代理,访问this.**,相当于访问 this._props.**
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

初始化data

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  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') {
    
      //data中属性不能和methods重名 
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      //data中属性不能和props重名
      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)) {
      //代理,访问this.** 相当于访问 this._data.**
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  //观察数据
  observe(data, true /* asRootData */)
}


观察数据observe

export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
   
    //实例化观察者
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

针对data进行了实例化观察者操作,下一节我们来看vue的观察者设计模式。





分享到

点赞(0)