前言
今天在面试的时候被问到,vue2有什么不足之处?我脑海中朦朦胧胧的看过几篇文章,说vue2监测不到对数组的变化。同时,在看一个项目的时候,记得通过arr[0] = 1修改数组,结果页面内容没有任何变化。于是不假思索的说到“vue2不能监测数组的变化”。面试官又问,嗯?为什么不能监测呢?你说说呢?这一点我还真没进行深入研究,就单纯只知道它不能监测数组的变化。
这就来仔细分析下为什么它对数组有特殊的处理。是因为它监测不了吗?还是别的原因?更或者说它可以监测到数组数据的变化方式,只是我回答错误了。
1、Vue2.0中监听数据据
众所周知啊,vue2.0中监听数据是通过Object.defineProperty来递归查询对象全部属性,从而实现对一个对象全部属性的监听。我们首先来验证一下,Object.defineProperty这个API能否监测数组的变化。
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log(`您试图访问value值: ${value}`)
return value
},
set(newVal) {
console.log(`您试图改变value值: ${newVal}`)
value = newVal
}
})
}
function observe(data) {
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key])
})
}
let arr = ["apple", "banana"]
observe(arr)
通过以上代码,我们可以得出结论,通过Object.defineProperty这个API是可以监测到数组的变化的。
那么问题来了,既然
vue2是通过Object.defineProperty这个API来监测数据的,那么为什么数组就不行呢?还是说它vue对数组有特殊的处理?
2、验证vue2中改变数组
我们简单通过几行代码回顾一下vue2对数组变化的监测。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./vue2.js"></script>
</head>
<body>
<div id="app">
<h1>{{arr}}</h1>
<button @click="changeval">change</button>
<button @click="changeval2">change2</button>
<button @click="printarr">print</button>
</div>
<script>
var v = new Vue({
el:"#app",
data(){
return{
arr : ["apple", "banana"]
}
},
methods:{
changeval(){
this.arr[0] = 10
},
changeval2(){
this.arr.splice(0, 1, 10)
},
printarr(){
console.log(this.arr);
}
}
})
</script>
</body>
</html>
- 首先我定义了一个
changeval方法,此方法是通过直接赋值的方式this.arr[0] = 10这种方式改变数组。同时定义了一个printarr方法在控制台输出数组。让我们来看一下vue2能否检测到数组的变化(直接赋值)
当我们点击change按钮时,arr的确被改变了,但是vue没有捕获到。
- 当我们通过第二种方式-
splice方法,改变数组,看vue2能否监测到呢?
通过上述实验可以得出,通过splice这种方式改变数组,vue2就可以监测到
我们可以得出一个大概的结论,就是
vue2是通过数组的一些操作(如splice等)来监测数组的变化的。
3、vue2监测数组变化原理
通过阅读源码,我们发现,在用Object.defineProperty监测数据的时候,它对Array做了特殊处理。不使用下面代码对数据进行监听
var keys = Object.keys(value);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock);
}
我们尝试修改一下,把它改成对数组也进行defineReactive
用之前同样的代码,我们这次通过changeval(this.arr[0] = 10)来修改数组,看vue2是否能够监测到。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./vue2.js"></script>
</head>
<body>
<div id="app">
<h1>{{arr}}</h1>
<button @click="changeval">change</button>
<button @click="changeval2">change2</button>
<button @click="printarr">print</button>
</div>
<script>
var v = new Vue({
el:"#app",
data(){
return{
arr : ["apple", "banana"]
}
},
methods:{
changeval(){
this.arr[0] = 10
},
changeval2(){
this.arr.splice(0, 1, 10)
},
printarr(){
console.log(this.arr);
}
}
})
</script>
</body>
</html>
可以发现,数组中数据变化被vue2检测到。
可见,使用Object.defineProperty是可以监测到数组中值的变化的,那为什么vue2没有这样做呢?那为什么通过splice等方式改变数组值就可以被vue2检测到呢
4、数组数据是怎么被监听的
首先来回答第二个问题,为什么通过splice等方式改变数组值就可以被vue2检测到呢?原因是vue2对数组的方法进行重写。
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 = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
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({
type: "array mutation" /* TriggerOpTypes.ARRAY_MUTATION */,
target: this,
key: method
});
}
return result;
});
});
可以看到,vue2对'push','pop','shift','unshift','splice','sort','reverse'这些方法进行了重写
5、Vue为什么不通过Object.defineProperty检测数组变动
性能问题
请参考尤大的回答:为什么vue没有提供对数组属性的监听
个人认为:在
js中,数组经常被当作栈、队列,大多数情况,我们对数组的操作就是遍历,如果用Object.defineProperty监测它的变化,这个就会有很大的性能问题。正如尤大的“性能代价和获得的用户体验收益不成正比。”
所以 Vue 不在数组每个键上设置,而是在数组上定义__ob__ (var ob = this.__ob__;),并且替换了 push 等等能够影响原数组的原型方法。
总结
所以,vue2并不是不能监测数组的变化,而是不能监听直接给数组赋值的这种变化(this.arr[0] = 10)。因为性能问题,vue2并没有使用Object.defineProperty来监测数组,而是通过重写数组方法的这一方式,来监测数组变化。