現象:node
在Vue開發的時候,data初始化一個對象沒有定義任何屬性,通過變量賦值的以後,不須要$set方法,該對象下面的屬性就也能變成響應式屬性react
提問:分析下面代碼,頁面首先先顯示什麼?先點擊changeTest1顯示什麼?而後點擊chageTest2顯示什麼?數組
<body>
<div id="app">
<button @click="changeTest1">changeTest1</button>
<button @click="changeTest2">changeTest2</button>
<div>
{{ form.test1 }}-{{ form.test2 }}
</div>
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
form: {},
},
mounted() {
this.form = { test1: 1};
this.form.test2 = 2;
},
methods: {
changeTest2() {
this.form.test2 = 'change2';
},
changeTest1() {
this.form.test1 = 'change1';
},
}
});
</script>
</body>
複製代碼
首先顯示:
1-2
, 點擊changeTest1顯示:change1-2
,點擊changeTest2顯示:change1-2
bash
1): 打印一下form對象,能夠看到test1有set,get修飾符,而test2沒有get,set修飾符app
2): 原來test1可以改變視圖是由於被Vue用Object.defineProperties()處理,有get,set修飾符
,收集到渲染watch。dom
3): 而test2沒有get,set修飾符
,沒有收集到渲染watch。異步
4): 從這個表現就能知道test1能夠更新視圖,而test2不能夠更新視圖函數
1): 上面代碼能夠注意到在mounted生命週期那裏,對this.form進行了賦值,因此觸發了form的set修飾符,它會執行如下函數中的set函數ui
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
console.log(obj, key)
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
複製代碼
2):能夠看到newVal是{test1: 1}, 拋掉全部if-else的判斷,會執行一行代碼 childOb = !shallow && observe(newVal)
,shallow是undefined,而後執行observe函數,參數爲{test1: 1}this
看一下observe函數的功能
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
複製代碼
3): 會發現最終會執行ob = new Observer(value)
, 再看Observer函數
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
複製代碼
4): 最終發現ob = new Observer(value)
,就是把對象定義響應式的入口函數
5): 原來值{ test1: 1 },就這樣被隱式
添加了set,get修飾符
總結:對this.form = { test1: 1 } 進行賦值的時候,就會觸發到form的set函數。在set函數裏面,會對newVal也就是{test1: 1} 走一次new Obsever(value)來添加set,get的修飾符,所以test1就變成了響應式屬性
1): 應爲對象沒法監聽新增和刪除,對this.form.test2 = 2的時候,沒有觸發form的set函數
,於是沒有添加get,set修飾符
1): Vue在初始化階段對data定義的對象form添加set,get修飾符
2): 執行render函數(生成vnode的過程),form取值一次,觸發get函數,收集渲染watch
。form.test1取值一次爲1,沒有get修飾符。form.test2取值一次爲2,沒有get修飾符
3): 把vnode(虛擬dom)變成真實dom,頁面顯示: 1-2
4): 在mounted生命週期中對this.form = { test1: 1 }進行了賦值
,觸發了form的set函數,而後對{ test1: 1 }添加get,set修飾符,test1變成了響應式屬性
5): 而後執行dep.notify()
,觸發渲染watch,執行render函數
6): 在生成vnode的時候(模板中的{{ form.test1 }} {{ form.test2 }}),須要form.test1,form.test2取值一次,這時候就觸發了test1的get修飾符,收集該渲染watch
, 使得test1有了更新視圖的能力,而test2沒有get修飾符,沒法收集改渲染watch,沒有更新視圖的能力
7): 點擊chageTest1的時候,修改了test1的值,就會執行set修飾符
,執行dep.notify()去更新視圖
8): 點擊changeTest2的時候,修改了test2的值,因爲test2沒有get,set修飾符
,全部沒法更新視圖
1): 原來Vue對數組的push,pop,shift,unshfit,sort,reverse方法額外處理,例如:當數組新增的時候,可以給新增的項添加get,set修飾符,使得它們變成響應式屬性,這就是對象和數組的區別
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
複製代碼
給Vue中data的值賦值對象或者數組,Vue內部會自動幫咱們給該對象或數組添加get,set修飾符,而且爲每個屬性收集渲染watch,使得它們有更新視圖的能力
文章大概說了Vue如何添加響應式屬性,如何觸發視圖的更新,忽略了不少細節,好比渲染watch,render函數,異步更新,虛擬dom變成真實dom等等。文章的目的是想說:什麼狀況屬性會變成響應式屬性,什麼狀況下沒有響應式屬性(固然手動調用$set能夠變成響應式屬性)