欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

vue源码探索之初始化

程序员文章站 2024-01-03 21:47:34
...

vue 初始化

在使用vue的过程中,使用new vue 是个必须的过程,但是在这个过程中发生了些什么,今天主要就是来探索这部分的内容,下面是整体结构图。

创建 Vue 构造函数
初始化 _init 方法
初始化数据部分
初始化事件绑定与解绑的方法
初始化生命周期相关方法和属性变量
初始化页面渲染和虚拟dom相关功能
合并options
初始化生命周期
初始化私有事件绑定和解绑的方法
初始化虚拟dom挂在函数vm._c 和 vm.$createElement
触发生命周期函数
依赖注入相关
初始化vue实例常用属性
依赖注入相关
触发生命周期函数
在Vue的原型上增加data和props和set和delete和$watch
增加提供外部使用的事件绑定相关的函数方法
事件绑定
事件一次绑定
事件触发
事件解绑
为Vue的原型增加生命周期相关方法
强制更新方法
销毁方法
虚拟dom更新渲染方法
vue原型上增加渲染相关的函数
为vue原型增加渲染需要的函数方法
虚拟dom渲染相关
vue源码
vue初始化 instence/index.js
function Vue
initMixin
stateMixin
eventsMixin
lifecycleMixin
renderMixin
resolveConstructorOptions
inieLifecycle
initEvents
initRender
beforeCreate
initInjections
initState
initProvide
created
doState
addEventFunc
$on
$once
$emit
$off
addLifecycleFunc
$forceUpdate
$destroy
_update
addRenderFunc
installRenderHelpers
$nextTick
_render

开始部分

通常我们使用下面这段代码来生成一个vue实例,用于页面渲染,其中我们提供的配置对象中包含vue常见的几个属性,el、data、computed、methods等

var demo = new Vue({
  el: '#demo',
  props: {
    name: {
      type: String,
      default: 'jerry'
    }
  },
  data: {
    treeData: data
  },
  computed: {
    dataLength () {
      return this.treeData.children.length
    }
  },
  methods: {
    clickLength () {
      console.log('click data leength: ', this.dataLength())
    }
  }
})

vue 的开始部分很简单,只有一个叫做Vue的构造函数,在new Vue的过程中调用Vue原型上的_init方法,对新建的对象进行初始化

function Vue (options) {
// 如果直接执行Vue,当作一个函数来执行的情况下 开发环境中给出警告 需要通过new 来创建对象, 并且在 ES6 语法中此时的this为空,代码也会报错 如下图所示
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源码探索之初始化
有了Vue的构造函数之后,在真正开始创建对象之前还需要初始化这个构造函数本身的一些属性和方法

构造函数初始化部分

构造函数的初始化分为以下五个部分

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

initMixin(Vue)

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    // 场景不同分别执行不同的方法,根据options内部的_isComponent值确定,具体情况后面会讲到,在使用new vue创建对象的过程中使用的是第二个函数,它会将继承过来的options与当前的options合并处理
    initInternalComponent(vm, options)||resolveConstructorOptions(vm.constructor)
    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')
    vm.$mount(vm.$options.el)
  }
}

这个函数的作用是初始化 Vue 的 _init 方法,该方法内部实现的功能主要由以下几部分

  • 合并配置项,得到合并之后的 options,用于最终创建Vue的示例,主要使用的方法有两个:initInternalComponent 和 resolveConstructorOptions
  • 初始化生命周期
    initLifecycle(vm),这个函数的作用是在vue示例上创建需要后续需要使用到的parent children root refs等属性,并且将vue生命周期状态相关的变量初始化
vm.$parent = parent // 父节点
vm.$root = parent ? parent.$root : vm // 跟节点

vm.$children = [] // 子节点列表
vm.$refs = {} // refs 引用数组

vm._watcher = null // 观察者对象
vm._inactive = null
vm._directInactive = false
vm._isMounted = false // 是否挂载
vm._isDestroyed = false // 是否销毁
vm._isBeingDestroyed = false // 是否正在销毁
  • 初始化绑定事件
    initEvents(vm)
    这个函数的作用是使用on once remove 将从父级继承来的事件绑定在元素上
    实现once 的方法是使用必报保存需要执行的目标元素,并返回真正的绑定函数,并且在执行之后立即销毁这个绑定的事件
function createOnceHandler (event, fn) {
  const _target = target
  return function onceHandler () {
    const res = fn.apply(null, arguments)
    if (res !== null) {
      _target.$off(event, onceHandler)
    }
  }
}

在new vue的过程中,因为options项中不会有事件监听的信息,所以不会事件的绑定,并且此时还未执行eventsMixin创建事件绑定所需要的方法

  • 初始化render函数
    initRender(vm)
    这个方法主要实现的将vue示例渲染到dom元素上的过程,包括
    • 初始化插槽和作用域插槽 $slots 和 $scopedSlots
    • 初始化虚拟节点 _vnode = null
    • 初始化创建 虚拟dom 元素的方法 vm._c 和 vm.$createElement
      这部分内容较多,放到后面作为专门探索的部分
    • 初始化设置元素的属性 $attrs
    • 初始化设置元素监听的事件 $listeners
  • 触发vue生命周期钩子函数 beforeCreate
callHook(vm, 'beforeCreate')
  • 初始化依赖注入 (依赖注入部分的内容放到后面)
  • initData 初始化vue对象的数据,包括以下五个方面
    • initProps(vm, opts.props)
    • initMethods(vm, opts.methods)
    • initData(vm)
      observe(data, true) 使用observe方法使data中的数据编程响应式的
    • initComputed(vm, opts.computed)
    • initWatch(vm, opts.watch)
    • toggleObserving 方法能够切换数据是否响应的状态,在初始化props的过程中需要关闭响应

  • 初始化provide
  • 触发vue生命周期 created 函数
    callHook(vm, ‘created’)
  • 最后执行vue的mountvuemount将vue对象挂载到el指定的dom元素上
    vm.mount(vm.mount(vm.options.el)

stateMixin(Vue)

这一步的作用是在Vue的原型上增加datadata、props、setset、delete、$watch

Vue.prototype.$watch = function (expOrFn, cb, options) {
// expOrFn 分为两种情况 一种是字符串 一种是一个函数
// 如果是字符串 则截取字符串中的变量,得到getter
this.getter = parsePath(expOrFn)
// 如果是函数 则函数执行结果为 getter
this.getter = expOrFn

// cb 回调函数也分两种情况 对应两种写法
// 当cb是一个函数时 例如 v.$watch('age', () => {console.log(arguments), {}}),vue直接创建一个新的 watcher
new Watcher(vm, expOrFn, cb, options)
// 当cb 时一个{}类型的对象时,对应另一种写法,例如
v.$watch('age', {handler: () => { console.log(111)}, deep: true}, {})
// vue 会将cb这个对象中的handler取出来作为真正的回调函数,重新掉一遍函数$watch
vm.$watch(expOrFn, cb.handler, options)
}
// cb对象中handler也可以时一个字符串,对应的是vm对象上的某个方法,如下图

cb对象中handler也可以时一个字符串,对应的是vm对象上的某个方法,如下图
vue源码探索之初始化

eventsMixin(Vue)

这一步给Vue的原型增加了事件绑定和解绑相关的工具函数

  • $on
(vm._events[event] || (vm._events[event] = [])).push(fn)
  • $once
    使用闭包保存下vue示例对象vm 绑定一个新的函数,函数内部执行绑定的函数并且解绑
  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }
  • $emit
    将 vm._events[event] 中存储的方法依次执行一遍
  • $off
    如果off方法没有提供需要解绑的函数,则情况事件下所有的方法,否则只解绑指定的方法
let cbs = vm._events[event]
// 不传需要解绑的方法 视为解绑此事件的所有事件函数
if (!fn) {
  vm._events[event] = null
    return vm
  }
 // 否则解绑指定的事件函数
 let cb
 let i = cbs.length
 while (i--) {
 cb = cbs[i]
 if (cb === fn || cb.fn === fn) {
   cbs.splice(i, 1)
   break
 }
}

lifecycleMixin(Vue)

这一步为Vue的原型增加生命周期相关方法

  • _update
    setActiveInstance(vm): 将全局变量 activeInstance 临时保存并替换为当前的vue实例 vm ,并返回恢复原来 activeInstance 原值的方法
export function setActiveInstance(vm: Component) {
// 临时存储原始值
  const prevActiveInstance = activeInstance
  activeInstance = vm
// 对外提供复原原始值的方法
  return () => {
    activeInstance = prevActiveInstance
  }
}

这个方法跟虚拟dom关系比较紧密,放到虚拟dom部分详细探索

  • $forceUpdate
    调用vue实例上的watcher对象,强制跟新所有依赖
  • $destroy
    触发生命周期钩子函数 beforeDestroy 和 destroyed,并且释放vue对象的引用
vm.$off()
// remove __vue__ reference
if (vm.$el) {
  vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
  vm.$vnode.parent = null
}

renderMixin(Vue)

这一步的作用是为vue原型上增加渲染相关的函数

  • installRenderHelpers(Vue.prototype)
    为vue原型增加渲染需要的函数方法,作为后续研究的一项暂时保留
  • $nextTick
    内部实现方式有四种,依赖执行环境的支持情况:
    • Promise
    • MutationObserver
    • setImmediate
    • setTimeout(callback, 0)
  • _render
    创建虚拟 dom 节点 vnode,暂时保留

上一篇:

下一篇: