草庐IT

Vue 2.x源码学习:应用初始化大致流程

欲速则不达 2023-03-28 原文

内容乃本人学习Vue2源码的一点笔记,若有错误还望指正。
源码版本:
vue: 2.6
vue-loader: 13.x
vue-template-compiler: 2.6

相关学习笔记:

我们使用vue-cli搭建vue 2.x项目时,大致由如下代码来做一个vue应用的初始化:

import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App),
}).$mount("#app");

我们可以就从此处开始对Vue的认识。可以看到,这里表面上只做了一个简单的工作,就是通过new操作创建了一个vue的实例,并传递了一个配置项对象,该对象包含了一个render方法。

根据这个调用,我们找到src/core/instance/index.js文件,内容如下:

// src/core/instance/index.js
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)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

内容也很直观,这里定义了一个只接受new构造调用的Vue Function,并对Vue进行了一系列的混入操作。

再粗浅地看一下这些Mixin都做了什么,可以看到是往Vue的prototype对象上挂了一些属性和方法。

大致如下:

Vue.prototype
|- initMixin
    |- _init(options?: Object)
|- stateMixin
    |- $data
    |- $props
    |- $set(target: Array<any> | Object, key: any, val: any): any   <- ../observer/index
    |- $delete(target: Array<any> | Object, key: any)               <- ../observer/index
    |- $watch(expOrFn: string | Function, cb: any, options?: Object): Function
|- eventMixin
    |- $on(event: string | Array<string>, fn: Function): Component
    |- $once(event: string, fn: Function): Component
    |- $off(event?: string | Array<string>, fn?: Function): Component
    |- $emit(event: string): Component
|- lifecycleMixin
    |- $_update(vnode: VNode, hydrating?: boolean)
    |- $forceUpdate()
    |- $destrouy()
|- renderMixin
    |- $nextTick(fn: Function)
    |- _render(): VNode

Vue的函数体中,调用了一个_init的方法,并将参数传入,可以看到,_init方法是在initMixin中定义的。

继续看_init方法的定义:

// src/core/instance/init.js
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)
    }
}

见名知意,这个函数是对vue实例做一系列的初始化操作。

  1. 获取vue实例的构造器以及父级构造器(依次递归)上的配置项,以及参数传递进来的配置项,在加上实例自带的属性,都合并到一起,挂在实例的$option属性身上

  2. 将vue实例自身挂在_renderProxy属性上

  3. 初始化数据和方法前做一些准备工作

    1. initLifecycle:初始化生命周期
    2. initEvents:初始化事件
    3. initRender:初始化render
    4. 触发beforeCreate钩子
  4. 初始化数据和方法

    1. initInjections:处理$options.inject,对注入的数据做响应式处理

    2. initState做的几件事

      1. initProps:对$options.props做响应式处理

      2. initMethods:对$options.methods对象做处理,将所有的方法直接挂在实例对象上,并将方法的this绑定到vue实例对象vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)

      3. initData:对$options.data进行observeobserve(data, true /* asRootData */),继续追踪可以看到observe方法是对data进行响应式处理,返回一个Observer实例

        // src/core/boserver/index.js
        export class Observer {
          value: any;
          dep: Dep;
          vmCount: number; // number of vms that have 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)) {
              if (hasProto) {
                protoAugment(value, arrayMethods)
              } else {
                copyAugment(value, arrayMethods, arrayKeys)
              }
              this.observeArray(value)
            } else {
              this.walk(value)
            }
          }
        
          /**
           * Walk through all properties and convert them into
           * getter/setters. This method should only be called when
           * value type is Object.
           */
          walk (obj: Object) {
            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])
            }
          }
        }
        
      4. initComputed:处理计算属性$options.computed

        给每个计算属性创建Watcher实例

        // src/core/instance/state.js
        const computedWatcherOptions = { lazy: true }
        
        function initComputed(vm: Component, computed: Object) {
          // ...
          const watchers = (vm._computedWatchers = Object.create(null))
          // ...
          const isSSR = isServerRendering()
          
          for (const key in computed) {
            const userDef = computed[key]
            const getter = isFunction(userDef) ? userDef : userDef.get
            if (__DEV__ && getter == null) {
              warn(`Getter is missing for computed property "${key}".`, vm)
            }
        
            if (!isSSR) {
              // create internal watcher for the computed property.
              watchers[key] = new Watcher(
                vm,
                getter || noop,
                noop,
                computedWatcherOptions
              )
            }
            
            if (!(key in vm)) {
              defineComputed(vm, key, userDef)
            }
            // ...
          }
          
          // ...
        }
        
        export function defineComputed (
          target: any,
          key: string,
          userDef: Object | Function
        ) {
          const shouldCache = !isServerRendering()
          if (typeof userDef === 'function') {
            sharedPropertyDefinition.get = shouldCache
              ? createComputedGetter(key)
              : createGetterInvoker(userDef)
            sharedPropertyDefinition.set = noop
          } else {
            // ...
          }
          // ...
          Object.defineProperty(target, key, sharedPropertyDefinition)
        }
        
        function createComputedGetter (key) {
          return function computedGetter () {
            const watcher = this._computedWatchers && this._computedWatchers[key]
            if (watcher) {
              if (watcher.dirty) {
                watcher.evaluate()
              }
              if (Dep.target) {
                watcher.depend()
              }
              return watcher.value
            }
          }
        }
        

        可以看到创建Watcher实例时传入一个配置项{ lazy: true },再看Watcher的构造器中的代码,即默认watcher.dirtytrue,所以执行watcher.evaluate()watcher.get()

        watcher.get()会去执行计算方法或者计算属性的get()方法,即this.getter.call(vm, vm)

        // src/core/observer/watcher.js
        constructor (
            vm: Component,
            expOrFn: string | Function,
            cb: Function,
            options?: ?Object,
            isRenderWatcher?: boolean
          ) {
            this.vm = vm
            if (isRenderWatcher) {
              vm._watcher = this
            }
            vm._watchers.push(this)
            // options
            if (options) {
              // ...
              this.lazy = !!options.lazy
              // ...
            } else {
              // ...
            }
            // ...
            this.dirty = this.lazy // for lazy watchers
            // ...
        }
        
        evaluate () {
            this.value = this.get()
            this.dirty = false
        }
        
        get() {
            pushTarget(this)
            let value
            const vm = this.vm
            try {
              value = this.getter.call(vm, vm)
            } catch (e: any) {
              if (this.user) {
                handleError(e, vm, `getter for watcher "${this.expression}"`)
              } else {
                throw e
              }
            } finally {
              // "touch" every property so they are all tracked as
              // dependencies for deep watching
              if (this.deep) {
                traverse(value)
              }
              popTarget()
              this.cleanupDeps()
            }
            return value
        }
        
        depend() {
            let i = this.deps.length
            while (i--) {
              this.deps[i].depend()
            }
        }
        
      5. initWatch:处理自定义监听$options.watch

        执行了$watch方法,可以先看下它的定义:

        // src/core/instance/state.js
        Vue.prototype.$watch = function (
            expOrFn: string | (() => any),
            cb: any,
            options?: Record<string, any>
          ): Function {
            const vm: Component = this
            if (isPlainObject(cb)) {
              return createWatcher(vm, expOrFn, cb, options)
            }
            options = options || {}
            options.user = true
            const watcher = new Watcher(vm, expOrFn, cb, options)
            if (options.immediate) {
              const info = `callback for immediate watcher "${watcher.expression}"`
              pushTarget()
              invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
              popTarget()
            }
            return function unwatchFn() {
              watcher.teardown()
            }
         }
        

        可以看到也是创建了一个Watcher实例对象。

    3. initProvide:处理$options.provide,将provide的数据(或者provide执行后的数据)挂在实例的_provide属性上

    4. 触发created钩子

  5. 最后执行vm.$mount方法,执行挂载流程,由于挂载的方式由平台决定,所以$mount的方法并未定义在src/core中;web端的$mount方法定义在src/platforms/web/runtime/index.js中。

    // src/platforms/web/runtime/index.js
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && inBrowser ? query(el) : undefined
      return mountComponent(this, el, hydrating)
    }
    

    调用的mountComponent(this, el, hydrating)定义在src/core/instance/lifecycle.js中。

    // src/core/instance/lifecycle.js
    export function mountComponent (
      vm: Component,
      el: ?Element,
      hydrating?: boolean
    ): Component {
      vm.$el = el
      if (!vm.$options.render) {
        vm.$options.render = createEmptyVNode
        if (process.env.NODE_ENV !== 'production') {
          /* istanbul ignore if */
          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')
    
      let updateComponent
      /* istanbul ignore if */
      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)
        }
      }
    
      // we set this to vm._watcher inside the watcher's constructor
      // since the watcher's initial patch may call $forceUpdate (e.g. inside child
      // component's mounted hook), which relies on vm._watcher being already defined
      new Watcher(vm, updateComponent, noop, {
        before () {
          if (vm._isMounted && !vm._isDestroyed) {
            callHook(vm, 'beforeUpdate')
          }
        }
      }, true /* isRenderWatcher */)
      hydrating = false
    
      // manually mounted instance, call mounted on self
      // mounted is called for render-created child components in its inserted hook
      if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted')
      }
      return vm
    }
    

    见名知意,是对挂载的处理:

    1. 拿到el放在vm.$el上

    2. 确认是否有vm.$options.render,没有则赋值创建一个空的VNode实例的方法

    3. 触发beforeMount钩子

    4. 创建一个新的Watcher实例,用于实例更新后触发重新渲染

      updateComponent = () => {
        vm._update(vm._render(), hydrating)
      }
      

      并传递一个before方法,用于在组件更新前触发beforeUpdate钩子

    5. 触发mounted钩子

Vue应用初始化大致就是这样一个流程

有关Vue 2.x源码学习:应用初始化大致流程的更多相关文章

  1. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  2. ruby-on-rails - 未初始化的常量 Psych::Syck (NameError) - 2

    在我的gem中,我需要yaml并且在我的本地计算机上运行良好。但是在将我的gem推送到ruby​​gems.org之后,当我尝试使用我的gem时,我收到一条错误消息=>"uninitializedconstantPsych::Syck(NameError)"谁能帮我解决这个问题?附言RubyVersion=>ruby1.9.2,GemVersion=>1.6.2,Bundlerversion=>1.0.15 最佳答案 经过几个小时的研究,我发现=>“YAML使用未维护的Syck库,而Psych使用现代的LibYAML”因此,为了解决

  3. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  4. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  5. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  6. ruby-on-rails - 未在 Ruby 中初始化的对象 - 2

    我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调

  7. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  8. ruby-on-rails - ActionController::RoutingError: 未初始化常量 Api::V1::ApiController - 2

    我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc

  9. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  10. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

随机推荐