从vue的构建过程能够知道,web环境下,入口文件在 src/platforms/web/entry-runtime-with-compiler.js
(以Runtime + Compiler模式构建,vue直接运行在浏览器进行编译工做)html
import Vue from './runtime/index'
复制代码
下一步,找到./runtime/index
,发现:vue
import Vue from 'core/index'
复制代码
下一步,找到core/index
,发现:java
import Vue from './instance/index'
复制代码
按照这个思路找,最后发现:Vue是在'core/index'下定义的web
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个方法按功能放入不一样的模块中,这很利于代码的维护和管理缓存
回到core/index.js
, 看到除了引入已经在原型上挂载方法后的 Vue 外,还导入initGlobalAPI 、 isServerRendering、FunctionalRenderContext
,执行initGlobalAPI(Vue)
,在vue.prototype
上挂载$isServer、$ssrContext、FunctionalRenderContext
,在vue
上挂载 version
属性,bash
看到initGlobalAPI
的定义,主要是往vue.config、vue.util等上挂载全局静态属性和静态方法(可直接经过Vue调用,而不是实例调用),再把builtInComponents 内置组件
扩展到Vue.options.components
下。此处大体了解下它是作什么的便可,后面用到再作具体分析。app
通常咱们用vue都采用模板语法来声明:dom
<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
查看该方法。
一开始在this
对象上定义_uid、_isVue
,判断options._isComponent
,这次先不考虑options._isComponent
为true
的状况,走else
,合并options
,接着安装proxy
, 初始化生命周期,初始化事件、初始化渲染、初始化data、钩子函数等,最后判断有vm.$options.el
则执行vm.$mount()
,便是把el
渲染成最终的DOM
。
_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的核心之一,此处先不分析
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方法,在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
定义在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
对象。