Question問題
寫元件的時候遇到這樣一個問題,在template裡繫結了一個物件<span>{{aa.bb}}</span>
類似這個,,然後進行點選按鈕的函式裡執行this.aa.bb = 1
,但是我忘記在data裡的aa物件提前註冊bb這個屬性了,頁面上的值竟然也變成了1,我記得vue文件裡有這樣一句話向響應式物件中新增一個屬性,並確保這個新屬性同樣是響應式的,且觸發檢視更新。它必須用於向響應式物件上新增新屬性,因為 Vue 無法探測普通的新增屬性 (比如 this.myObject.newProperty = 'hi')
以上出自vue文件set描述。所以說為什麼我並沒有用$set,為什麼還產生了響應式效果了呢?
找到問題所在
仔細一看,是因為我在觸發this.aa.bb=1
這個函式裡,也修改了一個響應式的變數this.cc = 1
,所以導致模版整個重新渲染,把bb的值也一起重新渲染了,也就是為什麼我看到了bb的值也在變化,誤以為是響應式了。
繼續深挖
之後我做了一系列的測試,首先我把影響判斷的cc幹掉,果然我繼續觸發this.aa.bb=1
,頁面果然沒有響應了,之後我在替換成this.$set(this.aa, 'bb', '1')
,果然有了效果,所以引出主題$set到底幹了什麼?
Answer答案
vue原始碼裡的set定義
function set (target, key, val) {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val
}
var ob = (target).__ob__;
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
);
return val
}
if (!ob) {
target[key] = val;
return val
}
defineReactive(ob.value, key, val);
ob.dep.notify();
return val
}
複製程式碼
很明顯,set函式裡有加限制,if (key in target && !(key in Object.prototype)) {
,物件裡含有這個變數的話,就直接賦值然後return val
了。注意下defineReactive
這個函式,這個就是響應式的繫結,通過debugger發現,vue初始化過程會把data裡的值一一遞迴進行響應式繫結。然後我們們看看他是怎麼繫結的?
function defineReactive (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
if (!getter && arguments.length === 2) {
val = obj[key];
}
var setter = property && property.set;
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var 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) {
var 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();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
複製程式碼
看重點,Object.defineProperty
看一下這個函式的定義,Object.defineProperty() 方法會直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性, 並返回這個物件。
來自MDN[developer.mozilla.org/zh-CN/docs/…]。
可以看到vue利用Object.defineProperty
這個函式的get,set進行響應式繫結,當變數被重新賦值的時候會觸發set函式,重新渲染更新dom,進行更復雜的事情。通過文件可以得知,set,get函式是與value和writable配置項是互斥不共存的。在嚴格模式下會報錯的。也就是為什麼vue要攔截住不會再次進行響應式繫結。