Obserber类:
作用
数组,对象等转化成可观测数据
实现分析
- 创建Dep实例(每个Observe都有一个Dep实例)
- 标注数据已观测:给数据挂载__ob属性__
- 判断类型
- 数组
- 数组原型上挂载重写的数组方法
- 递归调用observe()
- 对象 walk()中调用defineReactive(),将data中的键值转化为getter/setter形式
- 数组
class Observer {
constructor(v){
// 每一个Observer实例身上都有一个Dep实例
this.dep = new Dep()
// 如果数据层次过多,需要递归去解析对象中的属性,依次增加set和get方法
def(v,'__ob__',this) //给数据挂上__ob__属性,表明已观测
if(Array.isArray(v)) {
// 把重写的数组方法重新挂在数组原型上
v.__proto__ = arrayMethods
// 如果数组里放的是对象,再进行监测
this.observerArray(v)
}else{
// 非数组就直接调用defineReactive将数据定义成响应式对象
this.walk(v)
}
}
observerArray(value) {
for(let i=0; i<value.length;i++) {
observe(value[i])
}
}
walk(data) {
let keys = Object.keys(data); //获取对象key
keys.forEach(key => {
defineReactive(data,key,data[key]) // 定义响应式对象
})
}
}
function defineReactive(data,key,value){
const dep = new Dep() //实例化dep,用于收集依赖,通知订阅者更新
observe(value) // 递归实现深度监测,注意性能
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get(){
// 如果现在处于依赖的收集阶段
if(Dep.target) {
dep.depend()
}
// 依赖收集
return value
},
set(newV) {
if(newV === value) return
//继续劫持newV,用户有可能设置的新值还是一个对象
observe(newV)
value = newV
console.log('值变化了:',value)
// 发布订阅模式,通知
dep.notify()
// cb() //订阅者收到消息回调
}
})
}
Dep类(订阅者)
实现分析
被observe的data在触发getter时,会:
- 打上标记:Dep.target(在watcher类中实现),这里用到判断
- 收集依赖:在subs队列当中添加watcher(只在实际页面当中用到的data数据,没用到可以不用做响应式处理)
当数据发生变动的时候,通过dep给watcher发通知更新:notify中调用watcher.update()
,进行更新视图
var uid = 0
export default class Dep {
constructor() {
this.id = uid++
this.subs = [] // subscribes订阅者,存储Watcher的实例
}
//收集watcher
addSub(watcher) {
this.subs.push(watcher)
}
//移除watcher
removeSub(watcher) {
remove(this.watcher, watcher)
}
// 收集依赖
depend() {
//全局唯一,实例化Watcher时会赋值Dep.target = Watcher实例
if(Dep.target) {
this.addSub(Dep.target)
}
}
//通知watcher去更新
notify() {
console.log('通知观察者更新~')
const subs = this.subs.slice() // 复制一份
subs.forEach(w=>w.update())
}
}
Watcher类(观察者)
实现分析
被observe的data在触发getter时,会:
- 打上标记:Dep.target
- 收集依赖:只在实际页面当中用到的data数据,没用到可以不用做响应式处理
var uid = 0
import {parsePath} from "../util/index"
import Dep from "./dep"
export default class Watcher{
constructor(vm,expr,cb,options){
this.vm = vm // 组件实例
this.expr = expr // 需要观察的表达式
this.cb = cb // 当被观察的表达式发生变化时的回调函数
this.id = uid++ // 观察者实例对象的唯一标识
this.options = options // 观察者选项
this.getter = parsePath(expr)
this.value = this.get()
}
get(){
// 依赖收集,把全局的Dep.target设置为Watcher本身
Dep.target = this
const obj = this.vm
let val
// 只要能找就一直找
try{
val = this.getter(obj)
} finally{
// 依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。
Dep.target = null
}
return val
}
// 当依赖发生变化时,触发更新
update() {
this.run()
}
run() {
this.getAndInvoke(this.cb)
}
getAndInvoke(cb) {
let val = this.get()
if(val !== this.value || typeof val == 'object') {
const oldVal = this.value
this.value = val
cb.call(this.target,val, oldVal)
}
}
}
关系分析
首先observer可以处理对象数组数据,将这些数据中添加getter和setter属性
其次在每个Observe当中,都有一个dep(私以为,observe中有很多个变量需要监控)
当数据的getter被调用,也就是页面用到这个数据的时候,我们在watcher类中将dep.target设置为watcher(也就是获得当前的环境,确定到底是哪个变量发生调用),之后在dep类将已经有dep.target内容的部分添加到dep.subs当中,其实也就是将调用到的watcher添加到subs队列当中,这样我们就可以知道并保存被用到的watcher
注意,在添加(收集依赖)完成之后,需要将target重新设置为null,方便下一个watcher指向
而当数据发生改变的时候,watcher类会触发update方法