我们先来看一下 Vue 到底对 data 做了什么
// index.html
<body>
<div id="app"></div>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
</body>
// main.js
const Vue = window.Vue
const myData = {n:0}
console.log(myData) // 第一次打印myData {n:0}
new Vue({
data: myData,
template: `
<div>{{n}}</div>
`
}).$mount('#app')
setTimeout(() => {
myData.n += 10
console.log(myData) // 第一次打印myData,此时myData已经传给了vue实列了,数据会悄悄发生改变如下图
}, 0)
控制台打印的两次n
-
第一次:{n:0}
-
第二次:
{__ob__: Observer}
n: (...) //看看此时的n,你究竟经历了什么哈哈哈哈哈哈哈
__ob__: Observer {value: {...}, dep: ce, vmCount: 1}
get n: f ()
set n: f (t)
__proto__: Object
这就是响应式,vue监听了这个数值,vue 会让 vm 成为 myData 的代理,vue 会对 myData 的所有属性进行监控,此时的n: (...)不再是一个单纯的n属性了,或者说根本就不是一个属性了,但是通过这个key(n)读取他,但是他的改变会通过get和set变化。当数值发生改变的时候,vue就重新渲染
vue中data是响应式的
-
const vm=new Vue({data:{n:0}) 创建了一个有data属性的vm实列
-
如果我们修改了data的数据,那么UI就会响应数据的改变
原理是Vue 将遍历此对象所有的属性,并使用Object.defineProperty 把这些属性全部转为 getter/setter对data里面的数据n进行监听
function proxy({data}){
const obj = {}
Object.defineProperty(obj,'n',{
get(){
return data.n //获取
},
set(newValue){
data.n = newValue
}
})
return obj //返回一个代理对象
}
let dataProxy = proxy({data:{n:0})
//便可以dataProxy.n来访问和修改原data中的数据,以便达到监听数据变化的作用
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
- Object.defineProperty(obj,'n',{...}) 有一个n就能监听n
- 如果页面出现了一个b并对其发生改变,如果Object.defineProperty(obj,'n',{...})没有监听b则不会发生响应
怎样在Vue中完完全全做到数据响应呢
1. 对象
-
在data中提前申明好所有的key,不给属性就行
-
使用Vue.set或者
vm.$set (this.$set)
- 自动新增key
- 自动创建key的监听和代理(如果没有创建过)Object.defineProperty(obj,'n',{...})
- 响应UI(但不会立刻更新)
var vm = new Vue({
data:{
a:1
}
})
// `vm.a=33` 是响应式的
vm.b = 2 // `vm.b` 是非响应式的
做到b响应呢
可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:
this.$set(this.someObject,'b',2)
2. 数组
- 数组的长度是可以一直增加的,数组的下标就是key
- 我们就没办法提前把数组的key都申明 因为不知道长度具体会是多少
- 总不能每次数组新增一项就用一次Vue.set()吧?
so,vue中的做法是:变异方法 (mutation method)
Vue 将被侦听的数组的变异方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()