深入响应式原理
Vue 最独特的特性之一,是其非侵入性的响应式系统。
数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。这使得状态管理非常简单直接,不过理解其工作原理同样重要,这样你可以避开一些常见的问题。
# Object.defineProperty
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 把这些 property 全部转为 getter/setter。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
# 响应式原理图解
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
# 源码浅析
下面从一个组件初始化开始,了解一下响应式实现过程
在 Vue 初始化时,会调用 initState ,它会初始化 props ,methods ,data ,computed ,watch 等.
initState
// src/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)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
initProps
// src/core/instance/state.js
function initProps(vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = (vm._props = {}) // 缓存 props 的每个 key,性能优化
const keys = (vm.$options._propKeys = [])
const isRoot = !vm.$parent // root instance props should be converted // 非根实例的情况
if (!isRoot) {
// 响应式的优化,主要优化在响应式处理的递归过程
toggleObserving(false)
}
for (const key in propsOptions) {
// 缓存 key
keys.push(key) // 校验并返回值,主要检查传递的数据是否符合 prop 的定义规范
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 的每个 key 设置响应式
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.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
} // 响应式的优化,主要优化在响应式处理的递归过程
toggleObserving(true)
}
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
props 的初始化就是对其进行遍历,遍历过程主要做两件事:
- 调用 defineReactive 对每个值做响应式处理。
- 通过 proxy 把 vm._props.xxx 的访问代理到 vm.xxx 上。
initData
// src/core/instance/state.js
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
)
} // 代理数据到 vm 实例上。 // 判断去重, data 上的属性不能和 props、methods 上的属性相同。
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 /* asRootData */)
}
export function getData(data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}
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
data 的初始化和 props 目的差不多,这里主要做了三件事:
- 检查 data 上的属性不能和 props 、methods 上的属性相同。
- 通过 proxy 把 vm._data.xxx 的访问代理到 vm.xxx 上。
- 调用 observe 把 data 上的数据变成响应式。
proxy
// 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
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
2
3
4
5
6
7
8
9
10
代理的作用是把 props 和 data 上的属性代理到 vm 实例上,这就是为什么我们定义了 props.xxx,却可以通过 this.xxx 进行访问。
observe
// src/core/observer/index.js
export function observe(value: any, asRootData: ?boolean): Observer | void {
// 非对象和 VNode 实例不做响应式处理
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
// 如果 value 对象上存在 __ob__ 属性,则表示已经做过观察了,直接返回 __ob__ 属性
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
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
observe 就是给非 VNode 的对象类型创建观察者实例 Observer,如果成功创建过,直接返回已有的观察者,否则创建新的实例。
Observer
// src/core/observer/index.js
/**
* Observer class that is attached to each observed
* nce attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
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 // 为什么在 Observer 里面声明 Dep?
this.dep = new Dep()
this.vmCount = 0 // 在 value 对象上设置 __ob__ 属性,引用了当前 Observer 实例
def(value, '__ob__', this) // 判断类型
if (Array.isArray(value)) {
// 覆盖数组默认的七个原型方法,以实现数组响应式
// hasProto = '__proto__' in {}
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* 遍历对上的每个 key,设置响应式
* 只有类型为 Object 时才走到这里
*/
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])
}
}
}
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
**Observer 类会被附加到被观察的对象上,也就是说,每一个响应式对象上都会有一个 **ob****;
然后对数据类型进行了一个判断;若是数组,则判断是否存在 proto 属性,因为要通过原型链覆盖数组的几个方法,判断有无 proto 属性,主要是一种兼容的处理方式,proto 不是标准属性,所以有些浏览器不支持,比如 IE6-10,Opera10.1。
为什么在 Observer 里面声明 Dep? 了解过响应式的道友都知道,我们应该是一个 key 对应一个 dep 嘛,来管理依赖,当 key 的值发生变化时,触发 setter 通知更新。这里的 dep 主要是作用于 Object 属性增加和删除,Array 的变更方法。比如: { a: { b: 1 } } 使用了 $set 增加了一个属性 { a: { b: 1, c: 2 } } ,不管 a.b 还是 a.c ,增加还是删除,只要是和 a 相关的,就直接更新。
下面来看响应式核方法
defineReactive
// src/core/observer/index.js
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 实例化 dep,一个 key 一个 dep
const dep = new Dep() // 获取 obj[key] 的属性描述符,发现它是不可配置对象的话直接 return
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
} // 记录 getter 和 setter,获取 val 值
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
} // 递归调用,处理 val 的值为对象的情况
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true, // 劫持读取操作
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val // Dep.target 是 Dep的一个静态属性,保存的是当前的 Watcher 实例。 // 在 new Watcher 实例化的时候(computed 除外,因为它懒执行)会触发读取造作,被劫持运行这个 get 函数,进行依赖收集。 // 在实例化 Watcher 最后,Dep.target 设置为 null,避免重复收集。
if (Dep.target) {
// 依赖收集,在 dep 中添加 watcher,也在 watcher 中添加 dep
dep.depend() // childOb 表示当前的 val 还是一个复杂类型,对象或者数组。
if (childOb) {
// 这个 dep 是在 Observer中创建的,之前提到过。
childOb.dep.depend()
if (Array.isArray(value)) {
// 处理数组内还是对象的情况
dependArray(value)
}
}
}
return value
}, // 劫持修改操作
set: function reactiveSetter(newVal) {
// 旧的 obj[key]
const value = getter ? getter.call(obj) : val // 如果新旧值一样,则直接 return,无需更新
if (newVal === value || (newVal !== newVal && value !== value)) {
return
} /* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
} // setter 不存在说明该属性是一个只读属性,直接 return
if (getter && !setter) return // 设置新值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
} // 对新值进行观察,让新值也是响应式的
childOb = !shallow && observe(newVal) // 依赖通知更新
dep.notify()
}
})
}
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
// src/core/observer/index.js
/**
* 遍历每个数组元素,递归处理数组项为对象的情况,为其添加依赖.
* 因为前面的递归阶段无法为数组中的对象元素添加依赖.
*/
function dependArray(value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
defineReactive 的作用就是利用 Object.defineProperty 对数据的读写进行劫持,给属性 key 添加 getter 和 setter ,用于依赖收集和通知更新。
如果传进来的值依旧是一个对象,则递归调用 observe 方法,保证子属性都能变成响应式。
# 依赖收集
Dep
// src/core/observer/dep.js
/* @flow */
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
let uid = 0
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
static target: ?Watcher
id: number
subs: Array<Watcher>
constructor() {
this.id = uid++
this.subs = []
} // 添加订阅,把 Watcher实例,保存到 subs中
addSub(sub: Watcher) {
this.subs.push(sub)
} // 移除订阅,把 Watcher实例,从 subs 中移除
removeSub(sub: Watcher) {
remove(this.subs, sub)
} // 向 Watcher 中添加 dep
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
} // 通知更新
notify() {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
} // 遍历 dep 中存储的 watcher,执行 watcher.update()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
/**
* 当前正在执行的 watcher,同一时间只会有一个 watcher 在执行
* Dep.target = 当前正在执行的 watcher ,并推入栈中
* 通过调用 pushTarget 方法完成赋值,调用 popTarget 方法完成重置
*/
Dep.target = null
const targetStack = []
export function pushTarget(target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget() {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 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
Watcher
// src/core/observer/watcher.js
/* @flow */
import {
warn,
remove,
isObject,
parsePath,
_Set as Set,
handleError,
invokeWithErrorHandling,
noop
} from '../util/index'
import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'
import type { SimpleSet } from '../util/index'
let uid = 0
/**
* 一个组件一个 watcher(渲染 watcher)或者一个表达式一个 watcher(用户watcher)
* 当数据更新时 watcher 会被触发,访问 this.computedProperty 时也会触发 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: 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.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy 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 = function() { return this.xx }
// 在 this.get 中执行 this.getter 时会触发依赖收集
// 待后续 this.xx 更新时就会触发响应式
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
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
)
}
}
this.value = this.lazy ? undefined : this.get()
}
/**
* 执行 this.getter,并重新收集依赖
* this.getter 是实例化 watcher 时传递的第二个参数,一个函数或者字符串,比如:updateComponent 或者 parsePath 返回的读取 this.xx 属性值的函数
* 为什么要重新收集依赖?
* 因为触发响应式数据更新时,虽然已经经过 observe 观察,但却没有进行依赖收集,
* 所以,在更新页面时,会重新执行一次 render 函数,执行期间会触发读取操作,这时候进行依赖收集
*/
get() {
// 在需要进行依赖收集的时候调用
// 设置 targetStack.push(target) 和 Dep.target = watcher
pushTarget(this)
let value
const vm = this.vm
try {
// 执行回调函数,比如 updateComponent,进入 patch 阶段
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)
}
// 依赖收集结束调用
// 设置 targetStack.pop() 和 targetStack[targetStack.length - 1]
popTarget() // 清除依赖
this.cleanupDeps()
}
return value
}
/**
* 添加 dep 到 watcher
* 添加 watcher 到 dep
*/
addDep(dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
// 保存 id 用于去重
this.newDepIds.add(id) // 添加 dep 到当前 watcher
this.newDeps.push(dep) // 避免在 dep 中重复添加 watcher
if (!this.depIds.has(id)) {
// 添加当前 watcher 到 dep
dep.addSub(this)
}
}
}
/**
* 清除依赖,每次数据变化都会重新 render,
* 那么 vm._render() 方法又会再次执行,并再次触发数据的 getters,所以 Watcher 在构造函数中会初始化 2 个 Dep 实例数组.
* this.deps 表示上一次的 dep 实例数组,this.newDeps 表示新添加的 dep 实例数组。
*/
cleanupDeps() {
let i = this.deps.length // 遍历 deps,移除对 dep.subs 数组中 Wathcer 的订阅
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
} // newDepIds 和 depIds 做交换,然后清空 newDepIds
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear() // newDeps 和 deps 做交换,然后清空 newDeps
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
// ... 下面还有几个方法,暂时不看,放在别的地方一块看
}
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
依赖收集流程
回顾一下在执行 mount 挂载过程中 mountComponent 里有这么一段逻辑。
// src/core/instance/lifecycle.js
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
},
true /* isRenderWatcher */
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
收集流程:
- 当实例化 Watcher 时,会执行 Watcher 构造函数中的 this.get()。
- get 中首先调用 pushTarget(this), 其实就是 Dep.target = 当前正在执行的 Watcher 并推入栈中。
- 然后执行 value = this.getter.call(vm, vm) 。
- this.getter 对应的就是 updateComponent,实际执行的是 vm._update(vm._render(), hydrating);它会先调用 vm._render()。
- vm._render() 的作用就是生成 VNode,过程中会触发对数据的访问,也就是触发了 getter.
- 每个对象的 key 都会有一个对应的 dep,在 getter 中会调用 dep.depend(),也就会调用 Dep.target.addDep(this);因为在第2步时 Dep.target = 当前正在执行的 Watcher,所以调用的应该是 当前正在执行的 Watcher.addDep(this)。
- addDep 中在不重复的情况下调用 dep.addSub(this),也就会执行 this.subs.push(sub) ,也就是把 Watcher 实例保存到 dep 的 subs 中。
- 在 vm._render() 过程中,会触发所有数据的 getter,这样实际就完成了依赖收集的过程,但是 Watcher 构造函数中的 this.get() 后续还有一些操作。
- if (this.deep) { traverse(value) } 要递归去访问 value,触发它所有子项的 getter 。
- 然后执行 popTarget() , 依赖收集结束,重新设置 Dep.target。
- 最后就是调用 this.cleanupDeps(),清除依赖。
# 数组七个方法的重写
// src/core/observer/array.js
/*
* 定义 arrayMethods 对象,用于增强 Array.prototype
* 当访问 arrayMethods 对象上的那七个方法时会被劫持,以实现数组响应式
*/
import { def } from '../util/index'
// 备份 数组 原型对象
const arrayProto = Array.prototype
// 通过继承的方式创建新的 arrayMethods
export const arrayMethods = Object.create(arrayProto)
// 操作数组的七个方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* 拦截方法并触发事件
*/
methodsToPatch.forEach(function (method) {
// 缓存原生方法,比如 push
const original = arrayProto[method]
// def 就是 Object.defineProperty,劫持 arrayMethods.method 的访问
def(arrayMethods, method, function mutator (...args) {
// 先执行原生方法,比如 push.apply(this, args)
const result = original.apply(this, args)
const ob = this.__ob__
// 如果 method 是以下三个之一,说明是新插入了值
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 对新插入的值做响应式处理
if (inserted) ob.observeArray(inserted)
// 通知更新
ob.dep.notify()
return result
})
})
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
数组方法重写主要做了这么几件事:
- arrayMethods 继承了 Array。
- 对数组中能改变数组自身的七个方法进行了劫持和重写,劫持是使其被调用时,调用的是重写后的方法。
- 重写后的方法首先会调用原来原型上的逻辑。
- 判断可以添加值的三个方法 push、unshift、splice ,获取新插入的值,进行响应处理。
- 最后调用 ob.dep.notify() 通知更新。
响应式原理的核心流程代码大概就这些,还有一些watch computed $set 和 $delete等的实现都是基于响应式原理的应用
todo
vue源码有关响应式的代码还是比较多的,有时间了自己实现一个简易版的响应式