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
对象。