vm.$set(target,key,value)
(1)参数
(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){
//做点什么
}
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即可。
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;
}