vue 源码之双向绑定

vue 双向绑定

vue 双向绑定,问就是 Object.defineProperty 劫持数据获得状态变更,发布订阅者模式进行变更通知。好吧,现在地球人都知道这两句话了,要是别人再问你实现细节,我们应该如何回答呢。也是为了让自己更加了解,从自己大概知道和能与人清楚表达,这就是今天写文章的目的了。

阅读前,你需要具备 vue 生命周期,Object.defineProperty,以及发布订阅者模式(相关博客)的一些知识。

劫持数据

......
callHook(vm, 'beforeCreate')
initInjections(vm)  //注入 Data/prop 数据
initProvide(vm)		//注入完成
callHook(vm, 'created')

在进入 beforeCreate 周期后,会注入 data/prop 等数据,数据劫持就是发生在此时(initInjections 函数)。

function initInjections(){
	......
	Object.keys(result).forEach(key => {
		defineReactive(vm, key, result[key])
	})
}

export function defineReactive( obj: Object, key: string, val: any ......){
	......
	Object.defineProperty(obj, key, {
		getfunction(){
			......
		},
		set:function(){
			......
		}	
	}
}

把对象的属性键转换为getter/setter,一旦劫持了数据,我们就可以为所欲为了。

但是这里只转换了第一层数据,我们还要嵌套遍历对象。

Observer 监听类

在转化对象属性 defineReactive 函数中,会先 new 一个监听类(Observer)。监听类通过递归数据,把 getter/setter 附加到每个被观察对象(让数据变化绑定订阅和通知这两个行为)。

function observe(value){
	let ob
	if (hasOwn(value, '__ob__') 	//通过有无__ob__属性判断是否处于监听中
		ob = value.__ob__
	else
		ob = new Observer(value)
	return ob	
}

export class Observer{
	......
	def(value, '__ob__', this)		//为属性添加属性__ob__,指向监听实例
	this.walk(value)
}

walk (obj: Object) {
    const keys = Object.keys(obj)
   	for (let i = 0; i < keys.length; i++) {
		defineReactive(obj, keys[i], obj[keys[i]])	//通过第三个参数递归,继续监听对象
   	}
}

单单只是获取数据状态变更不行,还得发送通知。

Dep 调度中心类

了解发布订阅者模式就该知道,订阅者(观察者)把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者(目标)发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码。

我们通过 getter 为每个订阅者订阅事件,setter 来发送通知。

export function defineReactive( obj, key, val ......){
	......
	const dep = new Dep()		//每一个数据都会有一个专门的 Dep 实例。
	observe(val)				//递归遍历数据
	Object.defineProperty(obj, key, {
		getfunction(){
			......
			dep.depend()		//订阅事件
			return value
		},
		set:function(){
			......
			 dep.notify()		//发送通知
		}	
	}
}

在 defineReactive 的 get 里面订阅事件。然后在 set 中发送通知。

export class Dep{
	......
	constructor () {
	    this.subs = []		//依赖队列
  	}
	//订阅
	depend () {
	    if (Dep.target) {
	      Dep.target.addDep(this)
	    }
	  }
	//发送通知
	notify () {	
	    const subs = this.subs.slice()
	    for (let i = 0, l = subs.length; i < l; i++) {
	      subs[i].update()
	    }
	}
	//添加依赖(订阅者)
	addSub (sub: Watcher) {
	    this.subs.push(sub)
  	}
}

Dep.target 后续会讲到,指向目标 watcher 实例

Watcher 订阅者类

......
callHook(vm, 'beforeMount') 
new Watcher()
......

在 beforeMount 生命周期后,会 new 一个 Watcher 对象。

export class Watcher {
	......
	constructor (){
		this.cb = cb
		this.get()
	}
	
	get(){
		......
		Dep.target = this;   			//让当前组件实例的 Dep.target 指向这个 Watcher 对象
		this.getter.call(vm, vm)		//执行一次 getter
		Dep.target = null
		return value
	}

	addDep(dep){						//为调度中心依赖队列添加当前 Watcher 对象
		dep.addSub(this)
	}

	update(){
		this.cb.call(this.vm, value, oldValue)		//执行 setter
	}
}

生成订阅者 watcher,会执行一次数据的 getter(通过监听类递归,监听并让每一个属性都有一个调度中心),然后通过订阅事件,把这个订阅者关联到所有调度中心的依赖内(通过 Dep.target 的指向互相调用)。当数据变化时,该处的调度中心就会通知相应的订阅者。

每个数据都有一个调度中心,每个组件实例都有一个订阅者

在这里插入图片描述

总结

实现数据的双向绑定,首先要对数据进行劫持监听,所以需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。我们通过消息调度中心Dep来专门收集这些订阅者并发布消息通知,以此在监听器Observer和订阅者Watcher之间进行统一管理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值