vue雙向綁定及觀察者模式詳解
在Vue中,使用了Object.defineProterty()這個(gè)函數(shù)來實(shí)現(xiàn)雙向綁定,這也就是為什么Vue不兼容IE8
1 響應(yīng)式原理
讓我們先從相應(yīng)式原理開始。我們可以通過Object.defineProterty()來自定義Object的getter和setter 從而達(dá)到我們的目的。
代碼如下
function observe(value, cb) {
Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}
function defineReactive (obj, key, val, cb) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
/*....依賴收集等....*/
/*Github:https://github.com/answershuto*/
return val
},
set:newVal=> {
val = newVal;
cb();/*訂閱者收到消息的回調(diào)*/
}
})
}
class Vue {
constructor(options) {
this._data = options.data;
observe(this._data, options.render)
}
}
let app = new Vue({
el: '#app',
data: {
text: 'text',
text2: 'text2'
},
render(){
console.log("render");
}
})
通過observe函數(shù)對app.data上的每一個(gè)key和value都設(shè)定getter和setter。當(dāng)value改變的時(shí)候觸發(fā)setter,就會觸發(fā)render這個(gè)函數(shù)。響應(yīng)式的目的就達(dá)成,如果是視圖更新的話我們通過監(jiān)聽dom的input事件來觸發(fā)數(shù)據(jù)更新
但是現(xiàn)在我們只有在改變vue._data.text的時(shí)候才會觸發(fā)他們的setter,但是我想偷懶,只改變vue.text就能觸發(fā)到setter怎么做呢?
我們使用代理的方法
_proxy.call(this, options.data);/*構(gòu)造函數(shù)中*/
/*代理*/
function _proxy (data) {
const that = this;
Object.keys(data).forEach(key => {
Object.defineProperty(that, key, {
configurable: true,
enumerable: true,
get: function proxyGetter () {
return that._data[key];
},
set: function proxySetter (val) {
that._data[key] = val;
}
})
});
}
依賴收集
讓我們再來看看下面的代碼
new Vue({
template:
`<div>
<span>text1:</span> {{text1}}
<span>text2:</span> {{text2}}
<div>`,
data: {
text1: 'text1',
text2: 'text2',
text3: 'text3'
}
});
當(dāng)你的text3變化的時(shí)候,實(shí)際上text3并沒有被渲染,但是也會觸發(fā)一次render函數(shù),這顯然是不對的。所以我們需要收集依賴。
我們只需要在初始化的時(shí)候渲染一遍,那所有渲染所依賴的數(shù)據(jù)都會被觸發(fā)getter,這時(shí)候我們只要把這個(gè)數(shù)據(jù)放到一個(gè)列表里就好啦!
我們先來認(rèn)識一下Dep(dependencies)這個(gè)類,下圖是一個(gè)最簡單的Dep類。我們可以把他理解為發(fā)布者(這點(diǎn)很重要?。。?br />
class Dep {
constructor () {
this.subs = [];
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
/*Github:https://github.com/answershuto*/
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
我們每次觸發(fā)getter的時(shí)候,只要把觸發(fā)的對象放到dep.sub里面就好啦!
但是現(xiàn)在問題來了,我們用什么來裝這個(gè)觸發(fā)的'對象',也可以說式訂閱者呢?
我們使用Watcher這個(gè)類
class Watcher {
constructor (vm, expOrFn, cb, options) {
this.cb = cb;
this.vm = vm;
/*在這里將觀察者本身賦值給全局的target,只有被target標(biāo)記過的才會進(jìn)行依賴收集*/
Dep.target = this;
/*Github:https://github.com/answershuto*/
/*觸發(fā)渲染操作進(jìn)行依賴收集*/
this.cb.call(this.vm);
}
update () {
this.cb.call(this.vm);
}
}
vm即是vue實(shí)例, expOrFn就是{{a+b}}里面的a+b, cb就是回調(diào)函數(shù)就是return a+b, options是一些配置項(xiàng)。
Vue在第一次渲染列表的時(shí)候如果碰到{{xxx}}這樣的表達(dá)式,就會new Watcher()。解析里面的函數(shù),然后把當(dāng)前的watcher實(shí)例賦給Dep.target(Dep.target是全局的,一次性只能有一個(gè)存在,因?yàn)閂ue一次只處理一個(gè)依賴)。然后執(zhí)行回調(diào)函數(shù)。(這里看似是執(zhí)行回調(diào)函數(shù)渲染,其實(shí)又觸發(fā)了一次getter,然后就會把當(dāng)前的依賴添加到sub里去)
接下來開始依賴收集
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data, options.render);
let watcher = new Watcher(this, );
}
}
function defineReactive (obj, key, val, cb) {
/*在閉包內(nèi)存儲一個(gè)Dep對象*/
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
if (Dep.target) {
/*Watcher對象存在全局的Dep.target中*/
dep.addSub(Dep.target);
}
},
set:newVal=> {
/*只有之前addSub中的函數(shù)才會觸發(fā)*/
dep.notify();
}
})
}
Dep.target = null; //防止依賴重復(fù)添加
這兒我們通過示例來講解
<template>
<div>
{{a+b}}
</div>
<div>
{{a-c}}
</div>
</template>
<script>
let app = new Vue( {
data :{
a: 1,
b: 2,
c: 3
}
})
我們編譯到{{a+b}},會去實(shí)例化一個(gè)對應(yīng)的Watcher對象,Watcher的構(gòu)造函數(shù)中有這么一句
this.cb.call(this.vm);this.cb指的是function(){return a+b};this.vm指的是這個(gè)vue對象,這樣就會觸發(fā)vue.a和vue.b的getter方法,a,b都有自己的dep對象,我們通過Dep.target將這個(gè)Watcher對象就加到dep的subs數(shù)組中去了,當(dāng)我們變更a或者b是就會觸發(fā)setter,進(jìn)而觸發(fā)subs數(shù)組中的update方法,視圖中的a+b就會更新
有個(gè)小知識點(diǎn):我們新建一個(gè)屬性對象時(shí)必須通過Vue.set的方法去實(shí)現(xiàn),而不能直接通過=實(shí)現(xiàn),這樣會檢測不到,因?yàn)槲覀冊诔跏蓟瘯r(shí)就通過defineProperty重構(gòu)了這個(gè)對象屬性的getter和setter方法,新建的屬性則沒有所以不會被檢測到
下圖為Vue框架在數(shù)據(jù)初始化中使用觀察者模式的示意圖:

以上所述是小編給大家介紹的vue雙向綁定及觀察者模式詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Vue2響應(yīng)式系統(tǒng)之set和delete
這篇文章主要介紹了Vue2響應(yīng)式系統(tǒng)之set和delete,通過為對象收集依賴,將對象、數(shù)組的修改、刪除也變成響應(yīng)式的了,同時(shí)為用戶提供了和方法,下文詳細(xì)介紹需要的朋友可以參考一下2022-04-04
vue?內(nèi)置組件?component?的用法示例詳解
這篇文章主要介紹了vue內(nèi)置組件component的用法,本文給大家介紹了component內(nèi)置組件切換方法,通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
關(guān)于vue狀態(tài)過渡transition不起作用的原因解決
這篇文章主要介紹了關(guān)于vue狀態(tài)過渡transition不起作用的原因解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-04-04
vue實(shí)現(xiàn)codemirror代碼編輯器中的SQL代碼格式化功能
這篇文章主要介紹了vue實(shí)現(xiàn)codemirror代碼編輯器中的SQL代碼格式化功能,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
Vue Element前端應(yīng)用開發(fā)之表格列表展示
在我們一般開發(fā)的系統(tǒng)界面里面,列表頁面是一個(gè)非常重要的綜合展示界面,包括有條件查詢、列表展示和分頁處理,以及對每項(xiàng)列表內(nèi)容可能進(jìn)行的轉(zhuǎn)義處理,本篇隨筆介紹基于Vue +Element基礎(chǔ)上實(shí)現(xiàn)表格列表頁面的查詢,列表展示和字段轉(zhuǎn)義處理。2021-05-05
vuejs如何清空表單數(shù)據(jù)、刪除對象中的空屬性公共方法
這篇文章主要介紹了vuejs如何清空表單數(shù)據(jù)、刪除對象中的空屬性公共方法,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03

