Vue - 源码解析

avatarplhDigital nomad
从开头到结尾,感觉有点像黑客破解网站一样😄。

前言

从年初开始,用了将近6个月的vue,起初很懵逼,但是撸完一遍vue官方教程后,会用一点,但是vuex我还是很懵逼,偶然看到一篇博客解析vue源码,很受启发,决定挖坑,我也要做vue源码解析,我觉得我也可以做到。虽然我仅仅只是知道vueMVVM双向绑定是基于Object.definedPrototype()来设置set 和get 。

版本

version": "2.5.17-beta.0

主线索

当然就是vue这个对象了。先看web端的吧,服务器端的不看先。Vue.js\src\platforms\web\entry-runtime.js这就是我第一次见到的最表层的vue对象了, image

进一步深入,Vue对象在runtime/index.js文件中引入,

image

点开一看,发现依然在更深层次core中的index.js文件中引入 image

继续点开,终于发现了Vue的构造函数,结构如下图。它位于Vue.js\src\core\instance\index.js中, image

构造函数,做了啥???不难看出,this instanceof Vue,this即是vue构造函数本身,vue构造函数是实例化的 new Vue所返回的vue实例,this指向vue实例,实例原型必须是有vue,否则报错,意思就是强制使用new 实例化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)
}

image

  • 首先看 initMixin函数,他到底做了啥??他为vue扩展了_init方法

  • stateMixin到底做了啥??他为vue扩展了$watch方法,

  • 接下来是eventsMixin函数,他为vue扩展了$on$once$off,$emit方法;

  • 接下来是lifecycleMixin函数,他为vue扩展了_update,forceUpdate,destroy,方法,表面意思就是声明周期混合。

  • 接下来是renderMixin函数,他为vue扩展了$nextTick_render,方法,

好吧,拥有这些初始化方法的vue构造函数成为了一个最基本的vue对象。 下面的百度脑图清晰描述vue构造函数下面的基本方法 其中_开头的是内部方法,而$开头的方法是外部方法,供外部使用。

image

那么vue构造函数就基本方法已经清楚,那么接下来从初始化一个vue实例开始看,vue源码。

import App from './App.vue';
window.app = new Vue({
  data: {
    msg: 'sfdf'
  },
  render (h) {
    return h('p', this.msg)
  }
}).$mount('#root')
setTimeout(() => {
  app.msg = '232423'
}, 1000)

在浏览器控制台打印app,发现有msg这条属性,同时有set和get image 打开源码发现首先执行下面代码,option就是以下内容

this._init({
  data: {
    msg: 'sfdf'
  },
  render (h) {
    return h('p', this.msg)
  }
})

,那么下一步查看_init方法,

// 只截取关键代码
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
    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
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    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)
    }
  }
}
  • 首先 vm=this,而this指向vue实例,uid是一个自增id,初始化时刻自增。 vm.isVue一个属性,不知道做啥用 然后是
vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)

字面意思就知道,是合并option属性的,mergeOption函数接受3个参数,第一个是父,第二个是子,第三个是vue对象本身。

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

image

上面这段函数可以看出,他会合并父子属性,而后合并子属性的宏。 image

之后就是核心部分,

    vm._self = vm                         // 初始化的时候,自己就是自己
    initLifecycle(vm)                    // 执行初始化生命周期
    initEvents(vm)                          // 初始化事件
    initRender(vm)                       // 初始化渲染
    callHook(vm, 'beforeCreate')                  // 初始化  执行  beoforeCreate   钩子
    initInjections(vm)               // resolve injections before data/props
    initState(vm)                   // 初始化状态
    initProvide(vm) // resolve provide after data/props   初始化提供???
    callHook(vm, 'created')                      // 调用created钩子

首先看 initLifeCycle 函数

  vm.$parent = parent      // 将传入参数的parent赋值到vm实例的parent上面,parent是对外暴露属性。
  vm.$root = parent ? parent.$root : vm// 根节点如果是parent,则是它的根节点,否则根节点就是我本身
  vm.$children = []    // 子节点是数组
  vm.$refs = {}     // 用于访问子组件 中 定义了ref=“name”,
  vm._watcher = null       // 观察者
  vm._inactive = null       // 活动的
  vm._directInactive = false       // 啥意思,草
  vm._isMounted = false        // 是否挂载
  vm._isDestroyed = false      // 是否被销毁
  vm._isBeingDestroyed = false     // 是否正在被销毁

明显就是为vue构造对象,初始化一些实例,

接下来是initEvents函数

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

貌似是父子组件在通讯的时候用到,this.emit('234')方法;

接下来是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
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    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)
  }
}

初始化渲染函数,查看其中的$createElement,这个属性用于创建虚拟节点, image

vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)    // 第一个参数默认是 vm
vm.$createElement('p','12321')          // 会输出一个虚拟的<p>123</p> 节点

下面查看createElement方法

export function createElement (
  context: Component,                 // vm实例
  tag: any,        // 标签名  eg:   p,div,span
  data: any,         // 值
  children: any,      // 子节点,类似于domcument.body.children // []
  normalizationType: any,     // 他应该是某个数字吧。
  alwaysNormalize: boolean      // 是否需要初始化
): VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}

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++) { // beforeCreate, 遍历他所有的方法
      try {
        handlers[i].call(vm)     // 为每一个方法调用的时候绑定this。方法内部的this就是vm,前提是你用的普通函数,才有this,箭头函数是没有this的。
      } catch (e) {
        handleError(e, vm, `${hook} hook`)  // 否则报错
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)     // 如果有钩子事件,就发送给他的父组件。
  }
  popTarget()
}

接下来是initInjections函数,

export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)  // 另一个函数,传入vm实例,
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}

暂时看不懂,下一个

initState函数

export function initState (vm: Component) {    // 用于初始化一些状态
  vm._watchers = []    // 设置一个要被观察的空对象,
  const opts = vm.$options      //  opts就是vm的传入的参数
  initProps(vm, opts.props)    //  初始化prop,也就是父组件传入的参数
  initMethods(vm, opts.methods)    //  初始化一些可以在模板中,使用的自定义函数
  initData(vm)    // 初始化data,如果没有,就设置成根节点 传入`{}`
  initComputed(vm, opts.computed)    // 初始化计算属性 computed
  initWatch(vm, opts.watch)   // 初始化要观察的对象
}

挑一两个来说吧,

  • 首先说initdata 用于初始化数据模型,下面是删减过的函数initdata
function initData (vm: Component) {
  let data = vm.$options.data    // 拿到data函数返回的一个新的对象(数据模型Model)
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // proxy data on instance
  const keys = Object.keys(data)    // 枚举所有属性的键值
  const props = vm.$options.props    // 检查data是否和prop重复要用到
  const methods = vm.$options.methods   // 检查data是否和methods中的属性是否重,要用到
  let i = keys.length
  while (i--) {     // 遍历每一个data属性,
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {   // 如果  methods有 和 data的属性 重复就报错
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {    // 如果  prop 有 和 data的属性 重复就报错
      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)) {
      proxy(vm, `_data`, key)   // 如果都可以就代理vm对象  中的所有data属性
    }
  }
   // observe data   ,data 和 prop对象不能有重复的属性,
  observe(data, true /* asRootData */)    // 最主要的函数,观察data对象,
}

proxy函数

const sharedPropertyDefinition = {
  enumerable: true,    // 可枚举
  configurable: true,    // 可设置
  get: noop,      // function(){} 空函数
  set: noop       // function(){}  空函数
}
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {   // 空函数的get返回这个属性的值,代理优化了值,this._data.msg = this.msg
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {  // 空函数的set设置函数的值,代理优化了值,this._data.msg = this.msg
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

observe 可以说是vue的核心,下面就是obserber函数。

// 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
}

这个没啥好看,虽然我也看不太懂,但是关键在于Observe对象

// Observe
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)    // 如果是数组
    } else {
      this.walk(value)     // 如果是对象
    }
  }
  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {      // 如果是对象,用object.keys 来遍历对象所有可枚举属性,然后调用defineReactive函数,并传入对象,和他的键值。
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {      // 如果是数组,遍历所有数组,重新调用该方法,直到它全部是对象位置,真的很巧妙。
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])     // 如果是数组就递归自身。
    }
  }
}
/**
 * 设置响应式函数
 */
export function defineReactive (
  obj: Object,    // 对象
  key: string,     // 键值
  val: any,       // 值
  customSetter?: ?Function,     // set函数?
  shallow?: boolean    // 浅??
) {
  const dep = new Dep()   // 构造dep对象
  const property = Object.getOwnPropertyDescriptor(obj, key)    // 用于获取obj对象的描述,也就是获取传入对象的一些自定义属性。
  if (property && property.configurable === false) {
    return
  }
  // cater for pre-defined getter/setters
  const getter = property && property.get    // 获取传入的data原本的get属性,
  const setter = property && property.set    // 获取传入的data原本的set属性,
  if ((!getter || setter) && arguments.length === 2) {      
    val = obj[key]
  }
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

image

Reference