官网对data属性的介绍如下:
意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方法。
Vue内部实现时用到了ES5的Object.defineProperty()这个API,也正是这个原因,所以Vue不支持IE8及以下浏览器(IE8及以下浏览器是不支持ECMASCRIPT 5的Object.defineProperty())。
以一个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!:
注:对于组件来说,需要把data属性设为一个函数,内部返回一个数据对象,因为如果只返回一个对象,当组件复用时,不同的组件引用的data为同一个对象,这点和根Vue实例不同的,可以看官网的例子:点我点我
源码分析
Vue实例后会先执行_init()进行初始化(4579行),如下:
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 = {}; "