深入浅出Vue.js----变化侦测相关的API实现原理----vm.$set

一、用法

vm.$set(target,key,value)

(1)参数

  • {Object | Array} target
  • {string | number} key
  • {any} value

(2)返回值

{ Function } unwatch

(3)用法

在object上设置一个属性,如果object是响应式的,Vue.js会保证属性被创建后也是响应式的,并且触发视图更新。这个方法主要用来避开Vue.js不能侦测属性被添加的限制。

(4)注意

target不能是Vue.js实例或则Vue.js实例的根数据对象。

(5)解决问题

1、只有已经存在的属性的变化会被追踪到,新增的属性无法追踪到。因为在ES6之前,Javascript并没有提供元编程的能力,所以根本无法侦测object什么时候被添加了一个属性。

2、而vm.$set就是为了解决这个问题而出现的。使用它,可以为object新增属性,然后Vue.js就可以将这个新增属性转换成响应式的。

3、例

var vm = new Vue({
	el:'#el',
	template:'#demo-template',
	methods:{
		//直接给obj设置一个属性,当action方法被调用时,会为obj新增一个name属性,而Vue.js并不会得到        
        //任何通知,新增的这个属性也不是响应式的,Vue.js根本不知道这个obj新增了属性,就好像
        //Vue.js无法知道我们使用array.lenght = 0 清空了数组一样
		action(){
			this.obj.name = 'berwin'
		}
	}
	data:{
		obj:{}
	}
})

4、vm.$set就可以解决这个事情。vm.$set实现

import {set} from '../observer/index'
Vue.prototype.$set = set;

1)在Vue.js的原型上设置$set属性。其实我们使用的所有以vm.$开头的方法都是在Vue.js的原型上设置的。

2)vm.$set的具体实现其实是在observer中抛出的set方法。

5、先创建一个set方法

export function set(target,key,val){
	//做点什么
}

二、Array的处理

export function set(target,key,val){
	if(Array.isArray(target)&&isValidArrayIndex(key)){
		target.length = Math.max(target.length,key)
		target.splice(key,1,val)
		return val
	}
}

(1)如果target是数组并且key是一个有效的索引值,就先设置length属性。这样如果我们传递的索引值大于当前数组的length,就需要让target的length等于索引值。

(2)通过splice方法把val设置到target中的指定位置(参数中提供的索引值的位置)。当我们使用splice方法把val设置到target中的时候,数组拦截器会侦测到target发生了变化,并且会自动帮助我们把这个新增的val转换成响应式的。

(3)最后,返回val即可。

  • key已经存在于target中

export function set(target,key,val){
	if(Array.isArray(target)&&isValidArrayIndex(key)){
		target.length = Math.max(target.length,key)
		target.splice(key,1,val)
		return val
	}
	//新增
	if(key in target && !(key in Object.prototype)){
		target[key] = val;
		return val;
	}
}

(1)由于key已经存在于target中,所以其实这个key已经被侦测了变化。也就是说,这种情况属于修改数据,直接用key和val改数据就好了。修改数据的动作会被Vue.js侦测到,所以数据发生变化后,会自动向依赖发送通知。

  • 处理新增的属性

export function set(target,key,val){
	if(Array.isArray(target)&&isValidArrayIndex(key)){
		target.length = Math.max(target.length,key)
		target.splice(key,1,val)
		return val
	}
	if(key in target && !(key in Object.prototype)){
		target[key] = val;
		return val;
	}
	//新增
	const ob = target._ob_;
	//target不能是Vue.js实例或则Vue.js实例的根数据对象
	if(target._isVue || (ob && ob.vmCount)){
		process.env.NODE_ENV !== 'production' && warn(
			'Avoid adding reactive properties to a Vue instance or its root $data' +
			'at runtime - declare it upfront in the data option'
		)
		retun val;
	}
	//不是响应式的
	if(!ob){
		target[key] = val;
		return val;
	}
	//响应式的将新增属性变为响应式的
	defineReactive(ob.value,key,val);
	//触发变化通知
	ob.dep.notify();
}

(1)获取target的_ob_属性

(2)处理target不能是Vue.js实例或则Vue.js实例的根数据对象的情况。

1、使用target_isVue来判断target是不是Vue.js实例

2、使用ob.vmCount来判断它是不是根数据对象。(this.$data就是根数据)

(3)处理target不是响应式的情况。

如果target身上没有_ob_属性,说明它并不是响应式的,并不需要做什么特殊处理,只需要通过key和val在target上设置就行了。

(4)如果前面的所有判断条件都不满足,那么说明用户是在响应式数据上新增了一个属性,这种情况下需要追踪这个新增属性的变化,即使用defineReactive将新增属性转换成getter/setter的形式即可。

(5)最后,向target的依赖发送变化通知,并返回val。

function defineReactive(data,key,val){
	
	let childOb = observer(val);
	let dep = new Dep();
	Object.defineProperty(data,key,{
		enumerable:true,
		get:function(){
			dep.depend();
			
			if(childOb){
				childOb.dep.depend()
			}
			return val
		}
		set:function(newVal){
			if(val==newVal){
				return;
			}
			val = newVal;
			dep.notify();//修改
	})
}

export function observer(value,asRootData){
	if(!isObject(value)){
		return
	}
	let ob;
	if(hasOwn(value,'_ob_')&&value._ob_instanceof Observer){
		ob = value._ob_
	}else{
		ob = new Observer(value);
	}
	return ob;
}

 

你可能感兴趣的:(vue学习)