在負責的後臺管理系統中,個人添加人員與編輯人員兩個功能共用了一個組件,可是碰見一個問題.一樣是用v-for去渲染一些標籤,在使用編輯人員功能時,刪除對象數組元素,對應的標籤在頁面上也會消失.可是在使用添加人員功能時,刪除對象數組元素,對應的標籤卻不會在頁面上消失.html
this.$set(this.numbers,index,++this.numbers[index]);
this.$set(this.numbers,index,1);
貌似沒必要每次都要用this.$set去接收改變的數據,以爲掛一次就能夠啦吧?
複製代碼
Object.defineProperty()和響應式原理 仍是須要細細研究一下,還有Object.defineProperty()的數據屬性和瀏覽器屬性
複製代碼
問題描述:vue
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue</title>
<script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script>
<style>
li:hover {
cursor: pointer;
}
</style>
</head>
<body>
<div class="wrap">
<ul>
<li v-for="item,index in items" v-on:click="handle(index)">
<span>{{item.name}}</span>
<span>{{numbers[index]}}</span>
</li>
</ul>
</div>
<script>
var vm = new Vue({
el: ".wrap",
data: {
numbers: [],
items: [
{name: 'jjj'},
{name: 'kkk'},
{name: 'lll'},
]
},
methods: {
handle: function (index) {
// WHY: 更新數據,view層未渲染,但經過console這個數組能夠發現數據確實更新了
if (typeof(this.numbers[index]) === "undefined" ) {
this.numbers[index] = 1;
} else {
this.numbers[index]++;
}
console.log(this.numbers);
}
}
});
</script>
</body>
</html>
複製代碼
這裏的實現目的很明確 --- 我但願在點擊li時先檢測是否存在,固然是不存在的,因此就將值設置爲1, 若是再次點擊,就讓數字累加。可是出現的問題是: 點擊以後數字並無在view層更新,而經過console打印發現數據確實更新了,只是view層沒有及時的檢測到, 而我一直以來的想法就是: 既然vue實現的時數據雙向綁定,那麼在model層發生了變化以後爲何就沒有在view層更新呢?segmentfault
首先,我就考慮了這是否是數組的問題,因而,我測試了下面的例子:數組
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue</title>
<script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script>
<style>
li:hover {
cursor: pointer;
}
</style>
</head>
<body>
<div class="wrap">
<ul>
<li v-for="item,index in items" v-on:click="handle(index)">
<span>{{item.name}}</span>
<span>{{numbers[index]}}</span>
</li>
</ul>
</div>
<script>
var vm = new Vue({
el: ".wrap",
data: {
numbers: [],
items: [
{name: 'jjj'},
{name: 'kkk'},
{name: 'lll'},
]
},
methods: {
handle: function (index) {
// 不是數組,這裏更新數據就能夠直接在view層渲染
this.items[index].name += " success";
}
}
});
</script>
</body>
</html>
複製代碼
這時,我再測試時就發現,這裏的model層發生了變化時,view層就能及時、有效的獲得更新。瀏覽器
而數組爲何不能夠呢?bash
因而在文檔上的一個不起眼的地方找到了下面的說明:異步
Vue實例的根數據對象:data是Vue 實例的根數據對象,Vue 將會遞歸將 data 的屬性轉換爲 getter/setter,從而讓 data的屬性可以響應數據變化。推薦在建立實例以前,就聲明全部的根級響應式屬性.(那我在data中掛載一個對象,也要把這個對象的屬性整一個初始值嘍?此處有待實踐論證!)
ide
把一個普通 JavaScript 對象傳給 Vue 實例的 data 選項,Vue 將遍歷此對象全部的屬性,並使用 Object.defineProperty 把這些屬性所有轉爲getter/setter。Object.defineProperty 是僅 ES5 支持,且沒法 shim 的特性,這也就是爲何Vue 不支持 IE8以及更低版本瀏覽器的緣由。函數
訪問器屬性不能直接定義,必須是用Object.defineProperty()來定義。性能
參考文章:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue</title>
</head>
<body>
<script>
var book={
_year:2004,
edition:1
};
Object.defineProperty(book,"year",{
get:function(){
return this._year;
},
set:function(newValue){
if(newValue>2004){
this._year=newValue;
this.edition+=newValue-2004;
}
}
});
console.log(book.year); // 2004 在讀取訪問器屬性時會調用get函數
book.year=2005; // 在給訪問器屬性賦值時會調用set函數
console.log(book.edition); // 2
</script>
</body>
</html>
複製代碼
這個例子應該能夠很好的理解訪問器屬性了。
因此,當對象下的訪問器屬性值發生了改變以後(vue會將屬性都轉化爲訪問器屬性,以前提到了), 那麼就會調用set函數,這時vue就能夠經過這個set函數來追蹤變化,調用相關函數來實現view視圖的更新。
每一個組件實例都有相應的 watcher 實例對象,它會在組件渲染的過程當中把屬性記錄爲依賴,以後當依賴項的 setter 被調用時,會通知 watcher 從新計算,從而導致它關聯的組件得以更新。
OK!既然知道了原理,咱們就能夠進一步瞭解爲何出現了以前數組的問題了!
收到現代JavaScript瀏覽器的限制,其實主要是 Object.observe() 方法支持的很差,Vue不能檢測到對象的添加或者刪除。然而Vue在初始化實例時就對屬性執行了setter/getter轉化過程,因此屬性必須開始就在對象上,這樣才能讓Vue轉化它。
因此對於前面的例子就不難理解了 --- 數組中index均可以看作是屬性,當咱們添加屬性並賦值時,Vue並不能檢測到對象中屬性的添加或者刪除,可是其的確是添加或刪除了,故咱們能夠經過console看到變化,因此就沒有辦法作到響應式; 而在第二個例子中,咱們是在已有的屬性的基礎上進行修改的,這些屬性是在最開始就被Vue初始化實例時執行了setter/getter的轉化過程,因此說他們的修改是有效的,model的數據能夠實時的在view層中獲得相應。
在介紹以前,不得不殘忍的說,儘管這個方法能夠在某些瀏覽器上運行,但事實是這個方法已經廢棄!
概述:此方法用於異步地監視一個對象的修改。當對象的屬性被修改時,方法的回調函數會提供一個有序的修改流,然而這個接口已經從各大瀏覽器移除,可使用通用的 proxy 對象。
方法:
Object.observe(obj, callback[, acceptList])
其中obj就是被監控的對象, callback是一個回調函數,其中的參數包括changes和acceptList, changes一個數組,其中包含的每個對象表明一個修改行爲。每一個修改行爲的對象包含:
acceptList在給定對象上給定回調中要監視的變化類型列表。若是省略, ["add", "update", "delete", "reconfigure", "setPrototype", "preventExtensions"] 將會被使用。
var obj = {
foo: 0,
bar: 1
};
Object.observe(obj, function(changes) {
console.log(changes);
});
obj.baz = 2;
// [{name: 'baz', object: <obj>, type: 'add'}]
obj.foo = 'hello';
// [{name: 'foo', object: <obj>, type: 'update', oldValue: 0}]
delete obj.baz;
// [{name: 'baz', object: <obj>, type: 'delete', oldValue: 2}]
複製代碼
參考文檔:Object.ovserve()
推薦閱讀文章:Object.observe() 引爆數據綁定革命
使用 Vue.set(object, key, value) 方法將響應屬性添加到嵌套的對象上。 還可使用 vm.$set 實例方法,這也是全局 Vue.set 方法的別名。
解決代碼示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue</title>
<script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script>
<style>
li:hover {
cursor: pointer;
}
</style>
</head>
<body>
<div class="wrap">
<ul>
<li v-for="item,index in items" v-on:click="handle(index)">
<span>{{item.name}}</span>
<span>{{numbers[index]}}</span>
</li>
</ul>
</div>
<script>
var vm = new Vue({
el: ".wrap",
data: {
numbers: [],
items: [
{name: 'jjj'},
{name: 'kkk'},
{name: 'lll'},
]
},
methods: {
handle: function (index) {
// WHY: 更新數據,view層未渲染,但經過console這個數組能夠發現數據確實更新了
if (typeof(this.numbers[index]) === "undefined" ) {
this.$set(this.numbers, index, 1);
} else {
this.$set(this.numbers, index, ++this.numbers[index]);
}
}
}
});
</script>
</body>
</html>
複製代碼
這樣,咱們就能夠實現最終的目的了!
[ADD_ONE] (state, index) {
if ( typeof state.numbers[index] == "undefined") {
Vue.set(state.numbers, index, 1)
} else {
Vue.set(state.numbers, index, ++state.numbers[index])
}
}
複製代碼
便是用 Vue.set() 的方式來改變、增長。
注意:這裏是肯定index的增長和減小,因此用 Vue.set() 的方式
方法以下:
state內容:
kindnames: []
Mutations內容:
[ADD_KIND_NAME] (state, name) {
state.kindnames.push(name);
}
複製代碼
注意: 這裏直接使用push的方式
固然,除了push,咱們還能夠shift等各類方式。
actions的內容:
commit(ADD_KIND_NAME, state.items[index++].name);
複製代碼
這裏,state.items[index++].name獲取到的是一個一個的字符串。
注:一樣能夠參考文檔 --- 細節與最佳實踐