内容乃本人学习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. SpringMVC源码学习之request处理流程

    目的:为看源码提供调用地图,最长调用逻辑深度为8层,反正我是springMVC源码学习地址看了两周才理出来的. 建议看完后还比较晕的,参照这个简单的模型深入底层,仿SpringMVC自己写框架,再来理 ...

  2. SpringBoot源码学习3——SpringBoot启动流程

    系列文章目录和关于我 一丶前言 在 <SpringBoot源码学习1--SpringBoot自动装配源码解析+Spring如何处理配置类的>中我们学习了SpringBoot自动装配如何实现 ...

  3. Vue源码学习02 初始化模块init.js

    接上篇,我们看到了VUE分了很多模块(initMixin()stateMixin()eventsMixin()lifecycleMixin()renderMixin()),通过使用Mixin模式,都是 ...

  4. vue 2.0源码学习笔记—new Vue ( Vue 初始化过程 )

    new Vue(Vue 初始化) 一个vue实例化到底经历了什么?已下是博主自己的总结,不正确的地方请指出,谢谢~ 一.简述 从使用角度来看,挂载的顺序如下 1. $slots 2. $scopedS ...

  5. ibatis源码学习2_初始化和配置文件解析

    问题在详细介绍ibatis初始化过程之前,让我们先来思考几个问题. 1. ibatis初始化的目标是什么?上文中提到过,ibatis初始化的核心目标是构造SqlMapClientImpl对象,主要是其 ...

  6. Vben Admin 源码学习:项目初始化

    0x00 前言 Vue-Vben-Admin 是一个免费开源的中后台模版.使用了最新的vue3,vite2,TypeScript等主流技术开发,开箱即用的中后台前端解决方案考. 本系列本着学习参考的目 ...

  7. ASP.NET Core MVC 源码学习:MVC 启动流程详解

    前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...

  8. vue虚拟DOM源码学习-vnode的挂载和更新流程

    代码如下: <div id="app"> {{someVar}} </div> <script type="text/javascript& ...

  9. Python源码学习之初始化(三)-PyDictObject的初始化

    先来看它的定义 typedef struct _dictobject PyDictObject; struct _dictobject { PyObject_HEAD Py_ssize_t ma_fi ...

  10. ThinkPHP5.0源码学习之框架启动流程

    ThinkPHP5框架的启动流程图如下: ThinkPHP5的启动流程按照文件分为三步: 1.请求入口(public/index.php) 2.框架启动(thinkphp/start.php) 3.应 ...

随机推荐

  1. 通配符SSL证书自动续签自动部署方案

    最开始接触 https 的时候一直是使用的 阿里云和腾讯云的免费 SSL证书,免费的SSL证书用了几年后,慢慢的部署https证书的项目越来越多,时间久了发现每个网站都需要一个 SSL证书,每个SSL ...

  2. JVM篇(一) 什么是JVM,它有什么用

    一.JVM的组成 1. JVM由那些部分组成,运行流程是什么? 从图中可以看出 JVM 的主要组成部分 ClassLoader(类加载器) Runtime Data Area(运行时数据区,内存分区) ...

  3. rpm安装21c单实例数据库

    linux 7.6 使用rpm安装21c单实例数据库 一.基础环境配置 1.1 关闭防火墙 systemctl stop firewalld systemctl disable firewalld s ...

  4. asp.net core之路由

    在 ASP.NET Core 中,路由是一个非常重要的概念,它决定了如何将传入的请求映射到相应的处理程序.本文将详细介绍 ASP.NET Core 中的路由系统,包括路由的基本原理.路由模板.路由参数 ...

  5. 彻底弄懂js中this指向(包含js绑定、优先级、面试题详解)

    为什么要使用this 在javascript中,this可谓是无处不在,它可以用来指向某些元素.对象,在合适的地方使用this,能让我们减少无用代码的编写 var user = {   name: & ...

  6. 古早wp合集

    0x00 首先非常感谢大家阅读我的第一篇.本文章不仅仅是题解,一些细枝末节的小问题也欢迎大家一起解答. 小问题的形式如Qx:xxxxxxx? 欢迎发现小问题并讨论~~ N1nE是本人另外一个名字,目前 ...

  7. C#程序随系统启动例子 - 开源研究系列文章

    今天讲讲C#中应用程序随系统启动的例子. 我们知道,应用程序随系统启动,都是直接在操作系统注册表中写入程序的启动参数,这样操作系统在启动的时候就根据启动参数来启动应用程序,而我们要做的就是将程序启动参 ...

  8. Java将MySQL建表语句转换为SQLite的建表语句

    Java将MySQL建表语句转换为SQLite的建表语句 源代码: package com.fxsen.platform.core.util; import java.util.HashMap; im ...

  9. 浅谈基于QT的截图工具的设计与实现

    本人一直在做属于自己的一款跨平台的截图软件(w4ngzhen/capi(github.com)),在软件编写的过程中有一些心得体会,所以有了本文.其实这篇文章酝酿了很久,现在这款软件有了雏形,也有空梳 ...

  10. 如何使用Grid中的repeat函数

    在本文中,我们将探索 CSS Grid repeat() 函数的所有可能性,它允许我们高效地创建 Grid 列和行的模式,甚至无需媒体查询就可以创建响应式布局. 不要重复自己 通过 grid-temp ...