二、响应式原理initState(vm)
1.inState响应式入口
- initState在
import { initState } from './state'
- 同级目录下找到state.js
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options // 解决props对象 为每一个props对象下面设置响应式,并将其代理到vm的实例上 if (opts.props) initProps(vm, opts.props) 解决methods 校验每个属性的值是否为函数 和props属性比对进行判重解决,并将其代理到vm的实例上 if (opts.methods) initMethods(vm, opts.methods) 1.判重解决,data对象上的属性不能和props、methods对象上的属性雷同 2.代理data到vm实例上的 3.为data对象上的数据设置响应式 if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } 解决conputed 1.为computed【key】 创立 watcher 实例 默认是懒执行 也就是有缓存 2.代理computed【key】,到vm实例 3.判重,computed中的key不能和data,props中的属性反复 if (opts.computed) initComputed(vm, opts.computed) 解决watch 1.解决watch对象 2.为每一watch.key 创立watcher实例,key和watcher 实例可能是一对多的关系 3.如果设置了 immediate,则立刻执行 回调函数 if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } computed和watch 在实质上是没有区别的,都是通过 watcher 去实现的响应式 1.watch 实用于当数据变动时执行异步函数或者开销比拟大的操作应用 须要长时间期待操作能够放在watch 2. computed 其中能够应用异步函数,computed更适宜做一些同步计算。 }
2.initProps办法
- 次要解决父组件传入props
- /src/core/instance/state.js
- 为props对象的每一个属性设置响应式,并代理到vm上
function initProps (vm: Component, propsOptions: Object) { // 写法一 props: ['name'] // 写法二 props: { name: String, // [String, Number] } // 写法三 props: { name:{ type: String } } propsData:父组件传入的实在props数据。 const propsData = vm.$options.propsData || {} props:指向vm._props的指针,所有设置到props变量中的属性都会保留到vm._props中。 const props = vm._props = {} // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. 缓存props 的每个key,做性能优化 const keys = vm.$options._propKeys = [] isRoot:以后组件是否为根组件 const isRoot = !vm.$parent // root instance props should be converted if (!isRoot) { toggleObserving(false) } // 遍历props 对象 for (const key in propsOptions) { // key 是props的key keys.push(key) // 收集所有的key,存到空数组外面 const value = validateProp(key, propsOptions, propsData, vm) /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { const hyphenatedKey = hyphenate(key) if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { warn( `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, vm ) } // 为props的每一个key是设置数据响应式 defineReactive(props, key, value, () => { if (!isRoot && !isUpdatingChildComponent) { warn( `Avoid mutating a prop directly since the value will be ` + `overwritten whenever the parent component re-renders. ` + `Instead, use a data or computed property based on the prop's ` + `value. Prop being mutated: "${key}"`, vm ) } }) } else { // 对props数据做响应式解决 defineReactive(props, key, value) } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. // 做代理,将props上的key代理到this下面 if (!(key in vm)) { proxy(vm, `_props`, key) } } toggleObserving(true)}
3.proxy 代理办法
- 设置代理,将key代理到target上
- /src/core/instance/state.js
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 } 次要应用defineproperty 对每一个key进行代理设置 Object.defineProperty(target, key, sharedPropertyDefinition)}
4.initMethods
- 初始化代理办法methods
- 代理到vm实例
function initMethods (vm: Component, methods: Object) { 获取 props 配置项 const props = vm.$options.props 遍历 methods 对象 for (const key in methods) { if (process.env.NODE_ENV !== 'production') { if (typeof methods[key] !== 'function') { 断定methods【key】中必须是一个函数 在非生产环境下判断如果methods中某个办法只有key而没有value, 即只有办法名没有办法体时,抛出异样:提醒用户办法未定义。 warn( `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` + `Did you reference the function correctly?`, vm ) } 断定 methods 的 key 和 props 不能反复 判断如果methods中某个办法名与props中某个属性名反复了,就抛出异样:提醒用户办法名反复了。 if (props && hasOwn(props, key)) { warn( `Method "${key}" has already been defined as a prop.`, vm ) } 判断如果methods中某个办法名如果在实例vm中曾经存在并且办法名是以_或$结尾的, 就抛出异样:提醒用户办法名命名不标准。 if ((key in vm) && isReserved(key)) { warn( `Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.` ) } } vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) }}
5.initData
- src/core/instance/state.js
- 初始化data
1.判重解决,data对象上属性和props、methods对象上的属性雷同2.代理 data 对象上的属性到 vm 实例3.为data对象上的数据设置响应式function initData (vm: Component) { let data = vm.$options.data 获取到用户传入的data选项,赋给变量data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} 将变量data作为指针指向vm。data,判断data是不是一个函数 如果是,执行getData函数获取其返回值 如果不是,就将其自身保留到vmdata中 就是传入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 做判重解决,data对象上的属性不能和props、methods对象上的属性雷同 代理data对象上的属性到vm实例 let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { 反复提醒 warn( `Method "${key}" has already been defined as a data property.`, vm ) } } 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 ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // observe data // 为 data 对象上的数据设置响应式 observe(data, true /* asRootData */)}
6.initComputed
- 初始化computed
- 为computed【key】创立wathcer,默认是懒执行
- 代理到vm实例上
计算属性的后果会被缓存,除非依赖的响应式属性变动才会从新计算。function initComputed (vm: Component, computed: Object) { // $flow-disable-line 定义了一个watchers,赋值给空对象,同时将其作为指针指向vm._computedWatchers const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() 遍历computed for (const key in computed) { const userDef = computed[key] 判断userDef是不是一个函数 是函数 默认赋值给getter的取值器 不是函数 就是个对象,取对象的get属性作为取值器赋值给变量getter const getter = typeof userDef === 'function' ? userDef : userDef.get 判断环境 if (process.env.NODE_ENV !== 'production' && getter == null) { 如果取值器getter没有取到值,则正告 提醒用户计算属性必须有取值器。 warn( `Getter is missing for computed property "${key}".`, vm ) } 判断是不是服务器渲染 if (!isSSR) { 创立一个watcher实例 创立的实例作为值存入watchers对象中 // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. 判断以后的值是否存在vm的实例上 放弃key的唯一性 if (!(key in vm)) { 不存在 调用函数 defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { 则在非生产环境下抛出正告 if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } else if (vm.$options.methods && key in vm.$options.methods) { warn(`The computed property "${key}" is already defined as a method.`, vm) } } }}
7.defineComputed
- computed中的key如果不存在vm的整个实例中(也就是这个key是惟一的)
- 执行defineComputed 办法
属性描述符const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop}该办法承受三个参数 target、key和userDef为target上定义一个属性key,并且属性key的getter和setter依据userDef的值来设置export function defineComputed ( target: any, key: string, userDef: Object | Function) { 标识计算属性是否该有缓存 判断是否是服务器渲染 true 非服务器渲染 false 服务器渲染 const shouldCache = !isServerRendering() 判断userDef是否函数,就是判断getter的取值 if (typeof userDef === 'function') { 判断是否是服务器渲染 非服务端渲染环境下并没有间接应用userDef作为getter,而是调用createComputedGetter函数 userDef只是一个一般的getter,它并没有缓存性能,所以咱们须要额定创立一个具备缓存性能的getter 服务端渲染环境不须要缓存 然而服务器渲染没有setter,所以将sharedPropertyDefinition.set设置为noop。 设置getter和setter sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop sharedPropertyDefinition.set = userDef.set || noop } if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } 调用Object.defineProperty办法将属性key绑定到target上 次要描述性就是sharedPropertyDefinition Object.defineProperty(target, key, sharedPropertyDefinition)}
- createComputedGetter()
function createComputedGetter (key) { 返回一个computedGetter return function computedGetter () { 存储在以后实例上key所对应watcher实例 const watcher = this._computedWatchers && this._computedWatchers[key] // 执行watcher.evalute 办法 // 执行computed.key的办法,失去函数的执行后果,赋值给watcher.value // 将watcher.dirty 赋值为false 如果watcher存在,判断dirty是否为true 这个watcher只是会在数据变动后,才会变为true if (watcher) { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } }}
在实例化Watcher类的时候,第四个参数传入了一个对象computedWatcherOptions = { computed: true },该对象中的computed属性标记着这个watcher实例是计算属性的watcher实例,即Watcher类中的this.computed属性,同时类中还定义了this.dirty属性用于标记计算属性的返回值是否有变动,计算属性的缓存就是通过这个属性来判断的,每当计算属性依赖的数据发生变化时,会将this.dirty属性设置为true,这样下一次读取计算属性时,会从新计算结果返回,否则间接返回之前的计算结果。
当调用watcher.depend()办法时,会将读取计算属性的那个watcher增加到计算属性的watcher实例的依赖列表中,当计算属性中用到的数据发生变化时,计算属性的watcher实例就会执行watcher.update()办法,在update办法中会判断以后的watcher是不是计算属性的watcher,如果是则调用getAndInvoke去比照计算属性的返回值是否产生了变动,如果真的发生变化,则执行回调,告诉那些读取计算属性的watcher从新执行渲染逻辑。
当调用watcher.evaluate()办法时,会先判断this.dirty是否为true,如果为true,则表明计算属性所依赖的数据产生了变动,则调用this.get()从新获取计算结果最初返回;如果为false,则间接返回之前的计算结果。
export default class Watcher { constructor (vm,expOrFn,cb,options,isRenderWatcher) { if (options) { // ... this.computed = !!options.computed // ... } else { // ... } this.dirty = this.computed 初始值为true if (typeof expOrFn === 'function') { this.getter = expOrFn } if (this.computed) { this.value = undefined this.dep = new Dep() } } evaluate () { if (this.dirty) { this.value = this.get() this.dirty = false } return this.value } /** * Depend on this watcher. Only for computed property watchers. */ depend () { if (this.dep && Dep.target) { this.dep.depend() } } update () { if (this.computed) { if (this.dep.subs.length === 0) { this.dirty = true } else { this.getAndInvoke(() => { this.dep.notify() }) } } } 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 ) { // set new value const oldValue = this.value this.value = value this.dirty = false if (this.user) { try { cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { cb.call(this.vm, value, oldValue) } } }} const computedWatcherOptions = { computed: true }watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions)
8.initWatch
- 初始化watch
function initWatch (vm: Component, watch: Object) { 遍历watch的所有值 for (const key in watch) { const handler = watch[key] watch对象的值 if (Array.isArray(handler)) { handler 为数组,遍历数组,获取其中的每一项,而后调用 createWatcher for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { 调用createWatcher,办法 createWatcher(vm, key, handler) } }}function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object) { 如果handler是对象,则获取其中 handler 选项的值 if (isPlainObject(handler)) { options = handler handler = handler.handler } 如果 hander 为字符串,则阐明是一个 methods 办法,获取 vm[handler] if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options)}定义vm.$watch创立watcher,返回一个unwatchFn Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this 兼容性解决,因为用户调用 vm.$watch 时设置的 cb 可能是对象 if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options.user 示意用户 watcher,还有渲染 watcher 即 updateComponent 办法中实例化的 watcher options = options || {} options.user = true const watcher = new Watcher(vm, expOrFn, cb, options) 如果用户设置了 immediate 为 true,则立刻执行一次回调函数 if (options.immediate) { const info = `callback for immediate watcher "${watcher.expression}"` pushTarget() invokeWithErrorHandling(cb, vm, [watcher.value], vm, info) popTarget() } 返回一个unwatch函数,用于解除监听 return function unwatchFn () { watcher.teardown() } }}