下面這段話:Vue3.0版本中將基于Proxy來改造觀察者模式。說明Vue3.0講不再借助于ES5的Object.defineProperty,轉(zhuǎn)而使用最新的Proxy語法實現(xiàn)Vue最根本的響應(yīng)式原理(注又名:數(shù)據(jù)劫持,下文統(tǒng)稱響應(yīng)式原理)。
下文主要簡述從Object.defineProperty到proxy的實現(xiàn)觀察者機制探索,目前關(guān)于深入響應(yīng)式原理的文章已經(jīng)很多了,很多都寫的很好,本文不做過深的vue里的源碼解析,只是淺入探索和自己動手手寫一個簡易的Object.defineProperty實現(xiàn)觀察者機制,以及手寫一個簡易的由Proxy實現(xiàn)觀察者機制,當(dāng)然最終以作者發(fā)布為準(zhǔn)。主要有以下幾個知識點帶大家一起進(jìn)入
1、Object.defineProperty實現(xiàn)觀察者機制
2、Object.defineProperty的缺點
3、利用proxy實現(xiàn)簡易的實現(xiàn)觀察者機制
4、總結(jié)
一、Object.defineProperty實現(xiàn)觀察者機制
這里我們照顧一下小白同學(xué),首先我們來補充一下ES5中對Object.defineProperty() 方法的定義和一些基礎(chǔ)知識,然后手寫一個簡易的響應(yīng)式,最后再結(jié)合vue源碼簡析
1.1 Object.defineProperty基礎(chǔ)知識
在developer.mozilla.org對Object.defineProperty()定義
Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性, 并返回這個對象
語法:
Object.defineProperty(obj, prop, descriptor)
參數(shù)說明:
1、obj要在其上定義屬性的對象
2、要定義或修改的屬性的名稱
3、將被定義或修改的屬性描述符
返回值:
被傳遞給函數(shù)的對象
這里我們重點關(guān)注一下語法中的第三個參數(shù)屬性描述符:descriptor
對象里目前存在的屬性描述符有兩種主要形式:數(shù)據(jù)描述符和存取描述符。數(shù)據(jù)描述符是一個具有值的屬性,該值可能是可寫的,也可能不是可寫的。存取描述符是由getter-setter函數(shù)對描述的屬性。描述符必須是這兩種形式之一;不能同時是兩者。這里descriptor有6個選鍵值configurable、enumerable、value、writableget、set這里他們分別的介紹可以移步
1.2 創(chuàng)建對象
通常我們創(chuàng)建對象來一步一步了解這個Object.defineProperty() 方法和屬性描述符descriptor里面鍵值的用法
【1】正常我們創(chuàng)建一個對象,如下,然后控制臺打印他們我們可以看到
let vm = {
name: '簡書'
}
console.log(vm)

【2】接下來我們通過Object.defineProperty創(chuàng)建一個對象,并設(shè)置這個對象要定義或者修改的屬性“name”
let vm = Object.defineProperty({},"name",{
get() {
console.log("執(zhí)行g(shù)et");
return name || "簡書"
},
set(newVal) {
console.log("執(zhí)行set");
console.log("新值:" + newVal);
if (newVal !== name) {
name = newVal;
}
}
})
其實這兩種都創(chuàng)建一個對象的方式,通過Object.defineProperty創(chuàng)建的對象,我們可以看到,多了上面說的兩個存取描述符鍵值方法get 和set 這樣的對象,就變得可控被觀察,也就是我們說的被劫持,當(dāng)我們改變或者獲取這對象的屬性的時候,我們就可以控制到它
下面我們通過改變vm.name = "1",我們通過控制臺可以看到
1.3 實現(xiàn)觀察者機制,響應(yīng)式對象
let vm = {
id:"jianshu",
name:"簡書"
};
let keys = Object.keys(vm);
keys.forEach(key=>{
let value = vm[key];
Object.defineProperty(vm, key,{
get() {
console.log("執(zhí)行g(shù)et");
return value
},
set(newVal){
console.log("執(zhí)行set");
if(newVal !== value){
value = newVal;
}
}
})
})
vm.id = "test";
console.log(vm)

這里主要是遍歷對象中的每一個屬性,每個屬性都是賦予get和set,讓對象中的每一個屬性的改變都會被監(jiān)測到,也就是實現(xiàn)了響應(yīng)式
1.4 vue源碼中的響應(yīng)式原理簡析
上面的例子我們試一下,用數(shù)組對象發(fā)現(xiàn)是不能生效的,那么在vue里數(shù)組是怎么實現(xiàn)響應(yīng)式原理的呢,我們可以看到vue源碼目錄src/core/observer/index.js里,其實他是對對象進(jìn)行了判斷,如果是數(shù)組對象,就會走observeArray()方法,而且你會發(fā)現(xiàn)里面還有一個arrayMethods,里面是對數(shù)組的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'進(jìn)行了重寫,重寫過后的方法不僅能實現(xiàn)原有的功能,還能發(fā)布消息給訂閱者。其他對象,都走walk()方法。
1.5 把數(shù)據(jù)渲染到頁面上
當(dāng)我們檢測到對象更新了,如何同步更新到頁面上呢?
1、首先,我們要找到作用域范圍內(nèi)(vue,里會有個 el:"#app")的節(jié)點,全部頁面內(nèi)容都會渲染到這個結(jié)點里面
2、然后遍歷結(jié)點上所有含有使用該對象的地方,也就是Mustache語法 (雙大括號) 的文本插值的地方,例如 {{ vm }}
3、綁定視圖更新
二、Object.defineProperty的缺點
2.1 無法監(jiān)聽對象非已有的屬性的添加和已有屬性的刪除
只會對對象原有的全部屬性進(jìn)行做數(shù)據(jù)劫持,也就是說Vue 不允許動態(tài)添加或者刪除對象已有屬性,它是不做數(shù)據(jù)劫持的,也就不能實現(xiàn)響應(yīng)式
舉例
<template>
<div>
<h1>{{ vm }}</h1>
<button @click="addAttribute">新增屬性</button>
<button @click="delAttribute">刪除屬性</button>
</div>
</template>
<script>
export default {
data() {
return {
vm:{
id:"juejin",
name:"掘金"
}
}
},
methods: {
addAttribute() {
this.vm.use = "codercao"
console.log(this.vm)
},
delAttribute() {
for(let k in this.vm) {
if(k=='id'){
delete this.vm[k]
}
}
console.log(this.vm)
}
},
}
</script>
新增,你會發(fā)現(xiàn)控制臺打印的vm已經(jīng)新增了use屬性,而頁面并沒有響應(yīng)式改變
刪除,你會發(fā)現(xiàn)控制臺打印的vm已經(jīng)刪除了id屬性,而頁面并沒有響應(yīng)式改變
2.2 數(shù)組變異
數(shù)組對象也不能通過屬性或者索引控制數(shù)組,比如length,index實現(xiàn)響應(yīng)式,通過1.4 里我們也看到vue源碼只是對數(shù)組的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'進(jìn)行了重寫,但是索引控制數(shù)組是沒有辦法實現(xiàn)響應(yīng)式的
2.3 解決以上的辦法。 使用Vue.set(object, propertyName, value) 方法
改進(jìn)上面2.1里的新增屬性方式,你會發(fā)現(xiàn)頁面就實現(xiàn)了響應(yīng)式,至于Vue.set方法介紹移步
addAttribute() {
//this.vm.use = "codercao"
this.$set(this.vm,'use','codercao')
console.log(this.vm)
},
三、利用proxy實現(xiàn)簡易的實現(xiàn)觀察者機制
3.1proxy基礎(chǔ)知識
Proxy 對象用于定義基本操作的自定義行為(如屬性查找,賦值,枚舉,函數(shù)調(diào)用等)
語法
let p = new Proxy(target, handler);
參數(shù)說明
target用Proxy包裝的目標(biāo)對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個代理)
handler一個對象,其屬性是當(dāng)執(zhí)行一個操作時定義代理的行為的函數(shù)
let vm = {
id:"jianshu",
name:"簡書"
}
let newVm = new Proxy(vm,{
get(target,key){
console.log("執(zhí)行g(shù)et");
return target[key];
},
set(target,key,newVal){
console.log("執(zhí)行set");
if(target[key]!==newVal)
target[key] = newVal;
}
})
newVm.use = "codercao"
console.log(newVm)
你會發(fā)現(xiàn)用Proxy 也一樣實現(xiàn)了一個簡易的觀察者機制,當(dāng)然深入研究的話,你還可以實現(xiàn)雙向綁定。
四、結(jié)尾
到這里我們這篇文章到此就結(jié)束了,至于最終作者會怎么用proxy來寫這個觀察者機制,待vue3.0發(fā)布可以一看究竟,文章主要是帶大家實踐探索一下Object.defineProperty實現(xiàn)觀察者機制,順便提了一下,這個Object.defineProperty缺陷和處理辦法,然后引入proxy,屬于比較初級的嘗試,Vue發(fā)展到現(xiàn)在幾年了,其實大部分人對其應(yīng)用已經(jīng)游刃有余了,關(guān)注源碼和實踐vue里功能的原理,或許對每個前端人都會有一些提升。