前言
首先声明一下,本来计划是一月一篇,三月加了一整个月的班,9107的这种,真挺累的。所以四月份就没有写了,而且最近觉得有道云笔记挺好用,所以很多东西都先在有道云里写了。就看后面啥时候有空贴出来。好了,步入正题。其实在这之前呢,就陆陆续续的阅读过vue2
的源码,但是看的都零零散散的,这次决定来系统的解读下vue2
的源码,希望自己能对vue2
掌握的更加深刻以及提升自己的水平。这篇文章主要解读一下vue2
的响应式原理,why? 因为这个问题实在是太常见了。😜
过程图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 有道云笔记的过程图实在太丑了,我之后用其他的软件画一个QWQ <!--graph TD A[data/props] A --> B(defineReactive) B --> |tracking| D[依赖收集] B --> |trigger| E[触发] D --> |depend| F[Dep] E --> |notify| F[Dep] G[computed] G --> H(computedWatcher) H --> |tracking| I[依赖收集] H --> |trigger| J[依赖收集] I --> |depend| F[Dep] J --> |notify| F[Dep] K[watch] K --> L(Watcher) L --> |update| F[Dep] L --> |addDep| F[Dep] L --> |run| M(callback) N[$mount] N --> O(render watcher) N --> P(updateComponent) N --> |update| F[Dep] N --> |addDep| F[Dep]-->
Object.defineProperty
Object.defineProperty(),它会在一个对象上定义一个新属性,或者修改一个对象的现有属性并返回此对象
vue的响应式主要是通过Object.defineProperty()
来完成的。但由于Object.defineProperty()
是ES5
的内容,所以vue
本身兼容不了ie8
。这里大致说明一下Object.defineProperty()
的作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 let obj = { name: 'zh' , age: '23' , } let obj1 = {};Object .defineProperty(obj1, 'name' , { enumerable: true , configurable: true , writable: true , value: 'zh' }) Object .defineProperty(obj1, 'age' , { enumerable: true , configurable: true , writable: true , value: 23 }) Object .keys(obj).forEach(key => { Object .defineProperty(obj,key,{ get (){ console .log(`get${key} ` ) }, set (newVal){ console .log(`set变化,${key} 的newVal是${newVal} ` ) return newVal; } }) }) console .log(obj.age);obj.name = 'zh1' ;
当我们给obj
设置好了get
和set
之后,我们就可以简单的把这个对象称为是一个响应式对象。那么,vue
中的响应式对象究竟是怎么样的呢?
响应式对象入口
想了解响应式,我们先看下vue
的初始化过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 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) Vue.prototype._init = function (options?: Object ) { const vm: Component = this vm._uid = uid++ 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 if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } if (process.env.NODE_ENV !== 'production' ) { initProxy(vm) } else { vm._renderProxy = vm } vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate' ) initInjections(vm) initState(vm) initProvide(vm) callHook(vm, 'created' ) 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) } } 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 ) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } } function initData (vm: Component ) { let data = vm.$options.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 ) } 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] 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, true ) }
observe
observe
定义在core/instance/observer.js
中。我们看下observe
里做了哪些事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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__ } else if ( shouldObserve && !isServerRendering() && (Array .isArray(value) || isPlainObject(value)) && Object .isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
我们可以看到,observe()
主要是对传入的value
做了是否缓存过的判断,然后返回的仍然是一个Observer
实例对象。接下来我们看一下Observer
里做了什么处理
Observer
Observer的定义和observe方法在同一个文件中,我们可以看到Observer
是一个类。我们看下这个类中做了哪些处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 const arrayProto = Array .prototypeexport const arrayMethods = Object .create(arrayProto)function protoAugment (target, src: Object ) { target.__proto__ = src } export class Observer { value: any; dep: Dep; vmCount: number; constructor (value: any) { this .value = value this .dep = new Dep() this .vmCount = 0 def(value, '__ob__' , this ) if (Array .isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this .observeArray(value) } else { this .walk(value) } } walk (obj: Object ) { const keys = Object .keys(obj) for (let i = 0 ; i < keys.length; i++) { defineReactive(obj, keys[i]) } } observeArray (items: Array <any>) { for (let i = 0 , l = items.length; i < l; i++) { observe(items[i]) } } } export function def (obj: Object, key: string, val: any, enumerable?: boolean ) { Object .defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true , configurable: true }) }
从上面可以知道,Observer
类就是给传入的object/array
设置getter
和setter
,并没有做什么太多的处理,就是声明了一个Dep
类。然后把自身实例设置到value
的__ob__
属性上,最后就是对对象和数组的差异化处理。
defineReactive
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object .getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false ) { return } const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments .length === 2 ) { 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 if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array .isArray(value)) { dependArray(value) } } } return value }, set : function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { return } if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
从上面我们可以知道,defineReactive()
主要是通过Object.defineProperty()
对obj
设置getter
和setter
。并且还对childObj
进行了observe
操作,这样就能保证不管obj
的结构多复杂,都能够把子属性变成响应式对象并能在访问或修改时及时的触发getter
或setter
。最后,我们在getter
中进行依赖收集的操作,在setter
中触发更新的操作。
总结
vue
的响应式核心就是通过Object.defineProperty()
来对传入对象添加getter
和setter
。由于篇幅已经挺长的了,之后的依赖收集和触发更新放到下一篇。
Author:
xzh
Permalink:
https://xzh97.github.io/2020/05/12/vue-reactivty/
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
最是人间孤愤难平,直消得几回潮落又潮生!