前言
今天在面试的时候被问到,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
来监测数组,而是通过重写数组方法的这一方式,来监测数组变化。