用法css
官網對data屬性的介紹以下:html
意思就是:data保存着Vue實例裏用到的數據,Vue會修改data裏的每一個屬性的訪問控制器屬性,當訪問每一個屬性時會訪問對應的get方法,修改屬性時會執行對應的set方法。vue
Vue內部實現時用到了ES5的Object.defineProperty()這個API,也正是這個緣由,因此Vue不支持IE8及如下瀏覽器(IE8及如下瀏覽器是不支持ECMASCRIPT 5的Object.defineProperty())。react
以一個Hello World爲例,以下:數組
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script> <title>Document</title> </head> <body> <div id="app">{{message}}</div> <button id="b1">測試按鈕</button> <script> var app = new Vue({ el:'#app', data:{ //data裏保存着Vue實例的數據對象,這裏只有一個message,值爲Hello World! message:"Hello World!" } }) document.getElementById('b1').addEventListener('click',function(){ //在b1這個按鈕上綁定一個click事件,內容爲修正app.message爲Hello Vue! app.message='Hello Vue!'; }) </script> </body> </html>
顯示的內容爲:瀏覽器
當咱們點擊測試按鈕後,Hello World!變成了Hello Vue!:app
注:對於組件來講,須要把data屬性設爲一個函數,內部返回一個數據對象,由於若是隻返回一個對象,當組件複用時,不一樣的組件引用的data爲同一個對象,這點和根Vue實例不一樣的,能夠看官網的例子:點我點我ide
源碼分析函數
Vue實例後會先執行_init()進行初始化(4579行),以下:oop
Vue.prototype._init = function (options) { var vm = this; // a uid vm._uid = uid$3++; /*略*/ if (options && options._isComponent) { //這是組件實例化時的分支,暫不討論 /*略*/ } else { //根Vue實例執行到這裏 vm.$options = mergeOptions( //這裏執行mergeOptions()將屬性保存到vm.$options resolveConstructorOptions(vm.constructor), options || {}, vm ); } /* istanbul ignore else */ { initProxy(vm); } // expose real self vm._self = vm; initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, 'beforeCreate'); initInjections(vm); // resolve injections before data/props initState(vm); /*略*/ };
mergeOptions會爲每一個不一樣的屬性定義不一樣的合併策略,好比data、props、inject、生命週期函數等,統一放在mergeOptions裏面合併,執行完後會保存到Vue實例.$options對象上,例如生命週期函數會進行數組合並處理,而data會返回一個匿名函數:
return function mergedInstanceDataFn () { //第1179行,這裏會作判斷,若是data時個函數,則執行這個函數,當爲組件定義data時會執行到這裏 // instance merge var instanceData = typeof childVal === 'function' ? childVal.call(vm, vm) : childVal; var defaultData = typeof parentVal === 'function' ? parentVal.call(vm, vm) : parentVal; if (instanceData) { return mergeData(instanceData, defaultData) } else { return defaultData } }
接下來返回到_init,_init()會執行initState()函數對props, methods, data, computed 和 watch 進行初始化,以下:
function initState (vm) { //第3303行 vm._watchers = []; var opts = vm.$options; if (opts.props) { initProps(vm, opts.props); } if (opts.methods) { initMethods(vm, opts.methods); } if (opts.data) { //若是定義了data,則調用initData初始化data initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } if (opts.computed) { initComputed(vm, opts.computed); } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
initData()對data屬性作了初始化處理,以下:
function initData (vm) { var data = vm.$options.data; data = vm._data = typeof data === 'function' //先獲取data的值,這裏data是個函數,也就是上面說的第1179行返回的匿名函數,能夠看到返回的數據對象保存到了當前實例的_data屬性上了 ? getData(data, vm) : data || {}; if (!isPlainObject(data)) { data = {}; "development" !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ); } // proxy data on instance var keys = Object.keys(data); //獲取data的全部鍵名 var props = vm.$options.props; var methods = vm.$options.methods; var i = keys.length; //鍵的個數 while (i--) { //遍歷data的每一個屬性 var key = keys[i]; { if (methods && hasOwn(methods, key)) { warn( ("Method \"" + key + "\" has already been defined as a data property."), vm ); } } if (props && hasOwn(props, key)) { "development" !== 'production' && warn( "The data property \"" + key + "\" is already declared as a prop. " + "Use prop default value instead.", vm ); } else if (!isReserved(key)) { proxy(vm, "_data", key); //依次執行proxy,這裏對data作了代理 注1 } } // observe data observe(data, true /* asRootData */); //這裏對data作了響應式處理,來觀察這個data }
****************我是分隔線****************
注1解釋
initData()函數開始的時候把的數據對象保存到了當前實例的_data屬性上了,這裏是給Vue作了一層代碼,當訪問每一個data屬性時將從實例的_data屬性上獲取對應的屬性,Vue內部以下:
var sharedPropertyDefinition = { //共享屬性的一些定義 enumerable: true, configurable: true, get: noop, set: noop }; function proxy (target, sourceKey, key) { //對data、props作了代理 sharedPropertyDefinition.get = function proxyGetter () { //獲取屬性 return this[sourceKey][key] }; sharedPropertyDefinition.set = function proxySetter (val) { //設置屬性 this[sourceKey][key] = val; }; Object.defineProperty(target, key, sharedPropertyDefinition); //對target的key屬性的get和set作了一層代碼 }
例如:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script> <title>Document</title> </head> <body> <div id="app">{{message}}</div> <button id="b1">測試按鈕</button> <script> debugger var app = new Vue({ el:'#app', data:{ message:"Hello World!" } }) console.log(app._data.message) //瀏覽器會輸出:(index):19 Hello World! </script> </body> </html>
****************我是分隔線****************
返回到initData()函數,最後會執行observe函數:
var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, '__ob__', this); if (Array.isArray(value)) { //若是value是個數組 var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); //例子中不是數組,所以調用walk()方法 } }; Observer.prototype.walk = function walk(obj) { //將obj這個對象,作響應式,處理 var keys = Object.keys(obj); //獲取obj對象的全部鍵名 for (var i = 0; i < keys.length; i++) { //遍歷鍵名 defineReactive(obj, keys[i]); //依次調用defineReactive()函數對象的屬性變成響應式 } };
defineReactive用於把對象的屬性變成響應式,以下:
function defineReactive(obj, key, val, customSetter, shallow) { //把對象的屬性變成響應式 var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); //獲取obj對象key屬性的數據屬性 if (property && property.configurable === false) { //若是該屬性是不能修改或刪除的,則直接返回 return } var getter = property && property.get; //嘗試拿到該對象原生的get屬性,保存到getter中 if (!getter && arguments.length === 2) { //若是getter不存在,且參數只有兩個 val = obj[key]; //則直接經過obj[ke]獲取值,並保存到val中 } var setter = property && property.set; //嘗試拿到該對象原生的set屬性,保存到setter中 var childOb = !shallow && observe(val); //遞歸調用observe:當某個對象的屬性仍是對象時會進入 Object.defineProperty(obj, key, { //調用Object.defineProperty設置obj對象的訪問器屬性 enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; if (Dep.target) { //這裏就是作依賴收集的事情 dep.depend(); //調用depend()收集依賴 if (childOb) { //若是childOb存在 childOb.dep.depend(); //則調用childOb.dep.depend()收集依賴 if (Array.isArray(value)) { dependArray(value); } } } return value }, set: function reactiveSetter(newVal) { //作派發更新的事情 var value = getter ? getter.call(obj) : val; //若是以前有定義gvetter,則調用getter獲取值,不然就賦值爲val if (newVal === value || (newVal !== newVal && value !== value)) { //若是value沒有改變 return //則直接返回,這是個優化錯誤,當data值修改後和以前的值同樣時不作處理 } if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); //再調用observe,傳遞newVal,這樣若是新值也是個對象也會是響應式的了。 dep.notify(); //通知訂閱的watcher作更新 } }); }
當render函數執行轉換成虛擬VNode的時候就會執行with(this){}函數,內部訪問到某個具體的data時就會執行到這裏的訪問器控制get函數了,此時會收集對應的渲染watcher做爲訂閱者,保存到對應屬性的dep裏面
當修改了data裏某個屬性時就會除法對應的set訪問器控制屬性,此時會執行對應的訪問其控制的set函數,會執行notify()通知訂閱的watcher作更新操做