今天看到一篇文章:爲何 Vue 實例化後,經過 this.能獲取到 data 內的數據javascript
一下是這篇文章的結論:vue
不賣關子啦!其實 Vue 實例化的時候,經過 getData 方法中 call 修改 this 指針來實現 Vue 實例後能獲取到 data 體內的數據,小夥伴可能感受認爲我將 vue 初始化部分流程拉出來是的你愈來愈糊塗,可是若是我不講上面的代碼拉出來,我直接來這句總結,你會更加懵的 🙂🙂🙂java
這篇文章根本沒有說清楚,或者說根本就說錯了。可能不知道誤導了多少人,並且網上對於該問題的解釋都一致的看向源碼中的:git
return data.call(vm, vm);
複製代碼
圖片截至:Vue 源碼解析:this.$data、this._data、this.xxx 爲何都能獲取數據?data 爲何是個函數?github
然而實現該功能的其實並非這句話!!!api
然而實現該功能的其實並非這句話!!!瀏覽器
然而實現該功能的其實並非這句話!!!函數
(重要的事情說三遍!)oop
首先得從 Vue 的實例化開始看起!源碼分析
function Vue(options) {
if (process.env.NODE_ENV !== "production" && !(this instanceof Vue)) {
warn("Vue is a constructor and should be called with the `new` keyword");
}
this._init(options);
}
複製代碼
在這段代碼中,對咱們實際有用的是:this._init(options)
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
...
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
...
initState(vm)
...
}
複製代碼
在 Vue.prototype._init() 中,要注意的是:
export function initState (vm: Component) {
...
const opts = vm.$options
...
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
...
}
複製代碼
在這段代碼中,若是 vm.$options.data(也就是咱們的數據源)若是有數據,咱們就進行 initData(vm)操做,傳入的 vm 即當前 Vue 對象!
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
...
// proxy data on instance
const keys = Object.keys(data)
...
let i = keys.length
while (i--) {
const key = keys[i]
...
proxy(vm, `_data`, key)
}
...
}
複製代碼
值得注意的是:
咱們實例化 Vue 數據源的時候寫法經常是:
data: function() {
return {
key: value,
...
}
}
複製代碼
那麼這時候咱們就會走向data = vm._data = getData(data, vm)
export function getData (data: Function, vm: Component): any {
...
return data.call(vm, vm)
...
}
複製代碼
這裏咱們就要理解一下,data.call(vm, vm)到底幹了啥?
對於 call 的深刻理解建議閱讀一下:對於 Function.call()的深刻理解
這裏我將簡單地進行解釋:
data.call(vm, vm)
// 實際上與是以以下方式運行的
vm.data(vm)
//假如咱們有以下數據源
data: function() {
return {
content: "Hello World!"
}
}
//那麼data.call(vm, vm)執行的實際上就是獲得了return中的對象
{
content: "Hello World!"
}
複製代碼
data.call(vm, vm) 並無實現咱們想要的功能。
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
複製代碼
這段代碼的意思其實是:不管咱們定義的 data 是一個 function 仍是一個{},最終都將返回一個{}而且賦值給 data 這個變量!
**值得注意:**vm._data 就是返回的{},能夠在控制的進行打印輸出看一看,和 vm.$data 的值是同樣的!
那麼真正的實如今哪呢?
咱們看到 initData()函數的後半段代碼:
const keys = Object.keys(data)
...
let i = keys.length
while (i--) {
const key = keys[i]
...
proxy(vm, `_data`, key)
}
複製代碼
這裏遍歷了咱們以前所取到的 data,對其中每個屬性都進行了proxy(vm,
_data, key)
調用。
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
export function proxy(target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
複製代碼
理解這段代碼,這裏得先普及一個知識點:
方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。
經過 defineProperty()實際上就是給 Vue 實例定義了新的屬性!!!
這纔是重點,這裏經過遍歷 data,將 data 中的屬性/值綁定在了 vm(也就是 Vue 實例)上。
在 proxy()中的傳入的第二個參數_data
,實際上爲了後面this[sourceKey][key]
調用 vm._data(前面有提)中的的屬性/值。
這裏經過 vm 上建立新屬性,而且採用 set,get 屬性描述符使得每一次改變數據都會直接做用於 vm._data 之上。
其實今天的話題在 Vue.js 的文檔就有解釋:
data
類型:
Object | Function
限制:組件的定義只接受
function
。詳細:
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(...))
獲得深拷貝的原始數據對象。
因此說,遇到問題要多看文檔,實在不行再看源碼!
-EFO-
筆者專門在 github 上建立了一個倉庫,用於記錄平時學習全棧開發中的技巧、難點、易錯點,歡迎你們點擊下方連接瀏覽。若是以爲還不錯,就請給個小星星吧!👍
2019/05/27