Vue 中 data 的初始化主要過程

在 Vue.js 中,props 和 data 是組件的兩個重要概念。props 是父組件向子組件傳遞數(shù)據(jù)的方式,而 data 是組件自身的狀態(tài)數(shù)據(jù)。這兩者都會被代理到 Vue 實例上,也就是說我們可以通過 this 訪問到它們。那么,這個代理是如何實現(xiàn)的呢?
Vue.js 使用了 ES5 提供的 Object.defineProperty 方法來實現(xiàn)數(shù)據(jù)的響應式綁定。這個方法可以讓我們在讀取或者修改一個對象的屬性時,執(zhí)行一些額外的操作。Vue.js 利用這個方法在讀取或者修改 props 和 data 時,進行依賴收集和派發(fā)更新。
當我們在組件中訪問 this.message 時,實際上是在訪問 this._data.message 或者 this._props.message。Vue.js 在初始化實例時,會遍歷 data 和 props 對象,使用 Object.defineProperty 為每一個屬性設置 getter 和 setter,然后將這些屬性代理到 Vue 實例上。
當我們訪問 this.message 時,實際上觸發(fā)了 getter,返回了 this._data.message 或者 this._props.message 的值。當我們修改 this.message 時,實際上觸發(fā)了 setter,修改了 this._data.message 或者 this._props.message 的值,并且觸發(fā)視圖的更新。
這就是 Vue.js 如何將 props 和 data 上的屬性代理到 vm 實例上的基本原理。
在 Vue.js 的源碼中,props 和 data 的代理實現(xiàn)是在初始化實例時進行的。以下是一個簡化的示例,展示了如何使用 Object.defineProperty 來實現(xiàn)這個代理:
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 的構(gòu)造函數(shù)中遍歷 data 和 props 對象,然后使用 Object.defineProperty 為每一個屬性設置 getter 和 setter。當我們訪問 this.message 時,實際上觸發(fā)了 getter,返回了 this._data.message 或者 this._props.message 的值。當我們修改 this.message 時,實際上觸發(fā)了 setter,修改了 this._data.message 的值,并且觸發(fā)視圖的更新。對于 props,由于它是只讀的,所以我們在 setter 中打印了一個警告消息。
源碼中 Vue 的初始化階段,_init 方法執(zhí)行的時候,會執(zhí)行 initState(vm) 方法,方法主要是對 props、methods、data、computed 和 wathcer 等屬性做了初始化操作。
data 的初始化主要過程也是做兩件事,一個是對定義 data 函數(shù)返回對象的遍歷,通過 proxy 把每一個值 vm._data.xxx 都代理到 vm.xxx 上;另一個是調(diào)用 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 */)}
