媒介
接着上一篇的初始化部份,我们细看initData
中做了什么。
正文
initData
function initData (vm: Component) {
let data = vm.$options.data
// 取得传入的data.此处为{a:1, b:2}
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 假如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
)
}
// 取得data中所有的key,即a,b
const keys = Object.keys(data)
// 取得组件中定义的props与methods
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
// data中的第i个k
const key = keys[i]
// 假如methods中定义过雷同的key,报错
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 假如props中定义过雷同的key,报错
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
// 假如key不是保存症结字,即不是_或许$开首的key。
} else if (!isReserved(key)) {
// 将属性代理到实例的_data属性上。
// 比方vm.a = 1现实上会被处置惩罚为vm._data.a = 1;
// vm.a 将返回 vm._data.a;
proxy(vm, `_data`, key)
}
}
// 让data变成相应式数据,即数据转变时刻,UI也能随着变。
observe(data, true /* asRootData */)
}
observe
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 假如设置的参数value不是一个对象或许是一个假造dom。则直接返回。
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 假如这个value存在视察者实例,则赋给返回值
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
observerState.shouldConvert && // 须要转化
!isServerRendering() && // 非服务端衬着
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue // 是数组或许对象且能够扩大属性,且不是vue组件实例
) {
// 依据给定的值建立一个视察者实例
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
// 返回视察者实例
return ob
}
new Observer(value)
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
// 要被视察的值
this.value = value
// new 一个用来网络依靠的对象
this.dep = new Dep()
this.vmCount = 0
// 将该Observer实例,赋值给value的__ob__属性。
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
// 假如是数组的话,须要对数组的要领做特别处置惩罚,
// 而且顺次的让数组的每一个对象元素变成可视察的
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
// 否则是对象的话,顺次遍历每一个属性,并设置其
// getter与setter,支撑后续的相应式变化。
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
observe 与 Observer简朴申明
observe使一个对象变成相应式对象,在其值转变时刻,可触发ui转变。其做法是为每一个value(非原始范例的)设置一个__ob__,即Observer对象实例。假如value是一个对象,会遍历它的每一个key,然后为其设置setter/getter,假如是数组会对数组元素顺次的递归observe。
假定输入为data = {a: 1, b: 2}
经由observe后,会变成
data = {a: 1, b:2, __ob__: ObserverInstance, a的 getter,setter,b的getter, b的setter}。
个中ObserverInstance.dep用来网络对这个对象变动感兴趣的watcher,
个中ObserverInstance.value指向这个对象data;
defineReactive
为了以最简朴的体式格局申明流程,这里我们只看对象的处置惩罚状况,到了这里,我们的逻辑进入walk
函数内部,遍历对象的每一个属性,即a,b;离别定义为相应式属性。
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 这里的Dep照样Watcher网络器,用来网络对该变量感兴趣的watcher
const dep = new Dep()
// 猎取属性描述符,不熟悉的同砚请查阅es6的文档
const property = Object.getOwnPropertyDescriptor(obj, key)
// 假如属性描述符显现该对象不可设置,
// 即没法设置getter,setter,也就没法处置惩罚为相应式属性了,那直接返回。
// 平常我们定义的data里很少设置属性描述符,默许property => undefined
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
// 假如该obj的key,对应的val照样对象,则使其也变成可视察的对象
// 这里是一个递归处置惩罚,shallow标识是不是深度处置惩罚该值。
// 相似深拷贝,浅拷贝中是不是深拷贝的逻辑。
let childOb = !shallow && observe(val)
// 重点,设置getter,setter,这里我们定义data.a的getter,setter,
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 这里平常对象的属性是没有getter的,直接返回val
const value = getter ? getter.call(obj) : val
// 在特定的机遇举行依靠网络。经由历程Dep.target
// 这里在后期vue处置惩罚模板template的时刻,会天生render函数,
// render函数实行天生假造dom时刻会去读取vm实例的属性,比方vm.a这时刻会触发这个getter,
// 此时的Dep.target为一个watcher,
// 内容为vm._update(vm._render)这个函数,用来更新视图用
// 将该函数添加到defineReactive内部定义的dep中。
// 接下来我们看背面的set部份
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 当我们触发点击事宜时刻,this.a += 1;
// 此时newVal是value值加1,所以代码会继承走
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// 这里设置了newVal为val
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 假如新值是一个对象,会继承被当作可视察的对象。
childOb = !shallow && observe(newVal)
// dep.notify就是挨个关照Dep中网络的watcher去搞事变。
// get函数内我提到,dep中被到场了一个watcher,
// 其watcher现实作用就是触发视图更新,get时刻,被网络了,
// set时刻就会触发ui的更新。
dep.notify()
}
})
}
initData的结束
至此initData
结束,上文中defineReactive
的getter和setter的设定,在前期到不会触发,这里只是把礼貌定下,真正用到的时刻还须要跟模板连系。这个章节我们主要剖析一下initData
对data
的处置惩罚。接下来我们看下模板的处所。
起首我们须要把代码跳出来看这里,不要问我为何晓得看这里,因为我是看完一遍后有个印象,如今只不过在梳理流程。
this.init -> initState -> initData
this.init -> initState ->
this.init
this.init -> vm.$mount(vm.options.el)
这里我们再贴一下init内部函数的代码,让人人对也许的方位有个相识
Vue.prototype._init = function (options?: Object) {
// ***************************省略顶部代码
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)
}
}
vm.$mount
接下来我们简述一下vm.$mount内部的代码
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 依据id查找dom元素
el = el && query(el)
// 巴拉巴拉省略魔法
// 假如没有render函数则天生render函数
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
return mount.call(this, el, hydrating)
}
compileToFunctions天生的render函数
compileToFunctions是怎样依据模板天生html的,这不本章节的重点,背面我会零丁去写compileToFunctions的历程,本章节重点是render函数的效果,我以上一章节的html为例,这是render函数的效果。
(function anonymous() {
with (this) {
return _c('div', {
attrs: {
"id": "demo"
}
}, [_c('div', [_c('p', [_v("a:" + _s(a))]), _v(" "), _c('p', [_v("b: " + _s(b))]), _v(" "), _c('p', [_v("a+b: " + _s(total))]), _v(" "), _c('button', {
on: {
"click": addA
}
}, [_v("a+1")])])])
}
})
个中_c就是vue封装的createElement,用来天生假造dom。_s就是toString要领,等等。这里我们能够看到个中有些参数是变量,a,b,total。这与我们在js中定义的一致。接下来我们看下这个衬着函数实行的处所。
mount.call
起首照样接上面的代码。mount.call(this, el, hydrating)。
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mountComponent
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 巴拉巴拉省略大法,去除无关代码
callHook(vm, 'beforeMount')
// 巴拉巴拉省略大法,去除无关代码
let updateComponent
/* istanbul ignore if */
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 中心就这一句话。new 一个Watcher,上文屡次提到的家伙。
vm._watcher = new Watcher(vm, updateComponent, noop)
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
}
new Watcher()
上个代码片断中心就这一句话。vm._watcher = new Watcher(vm, updateComponent, noop)
。
重点是这个Watcher第二个参数,updateComponent,很主要。
new Watcher我们偏重看下组织函数内部的代码即可,以下是精简事后的代码
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: ISet;
newDepIds: ISet;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
// 此时expOrFn为上述的updateComponent
this.getter = expOrFn
// 因为此时lazy是falsly值,触发get
this.value = this.lazy
? undefined
: this.get()
}
get () {
// 该函数将Dep.target置为当前watcher。
pushTarget(this)
let value
const vm = this.vm
try {
// 挪用getter现实是挪用updateComponent
// 因为updateComponent会挪用options.render,所以会触发
// vm._render函数,而vm._render函数中的中心则是
// vnode = render.call(vm, vm.$createElement)
// 在 compileToFunctions天生的render函数 一节我们已看到了一个rendre函数也许的相貌
// 此时render函数中有时刻会取读取vm.a的值。有时会取读取vm.b的值。
// 当读取vm.a或许b的时刻,就会触发对应属性的getter
// 然后会将当前的Watcher到场属性对应的dep中。
// 联络不起来的同砚能够往回看,defineReactive中的dep网络的就是当前watcher了。
// 当用户点击页面的a+1按钮时,则会触发this.a += 1。
// 则会触发defineReactive(obj, a, {get,set})中的set,
// set中会挪用dep.notify。实在就是让dep网络到的watcher挨个实行
// 下述中的run要领.
// 而run要领又触发了当前的这个get要领,实行到getter.call的时刻,界面就更新了。
value = this.getter.call(vm, vm)
} 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()
this.cleanupDeps()
}
return value
}
run () {
if (this.active) {
// 症结一句就是猎取值,现实上这里的猎取值就是
// get -> this.get -> updateComponent -> 假造节点中从新猎取
// 界面中须要的a,b的值。
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
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
总结
此时一个也许的关于data是怎样影响view的流程基础跑通了。以界面数据a为例,
中心头脑就是defineReactive(data, a, {get,set})去设置属性a的getter,setter。
getter将会在vue实行render函数天生假造dom的时刻,将界面更新的watcher放入a的dep中。
当鼠标单击界面上的a+1按钮触发this.a += 1时刻,会触发a的setter函数,此时会将getter时网络的依靠————更新界面的watcher————触发。watcher实行本身的run要领,即更新界面。
至此data -> view这一层算是通了,至于input中的v-model现实上是input + onInput事宜的语法糖,监听input,值转变时刻经由历程事宜修正vm.a的值,进一步触发————更新界面的watcher,使界面更新。