不知不覺,Vue的做者尤雨溪公佈了Vue3.0版本的開發計劃,發佈到如今已經一年了,看來Vue3.0的發佈尚需時日,在開發計劃中,下圖這段話:Vue3.0版本中將基於Proxy來改造觀察者模式。說明Vue3.0講再也不借助於ES5的Object.defineProperty,轉而使用最新的Proxy語法實現Vue最根本的響應式原理(注又名:數據劫持,下文統稱響應式原理)。html
下文主要簡述從Object.defineProperty到proxy的實現觀察者機制探索,目前關於深刻響應式原理的文章已經不少了,不少都寫的很好,本文不作過深的vue裏的源碼解析,只是淺入探索和本身動手手寫一個簡易的Object.defineProperty實現觀察者機制,以及手寫一個簡易的由Proxy實現觀察者機制,固然最終以做者發佈爲準。主要有如下幾個知識點帶你們一塊兒進入前端
一、Object.defineProperty實現觀察者機制
二、Object.defineProperty的缺點
三、利用proxy實現簡易的實現觀察者機制
四、總結
這裏咱們照顧一下小白同窗,首先咱們來補充一下ES5中對Object.defineProperty()
方法的定義和一些基礎知識,而後手寫一個簡易的響應式,最後再結合vue源碼簡析。
vue
在developer.mozilla.org對Object.defineProperty()
定義react
Object.defineProperty()
方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象
語法:
Object.defineProperty(obj, prop, descriptor)
參數說明:
一、obj要在其上定義屬性的對象
二、要定義或修改的屬性的名稱
三、將被定義或修改的屬性描述符
返回值:
被傳遞給函數的對象
複製代碼
這裏咱們重點關注一下語法中的第三個參數屬性描述符:descriptorgit
對象裏目前存在的屬性描述符有兩種主要形式:數據描述符和存取描述符。數據描述符是一個具備值的屬性,該值多是可寫的,也可能不是可寫的。存取描述符是由getter-setter函數對描述的屬性。描述符必須是這兩種形式之一;不能同時是二者。這裏descriptor有6個選鍵值configurable、enumerable、value、
writable
get、
set這裏他們分別的介紹能夠移步github
一般咱們建立對象來一步一步瞭解這個Object.defineProperty()
方法和屬性描述符descriptor裏面鍵值的用法數組
【1】正常咱們建立一個對象,以下,而後控制檯打印他們咱們能夠看到bash
let vm = {
name: '掘金'
}
console.log(vm)複製代碼
【2】接下來咱們經過Object.defineProperty建立一個對象,並設置這個對象要定義或者修改的屬性「name」
app
// let vm = {
// name: '掘金'
// }
let vm = Object.defineProperty({},"name",{
get() {
console.log("執行get");
return "掘金"
},
set(newValue) {
console.log("執行set");
console.log("新值:" + newValue);
}
})
console.log(vm)複製代碼
其實這兩種都建立一個對象的方式,經過Object.defineProperty建立的對象,咱們能夠看到,多了上面說的兩個存取描述符鍵值方法get 和set 這樣的對象,就變得可控被觀察,也就是咱們說的被劫持,當咱們改變或者獲取這對象的屬性的時候,咱們就能夠控制到它。ide
下面咱們經過改變vm.name = "juejin",咱們經過控制檯能夠看到
// let vm = {
// name: '掘金'
// }
let vm = Object.defineProperty({},"name",{
get() {
console.log("執行get");
return "掘金"
},
set(newValue) {
console.log("執行set");
console.log("新值:" + newValue);
}
})
vm.name = "juejin";
//console.log(vm)複製代碼
let vm = {
id:"juejin",
name:"掘金"};
let keys = Object.keys(vm);
keys.forEach(key=>{
let value = vm[key];
Object.defineProperty(vm, key,{
get() {
console.log("執行get");
return value
},
set(newValue){
console.log("執行set");
if(newValue!=value){
value = newValue;
}
}
})
})
vm.id = "test";
console.log(vm)複製代碼
這裏主要是遍歷對象中的每個屬性,每一個屬性都是賦予get和set,讓對象中的每個屬性的改變都會被監測到,也就是實現了響應式觀察者機制。
上面的例子咱們試一下,用數組對象發現是不能生效的,那麼在vue裏數組是怎麼實現響應式原理的呢,咱們能夠看到vue源碼目錄src/core/observer/index.js裏,其實他是對對象進行了判斷,若是是數組對象,就會走observeArray()方法,並且你會發現裏面還有一個arrayMethods,裏面是對數組的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'進行了重寫,重寫事後的方法不只能實現原有的功能,還能發佈消息給訂閱者。其餘對象,都走walk()方法。
當咱們檢測到對象更新了,如何同步更新到頁面上呢?
一、首先,咱們要找到做用域範圍內(vue,裏會有個 el:"#app")的節點,所有頁面內容都會渲染到這個結點裏面
二、而後遍歷結點上全部含有使用該對象的地方,也就是Mustache語法 (雙大括號) 的文本插值的地方,例如 {{ vm }}
三、綁定視圖更新
只會對對象原有的所有屬性進行作數據劫持,也就是說Vue 不容許動態添加或者刪除對象已有屬性,它是不作數據劫持的,也就不能實現響應式。
舉例
<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>複製代碼
點擊新增屬性,你會發現控制檯打印的vm已經新增了use屬性,而頁面並無響應式改變
點擊刪除屬性,你會發現控制檯打印的vm已經刪除了id屬性,而頁面並無響應式改變
數組對象也不能經過屬性或者索引控制數組,好比length,index實現響應式,經過1.4 裏咱們也看到vue源碼只是對數組的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'進行了重寫,可是索引控制數組是沒有辦法實現響應式的。
改進上面2.1裏的新增屬性方式,你會發現頁面就實現了響應式,至於Vue.set方法介紹移步
addAttribute() {
//this.vm.use = "codercao"
this.$set(this.vm,'use','codercao')
console.log(this.vm)
},複製代碼
Proxy 對象用於定義基本操做的自定義行爲(如屬性查找,賦值,枚舉,函數調用等)
語法
let p = new Proxy(target, handler);
參數說明
target用Proxy包裝的目標對象(能夠是任何類型的對象,包括原生數組,函數,甚至另外一個代理)
handler一個對象,其屬性是當執行一個操做時定義代理的行爲的函數複製代碼
let vm = {
id:"juejin",
name:"掘金"
}
let newVm = new Proxy(vm,{
get(target,key){
console.log("執行get");
return target[key];
},
set(target,key,newValue){
console.log("執行set");
if(target[key]!==newValue)
target[key] = newValue;
}
})
newVm.use = "codercao"
console.log(newVm)複製代碼
你會發現用Proxy 也同樣實現了一個簡易的觀察者機制,固然深刻研究的話,你還能夠實現雙向綁定。
到這裏咱們這篇文章到此就結束了,至於最終做者會怎麼用proxy來寫這個觀察者機制,待vue3.0發佈能夠一看究竟,文章主要是帶你們實踐探索一下Object.defineProperty實現觀察者機制,順便提了一下,這個Object.defineProperty缺陷和處理辦法,而後引入proxy,屬於比較初級的嘗試,Vue發展到如今幾年了,其實大部分人對其應用已經遊刃有餘了,關注源碼和實踐vue裏功能的原理,或許對每一個前端人都會有一些提高。