这一次 彻底理解Vue的watch实现原理及其实现方式

理解Vue中Watch的实现原理和方式之前,你需要深入的理解MVVM的实现原理,如果你还不是很理解,推荐你阅读我之前的几篇文章:

彻底搞懂Vue针对数组和双向绑定(MVVM)的处理方式

vue.js源码解读系列 - 双向绑定具体如何初始化和工作

vue.js源码解读系列 - 剖析observer,dep,watch三者关系 如何具体的实现数据双向绑定

也可以关注我的博客查看关于Vue更多的源码解析:https://github.com/wangweianger/myblog

备注:

1、此文大部分代码来自于Vue源码

2、此文MVVM部分代码来自于【彻底搞懂Vue针对数组和双向绑定(MVVM)的处理方式】,若有不懂之处,建议先看上文

3、部分代码为了兼容测试做了部分更改,但原理跟Vue一致


画一张watch的简单工作流程图:


把上文的 Dep,Oberver,Wather拿过来并做部分更改(增加收集依赖去重处理):

Dep代码如下:

//标识当前的Dep id
let uidep = 0
class Dep{
   
	constructor () {
   
		this.id = uidep++
		// 存放所有的监听watcher
    	this.subs = []
  	}

  	//添加一个观察者对象
  	addSub (Watcher) {
   
    	this.subs.push(Watcher)
  	}

  	//依赖收集
	depend () {
   
		//Dep.target 作用只有需要的才会收集依赖
	    if (Dep.target) {
   
	      Dep.target.addDep(this)
	    }
	}

	// 调用依赖收集的Watcher更新
    notify () {
   
	    const subs = this.subs.slice()
	    for (let i = 0, l = subs.length; i < l; i++) {
   
	      subs[i].update()
	    }
  	}
}

Dep.target = null
const targetStack = []

// 为Dep.target 赋值
function pushTarget (Watcher) {
   
	if (Dep.target) targetStack.push(Dep.target)
  	Dep.target = Watcher
}
function popTarget () {
   
  Dep.target = targetStack.pop()
}

Watcher代码如下:

//去重 防止重复收集
let uid = 0
class Watcher{
   
	constructor(vm,expOrFn,cb,options){
   
		//传进来的对象 例如Vue
		this.vm = vm
		if (options) {
   
	      this.deep = !!options.deep
	      this.user = !!options.user
	    }else{
   
	    	this.deep = this.user = false
	    }
		//在Vue中cb是更新视图的核心,调用diff并更新视图的过程
		this.cb = cb
		this.id = ++uid
		this.deps = []
	    this.newDeps = []
	    this.depIds = new Set()
	    this.newDepIds = new Set()
		if (typeof expOrFn === 'function') {
   
			//data依赖收集走此处
	      	this.getter = expOrFn
	    } else {
   
	    	//watch依赖走此处
	      	this.getter = this.parsePath(expOrFn)
	    }
		//设置Dep.target的值,依赖收集时的watcher对象
		this.value =this.get()
	}

	get(){
   
		//设置Dep.target值,用以依赖收集
	    pushTarget(this)
	    const vm = this.vm
	    //此处会进行依赖收集 会调用data数据的 get
	    let value = this.getter.call(vm, vm)
	    //深度监听
	    if (this.deep) {
   
	      traverse(value)
	    }
	    popTarget()
	    return value
	}

	//添加依赖
  	addDep (dep) {
   
  		//去重
  		const id = dep.id
	    if (!this.newDepIds.has(id)) {
   
	      	this.newDepIds.add(id)
	      	this.newDeps.push(dep)
	      	if (!this.depIds.has(id)) {
   
	      		//收集watcher 每次data数据 set
	      		//时会遍历收集的watcher依赖进行相应视图更新或执行watch监听函数等操作
	        	dep.addSub(this)
	      	}
	    }
  	}

  	//更新
  	update () {
   
	    this.run()
	}

	//更新视图
	run(){
   
		const value = this.get()
		const oldValue = this.value
        this.value = value
		if (this.user) {
   
			//watch 监听走此处
            this.cb.call(this.vm, value, oldValue)
        }else{
   
        	//data 监听走此处
        	//这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
			console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
        }
		
	}
	// 此方法获得每个watch中key在data中对应的value值
	//使用split('.')是为了得到 像'a.b.c' 这样的监听值
	parsePath (path){
   
		const bailRE = /[^w.$]/
	  if (bailRE.test(path)) return
	  	const segments = path.split('.')
	  	return function (obj) {
   
		    for (let i = 0; i < segments.length; i++) {
   
		      	if (!obj) return
		      	//此处为了兼容我的代码做了一点修改	 
		        //此处使用新获得的值覆盖传入的值 因此能够处理 'a.b.c'这样的监听方式
		        if(i==0){
   
		        	obj = obj.data[segments[i]]
		        }else{
   
		        	obj = obj[segments[i]]
		        }
		    }
		    return obj
		 }
	}
}
//深度监听相关代码 为了兼容有一小点改动
const seenObjects = new Set()
function traverse (val) {
   
  seenObjects.clear()
  
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值