Vue 之 Data
Vue 之 Data
描述
Vue 实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化。对象必须是纯粹的对象 (含有零个或多个的 key/value 对):浏览器 API 创建的原生对象,原型上的属性会被忽略。大概来说,data 应该只能是数据 - 不推荐观察拥有状态行为的对象。
一旦观察过,不需要再次在数据对象上添加响应式属性。因此推荐在创建实例之前,就声明所有的根级响应式属性。
实例创建之后,可以通过 vm.$data
访问原始数据对象。Vue 实例也代理了 data 对象上所有的属性,因此访问 vm.a
等价于访问 vm.$data.a
。
以 _
或 $
开头的属性 不会 被 Vue 实例代理,因为它们可能和 Vue 内置的属性、API 方法冲突。你可以使用例如 vm.$data._property
的方式访问这些属性。
当一个组件被定义,data
必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data
仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data
函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
如果需要,可以通过将 vm.$data
传入 JSON.parse(JSON.stringify(...))
得到深拷贝的原始数据对象。
注意,如果你为 data 属性使用了箭头函数,则 this 不会指向这个组件的实例,不过你仍然可以将其实例作为函数的第一个参数来访问。
data: vm => ({ a: vm.myProp })
Vue组件中的data为什么是函数
类比引用数据类型
Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;
javascipt只有函数构成作用域(注意理解作用域,只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域),data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。
举个例子:
const MyComponent = function( ) {};
MyComponent.prototype.data = {
a: 1,
b: 2,
}
const component1 = new MyComponent();
const component2 = new MyComponent();
component1.data.a === component2.data.a; // true
component1.data.b = 5;
component2.data.b // 5
如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改;
两个实例应该有自己各自的域才对,需要通过下面的方法来进行处理:
const MyComponent = function( ) {
this.data = this.data();
};
MyComponent.prototype.data = function( ) {
return {
a: 1,
b: 2,
}
};
这样么一个实例的data属性都是独立的,不会相互影响了。
所以,你现在知道为什么vue组件的data必须是函数了吧。这都是因为js本身的特性带来的,跟vue本身设计无关。其实vue不应该把这个方法名取为data(),应该叫setData或其他更容易理解的方法名。
不要把所有东西都放进data里
Vue组件实例中的data是我们再熟悉不过的东西了,用来存放需要绑定的数据但是对于一些特定场景,data虽然能够达到预期效果,但是会存在一些问题我们写下如下代码,建立一个名单,记录他们的名字,年龄和兴趣:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Data</title>
</head>
<body>
<div id="app"> </div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script> const template = ` <div> <h3>data列表</h3> <ol> <li v-for="item in dataList"> 姓名:{{item.name}},年龄:{{item.age}},兴趣:{{item.hobby.join('、')}} </li> </ol> </div> ` new Vue({ el: '#app', data () { return { dataList: [ { name: '张三', age: 33, hobby: ['唱','跳','rap','篮球'] }, { name: '李四', age: 24, hobby: ['唱','跳','rap','篮球'] }, { name: '王五', age: 11, hobby: ['唱','跳','rap','篮球'] }, { name: '赵六', age: 54, hobby: ['唱','跳','rap','篮球'] }, { name: '孙七', age: 23, hobby: ['唱','跳','rap','篮球'] }, { name: '吴八', age: 55, hobby: ['唱','跳','rap','篮球'] } ], } }, mounted () { console.table(this.dataList) // 打印列表形式的dataList console.log(this.dataList) // 打印字面量 }, template }) </script>
</body>
</html>
Vue通过data生成我们能用的绑定数据,大概走了以下几个步骤:
- 从
initData
方法 中获取你传入的data,校验data是否合法 - 调用
observe
函数,新建一个Observer
实例,将data
变成一个响应式对象,而且为data添加__ob__
属性,指向当前Observer
实例 Observer
保存了你的value
值、数据依赖dep
和vue
组件实例数vmCount
- 对你的data调用
defineReactive$$1
递归地监所有的key/value
(你在data中声明的),使你的key/value都有自己的dep
,getter
和setter
我们忽略html的内容,重点放在这个 dataList
上(我用2种不同的形式打印了dataList),如上述步骤2、3、4所说,data中每个key/value值(包括嵌套的对象和数组)都添加了一个Observer。
之前我们说滥用data会产生一些问题,问题如下:
设想一下这样的场景,如果你的data属于纯展示的数据,你根本不需要对这个数据进行监听,特别是一些比这个例子还复杂的列表/对象,放进data中纯属浪费性能。
那怎么办才好?
放进computed中
还是刚才的代码,我们创建一个数据一样的list,丢进computed里:
computed: {
computedList () {
return [
{ name: '张三', age: 33, hobby: ['唱','跳','rap','篮球'] },
{ name: '李四', age: 24, hobby: ['唱','跳','rap','篮球'] },
{ name: '王五', age: 11, hobby: ['唱','跳','rap','篮球'] },
{ name: '赵六', age: 54, hobby: ['唱','跳','rap','篮球'] },
{ name: '孙七', age: 23, hobby: ['唱','跳','rap','篮球'] },
{ name: '吴八', age: 55, hobby: ['唱','跳','rap','篮球'] }
]
}
},
打印computedList,你得到了一个没有被监听的列表
为什么computed没有监听我的data
因为我们的computedList中,没有任何访问当前实例属性的操作,根据Vue的依赖收集机制,只有在computed中引用了实例属性,触发了属性的getter,getter会把依赖收集起来,等到setter调用后,更新相关的依赖项。
我们来看官方文档对computed的说明:
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function ( ) {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
这里强调的是
所以,对于任何复杂逻辑,你都应当使用计算属性。
但是很少有人注意到api说明中的这一句:
计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。
也就是说,对于纯展示的数据,使用computed会更加节约你的内存
另外 computed 其实是Watcher的实现,有空的话会更新这部分的内容
为什么说“至少2.0是如此”
因为3.0将使用Proxy来实现监听,性能将节约不少,参见https://www.jianshu.com/p/f99822cde47c