vue源码阅读记录(comming soon)

56 阅读3分钟

一、源码目录:

  • 1、图示:
  • 2、说明:
目录说明
compiler编译相关
corevue核心代码
platforms平台,web和weex
server服务端渲染
sfc.vue文件解析
shared共享代码

二、Vue执行流程:

1、编译入口文件(platforms/web/entry-runtime-with-compiler.js)
  • 引入 platforms/web/runtime/index.js 得到Vue类
  • 缓存Vue的原型链上添加$mount方法,并重写该方法
2、platforms/web/runtime/index.js
  • 引入 core/index.js 得到Vue类
  • 往Vue类的config属性上添加mustUseProp,isReservedTag,isReservedAttr,getTagNamespace,isUnknownElement
  • 扩展Vue类options属性的directives,components
  • 给Vue类添加实例方法__patch__,$mount
3、core/index.js
  • 引入core/instance/index.js得到Vue类
  • 为Vue类添加添加全局API
  • 设置Vue实例属性isServer,ssrContext
  • 设置Vue类属性 FunctionalRenderContext
  • 添加Vue类的版本号
4、初始文件(core/instance/index.js)
  • 声明Vue类
  • 将Vue类传入各种初始化方法initMixin,stateMixin,eventsMixin,lifecycleMixin,renderMixin

三、初始文件core/instance/index.js

  • 溯源大致到此,现在从core/instance/index.js开始走读,可看到initMixin(Vue), stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)
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) // 1
stateMixin(Vue) // 2
eventsMixin(Vue) // 3
lifecycleMixin(Vue) // 4
renderMixin(Vue) // 5

export default Vue

1、initMixin(Vue):

  • 获取options,挂载至 vm.$options上
  • initLifecycle(vm):初始化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
的属性值
  • initEvents(vm): 初始化vm._events, 是父组件在模板中使用v-on或@注册的监听子组件内触发的事件
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
  • initRender(vm):
  // 此方法用于用户使用template模式
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // 此方法用于用户手写render函数
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  • callHook(vm, 'beforeCreate'): 调用 beforeCreate 生命周期钩子
  • initInjections(vm) : 初始化initInjections(inject:依赖和注入)
  • initState(vm): 初始化state, props, methods, computed, watch
  • initProvide(vm): 初始化initProvide(provide:依赖和注入)
  • callHook(vm, 'created'): 调用 created 生命周期钩子
  • vm.mount(vm.mount(vm.options.el):
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }

2、stateMixin(Vue):

  • 定义Vue.prototype.data,Vue.prototype.data, Vue.prototype.props,Vue.prototype.set,set, delete, $watch:
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)
  Vue.prototype.$set = set
  Vue.prototype.$delete = del
  Vue.prototype.$watch = fn (){...}

3、eventsMixin(Vue):

  • 定义on,on, once, off,off, emit 事件方法 image.png

4、lifecycleMixin(Vue):

  • 定义 _update, forceUpdate,forceUpdate, destroy方法 image.png

5、renderMixin(Vue):

  • 定义$nextTick, _render方法
  • _render(): 该方法会调用vm.$createElement创建虚拟DOM,如果返回值vnode不是虚拟DOM类型,将创建一个空的虚拟DOM。
if (!(vnode instanceof VNode)) {
  if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
    warn(
      'Multiple root nodes returned from render function. Render function ' +
      'should return a single root node.',
      vm
    )
  }
  vnode = createEmptyVNode()
}
6、core/instance/index.js初始化到此完毕,小块总结:
  • 其初始化了state, props, methods, computed, watch,destorydestory,data,propsprops,forceUpdate等
  • 对_data, _props使用 Object.defineProperty 添加响应式
  • 设置访问数据代理,访问this.xx,实际上访问的是vm._data[xx], vm._props[xx]
  • 添加event事件系统,实际上初始化的是父组件在模板中使用v-on或@注册的监听子组件内触发的事件
  • 挂载createElement,为后续render返回虚拟DOM做准备。
  • 调用对应的组件生命周期钩子, beforeCreate, created

四、关于$mount()

// platforms\web\entry-runtime-with-compiler.js
Vue.prototype.$mount = function(el?: string | Element, hydrating?: boolean): Component {
  el = el && query(el)
  // query方法,实际上是对el参数做了一个转化,el可能是string 或者 element。如果是string,将返回document.querySelector(el) 
  // ...
  const options = this.$options

  if (!options.render) {
    // render函数不存在
    let template = options.template
    
    if (template) {
       // 如果存在template配置项:
       // 1. template可能是"#xx",那么根据id获取element内容
       // 2. 如果template存在nodeType,那么获取template.innerHTML 内容
    }else {
       // 如果template配置项不存在template,但是存在el:
      /*  
       * 例如: new Vue({ 
       *       el: "#app",
      *       ...
       *  })
       *
       */
      // 那么根据el获取对应的element内容
    }

    // 经过上面的处理,将获取的template做为参数调用compileToFunctions方法
    // compileToFunctions方法会返回render函数方法,render方法会保存到vm.$options下面
    const { render, staticRenderFns } = compileToFunctions(template, {...})
    options.render = render
  }
  return mount.call(this, el, hydrating)
}

  • 由于el参数有两种类型,可能是string 或者 element,调用query方法,统一转化为Element类型
  • 如果没有手写render函数, 那么先获取template内容。再将template做为参数,调用compileToFunctions方法,返回render函数。
  • 最后调用mount.call,这个方法实际上会调用runtime/index.js的mount方法,这个方法将会调用mountComponent方法, 即挂载组件。

五、挂载组件mountComponent()

// core\instance\lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // vue 初始化的时候,无论是template还是 render 最终都会转化为 render函数 进行渲染,
  // 如果没有 render 函数,则会报错
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount') // 调用beforeMount钩子

  // 创建 updateComponent 函数, 之后更新数据 updateComponent 会被触发执行
  let updateComponent
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // 创建一个观察者实例
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false


  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted') // 调用mounted钩子
  }
  return vm
}

  • 调用beforeMount钩子
  • 创建 updateComponent 函数, 之后更新数据 updateComponent 会被触发执行
  • 实例化渲染watcher,执行回调
  • 根据render函数获取VNode节点树
  • 执行update方法,实际上是patch过程,vue会执行diff算法,完成一次渲染
  • 调用mounted钩子

...Loading

...Loading

...Loading

  • 六、创建虚拟节点
  • Dom的创建流程
  • Component的创建流程:
  • Tag的创建流程:

Last、关于vue的生命周期:

  • 1、vue2生命周期图示:

image.png

  • 2、vue3生命周期图示:

image.png

  • 3、vue2、vue3区别
vue2vue3
beforeCreatesetup
createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted

七、总结: