内容乃本人学习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. C++ 核心指南之 C++ P.哲学/基本理念(上)

    C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup.Herb Sutter 等顶尖 C+ 专家创建的一份 C++ 指南.规则及最佳实践.旨在帮助大家正确 ...

  2. 线上问题排查--进程重启失败,最后发现是忘了cd

    背景 我前面写了几篇文章,讲c3p0数据库连接池发生了连接泄露,但是随机出现,难以确定根因,最终呢,为了快速解决问题,我是先写了个shell脚本,脚本主要是检测服务的接口访问日志,看看过去的30s内是 ...

  3. [golang]gin框架接收websocket通信

    前言 WebSocket是一种在单个TCP连接上进行全双工通信的协议.WebSocket让客户端和服务端之间的数据交换变得非常简单,且允许服务器主动向客户端推送数据,并且之后客户端和服务端所有的通信都 ...

  4. 基于C#的无边框窗体阴影绘制方案 - 开源研究系列文章

    今天介绍无边框窗体阴影绘制的内容. 上次有介绍使用双窗体的方法来显示阴影,这次介绍使用API函数来进行绘制.这里使用的是Windows API函数,操作系统的窗体也是用的这个来进行的绘制. 1. 项目 ...

  5. linux 查找目录中的大文件

    find是Linux系统中常用的文件查找命令.它可以在文件系统中查找指定条件的文件,并执行相应的操作.语法格式如下: find [pathname] [options] pathname: 指定查找的 ...

  6. ImGui界面优化:使用图标字体、隐藏主窗口标题栏

    目录 使用图标字体 扩展:内存加载字体 隐藏主窗口标题栏 增加程序退出 改进HideTabBar 窗口最大化 总结 本文主要介绍ImGui应用中的一些界面优化方法,如果是第一次使用ImGui推荐从上一 ...

  7. Vue【原创】时间轴 【time-axis】&【date-axis】

    封装了关于时间轴的组件,有时候统计页面会用到. 效果图: 时间轴分为2种,一种是time-axis:范围选择模式,一种是date-axis:步长选择模式. 代码中涉及到的工具类和图片资源,请移步页面底 ...

  8. pycurl库使用详解

    要使用pycurl库 要初始化一个curl对象 c = pycurl.Curl() 设置选项 c.setopt

  9. 为什么 Python 代码在函数中运行得更快?

    哈喽大家好,我是咸鱼 当谈到编程效率和性能优化时,Python 常常被调侃为"慢如蜗牛" 有趣的是,Python 代码在函数中运行往往比在全局范围内运行要快得多 小伙伴们可能会有这 ...

  10. 「ABC 218」解集

    E 倒流一下,然后把负权边置零后跑 MST 即可. #include<cstdio> #include<vector> #include<algorithm> us ...