Vue.set和vm.$set源码解析
为什么要使用这两个API我们知道在Vue中,对象和数组在某些情况下无法触发响应式数据更新。比如:const vm = new Vue({el: '#root',data: {price: 10,},});vm.price = 20; // 重新渲染视图vm.discount = 10; // 并不是响应式的数据或者另一种情况,直接通过数组的下标修改数组的某...
为什么要使用这两个API
我们知道在Vue中,对象和数组在某些情况下无法触发响应式数据更新。比如:
const vm = new Vue({
el: '#root',
data: {
price: 10,
},
});
vm.price = 20; // 重新渲染视图
vm.discount = 10; // 并不是响应式的数据
或者另一种情况,直接通过数组的下标修改数组的某一项:
const vm = new Vue({
el: '#root',
data: {
list: ['cat', 'dog', 'pig'],
},
});
vm.list[1] = 'fish'; // 不会重新渲染视图
为了解决上面的问题,Vue给出了两个Api,分别是Vue.set和vm.$set
使用方式
这两个API的区别是Vue.set是定义在构造函数上的,而vm.$set是定义在原型对象上的。使用方式如下:
Vue.set(target, key, value);
// 或者
vm.$set(target, key, value)
修改数组也是同样的方法,key就是下标。
不允许动态地添加根级响应的属性,必须要在初始化实例前声明好所有根级属性,哪怕是一个空值。
解析源码
Vue.set和vm.$set指向的是一个方法set。它们要做的就是一件事情,就是要将传入的对象的属性变成响应式的。
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
// 这句话的意思就是: 在一个对象上设置属性。当这个属性不存在当前对象上,
// 那么就添加这个属性和变更通知。
function set(target, key, val) {
if (isUndef(target) || isPrimitive(target)) {
warn(
'Cannot set reactive property on undefined, null, or primitive value: ' +
target
);
}
}
首先set方法会进行判断,传入的target是否是null、undefined或是原始类型(string, number, boolean, symbol)。如果是就抛出异常—— 无法将undefined,null或者原始类型target设置为响应式属性。
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val;
}
第一行判断target是否是一个数组,并且key值是否是合法key。下面是检查的方法,相信都能看懂。
/**
* Check if val is a valid array index.
*/
function isValidArrayIndex(val) {
var n = parseFloat(String(val));
return n >= 0 && Math.floor(n) === n && isFinite(val);
}
第二行将target.length设置为target.length和key最大的值。这是为了防止某些情况下会报错,比如: 设置的key值,大于数组的长度。
new Vue({
el: '#root',
data: {
list: [1, 2]
}
})
Vue.set(vm.list, 10, 'error');
第三行是一个splice方法,将key位置的值替换为val。注意:当调用splice的时候就会重新渲染新的试图。因为这是一个特殊的splice方法,Vue将其改写了,看下面源码:
var arrayProto = Array.prototype;
// 创建了一个以arrayProto为原型的对象。
// 也就是 arrayMethods.prototype = arrayProto
var arrayMethods = Object.create(arrayProto);
var methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse'];
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// 缓存原始数组的方法
var original = arrayProto[method];
// def就是Object.definePropery 给对象定义上面7个方法
def(arrayMethods, method, function mutator() {
...省略部分代码(感兴趣可以看源码)
// 发送更新通知
ob.dep.notify();
return result;
});
});
这是Vue定义的7个对象原型上的方法。
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val;
}
上面代码的意思是如果target对象上已经存在key,且这个key不是原型对象上的属性。则直接将val赋值给这个属性。
var ob = target.__ob__;
if (target._isVue || (ob && ob.vmCount)) {
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
);
return val;
}
__ob__指的是Observer对象,vmCount用来表示当前对象是否是根级属性。_isVue用来表示是否是Vue实例,用来避免被observed,存在于Vue实例上。那么这个if就是用来判断target是否是根级属性或是Vue实例。
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
// 翻译: Observer这个类依"触摸"每个被观察的对象。一旦"触摸",
// observer将每个目标对象的属性转成getter/setter,收集所有依赖触发更新。
var Observer = function Observer(value) {
...省略
this.vmCount = 0;
def(value, '__ob__', this);
...省略
};
if (!ob) {
target[key] = val;
return val;
}
defineReactive$$1(ob.value, key, val);
ob.dep.notify();
return val;
第1行到第4行的意思是,如果target上不存在Observer对象(这说明target只是一个普通的对象,不是一个响应式数据),则直接赋值给key属性。
第5行,ob.value其实就target,只不过它是Vue实例上data里的已经被追踪依赖的对象。然后在这个被observed的对象上增加key属性。让key属性也设置getter/setter。
第6行,让dep通知所有watcher重新渲染组件。
完整代码
function set(target, key, val) {
if (isUndef(target) || isPrimitive(target)) {
warn(
'Cannot set reactive property on undefined, null, or primitive value: ' +
target
);
}
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;
}
var ob = target.__ob__;
if (target._isVue || (ob && ob.vmCount)) {
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
);
return val;
}
if (!ob) {
target[key] = val;
return val;
}
defineReactive$$1(ob.value, key, val);
ob.dep.notify();
return val;
}
更多推荐



所有评论(0)