整个vue架构看下来,次要分为三个局部:响应式原理,编译器的实现 以及 patch算法。
以下是对响应式原理的集体了解,看下来,大抵是利用 公布-订阅模式 + 异步更新。
当咱们初始化一个vue实例并把它绑定到相应的DOM节点时,其实曾经实现了属性响应式的设定。
所以咱们从VUE的构造函数动手。
关上 core/index.js
import Vue from './instance/index'import { initGlobalAPI } from './global-api/index'import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'initGlobalAPI(Vue)Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering})Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext }})// expose FunctionalRenderContext for ssr runtime helper installationObject.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext})Vue.version = '__VERSION__'export default Vue
发现构造函数的根,其实在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
咱们不细化到每一个函数,一句话,core/instance/index.js 饱满 Vue.prototype ;而 initGlobalAPI(Vue) 饱满Vue构造函数,即全局API。
以下是饱满后的Vue.prototype和Vue构造函数:
// initMixin(Vue) src/core/instance/init.js **************************************************Vue.prototype._init = function (options?: Object) {}// stateMixin(Vue) src/core/instance/state.js **************************************************Vue.prototype.$dataVue.prototype.$propsVue.prototype.$set = setVue.prototype.$delete = delVue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object): Function {}// eventsMixin(Vue) src/core/instance/events.js **************************************************Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {}Vue.prototype.$once = function (event: string, fn: Function): Component {}Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {}Vue.prototype.$emit = function (event: string): Component {}// lifecycleMixin(Vue) src/core/instance/lifecycle.js **************************************************Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}Vue.prototype.$forceUpdate = function () {}Vue.prototype.$destroy = function () {}// renderMixin(Vue) src/core/instance/render.js **************************************************// installRenderHelpers 函数中Vue.prototype._o = markOnceVue.prototype._n = toNumberVue.prototype._s = toStringVue.prototype._l = renderListVue.prototype._t = renderSlotVue.prototype._q = looseEqualVue.prototype._i = looseIndexOfVue.prototype._m = renderStaticVue.prototype._f = resolveFilterVue.prototype._k = checkKeyCodesVue.prototype._b = bindObjectPropsVue.prototype._v = createTextVNodeVue.prototype._e = createEmptyVNodeVue.prototype._u = resolveScopedSlotsVue.prototype._g = bindObjectListenersVue.prototype.$nextTick = function (fn: Function) {}Vue.prototype._render = function (): VNode {}// core/index.js 文件中Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering})Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext }})// 在 runtime/index.js 文件中Vue.prototype.__patch__ = inBrowser ? patch : noopVue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating)}// 在入口文件 entry-runtime-with-compiler.js 中重写了 Vue.prototype.$mount 办法Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { // ... 函数体}
// initGlobalAPIVue.configVue.util = { warn, extend, mergeOptions, defineReactive}Vue.set = setVue.delete = delVue.nextTick = nextTickVue.options = { components: { KeepAlive // Transition 和 TransitionGroup 组件在 runtime/index.js 文件中被增加 // Transition, // TransitionGroup }, directives: Object.create(null), // 在 runtime/index.js 文件中,为 directives 增加了两个平台化的指令 model 和 show // directives:{ // model, // show // }, filters: Object.create(null), _base: Vue}// initUse ***************** global-api/use.jsVue.use = function (plugin: Function | Object) {}// initMixin ***************** global-api/mixin.jsVue.mixin = function (mixin: Object) {}// initExtend ***************** global-api/extend.jsVue.cid = 0Vue.extend = function (extendOptions: Object): Function {}// initAssetRegisters ***************** global-api/assets.jsVue.component =Vue.directive =Vue.filter = function ( id: string, definition: Function | Object): Function | Object | void {}// expose FunctionalRenderContext for ssr runtime helper installationObject.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext})Vue.version = '__VERSION__'// entry-runtime-with-compiler.jsVue.compile = compileToFunctions
以一个例子为引
<div id="app">{{test}}</div>
var vm = new Vue({ el: '#app', data: { test: 1 }})
咱们晓得,实例化一个vue实例的要害,在this._init(options)上,所以让咱们走进这个函数。
core/instance/init.js
Vue.prototype._init = function (options?: Object) { const vm: Component = this vm._uid = uid++// vue实例ID // 开发环境下关上性能追踪-init,compile,render,patch let startTag, endTag if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // 以下就是被追踪性能的代码 vm._isVue = true// 标记该组件是一个vue实例 // merge options if (options && options._isComponent) {// 不存在_isComponent属性,走else分支 initInternalComponent(vm, options) } else { // 初始化并饱满$options属性 vm.$options = mergeOptions(// 1.规范化属性名 2.合并对象产生新对象 resolveConstructorOptions(vm.constructor),// 解析构造函数options options || {}, vm ) } if (process.env.NODE_ENV !== 'production') { initProxy(vm)// 对vue实例的渲染做一个代理过滤 } else { vm._renderProxy = vm } 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) }}
这个函数一共做了几件事:
1.给实例增加ID和标识为VUE实例的标记位。
2.初始化并饱满实例的$options属性。
vm.$options = mergeOptions(// 1.规范化属性名 2.合并对象产生新对象 resolveConstructorOptions(vm.constructor),// 解析构造函数options options || {}, vm)
相当于
vm.$options = mergeOptions( // resolveConstructorOptions(vm.constructor) { components: { KeepAlive Transition, TransitionGroup }, directives:{ model, show }, filters: Object.create(null), _base: Vue }, // options || {} { el: '#app', data: { test: 1 } }, vm)
所以咱们得去理解mergeOptions函数的实现(core/util/options.js)
次要做了两件事:
(1)规范化属性。
(2)利用相应的合并策略函数合并属性。
export function mergeOptions ( parent: Object,// 构造函数的options child: Object,// 初始化vue实例时传入options vm?: Component// vue实例): Object { if (process.env.NODE_ENV !== 'production') { checkComponents(child)// 测验options.components组件名称合法性 } // 解决VUE.extend的状况,合并子类构造函数的options if (typeof child === 'function') { child = child.options } // 规范化属性,因为开发者有多种定义形式,须要对立 normalizeProps(child, vm)// 对立成对象的模式 normalizeInject(child, vm)// 规范化为对象语法 inject和provide配合应用 normalizeDirectives(child)// directive-注册部分指令 const extendsFrom = child.extends if (extendsFrom) { parent = mergeOptions(parent, extendsFrom, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } const options = {} let key for (key in parent) { mergeField(key) } for (key in child) { if (!hasOwn(parent, key)) {// 判断一个属性是否是对象本身的属性(不包含原型上的) mergeField(key) } } function mergeField (key) { const strat = strats[key] || defaultStrat// 调用属性绝对应的策略函数,不存在则调用默认策略 options[key] = strat(parent[key], child[key], vm, key) } return options}
须要理解援用的strats策略对象(const strats = config.optionMergeStrategies,来自于 core/config.js ),引入在以后文件core/util/options.js中,该strats策略对象是空对象,须要在该文件中缓缓饱满本人。
咱们只剖析两个策略函数,其余便不再赘述了。
(1) 默认合并策略函数
const defaultStrat = function (parentVal: any, childVal: any): any { return childVal === undefined ? parentVal : childVal}
其实很简略,同一个属性,只有子选项不是 undefined 那么就是用子选项,否则应用父选项。
(2) data属性的合并策略函数
// 定义data属性的策略函数-最终把data属性定义成一个函数,执行该函数能力失去真正的数据对象strats.data = function ( parentVal: any, childVal: any, vm?: Component): ?Function { if (!vm) { // 以后解决的是子组件-阐明以后解决的是VUE.extend的状况 if (childVal && typeof childVal !== 'function') {// 子组件的data属性必须存在且为函数 process.env.NODE_ENV !== 'production' && warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm ) return parentVal } return mergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm)}
发现mergeDataOrFn的返回值便是data属性策略函数,所以进入该函数。
export function mergeDataOrFn ( parentVal: any,// Vue.options 的data对象 childVal: any,// 参数 options 的data对象 vm?: Component): ?Function { if (!vm) {// 解决VUE.extend的状况 if (!childVal) {// 子类不存在data对象,间接返回父类的data对象 return parentVal } if (!parentVal) {// 父类不存在data对象,间接返回子类的data对象 return childVal } return function mergedDataFn () { return mergeData(// 返回真正的数据对象,去除反复属性 typeof childVal === 'function' ? childVal.call(this, this) : childVal,// 调用子类的data函数 typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal// 调用父类的data函数 ) } } else {// 初始化实例走该分支 return function mergedInstanceDataFn () {// 返回一个函数,执行后就是真正的data数据对象 const instanceData = typeof childVal === 'function' ? childVal.call(vm, vm) : childVal const defaultData = typeof parentVal === 'function' ? parentVal.call(vm, vm) : parentVal if (instanceData) { return mergeData(instanceData, defaultData)// 返回真正的数据对象,去除反复属性 } else { return defaultData } } }}
到此咱们发现,data属性的策略函数,执行后会将data属性定义成一个函数,只有执行该函数能力失去真正的数据对象。
已下是vm.$options的截图
3.对vue实例的渲染做一个代理过滤。
4.预处理+调用钩子函数。
在这一步,蕴含着的initState(vm)函数,是实现响应式的根。
进入core/instance/state.js
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) }}
咱们以initData(vm)为切入点,看看vue是如何对data属性实现响应式的。
function initData (vm: Component) { let data = vm.$options.data// 此时的data是一个函数 data = vm._data = typeof data === 'function' ? getData(data, vm) // 获取真正的数据对象 : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] // props优先级 > data优先级 > methods优先级 if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) {// 防止method和data具备同名属性 warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) {// 防止props和data具备同名属性 process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) {// 键名不为保留字 // 在vue实例对象上增加代理拜访数据对象的同名属性 proxy(vm, `_data`, key)// vm._data.x => vm.x } } // observe data observe(data, true /* asRootData */)}
这个函数次要做了以下几件事:
(1)获取真正的data数据对象,因为vm.$options.data是一个函数。
(2)防止data中的属性与props和methods同名。
(3)对vue实例的属性增加一层根本代理,使vm.key指向vm._data.key。
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)}
(4)监测data对象,使之成为响应式。
进入observe函数(core/observer/index.js)
export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void // 防止反复观测一个数据对象 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__// 被观测过的对象都会带有__ob__属性 } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue// 防止vue实例被监测 ) { ob = new Observer(value)// 为data数据对象建设一个监测对象 } if (asRootData && ob) { ob.vmCount++ } return ob}
这个函数的外围,是new Observer(value),所以进入Observer的构造函数。
export class Observer { value: any;// data数据对象 dep: Dep;// 属于该数据对象的依赖(watcher)收集筐 vmCount: number; // number of vms that has this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this)//给数据对象定义一个__ob__属性,指向以后的observer实例,且该属性不可枚举 if (Array.isArray(value)) {// 解决数组对象 const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else {// 解决纯对象 this.walk(value) } }
依据数据对象data的类型,分为两个分支(解决纯对象和解决数组)。
联合以后的例子,咱们先只看解决纯对象的状况:
walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) }}
对data数据对象中的每一个属性,调用defineReactive()办法,将数据对象的数据属性转换为拜访器属性,该办法是整个响应式的外围。
// 让属性成为响应式export function defineReactive ( obj: Object,// data key: string,// 属性名 val: any, customSetter?: ?Function, shallow?: boolean) { // 一个key 对应一个 dep //每一个数据字段都通过闭包援用着属于本人的 dep 常量 const dep = new Dep() // 获取属性形容对象-之前设置的第一层根本代理 const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) {// 判断属性是否是可配置的 return } // 缓存原来设置的get set函数 const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) {// 如果val自身领有get函数但没有set,就不会执行深度监测 val = obj[key] } let childOb = !shallow && observe(val)// 默认深度察看-递归 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val// 调用缓存的根本get函数 vm.x=>vm._data.x if (Dep.target) {// 要被收集的依赖-观察者watcher dep.depend()// 将依赖收集到闭包dep的筐中 if (childOb) {//val.__ob__ childOb.dep.depend()//在应用 $set 或 Vue.set 给数据对象增加新属性时触发 if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) {//NaN return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal)// vm._data.x => vm.x } else { val = newVal } childOb = !shallow && observe(newVal)//监测新值 dep.notify()// 触发dep筐中依赖 } })}
次要做了两件事
(1)申明一个属于属性本人的dep收集依赖筐(闭包)。
(2)改良属性的get set代理。
get代理:返回值+收集依赖
get: function reactiveGetter () { const value = getter ? getter.call(obj) : val// 调用缓存的根本get函数 vm.x=>vm._data.x if (Dep.target) {// 要被收集的依赖-观察者watcher dep.depend()// 将依赖收集到闭包dep的筐中 if (childOb) {// 指向val.__ob__,解决深度监测的问题 childOb.dep.depend()// 在应用 $set 或 Vue.set 给数据对象增加新属性时触发 if (Array.isArray(value)) {// 解决属性为数组的状况 dependArray(value) } } } return value}
当获取test属性时,先判断以后存不存在要被收集的依赖(watcher对象),如果有,调用本人对应dep的depend()办法来收集依赖。(上面两个if分支在此例中不波及)
进入core/obsever/dep.js
export default class Dep { static target: ?Watcher;// 动态对象,全局只有一个 id: number;// dep惟一标识 subs: Array<Watcher>;// 监测此dep实例的观察者们 constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () {// 收集依赖(watcher) if (Dep.target) { Dep.target.addDep(this) } } notify () {// 触发依赖 // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update()// 调用每一个观察者的update办法 } }}
dep.depend()就是把以后的存储在全局的Dep.target对象(watcher)增加到dep的观察者数组中。
再看看watcher对象的addDep办法,进入core/obsever/watcher.js
addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) {// 防止反复收集dep this.newDepIds.add(id) this.newDeps.push(dep)// 将以后dep对象增加到watcher实例本人的newDeps数组中 if (!this.depIds.has(id)) { dep.addSub(this)// 将以后watcher实例增加到dep实例的观察者数组中 } }}
临时不必去思考if分支的作用,无非是做一些性能的优化,该办法最终的成果,就是让以后的dep实例和以后的watcher实例都彼此蕴含。换句话说,dep实例在收集依赖(观察者)的同时,依赖也保留了dep实例。
set代理:设置新值+触发依赖
set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val// 获取原有值 /* eslint-disable no-self-compare */ // 只有当原有值与新值不等时才触发set代理 if (newVal === value || (newVal !== newVal && value !== value)) {// NaN return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal)// 调用缓存的根本set函数 vm.x => vm._data.x } else { val = newVal } childOb = !shallow && observe(newVal)//监测新值 dep.notify()// 触发dep筐中依赖}
但批改test属性时,会先判断是否等于旧值,若不等,则设置新值且调用本人对应dep的notify()办法来触发依赖。
进入core/obsever/dep.js
notify () {// 触发依赖 // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update()// 调用每一个观察者的update办法 }}
发现无非是调用该dep实例存储的每一个依赖(watcher)的update办法。
update () { /* istanbul ignore else */ if (this.computed) {// 解决计算属性 // A computed property watcher has two modes: lazy and activated. // It initializes as lazy by default, and only becomes activated when // it is depended on by at least one subscriber, which is typically // another computed property or a component's render function. if (this.dep.subs.length === 0) { // In lazy mode, we don't want to perform computations until necessary, // so we simply mark the watcher as dirty. The actual computation is // performed just-in-time in this.evaluate() when the computed property // is accessed. this.dirty = true } else { // In activated mode, we want to proactively perform the computation // but only notify our subscribers when the value has indeed changed. this.getAndInvoke(() => { this.dep.notify() }) } } else if (this.sync) {// 同步更新 this.run() } else { queueWatcher(this)// 将以后观察者对象放到一个异步更新队列 }}
在此例中,会将以后依赖(watcher)放入一个异步更新队列中。但这块并不是咱们响应式流程的重点,无非是对触发依赖的性能优化,通过上一个if分支咱们晓得,最终所有的依赖(watcher)都会执行本人的run办法。
run () { if (this.active) {// 以后依赖(watcher)为激活状态 this.getAndInvoke(this.cb) }}
进入getAndInvoke函数,这也是咱们依赖触发的止境。
getAndInvoke (cb: Function) { const value = this.get() // 从新收集依赖 if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep// 深度检测标记位,默认true ) { // set new value const oldValue = this.value this.value = value this.dirty = false if (this.user) { try { cb.call(this.vm, value, oldValue)// 调用watcher构造函数中设置的回调 } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { cb.call(this.vm, value, oldValue) } }}
进入watcher.get()办法
get () { pushTarget(this)// 将此watcher实例设置为Dep.Target let value const vm = this.vm try { value = this.getter.call(vm, vm)// 获取组件中察看的属性值-获取时同时会触发属性的get } catch (e) { 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()// 还原Dep.Target this.cleanupDeps()// 清空newDeps } return value}
发现无非是把以后watcher设置为Dep.Target,再从新收集一次依赖。
所以getAndInvoke函数,无非做了两件事:从新收集依赖(其实还蕴含了视图更新)以及触发相应回调函数。
当初,咱们理解了响应式原理的实现机制,但咱们发现,在initData()阶段,只是对每个属性配置了相应的代理,那么在哪调用get,set? 初始的Dep.Target又是谁? 响应式批改数据后又是怎么自动更新视图的?
答案在_init()办法的最初一句代码:
if (vm.$options.el) { vm.$mount(vm.$options.el)}
接下来,咱们将解析vue.prototype.$mount()办法,即整个响应式的终点。
进入platforms/web/runtime/index.js
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { el = el && inBrowser ? query(el) : undefined// 获取实在DOM节点 return mountComponent(this, el, hydrating)// 真正的挂载工作}
这是vue.prototype.$mount()办法的第一层封装,咱们还失去src/platforms/web/entry-runtime-with-compiler.js,在这一层封装,咱们退出了编译模板的性能。
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating)}
看下来,在这层封装中,无非是对没有render函数的vue实例,通过template编译出render函数。
最终还是要调用 mountComponent(this, el, hydrating)。即这个函数才是真正的挂载函数,之前的操作无非是为了给它提供渲染函数。
进入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) { 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.判断渲染函数是否存在,不存在定义为一个空VNode节点。
2.定义并初始化updateComponent函数。
3.为updateComponent函数申明一个观察者对象。
咱们先看updateComponent函数,发现无论是执行 if 语句块还是执行 else 语句块,最终 updateComponent 函数的性能是不变的。都是以 vm._render() 函数的返回值作为第一个参数调用 vm._update() 函数,即把渲染函数生成的虚构DOM渲染成真正的DOM。
不深究那两个子函数,能够简略地认为:
vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚构节点(vnode)。
vm._update 函数的作用是把 vm._render 函数生成的虚构节点渲染成真正的 DOM。
接着,咱们看创立观察者的局部,这也是真正触发响应式的要害。
创立 Watcher 观察者实例将对 updateComponent 函数求值,咱们晓得 updateComponent 函数的执行会间接触发渲染函数(vm.$options.render)的执行,而渲染函数的执行则会触发数据属性的 get 拦截器函数,从而将依赖(观察者)收集,当数据变动时将从新执行 updateComponent 函数,这就实现了从新渲染。同时咱们把下面代码中实例化的观察者对象称为渲染函数的观察者。
最初,咱们进入core/observer/watcher.js做最初的摸索。
export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; computed: boolean; sync: boolean; dirty: boolean; active: boolean; dep: Dep; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; 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.deep = !!options.deep this.user = !!options.user this.computed = !!options.computed this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.computed = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.computed // for computed watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } if (this.computed) { this.value = undefined this.dep = new Dep() } else { this.value = this.get() } }
以后的expOfFn指向updateComponent函数,此时被赋值到this.getter。关注到最初一行的this.value=this.get();
get () { pushTarget(this)// 将此watcher实例设置为Dep.Target let value const vm = this.vm try { value = this.getter.call(vm, vm)// 获取组件中察看的属性值-获取时同时会触发属性的get } catch (e) { 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()// 还原Dep.Target this.cleanupDeps()// 清空newDeps } return value}
发现以后的渲染函数观察者被设置到Dep.Target上了,且触发this.getter就是执行updateComponent函数,执行时会触发相应属性的get代理。
当初所有都捋顺了,咱们来梳理一下流程。
咱们new一个vue实例的同时,会触发_init函数,这个函数做了几件事:
(1)初始化并饱满vm.$options属性。
(2)调用相应的初始化函数,让数据成为响应式。其中要害的initState(vm),会让设置的data,methods,props对象,成为响应式。
例如其中的initData(vm),会调用observe(data),即为data数据对象建设一个监测对象observer,具体的就是为纯对象调用walk(data)(数组对象递归调用observe()),该函数外部就是为data对象中的每一个属性调用defineReactive(obj,key)函数,使该属性成为响应式。而属性成为响应式的要害,就是
1)为每个属性设置对应的dep,由它来做公布信息的工作。
2)设置get代理来收集依赖。
3)设置set代理来触发依赖。
(3)将vue实例挂载到dom节点上。咱们在这一步为渲染函数设置了一个观察者watcher,这也是咱们整个响应式的终点。咱们在挂载的时候,会调用渲染函数,从而触发相干数据属性的 get 拦截器函数,从而将以后依赖(渲染函数观察者)收集,此时,每一个属性都收集着以后渲染函数观察者。将来,当数据变动时将触发依赖的update()函数->run()->getAndInvoke(),其中的this.get(),会从新执行 updateComponent 函数,这就实现了从新渲染。