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