vue响应式原理专题
1、vue何时做响应式?
1.vue的构造函数调用init
备注:
1.vue的所有方法都是通过挂到prototype上的
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) // 初始化调用 找到下步
}
2.init的initState()
备注:
- 从以下代码易知道初始化执行顺序为:
beforeCreate -> inject的处理 -> state的处理 -> provider处理
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
// 根走这一步, 合并挂载到静态的Vue.options
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm
// expose real self
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')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
3. initState做响应式
从代码易知对数据的顺序:
- props
- methods
- data
- computed
- watch
// state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
// 处理props
if (opts.props) initProps(vm, opts.props)
// 处理methods
if (opts.methods) initMethods(vm, opts.methods)
// 处理data
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)
}
}
4、关注initData
function initData (vm: Component) {
// 获取data
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 返回对象必须是一个普通对象, 否则dev警告提示
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]
// 和方法重名提示
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 和props属性重名提示
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
observe(data, true /* asRootData */)
}
2、怎样做响应式的?
1.调用流程顺序:
调用observe方法 --->通过new Observer(ob)生成观察对象 --->Observer内部constructor时做响应式 ---> (若是对象)调用walk(value)具体实现
observe方法解析
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 只能对对象进行处理
if (!isObject(value) || value instanceof VNode) {
return
}
// ob
let ob: Observer | void
// 通过判断是否有__ob__对象来判断是否做了响应式
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve && //默认为true
!isServerRendering() && // 非服务器渲染
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value) // 这里做响应式
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
Observer对象解析
1、图解
2、代码解析
- 生成dep对象
- 数组调用observeArray()
- 对象调用walk()
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this) // 给this挂上__ob__
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties 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])
}
}
// .... 其他方法
}
通过walk 我们知道defineReactive()方法, 接下来对defineReactive解析
3、defineReactive 针对对象作dep收集watcher
源码位置:src\core\observer\index.js
参考
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
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
// val赋初值
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
/* 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()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
以上代码的意思是
get操作进行是 如果Dep.target存在,即有watcher处在正在收集的状况,dep.depend()触发后当前watcher进行addDep(this)
watcher.addDep(dep) ---> watcher.newDeps.push(dep) 即watcher保存dep
同时 dep.addSub(this)
watcher 和 dep相互保存。
这样dep可以通过notify触发。
4、针对数组做响应式
对数组的每一个对象作响应式,不是对象则不是响应式
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}