You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
实例化 Vue 实例的时候,Vue.prototype._init 方法被第一个执行,在 initState 函数内部使用 initData 函数初始化 data 选项。这是数据响应的开始。initData 函数的主要作用是保证 options 中的 data 选项是个函数且返回的对象。同时在 Vue 实例对象上添加代理访问数据对象的同名属性。最后 通过调用了 observe 函数观测数据,将 data 变成响应式的(data数据类型 是对象或数组时处理的 方式是不同的)。实现对象类型的 data 的观测主要是通过 defineReactive 函数
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// 省略...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) { // watcher
// 这里闭包引用了上面的 dep 常量
dep.depend() // 收集依赖 Watcher
// 省略...
}
return value
},
set: function reactiveSetter (newVal) {
// 省略...
if (setter) { // 如果属性原本存在set函数则调用
setter.call(obj, newVal)
} else {
val = newVal
}
// 这里闭包引用了上面的 dep 常量
dep.notify() // 触发依赖
}
})
}
在访问器属性的 getter/setter 中,通过闭包引用了前面定义的“筐”,即 dep 常量。每一个数据字段都通过闭包引用着属于自己的 dep 常量。
那么为数组类型该如何处理???
处理数组的方式与纯对象不同,数组是一个特殊的数据结构,它有很多实例方法,并且有些方法会改变数组自身的值,这些方法有:push、pop、shift、unshift、splice、sort 以及 reverse 等。其通过拦截数组变异方法的方式得知用户调用这些变异方法,从而触发依赖。数组本身也是一个对象,所以它实例的 proto 属性指向的就是数组构造函数的原型,即 arr.proto === Array.prototype 为真。其是通过设置 proto 属性的值为一个新的对象,且该新对象的原型是数组构造函数原来的原型对象。在新对象的变异方法里收集依赖
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get() // 此时调用 Watcher 的实例方法 get
}
get () { // get 为 Watcher 的实例方法
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm) // 此时 this.getter 指的就是 updateComponent
} 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
}
3 触发依赖
修改属性值时会触发属性的 set 拦截器函数,这样就会调用 Dep 实例对象的 noitfy 方法,
set: function reactiveSetter (newVal) {
// 省略...
dep.notify()
}
export default class Dep {
// 省略...
constructor () {
this.id = uid++
this.subs = []
}
// 省略...
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
notify 方法只做了一件事,就是遍历当前 Dep 实例对象的 subs属性所保存的所有观察者对象,并逐个调 用观察者对象的 update 方法,这就是触发响应的实现机制,那么大家应该也猜到了,重新求值的操作应该是在 update 方法中进行的,那我们就找到观察者对象的update方法真正的更新变化操作都是通过调用 观察者实例对象的 run 方法完成的,run 方法内判断次 Watcher 是否是激活状态,若激活则调用实例方法getAndInvoke。
getAndInvoke (cb: Function) {
// 重新求值其实等价于重新执行渲染函数,最终结果就是重新生成了虚拟DOM并更新真实DOM,这样就完成了重新渲染的过程
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)
}
}
}
好啦,谢谢大噶!!!
The text was updated successfully, but these errors were encountered:
Vue 的数据响应是通过数据劫持结合发布者和订阅者实现的。其主要是通过Object.defineProperty()来实现数据劫持的。
本文的例子实现数据更新驱动视图更新是直接通过操作DOM,且是通过直接分析DOM来定位依赖的。其实Vue 内部机制不完全是这样。
下面我补充一些Vue 实现数据响应的一些大体细节。这块东西真的很多,我仔细说个大概,具体细节还需要大家自己去了解。
1 数据观察
实例化 Vue 实例的时候,Vue.prototype._init 方法被第一个执行,在 initState 函数内部使用 initData 函数初始化 data 选项。这是数据响应的开始。initData 函数的主要作用是保证 options 中的 data 选项是个函数且返回的对象。同时在 Vue 实例对象上添加代理访问数据对象的同名属性。最后 通过调用了 observe 函数观测数据,将 data 变成响应式的(data数据类型 是对象或数组时处理的 方式是不同的)。实现对象类型的 data 的观测主要是通过 defineReactive 函数
在访问器属性的 getter/setter 中,通过闭包引用了前面定义的“筐”,即 dep 常量。每一个数据字段都通过闭包引用着属于自己的 dep 常量。
那么为数组类型该如何处理???
处理数组的方式与纯对象不同,数组是一个特殊的数据结构,它有很多实例方法,并且有些方法会改变数组自身的值,这些方法有:push、pop、shift、unshift、splice、sort 以及 reverse 等。其通过拦截数组变异方法的方式得知用户调用这些变异方法,从而触发依赖。数组本身也是一个对象,所以它实例的 proto 属性指向的就是数组构造函数的原型,即 arr.proto === Array.prototype 为真。其是通过设置 proto 属性的值为一个新的对象,且该新对象的原型是数组构造函数原来的原型对象。在新对象的变异方法里收集依赖
2 收集依赖
正是因为 watcher 对表达式的求值,触发了数据属性的拦截器函数,从而收集到了依赖,当数据变化时能够触发响应。
在上面的代码中 Watcher 观察者实例将对 updateComponent() 函数求值, updateComponent 函数的执行会间接触发渲染函数(vm.$options.render)的执行,而渲染函数的执行则会触发数据属性的 get 拦截器函数,从而将依赖(观察者)收集,当数据变化时将重新执行 updateComponent 函数,这就完成了重新渲染。
Watcher constructor 的最后
3 触发依赖
修改属性值时会触发属性的 set 拦截器函数,这样就会调用 Dep 实例对象的 noitfy 方法,
notify 方法只做了一件事,就是遍历当前 Dep 实例对象的 subs属性所保存的所有观察者对象,并逐个调 用观察者对象的 update 方法,这就是触发响应的实现机制,那么大家应该也猜到了,重新求值的操作应该是在 update 方法中进行的,那我们就找到观察者对象的update方法真正的更新变化操作都是通过调用 观察者实例对象的 run 方法完成的,run 方法内判断次 Watcher 是否是激活状态,若激活则调用实例方法getAndInvoke。
好啦,谢谢大噶!!!
The text was updated successfully, but these errors were encountered: