Vue模擬響應(yīng)式原理底層代碼實(shí)現(xiàn)的示例
整體分析Vue的基本結(jié)構(gòu)如下圖所示:(備注:完整代碼github地址https://github.com/1512955040/MiniVue)

上圖中,為我們模擬最小vue的整體結(jié)構(gòu),首先創(chuàng)建一個(gè)vue類型,它負(fù)責(zé)把data中的成員注入到vue實(shí)例中,并且轉(zhuǎn)化成getter/setter,observer的作用是數(shù)據(jù)劫持,對(duì)data中的屬性進(jìn)行數(shù)據(jù)監(jiān)聽,如果數(shù)據(jù)發(fā)生變化會(huì)獲取到最新的值,并通知dep。Compiler的作用是解析每個(gè)元素中的指令和差值表達(dá)式并替換成相應(yīng)的數(shù)據(jù)。Dep的作用是添加觀察者,當(dāng)數(shù)據(jù)發(fā)生變化時(shí)通知所有的觀察者。Watcher內(nèi)部有一個(gè)Update方法負(fù)責(zé)更新視圖,下面我們用代碼的方式一一進(jìn)行實(shí)現(xiàn)。
1.Vue.js功能:
1-1負(fù)責(zé)接收初始化的參數(shù)(選項(xiàng))
1-2負(fù)責(zé)把data中的屬性注入到vue實(shí)例,轉(zhuǎn)化成getter/setter
1-3負(fù)責(zé)調(diào)用observer監(jiān)聽data中所有屬性的變化
1-4負(fù)責(zé)調(diào)用Compiler解析指令/差值表達(dá)式
類圖結(jié)構(gòu)如下:

如上圖所示:vue類中有三個(gè)屬性,分別是$options,$el,$data,這三個(gè)屬性記錄構(gòu)造函數(shù)中傳過來的參數(shù)。_proxyData為vue類中的方法
所以以_開頭的成員就是私有成員,這個(gè)方法的功能是把data中的屬性轉(zhuǎn)化為getter和setter注入到vue實(shí)例中。
class Vue{
constructor(options) {
//1.通過屬性保存選項(xiàng)中的數(shù)據(jù)
this.$options = options || {}
this.$data = options.data || {}
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
//2.把data中的成員轉(zhuǎn)化為getter和setter,注入到vue實(shí)例中
this._proxyData(this.$data)
//3.調(diào)用observer對(duì)象,監(jiān)聽數(shù)據(jù)的變化
new Observer(this.$data)
//4.調(diào)用compiler對(duì)象,解析指令和差值表達(dá)式
new Compiler(this)
}
//把Vue的屬性轉(zhuǎn)化為getter和setter,注入到Vue實(shí)例中
_proxyData(data){
//遍歷data中所有屬性
Object.keys(data).forEach(key=>{
//把data的屬性注入到vue實(shí)例全局中
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return data[key]
},
set(newValue){
if(newValue===data[key]){
return
}
data[key]=newValue
}
})
})
}
}
2.Observer.js功能(數(shù)據(jù)劫持):
2-1 負(fù)責(zé)把data選項(xiàng)中的屬性轉(zhuǎn)化為響應(yīng)式數(shù)據(jù)
2-2 data中的某個(gè)屬性也是對(duì)象,把該屬性轉(zhuǎn)化為響應(yīng)式數(shù)據(jù)
2-3 數(shù)據(jù)變化發(fā)送通知
類圖結(jié)構(gòu)如下:

如上圖所示:
walk方法的作用是遍歷data中的所有屬性,defineReactive是定義響應(yīng)式數(shù)據(jù),也就是通過調(diào)用defineReactive方法把屬性轉(zhuǎn)化為getter和setter。
class Observer{
constructor(data) {
this.walk(data)
}
//walk方法遍歷data中的所有屬性
walk(data) {
//1.判斷data是否對(duì)象
if(!data || typeof data !=='object'){
return
}
//2.遍歷data對(duì)象的所有屬性
Object.keys(data).forEach(key=>{
this.defineReactive(data,key,data[key])
})
}
//degineReactivce方法定義響應(yīng)式數(shù)據(jù) 把屬性轉(zhuǎn)化為getter和setter
defineReactive(obj,key,val) {
let that=this
// 負(fù)責(zé)收集依賴,并發(fā)送通知
let dep=new Dep()
//如果val傳入對(duì)象的話也給對(duì)象里面的屬性添加getter和setter方法
this.walk(val)
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
// 收集依賴
Dep.target && dep.addSub(Dep.target)
return val
},
set(newValue){
if(newValue==val){
return
}
val=newValue
//如果給屬性重新賦值成對(duì)象,給對(duì)象里面的屬性重新添加getter和setter方法
//比如:歷史數(shù)據(jù)vm.msg="Hello World" 修改之后vm.msg={a:'Hwllo World'}
//再次調(diào)用此方法給vm.msg.a重新添加getter和setter方法
that.walk(newValue)
//發(fā)送通知
dep.notify()
}
})
}
}
3.Compiler.js功能:
3-1 負(fù)責(zé)編譯模板,解析指令/差值表達(dá)式
3-2 負(fù)責(zé)頁面的首次渲染
3-3 當(dāng)數(shù)據(jù)變化后重新渲染視圖
類圖結(jié)構(gòu)如下:

如上圖所示:
el為構(gòu)造函數(shù)傳過來的options.el,vm是vue的實(shí)例,下面都是vm的方法,對(duì)DOM進(jìn)行操作。compile方法內(nèi)部遍歷dom對(duì)象的所有節(jié)點(diǎn),并且
判斷這些節(jié)點(diǎn)是文本節(jié)點(diǎn),如果是文本節(jié)點(diǎn)解析差值表達(dá)式,如果是元素節(jié)點(diǎn)解析指令,isTextNode和isElementNode方法判斷是文本節(jié)點(diǎn)還
是元素節(jié)點(diǎn)。compileElement和compileText方法解析差值表達(dá)式和指令。isDirective這個(gè)方法判斷元素屬性是否是指令。
4.Dep.js功能:
4-1 收集依賴,添加觀察者(watcher)
4-2 通知所有觀察者

如上圖所示:
在vue的響應(yīng)式機(jī)制中,使用觀察者模式來響應(yīng)數(shù)據(jù)的變化,Dep的作用是收集依賴,在getter方法中收集依賴,在setter方法中通知依賴,每
一個(gè)響應(yīng)式的屬性都會(huì)場(chǎng)景一個(gè)Dep對(duì)象,負(fù)責(zé)收集所有依賴于該屬性的地方,所有依賴于該屬性的位置都會(huì)創(chuàng)建一個(gè)watcher對(duì)象,所以
Dep就是收集于該屬性的watcher對(duì)象,使用setter方法去通知依賴,當(dāng)屬性發(fā)生變化時(shí)調(diào)用nodify方法去發(fā)送通知,然后調(diào)用watcher對(duì)象
的update方法。
類的機(jī)構(gòu)如下圖:

如上圖所示:
subs是一個(gè)數(shù)組,存儲(chǔ)dep中所有的watcher,addSub方法添加watcher,notify方法發(fā)布通知
class Dep{
constructor() {
//存儲(chǔ)所有的觀察者
this.subs=[]
}
// 添加觀察者
addSub(sub){
if(sub && sub.update) {
this.subs.push(sub)
}
}
//發(fā)送通知
notify(){
this.subs.forEach(sub =>{
sub.update()
})
}
}
5.Watcher.js功能:
5-1 當(dāng)數(shù)據(jù)變化觸發(fā)依賴,dep通知所有的Watcher實(shí)例更新視圖
5-2 自身實(shí)例化的時(shí)候往dep對(duì)象中添加自己

如上圖所示:
data中的每一個(gè)屬性都要?jiǎng)?chuàng)建一個(gè)Dep對(duì)象, 在收集依賴的時(shí)候把所有對(duì)象的watcher添加到dep對(duì)象的subs數(shù)組中,在setter對(duì)象中發(fā)送通
知,調(diào)用Dep對(duì)象的notify方法通知所有關(guān)聯(lián)的watcher對(duì)象更新視圖。
類圖結(jié)構(gòu)如下:

如上圖所示:
update對(duì)象更新視圖,cb回調(diào)函數(shù),指明如何更新視圖。在更新視圖的時(shí)候需要一個(gè)屬性key(data中的屬性名稱),oldvalue是key 對(duì)應(yīng)的值。
class Watcher{
constructor(vm,key,cb) {
this.vm=vm
//data中的屬性名稱
this.key=key
//回調(diào)函數(shù)負(fù)責(zé)更新視圖
this.cb=cb
//把watcher對(duì)象記錄到Dep類的靜態(tài)屬性target
Dep.target =this
//觸發(fā)get方法,在get方法中會(huì)調(diào)用addSub
this.oldValue=vm[key]
Dep.target=null
}
//當(dāng)數(shù)據(jù)發(fā)生變化時(shí)更新視圖
update(){
let newValue=this.vm[this.key]
if(this.oldValue === newValue){
return
}
this.cb(newValue)
}
}
下面通過這張圖作整體流程的總結(jié):

到此這篇關(guān)于Vue模擬響應(yīng)式原理底層代碼實(shí)現(xiàn)的示例的文章就介紹到這了,更多相關(guān)Vue 響應(yīng)式原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Electron進(jìn)程間通信的實(shí)現(xiàn)
本文主要介紹了Electron進(jìn)程間通信的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Vue項(xiàng)目動(dòng)態(tài)加載圖片正確寫法
最近做項(xiàng)目的時(shí)候遇到了動(dòng)態(tài)加載圖片的需求,所以這里給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目動(dòng)態(tài)加載圖片的正確寫法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
詳解vue-router2.0動(dòng)態(tài)路由獲取參數(shù)
本篇文章主要介紹了詳解vue-router2.0動(dòng)態(tài)路由獲取參數(shù),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06
vue-cli 腳手架基于Nightwatch的端到端測(cè)試環(huán)境的過程
這篇文章主要介紹了vue-cli 腳手架基于Nightwatch的端到端測(cè)試環(huán)境的過程,需要的朋友可以參考下2018-09-09
詳解windows下vue-cli及webpack 構(gòu)建網(wǎng)站(二)導(dǎo)入bootstrap樣式
這篇文章主要介紹了詳解windows下vue-cli及webpack 構(gòu)建網(wǎng)站(二)導(dǎo)入bootstrap樣式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
關(guān)于uniapp的高級(jí)表單組件mosowe-form
這篇文章主要介紹了關(guān)于uniapp的高級(jí)表單組件mosowe-form,由于一些表單標(biāo)簽改來改去比較繁瑣,重復(fù)性很多,且樣式布局啥的幾乎萬變不離其中,為了偷懶,開發(fā)了mosowe-form及mosowe-table兩款高級(jí)組件,需要的朋友可以參考下2023-04-04

