目录
Vue的响应式效果:
我们知道,当Vue的实例对象中的data元素发生改变时,页面中用到该元素的地方会进行重新解析模板。即更改数据会影响页面的显示。但对 数组元素中的元素 通过数组下标的形式进行更改时,页面不会进行响应式变化。即:
<div id="root">
<button @click="changehobby">将第一个爱好改为打游戏</button>
<ul>
<li v-for="h in hobby">{{h}}</li>
</ul>
</div>
<script>
new Vue({
el: '#root',
data: {
hobby: ['吃饭', '睡觉', '打豆豆']
},
methods: {
changehobby() {
this.hobby[0] = '打游戏'
}
}
})
</script>
当按下按钮时,页面不会响应式地将 “吃饭” 改为 “打游戏”,但通过控制台我们可以发现,在hobby数组内部已经进行了修改。
但我们将该数组以对象的形式储存时会发现,页面又会正常进行响应式变化。
<div id="root">
<button @click="changehobby">将第一个爱好改为打游戏</button>
<ul>
<li v-for="h in hobby">{{h}}</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
hobby: {
h1: '吃饭',
h2: '睡觉',
h3: '打豆豆'
}
},
methods: {
changehobby() {
this.hobby.h1 = '打游戏'
}
}
})
</script>
Vue的响应式原理:
通过控制台可知,进行vm._data === data 的判断结果为true,但显然初始化时 data 与vm._data 不相同。所以一定是Vue在进行数据处理时对data进行了操作。
let data = {
name: 'baciii',
age: 20
}
console.log(data);
let obs = new Obs(data);
let vm = {};
vm._data = data = obs;
console.log(data);
console.log(data === vm._data);
function Obs(data) {
Object.keys(data).forEach((e) => {
Object.defineProperty(this, e, {
get() {
return data[e];
},
set(val) {
console.log('The data has been modified');
data[e] = val;
}
})
})
}
内部进行的操作大致如上述代码所述,Obs 是一个构造函数,很容易可以看出来 Obs 对原始数据 data 进行了一次数据代理,并在 data 的属性被修改时发出提示信息。
vm可以理解为Vue的实例化对象,在进行数据代理后,将其结果返回给 vm._data 与原数据 data. Vue还另外将数据中的每个属性单独添加到了自身的实例化对象中,并在数据被修改时进行响应式操作。
数组元素单独修改页面不会响应式改变的原因:
通过对Vue响应式原理的大致了解,我们知道了Vue主要是通过数据代理的 set() 方法让页面进行响应式改变的。所以我们用控制台找到该数组,便可以发现其中的端倪:
可以看到,数组 hobby 是带有get() set()方法的,但数组中的每一个元素是没有这两个方法的。
我们再找到同样写法的对象:
可以发现,对象中的每一个元素都是带有get()、set()方法的。这就可以解释为什么直接改变数组元素不会引起页面响应式变化而对象可以。
Vue内置API:Vue.set():
Vue中内置的API--Vue.set(target, PropertyName/index, value)可以给响应式元素添加/修改一个对象,并保证该对象也是响应式的。根据此原理,便可以直接改变数组中的元素并使改变后的元素仍然有响应式效果。
changehobby() {
Vue.set(this.hobby, 0, '打游戏');
// this.hobby[0] = '打游戏';
}
值得注意的是,Vue.set的对象不能是 Vue 实例本身,也不能直接给 data 直接添加属性,只能添加到 data 内的某个对象。
Vue 官网的原话为:Vue.set() 的对象不能是 Vue 实例,或者 Vue 实例的跟数据对象。
Vue中的数组操作方法:
通过查阅 Vue 官网可以发现,Vue 对以下七个能够直接改变数组本身的方法进行了包装(即与数组原型对象上的同名方法不一样),并让使用了这些方法的数组中的元素能够保持响应式效果。
所以,通过splice()方法对元素进行修改同样可以保持其响应式属性。
changehobby() {
this.hobby.splice(0, 1, '打游戏');
// Vue.set(this.hobby, 0, '打游戏');
// this.hobby[0] = '打游戏';
}
一些需要注意的点:
若数组中的元素是对象的话,那么对象中的属性(不是对象本身!)可以直接更改,因为对象中的属性同样拥有 get()、set() 方法。
数组常用的方法如 filter() 等没有被 Vue 重新包装(因为没有直接改变数组的元素而是生成了一个新数组),但是可以直接将其新数组赋予原数组。因为数组本身是有 get()、set() 方法的,所以直接对数组本身进行改变也会保持其页面响应式属性。