博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入理解Vue的computed实现原理及其实现方式
阅读量:7143 次
发布时间:2019-06-29

本文共 16547 字,大约阅读时间需要 55 分钟。

继上一篇: 继续讲解

Vue的computed实现相对于watch和data来说比较难以理解,要真正的理解computed的工作方式,你需要深入理解Vue的双向数据绑定原理和实现方式。

如果你还不是很理解推荐你先看此文章:

首先来看一波Vue中computed的使用方式:

var vm = new Vue({  data: { a: 1 },  computed: {    // 仅读取    aDouble: function () {      return this.a * 2    },    // 读取和设置    aPlus: {      get: function () {        return this.a + 1      },      set: function (v) {        this.a = v - 1      }    }  }})vm.aPlus   // => 2vm.aPlus = 3vm.a       // => 2vm.aDouble // => 4复制代码

计算属性的主要应用场景是代替模板内的表达式,或者data值的任何复杂逻辑都应该使用computed来计算,它有两大优势:

1、逻辑清晰,方便于管理

2、计算值会被缓存,依赖的data值改变时才会从新计算

此文我们需要核心理解的是:

1、computed是如何初始化,初始化之后干了些什么

2、为何触发data值改变时computed会从新计算

3、computed值为什么说是被缓存的呢,如何做的

如果以上三个问题你都已知,你可以忽略下文了,若未知或一知半解,请抱着学习的态度看看别人的理解。

备注:以下只是我的个人理解,并不保证绝对的正确性,若有问题欢迎指正

以下大部分代码摘自Vue源码。

如果你看到了这里,就当做你已经深入理解了Vue的MVVM原理及其实现方式。相关Vue的MVVM实现直接取自上一篇文章。

Dep代码的实现:

//标识当前的Dep idlet uidep = 0class 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 = nullconst 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 = 0class Watcher{	constructor(vm,expOrFn,cb,options){		//传进来的对象 例如Vue		this.vm = vm		if (options) {	      this.deep = !!options.deep	      this.user = !!options.user	      this.lazy = !!options.lazy	    }else{	    	this.deep = this.user = this.lazy = false	    }	    this.dirty = this.lazy		//在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.lazy ? undefined : this.get()	}	get(){		//设置Dep.target值,用以依赖收集	    pushTarget(this)	    const vm = this.vm	    //此处会进行依赖收集 会调用data数据的 get	    let value = this.getter.call(vm, vm)	    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 () {  		if (this.lazy) {      		this.dirty = true    	}else{    		this.run()    	}	}	//更新视图	run(){		console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)		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过程从而更新视图			this.cb.call(this.vm, value, oldValue)        }	}    //如果计算熟悉依赖的data值发生变化时会调用    //案例中 当data.name值发生变化时会执行此方法	evaluate () {	    this.value = this.get()	    this.dirty = false	}	//收集依赖	depend () {	    let i = this.deps.length	    while (i--) {	      this.deps[i].depend()	    }	}	// 此方法获得每个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		 }	}}复制代码

在Watcher中对于computed来说核心注意点是以下方法:

//如果计算熟悉依赖的data值发生变化时会调用//案例中 当data.name值发生变化时会执行此方法evaluate () {    this.value = this.get()    this.dirty = false}复制代码

当computed中用到的data值发生变化时,视图更新调用computed值时会从新执行,获得新的计算属性值。

Observer代码实现

class Observer{	constructor (value) {	    this.value = value	    // 增加dep属性(处理数组时可以直接调用)	    this.dep = new Dep()	    //将Observer实例绑定到data的__ob__属性上面去,后期如果oberve时直接使用,不需要从新Observer,	    //处理数组是也可直接获取Observer对象	    def(value, '__ob__', this)	    if (Array.isArray(value)) {	    	//这里只测试对象	    } else {	    	//处理对象	      	this.walk(value)	    }	}	walk (obj) {    	const keys = Object.keys(obj)    	for (let i = 0; i < keys.length; i++) {    		//此处我做了拦截处理,防止死循环,Vue中在oberve函数中进行的处理    		if(keys[i]=='__ob__') return;      		defineReactive(obj, keys[i], obj[keys[i]])    	}  	}}//数据重复Observerfunction observe(value){	if(typeof(value) != 'object' ) return;	let ob = new Observer(value)  	return ob;}// 把对象属性改为getter/setter,并收集依赖function defineReactive (obj,key,val) {  	const dep = new Dep()  	//处理children  	let childOb = observe(val)  	Object.defineProperty(obj, key, {    	enumerable: true,    	configurable: true,    	get: function reactiveGetter () {    		console.log(`调用get获取值,值为${val}`)      		const value = val      		if (Dep.target) {	        	dep.depend()		        if (childOb) {		          	childOb.dep.depend()		        }	      	}      		return value	    },	    set: function reactiveSetter (newVal) {	    	console.log(`调用了set,值为${newVal}`)	      	const value = val	       	val = newVal	       	//对新值进行observe	      	childOb = observe(newVal)	      	//通知dep调用,循环调用手机的Watcher依赖,进行视图的更新	      	dep.notify()	    }  })}//辅助方法function def (obj, key, val) {  Object.defineProperty(obj, key, {    value: val,    enumerable: true,    writable: true,    configurable: true  })}复制代码

此文的重点Computed代码实现:

//空函数const noop = ()=>{}// computed初始化的Watcher传入lazy: true就会触发Watcher中的dirty值为trueconst computedWatcherOptions = { lazy: true }//Object.defineProperty 默认value参数const sharedPropertyDefinition = {  enumerable: true,  configurable: true,  get: noop,  set: noop}// 初始化computedclass initComputed {	constructor(vm, computed){		//新建存储watcher对象,挂载在vm对象执行		const watchers = vm._computedWatchers = Object.create(null)		//遍历computed		for (const key in computed) {		    const userDef = computed[key]		    //getter值为computed中key的监听函数或对象的get值		    let getter = typeof userDef === 'function' ? userDef : userDef.get		    //新建computed的 watcher		    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)		    if (!(key in vm)) {		      	/*定义计算属性*/		      	this.defineComputed(vm, key, userDef)		    }		}	}    //把计算属性的key挂载到vm对象下,并使用Object.defineProperty进行处理    //因此调用vm.somecomputed 就会触发get函数	defineComputed (target, key, userDef) {	  if (typeof userDef === 'function') {	    sharedPropertyDefinition.get = this.createComputedGetter(key)	    sharedPropertyDefinition.set = noop	  } else {	    sharedPropertyDefinition.get = userDef.get	      ? userDef.cache !== false	        ? this.createComputedGetter(key)	        : userDef.get	      : noop	      //如果有设置set方法则直接使用,否则赋值空函数	    	sharedPropertyDefinition.set = userDef.set	      	? userDef.set	      	: noop	  }	  Object.defineProperty(target, key, sharedPropertyDefinition)	}	//计算属性的getter 获取计算属性的值时会调用	createComputedGetter (key) {	  return function computedGetter () {	  	//获取到相应的watcher	    const watcher = this._computedWatchers && this._computedWatchers[key]	    if (watcher) {	    	//watcher.dirty 参数决定了计算属性值是否需要重新计算,默认值为true,即第一次时会调用一次	      	if (watcher.dirty) {	      		/*每次执行之后watcher.dirty会设置为false,只要依赖的data值改变时才会触发	      		watcher.dirty为true,从而获取值时从新计算*/	        	watcher.evaluate()	      	}	      	//获取依赖	      	if (Dep.target) {	        	watcher.depend()	      	}	      	//返回计算属性的值	      	return watcher.value	    }	  }	}}复制代码

代码已经写完,完整代码如下:

//标识当前的Dep idlet uidep = 0class 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 = nullconst 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 = 0class Watcher{	constructor(vm,expOrFn,cb,options){		//传进来的对象 例如Vue		this.vm = vm		if (options) {	      this.deep = !!options.deep	      this.user = !!options.user	      this.lazy = !!options.lazy	    }else{	    	this.deep = this.user = this.lazy = false	    }	    this.dirty = this.lazy		//在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.lazy ? undefined : this.get()	}	get(){		//设置Dep.target值,用以依赖收集	    pushTarget(this)	    const vm = this.vm	    //此处会进行依赖收集 会调用data数据的 get	    let value = this.getter.call(vm, vm)	    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 () {  		if (this.lazy) {      		this.dirty = true    	}else{    		this.run()    	}	}	//更新视图	run(){		console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)		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过程从而更新视图			this.cb.call(this.vm, value, oldValue)        }	}    //如果计算熟悉依赖的data值发生变化时会调用    //案例中 当data.name值发生变化时会执行此方法	evaluate () {	    this.value = this.get()	    this.dirty = false	}	//收集依赖	depend () {	    let i = this.deps.length	    while (i--) {	      this.deps[i].depend()	    }	}	// 此方法获得每个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		 }	}}/*----------------------------------------Observer------------------------------------*/class Observer{	constructor (value) {	    this.value = value	    // 增加dep属性(处理数组时可以直接调用)	    this.dep = new Dep()	    //将Observer实例绑定到data的__ob__属性上面去,后期如果oberve时直接使用,不需要从新Observer,	    //处理数组是也可直接获取Observer对象	    def(value, '__ob__', this)	    if (Array.isArray(value)) {	    	//这里只测试对象	    } else {	    	//处理对象	      	this.walk(value)	    }	}	walk (obj) {    	const keys = Object.keys(obj)    	for (let i = 0; i < keys.length; i++) {    		//此处我做了拦截处理,防止死循环,Vue中在oberve函数中进行的处理    		if(keys[i]=='__ob__') return;      		defineReactive(obj, keys[i], obj[keys[i]])    	}  	}}//数据重复Observerfunction observe(value){	if(typeof(value) != 'object' ) return;	let ob = new Observer(value)  	return ob;}// 把对象属性改为getter/setter,并收集依赖function defineReactive (obj,key,val) {  	const dep = new Dep()  	//处理children  	let childOb = observe(val)  	Object.defineProperty(obj, key, {    	enumerable: true,    	configurable: true,    	get: function reactiveGetter () {    		console.log(`调用get获取值,值为${val}`)      		const value = val      		if (Dep.target) {	        	dep.depend()		        if (childOb) {		          	childOb.dep.depend()		        }	      	}      		return value	    },	    set: function reactiveSetter (newVal) {	    	console.log(`调用了set,值为${newVal}`)	      	const value = val	       	val = newVal	       	//对新值进行observe	      	childOb = observe(newVal)	      	//通知dep调用,循环调用手机的Watcher依赖,进行视图的更新	      	dep.notify()	    }  })}//辅助方法function def (obj, key, val) {  Object.defineProperty(obj, key, {    value: val,    enumerable: true,    writable: true,    configurable: true  })}/*----------------------------------------初始化watch------------------------------------*///空函数const noop = ()=>{}// computed初始化的Watcher传入lazy: true就会触发Watcher中的dirty值为trueconst computedWatcherOptions = { lazy: true }//Object.defineProperty 默认value参数const sharedPropertyDefinition = {  enumerable: true,  configurable: true,  get: noop,  set: noop}// 初始化computedclass initComputed {	constructor(vm, computed){		//新建存储watcher对象,挂载在vm对象执行		const watchers = vm._computedWatchers = Object.create(null)		//遍历computed		for (const key in computed) {		    const userDef = computed[key]		    //getter值为computed中key的监听函数或对象的get值		    let getter = typeof userDef === 'function' ? userDef : userDef.get		    //新建computed的 watcher		    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)		    if (!(key in vm)) {		      	/*定义计算属性*/		      	this.defineComputed(vm, key, userDef)		    }		}	}    //把计算属性的key挂载到vm对象下,并使用Object.defineProperty进行处理    //因此调用vm.somecomputed 就会触发get函数	defineComputed (target, key, userDef) {	  if (typeof userDef === 'function') {	    sharedPropertyDefinition.get = this.createComputedGetter(key)	    sharedPropertyDefinition.set = noop	  } else {	    sharedPropertyDefinition.get = userDef.get	      ? userDef.cache !== false	        ? this.createComputedGetter(key)	        : userDef.get	      : noop	      //如果有设置set方法则直接使用,否则赋值空函数	    	sharedPropertyDefinition.set = userDef.set	      	? userDef.set	      	: noop	  }	  Object.defineProperty(target, key, sharedPropertyDefinition)	}	//计算属性的getter 获取计算属性的值时会调用	createComputedGetter (key) {	  return function computedGetter () {	  	//获取到相应的watcher	    const watcher = this._computedWatchers && this._computedWatchers[key]	    if (watcher) {	    	//watcher.dirty 参数决定了计算属性值是否需要重新计算,默认值为true,即第一次时会调用一次	      	if (watcher.dirty) {	      		/*每次执行之后watcher.dirty会设置为false,只要依赖的data值改变时才会触发	      		watcher.dirty为true,从而获取值时从新计算*/	        	watcher.evaluate()	      	}	      	//获取依赖	      	if (Dep.target) {	        	watcher.depend()	      	}	      	//返回计算属性的值	      	return watcher.value	    }	  }	}}复制代码

computed测试:

//1、首先来创建一个Vue构造函数:function Vue(){}//2、设置data和computed的值:let data={    name:'Hello',}let computed={	getfullname:function(){		console.log('-----走了computed 之 getfullname------')		console.log('新的值为:'+data.name + ' - world')		return data.name + ' - world'	}}//3、实例化Vue并把data挂载到Vue上let vue 		= new Vue()vue.data 		= data//4、创建Watcher对象let updateComponent = (vm)=>{	// 收集依赖	data.name	}let watcher1 = new Watcher(vue,updateComponent,()=>{})//5、初始化Data并收集依赖observe(data)//6、初始化computedlet watcher2 = new initComputed(vue,computed)复制代码

在浏览器console中测试:

//首先获得一次getfullnamevue.getfullname//第二次调用getfullname看看会有什么变化呢vue.getfullname复制代码

分析:调用vue.getfullname第一次会打印 '-----走了computed 之 getfullname------',即计算属性第一次计算了值,第二次调用时,不会再打印值

即直接获取的缓存值,为什么第二次是获得的缓存值呢,因为第二次执行时watcher.dirty=true,就会直接返回watcher.value值。

//为data.name赋值data.name = 'Hi'复制代码

分析:执行data.name时会触发两个Watcher监听函数(为什么是两个Watcher自己去脑补一下额!),一个是全局的watcher,一个是computed的watcher,第一个Watcher会更新视图,第二个Watcher会触发watcher.dirty=true。

//name值变更之后再次执行会是什么结果呢vue.getfullname//再执行一次vue.getfullname复制代码

分析:运行vue.getfullname时会执行computedGetter函数,因为watcher.dirty=true因此会从新计算值,因此会打印 '-----走了computed 之 getfullname------',值为'HI world', 再次执行只会获得计算属性的缓存值。

所有测试代码如下:

/*----------------------------------------Vue------------------------------------*/function Vue(){}/*----------------------------------------测试代码------------------------------------*/// 调用let data={    name:'Hello',}let computed={	getfullname:function(){		console.log('-----走了computed 之 getfullname------')		console.log('新的值为:'+data.name + ' - world')		return data.name + ' - world'	}}let vue 		= new Vue()vue.data 		= datalet updateComponent = (vm)=>{	// 收集依赖	data.name}let watcher1 = new Watcher(vue,updateComponent,()=>{})observe(data)let watvher2 = new initComputed(vue,computed)//测试 浏览器console中相继运行一下代码测试vue.getfullnamevue.getfullnamedata.name='Hi'vue.getfullnamevue.getfullname复制代码

若有疑问欢迎交流。

转载地址:http://hjwgl.baihongyu.com/

你可能感兴趣的文章
单例模式
查看>>
Android 数据库 LiteOrm 的使用
查看>>
标准Web系统的架构分层
查看>>
MVC4中EasyUI Tree异步加载JSON数据生成树
查看>>
jquery 画板折叠
查看>>
hibernate 缓存
查看>>
AES-GCM算法
查看>>
Apache Pulsar简介
查看>>
Perl引用入门
查看>>
关于端口号你知道多少!
查看>>
用Canvas写一个炫酷的时间更新动画玩玩
查看>>
windows sever2003安装Wamp 2.5不成功——VC 11不支持Windows Server 2003和win XP?
查看>>
分享一下上个星期的香港行程
查看>>
HUT-1675 找啊找啊找GF DP
查看>>
使用jQuery插件jScrollPane开发Mac OSX Lion风格的滚动条
查看>>
使用jQuery开发iOS风格的页面导航菜单
查看>>
SQL:事务(1)
查看>>
js事件定义方式和获取事件对象event总结
查看>>
Erlang练习-UDP
查看>>
ACE定时器
查看>>