vue 入口

从vue的构建过程可以知道,web环境下,入口文件在 src/platforms/web/entry-runtime-with-compiler.js(以Runtime + Compiler模式构建,vue直接运行在浏览器进行编译工作)

import Vue from './runtime/index'

下一步,找到./runtime/index,发现:

import Vue from 'core/index'

下一步,找到core/index,发现:

import Vue from './instance/index'

按照这个思路找,最后发现:Vue是在'core/index'下定义的

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

引入方法,用function定义了Vue类,再以Vue为参数,调用了5个方法,最后导出了vue

可以进入这5个文件查看相关方法,主要就是在Vue原型上挂载方法,可以看到,Vue 是把这5个方法按功能放入不同的模块中,这很利于代码的维护和管理

initGlobalAPI

回到core/index.js, 看到除了引入已经在原型上挂载方法后的 Vue 外,还导入initGlobalAPI 、 isServerRendering、FunctionalRenderContext,执行initGlobalAPI(Vue),在vue.prototype上挂载$isServer、$ssrContext、FunctionalRenderContext,在vue 上挂载 version 属性,

看到initGlobalAPI的定义,主要是往vue.config、vue.util等上挂载全局静态属性和静态方法(可直接通过Vue调用,而不是实例调用),再把builtInComponents 内置组件扩展到Vue.options.components下。此处大致了解下它是做什么的即可,后面用到再做具体分析。

new Vue()

一般我们用vue都采用模板语法来声明:

<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})

new Vue()时,vue做了哪些处理?

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)
}

看到vue只能通过new实例化,否则报错。实例化vue后,执行了this._init(),该方法在通过initMixin(Vue)挂载在Vue原型上的,找到定义文件core/instance/init.js 查看该方法。

_init()

一开始在this对象上定义_uid、_isVue,判断options._isComponent,此次先不考虑options._isComponenttrue的情况,走else,合并options,接着安装proxy, 初始化生命周期,初始化事件、初始化渲染、初始化data、钩子函数等,最后判断有vm.$options.el则执行vm.$mount(),即是把el渲染成最终的DOM

初始化data 数据绑定

_init()中通过initState()来绑定数据到vm上,看下initState的定义:

export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}

获取options,初始化propsmethodsdata、计算属性、watch绑定到vm上,先来看下initData()是如何把绑定data的:

  • 先判断data是不是function类型,是则调用getData,返回data的自调用,不是则直接返回data,并将data赋值到vm._data

  • data、props、methods,作个校验,防止出现重复的key,因为它们最终都会挂载到vm上,都是通过vm.key来调用

  • 通过proxy(vm, `_data`, key)把每个key都挂载在vm

    export function proxy (target: Object, sourceKey: string, key: string) {
    sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    const sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
    }

    proxy() 定义了一个get/set函数,再通过Object.defineProperty定义\修改属性(不了解Object.defineProperty()的同学可以先看下文档,通过Object.defineProperty()定义的属性,通过描述符的设置可以进行更精准的控制对象属性),将对target的key访问加了一层get/set,即当访问vm.key时,实际上是调用了sharedPropertyDefinition.get,返回this._data.key,这样就实现了通过vm.key来调用vm._data上的属性

  • 最后,observe(data, true /* asRootData */) 观察者,对数据作响应式处理,这也是vue的核心之一,此处先不分析

$mount() 实例挂载

Vue的核心思想之一是数据驱动,在vue下,我们不会直接操作DOM,而是通过js修改数据,所有逻辑只需要考虑对数据的修改,最后再把数据渲染成DOM。其中,$mount()就是负责把数据挂载到vm,再渲染成最终DOM

接下来将会分析下vue是如何把javaScript对象渲染成dom元素的,和之前一样,主要分析主线代码

预处理

还是从src/platform/web/entry-runtime-with-compiler.js 文件入手,

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
···
}

首先将原先原型上的$mount方法缓存起来,再重新定义$mount

  • 先判断 elel 不能是 body, html ,因为渲染出来的 DOM 最后是会替换掉el
  • 判断render方法, 有的话直接调用mount.call(this, el, hydrating)
  • 没有render方法时:
  1. 判断有没有template ,有则用compileToFunctions将其编译成render方法
  2. 没有template时,则查看有没有el,有转换成template,再用compileToFunctions将其编译成render方法
  3. render挂载到options下
  4. 最后调用 mount.call(this, el, hydrating),即是调用原先原型上的mount方法

我们发现这一系列调用都是为了生成render函数,说明在vue中,所有的组件渲染最终都需要render方法(不管是单文件.vue还是el/template),vue 文档里也提到:

Vue 选项中的 render 函数若存在,则 Vue 构造函数不会从 template 选项或通过 el 选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。

原先原型上的mount方法

找到原先原型上的mount方法,在src/platform/web/runtime/index.js中:

// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}

这个是公用的$mount方法,这么设计使得这个方法可以被 runtime onlyruntime+compiler 版本共同使用

$mount 第一个参数el, 表示挂载的元素,在浏览器环境会通过query(el)获取到dom对象,第二个参数和服务端渲染相关,不进行深入分析,此处不传。接着调用mountComponent()

看下query(),比较简单,当el string时,找到该选择器返回dom对象,否则新创建个div dom对象,eldom对象直接返回el.

mountComponent

mountComponent定义在src/core/instance/lifecycle.js中,传入vm,el,

  • el缓存在vm.$el

  • 判断有没有render方法,没有则直接把createEmptyVNode作为render函数

  • 开发环境警告(没有Render但有el/template不能使用runtime-only版本、rendertemplate必须要有一个)

  • 挂载beforeMount钩子

  • 定义 updateComponent , 渲染相关

    updateComponent = () => {
    vm._update(vm._render(), hydrating)
    }
  • new Watcher() 实例化一个渲染watcher,简单看下定义,

    this.getter = expOrFn

    updateComponent挂载到this.getter

    this.value = this.lazy ? undefined : this.get()

    get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
    value = this.getter.call(vm, vm)
    } catch (e) {...}
    return value
    }

    执行this.get(),则执行了this.getter,即updateComponent,所以new Watcher()时会执行updateComponent,也就会执行到vm._update、vm._render方法。

    因为之后不止初始化时需要渲染页面,数据发生变化时也是要更新到dom上的,实例watcher可以实现对数据进行监听以及随后的更新dom处理,watcher会在初始化执行回调,也会在数据变化时执行回调,此处先简单介绍为什么要使用watcher,不深入分析watcher实现原理。

  • 最后判断有无根节点,无则表示首次挂载,添加mounted钩子函数 ,返回vm

总结

实例初始化:new Vue()->挂载方法属性->this._init->初始化data->$mount

挂载过程:(在complier版本,生成render函数)对el作处理,执行mountComponent,mountComponent中定义了updateComponent,通过实例化watcher的回调执行updateComponent,执行updateComponent,即调用了vm._update、vm._render真实渲染成dom对象。

vue 源码学习二 实例初始化和挂载过程的更多相关文章

  1. Vue源码学习二 ———— Vue原型对象包装

    Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...

  2. Vue源码学习(二)$mount() 后的做的事(1)

    Vue实例初始化完成后,启动加载($mount)模块数据. (一)Vue$3.protype.$mount             标红的函数 compileToFunctions 过于复杂,主要是生 ...

  3. Vue源码学习之数据初始化

    首发地址:CJWbiu's Blog 在这里思考一个问题,使用Vue的时候需要在创建Vue实例时传入一个option,这里包含了我们定义的props.methods.data等.而在methods的方 ...

  4. Vue源码学习三 ———— Vue构造函数包装

    Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...

  5. 手牵手,从零学习Vue源码 系列二(变化侦测篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...

  6. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  7. Vue源码学习1——Vue构造函数

    Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...

  8. 【Vue源码学习】依赖收集

    前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...

  9. Dubbo源码学习(二)

    @Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented ...

随机推荐

  1. L1-Day10

    1.你需要的是更多的练习.[我的翻译]That you need is more practice.[标准答案]What you need is more practice[对比分析]主语从句用Tha ...

  2. sql的四种匹配模式

    1. % 表示任意0个或多个字符.如下语句:Select * FROM user Where name LIKE '%三%'; 将会把name为“张三”,“三脚猫”,“唐三藏”等等有“三”的全找出来. ...

  3. 【转】一文掌握 Linux 性能分析之 I/O 篇

    [转]一文掌握 Linux 性能分析之 I/O 篇 这是 Linux 性能分析系列的第三篇,前两篇分别讲了 CPU 和 内存,本篇来看 IO. IO 和 存储密切相关,存储可以概括为磁盘,内存,缓存, ...

  4. python中的多线程和多进程编程

    注意:多线程和多线程编程是不同的!!! 第一点:一个进程相当于一个要执行的程序,它会开启一个主线程,多线程的话就会再开启多个子线程:而多进程的话就是一个进程同时在多个核上进行: 第二点:多线程是一种并 ...

  5. 走进异步编程的世界 - 开始接触 async/await(转)

    序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $"" 来拼接字符串,相当于string.Fo ...

  6. pycharm安装package时报错

    在pycharm pip 包时,提示报错:module 'pip' has no attribute 'main' 原因:由于我的是pip 18.1 版本里没有main() 解决方法: 如不降级 pi ...

  7. 经验分享:PDF怎么提取页面

    PDF文件的页面有很多但有需要的并不是全部,有时候需要其中一页或几页的时候,这个时候我们就需要把单独的页面提取出来,这个时候应该怎么做呢,上次有小伙伴来询问小编,今天小编就为大家分享一下小编自己的编辑 ...

  8. PADS Layout VX.2.3 出Gerber文件时遇到一个奇怪的现象

    操作系统:Windows 10 x64 工具:PADS Layout VX.2.3 对CAM Documents的NC Drill的Units进行如下设置: 之后使用CAM Preview功能,查看N ...

  9. 【转载】详解一条sql语句的执行过程

    转载自 https://www.cnblogs.com/cdf-opensource-007/p/6502556.html SQL是一套标准,全称结构化查询语言,是用来完成和数据库之间的通信的编程语言 ...

  10. About The Order of The Declarations And Definition When Making a Member Function a Friend.关于使类成员成为另一个类友元函数的声明顺序和定义。

    If only member function clear of WindowMgr is a friend of Screen, there are some points need to note ...