Vue 中 data 的初始化主要过程

在 Vue.js 中,props 和 data 是组件的两个重要概念。props 是父组件向子组件传递数据的方式,而 data 是组件自身的状态数据。这两者都会被代理到 Vue 实例上,也就是说我们可以通过 this 访问到它们。那么,这个代理是如何实现的呢?
Vue.js 使用了 ES5 提供的 Object.defineProperty 方法来实现数据的响应式绑定。这个方法可以让我们在读取或者修改一个对象的属性时,执行一些额外的操作。Vue.js 利用这个方法在读取或者修改 props 和 data 时,进行依赖收集和派发更新。
当我们在组件中访问 this.message 时,实际上是在访问 this._data.message 或者 this._props.message。Vue.js 在初始化实例时,会遍历 data 和 props 对象,使用 Object.defineProperty 为每一个属性设置 getter 和 setter,然后将这些属性代理到 Vue 实例上。
当我们访问 this.message 时,实际上触发了 getter,返回了 this._data.message 或者 this._props.message 的值。当我们修改 this.message 时,实际上触发了 setter,修改了 this._data.message 或者 this._props.message 的值,并且触发视图的更新。
这就是 Vue.js 如何将 props 和 data 上的属性代理到 vm 实例上的基本原理。
在 Vue.js 的源码中,props 和 data 的代理实现是在初始化实例时进行的。以下是一个简化的示例,展示了如何使用 Object.defineProperty 来实现这个代理:
function Vue (options) {this._data = options.data;this._props = options.props;var self = this;// 遍历 data 对象Object.keys(this._data).forEach(function(key) {self.proxyData(key);});// 遍历 props 对象Object.keys(this._props).forEach(function(key) {self.proxyProps(key);});}Vue.prototype.proxyData = function(key) {var self = this;Object.defineProperty(self, key, {enumerable: true,configurable: true,get: function proxyGetter() {return self._data[key];},set: function proxySetter(newVal) {self._data[key] = newVal;}});};Vue.prototype.proxyProps = function(key) {var self = this;Object.defineProperty(self, key, {enumerable: true,configurable: true,get: function proxyGetter() {return self._props[key];},set: function proxySetter(newVal) {console.warn('You are trying to modify a read only value');}});};
在这个示例中,我们在 Vue 的构造函数中遍历 data 和 props 对象,然后使用 Object.defineProperty 为每一个属性设置 getter 和 setter。当我们访问 this.message 时,实际上触发了 getter,返回了 this._data.message 或者 this._props.message 的值。当我们修改 this.message 时,实际上触发了 setter,修改了 this._data.message 的值,并且触发视图的更新。对于 props,由于它是只读的,所以我们在 setter 中打印了一个警告消息。
源码中 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法,方法主要是对 props、methods、data、computed 和 wathcer 等属性做了初始化操作。
data 的初始化主要过程也是做两件事,一个是对定义 data 函数返回对象的遍历,通过 proxy 把每一个值 vm._data.xxx 都代理到 vm.xxx 上;另一个是调用 observe 方法观测整个 data 的变化。
function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}if (!isPlainObject(data)) {data = {}process.env.NODE_ENV !== '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 instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}}if (props && hasOwn(props, key)) {process.env.NODE_ENV !== '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)}}// observe dataobserve(data, true /* asRootData */)}
