vue 源码学习二 实例初始化和挂载过程
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._isComponent
为true
的情况,走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
,初始化props
、methods
、data
、计算属性、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
:
- 先判断
el
,el
不能是body, html
,因为渲染出来的DOM
最后是会替换掉el
的 - 判断
render
方法, 有的话直接调用mount.call(this, el, hydrating)
- 没有
render
方法时:
- 判断有没有
template
,有则用compileToFunctions
将其编译成render方法 - 没有
template
时,则查看有没有el
,有转换成template
,再用compileToFunctions
将其编译成render
方法 - 将
render
挂载到options下 - 最后调用
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 only
和runtime+compiler
版本共同使用
$mount
第一个参数el
, 表示挂载的元素,在浏览器环境会通过query(el)
获取到dom
对象,第二个参数和服务端渲染相关,不进行深入分析,此处不传。接着调用mountComponent()
看下query()
,比较简单,当el
是string
时,找到该选择器返回dom
对象,否则新创建个div
dom对象,el
是dom
对象直接返回el
.
mountComponent
mountComponent
定义在src/core/instance/lifecycle.js
中,传入vm,el
,
将
el
缓存在vm.$el
上判断有没有
render
方法,没有则直接把createEmptyVNode
作为render
函数开发环境警告(没有
Render
但有el/template
不能使用runtime-only
版本、render
和template
必须要有一个)挂载
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 源码学习二 实例初始化和挂载过程的更多相关文章
- Vue源码学习二 ———— Vue原型对象包装
Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...
- Vue源码学习(二)$mount() 后的做的事(1)
Vue实例初始化完成后,启动加载($mount)模块数据. (一)Vue$3.protype.$mount 标红的函数 compileToFunctions 过于复杂,主要是生 ...
- Vue源码学习之数据初始化
首发地址:CJWbiu's Blog 在这里思考一个问题,使用Vue的时候需要在创建Vue实例时传入一个option,这里包含了我们定义的props.methods.data等.而在methods的方 ...
- Vue源码学习三 ———— Vue构造函数包装
Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...
- 手牵手,从零学习Vue源码 系列二(变化侦测篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...
- Vue源码分析(二) : Vue实例挂载
Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...
- Vue源码学习1——Vue构造函数
Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...
- 【Vue源码学习】依赖收集
前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...
- Dubbo源码学习(二)
@Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented ...
随机推荐
- python 文本比对
# -*- coding:utf-8 -*- import difflib import sys def readfile(filename): try: fileHandle = open(file ...
- LeetCode第十七题-电话号码的字母组合
Letter Combinations of a Phone Number 问题简介: 给定包含2-9的数字的字符串,返回该数字可能表示的所有可能的字母组合. 下面给出了数字到字母的映射(就像在电话按 ...
- 【译】索引进阶(八):SQL SERVER唯一索引
[译注:此文为翻译,由于本人水平所限,疏漏在所难免,欢迎探讨指正] 原文链接:传送门. 在本章节我们检查唯一索引.唯一索引的特别之处在于它不仅提供了性能益处,而且提供了数据完整性益处.在SQL SER ...
- git知识总结-3.gitignore文件说明
1.前言 一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表. 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等. 在这种情况下,我们可以创建一个名 ...
- Eclipse 配置Tomcat 服务器
第一部分:eclipse环境下如何配置tomcat 1.下载并成功安装Eclipse和Tomcat 2.打开Eclipse,单击“window”菜单,选择下方的“Preferences” . 选择好自 ...
- Mongodb 安装错误汇总
Failed to restart mongod.service: Unit mongod.service not found. 解决方法: Most probably unit mongodb.se ...
- 面向对象之组合VS继承:继承过时了?
在阅读Effective Java中的第16条时发现了一个有趣的机制或者说是模式,那就是组合(文中翻译为复用,但是作者认为组合更能体现这种模式的精神),并且文中建议使用组合. 那什么是组合, ...
- jsp多模块相同数据提交到后台之数据处理
最近在写一个java多模块表单提交,起初想的只是一个简单的form表单提交,写的时候发现不是真简单.多个相同类型数据提交到后台接收的问题很困难. 于是,和人进行深入的讨论,感觉j以json的格式提交时 ...
- js中valueOf方法的使用
今天一位刚毕业的同事问了我一个问题,为什么这段代码执行结果是-1.代码如下: var o = { valueOf: function(){ return -1; } }; o = +o; 当时我也是懵 ...
- 设计模式 — 工厂方法模式(Factory Method)
在开发系统中,经常会碰到一个问题.现在需要实现的一些功能,但是这个功能模块以后一定是需要扩展的,那么现在开发中就不仅要实现现在的功能,还要考虑以后的扩展.那么为了系统的健壮,扩展就要遵循开闭原则(简单 ...