前言
Vue2.0中的响应式处理(数据改变视图改变)的核心是Object.definedProperty。
data中的变量才是响应式变量,但对于对象类型的变量,给其添加新的属性或删除属性时,视图不能响应到值的变化;对于数组类型的变量,通过数组下标修改属性值,视图不是不能响应到值的变化,通过push、pop、splice等方法修改数组的值,视图能够响应值的变化。为什么是这样的规则?先从Object.definedProperty的使用规则开始说起。
Object.definedProperty的使用规则
1、给{}类型对象的属性重新定义规则
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可写
get: function() {
console.log('get');
return val;
},
set: function(newVal) {
// 设置时,可以添加相应的操作
console.log('set:', val);
val += newVal;
}
});
}
let obj = {name: '成龙大哥', say: ':其实我之前是拒绝拍这个游戏广告的,'};
Object.keys(obj).forEach(k => {
defineReactive(obj, k, obj[k]);
});
obj.say = '后来我试玩了一下,哇,好热血,蛮好玩的'; //会触发set
obj.age = 20 //不会触发set
console.log(obj.name + obj.say, obj);
对于新增的对象属性不会触发set方法。
2、给数组[]类型对象的属性重新定义规则
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可写
get: function() {
console.log('get');
return val;
},
set: function(newVal) {
// 设置时,可以添加相应的操作
console.log('set:', val);
val += newVal;
}
});
}
let arr = [1,2,3,4,5];
arr.forEach((v, i) => {
defineReactive(arr, i, v);
});
arr[0] = 'oh nanana'; // 触发set
arr.push(6) // 不会触发set
console.log(arr)
let arr2 = [{name: '1'},{name: '2'}, {name: '3'}];
arr2.forEach((v, i) => {
defineReactive(arr2, i, v);
});
arr2.forEach((v, i) => {
v.status= true // 不会触发set
});
arr2.forEach((v, i) => {
v.name = true // 不会触发set
});
arr2[0] = {name: 2} // 会触发set
arr2.splice(1,1,{name: 111}) // 会触发set
arr2.push({name: 'tt'}) // 不会触发set
arr2.length = 2 // 不会触发set
初始化时会将data中的值通过defineProperty设置为响应式,当给变量赋值时就会触发变量对应的set方法,从而调用视图更新的函数。
这里要说的是Object.definedProperty是可以通过数组下标修改值之后触发对应的set方法的,通过push、pop等方法修改数组的值之后是不会触发set方法。
但是Vue中考虑到性能问题,对于数组类型的数据改变自己定义了一套触发响应的规则,通过数组下标修改数组的值是不会触发视图更新的,但是通过push、pop、shift、unshift等方法修改数组的值是可以触发视图更新的。
Vue官方说法不会触发视图更新的情况:
1、对于数组对象
2、对于{}对象
vue对数组做的处理
默认的Object.definedProperty中,对于操作数组的方法,默认只有利用数组中的splice修改数组值时可以触发set(splice(1, 1, value), 相当于通过数组下标修改属性值)。
但因为Vue做了处理,以下变异方法修改数组的值都可以触发视图的更新。
vue源码如下:
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// notify change
ob.dep.notify();
return result
});
});
/**
* Define a property.
*/
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
也就是说vue中基于原生Array的原型对象创建了一个新对象,重新定义了数组中的以下方法:
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
将以上方法定义通过defineProperty定义成了响应式, 对于push、unshift、splice方法给数组新增值时还做了特别的处理。在vue中直接操作以上方法修改数组值,视图都能响应。
Object.defineProperty作为vue2.0响应式的核心将在vue3.0 被Proxy取代。因为前者存在一些局限,前者不能监听到对象的属性的增加和删除,以及不能监听到数组通过原生的方法对数组做的修改等问题,后者提供了对更多种类数据的拦截和对对象更好的支持。
参考资料:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。