Vue2?與?Vue3?的數(shù)據(jù)綁定原理及實(shí)現(xiàn)
介紹
數(shù)據(jù)綁定是一種把用戶界面元素(控件)的屬性綁定到特定對象上面并使其同步的機(jī)制,使開發(fā)人員免于編寫同步視圖模型和視圖的邏輯。

觀察者模式又稱為發(fā)布-訂閱模式,定義對象間的一種一對多的依賴關(guān)系,當(dāng)它本身的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對象都得到通知并被自動(dòng)更新。比如用戶界面可以作為一個(gè)觀察者,業(yè)務(wù)數(shù)據(jù)是被觀察者,用戶界面觀察業(yè)務(wù)數(shù)據(jù)的變化,發(fā)現(xiàn)數(shù)據(jù)變化后,就同步顯示在界面上。這樣可以確保界面和數(shù)據(jù)之間劃清界限,假定應(yīng)用程序的需求發(fā)生變化,需要修改界面的表現(xiàn),只需要重新構(gòu)建一個(gè)用戶界面,業(yè)務(wù)數(shù)據(jù)不需要發(fā)生變化。有以下幾個(gè)角色:
- 抽象主題(Subject):提供一個(gè)接口,把所有觀察者對象的引用保存到一個(gè)集合里,可以增加和刪除觀察者對象。
- 具體主題(Concrete Subject):將有關(guān)狀態(tài)信息存入觀察者對象,在本身的內(nèi)部狀態(tài)改變時(shí),給所有登記過的觀察者發(fā)出通知。
- 抽象觀察者(Observer):為所有的具體觀察者定義一個(gè)接口,在得到主題通知時(shí)更新自己。
- 具體觀察者(Concrete Observer):實(shí)現(xiàn)更新接口。
Vue2 和 Vue3 的數(shù)據(jù)綁定都是觀察者模式的實(shí)現(xiàn),前者使用 Object.defineProperty,后者使用的是 Proxy。
有以下 HTML:
<div id="app">
<input type="radio" name="hello" id="hello" value="hello" v-model="title">
<label for="hello">hello</label>
<input type="radio" name="hello" id="hello2" value="hello2" v-model="title">
<label for="hello2">hello2</label>
<div v-bind="title"></div>
<input v-model="content">
<select v-model="content">
<option>world</option>
<option>world1</option>
<option>world2</option>
</select>
<div v-bind="content"></div>
<input type="checkbox" id="hobby1" value="hobby1" v-model="hobby">
<label for="hobby1">hobby1</label>
<input type="checkbox" id="hobby2" value="hobby2" v-model="hobby">
<label for="hobby2">hobby2</label>
<input type="checkbox" id="hobby3" value="hobby3" v-model="hobby">
<label for="hobby3">hobby3</label>
<br>
{{ hobby }}
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
title: 'hello',
content: 'world2',
hobby: ['hobby2'],
}
});
</script>下面使用兩種方法進(jìn)行簡單實(shí)現(xiàn)上面的雙向綁定。
Object.defineProperty
語法:
Object.defineProperty(obj, prop, descriptor)
- obj:要定義屬性的對象。
- prop:要定義或修改的屬性的名稱或 Symbol 。
- descriptor:要定義或修改的屬性描述符。
- 返回值:被傳遞給函數(shù)的對象。
首先定義一個(gè)觀察者構(gòu)造函數(shù),并實(shí)現(xiàn)得到主題通知時(shí)更新自己的邏輯。第一行將當(dāng)前觀察者綁定到函數(shù)屬性上面,是為了避免全局作用域變量。
function Observer(vm, node, name, nodeType) {
// 構(gòu)造函數(shù)被調(diào)用時(shí),將當(dāng)前對象綁定到函數(shù)屬性上面,接下來觸發(fā) getter 時(shí)使用
Observer.target = this;
this.update = () => {
// 這里 vm[name] 讀取操作會(huì)觸發(fā) getter
if (node.type === 'radio') node.checked = node.value === vm[name];
else if (node.type !== 'checkbox') node[nodeType] = vm[name];
};
this.update();
Observer.target = null; // 設(shè)置為空,避免首次觸發(fā)get后重復(fù)添加
}然后定義 Vue 構(gòu)造函數(shù),遍歷 options.data 對象,為每個(gè)屬性都生成一個(gè)主題(包含當(dāng)前屬性的觀察者數(shù)組),然后使用 Object.defineProperty 劫持屬性的讀取和寫入操作,在首次讀取時(shí)添加一個(gè)對應(yīng)的觀察者對象,為了避免后面讀取操作重復(fù)添加,在觀察者構(gòu)造函數(shù)里面首次更新操作完成后設(shè)置了空。
function Vue(options) {
const obj = options.data;
Object.keys(obj).forEach(key => {
const subjects = [];
Object.defineProperty(this, key, {
get() {
if (Observer.target) subjects.push(Observer.target);
return obj[key];
},
set(newVal) {
if (newVal === obj[key]) return;
obj[key] = newVal;
// 給當(dāng)前主題所有登記過的觀察者發(fā)出通知
subjects.forEach(observer => observer.update());
}
});
});
}接下來就是遍歷根節(jié)點(diǎn)(這里只遍歷一層),根據(jù)子節(jié)點(diǎn)的類型,傳入不同的參數(shù)調(diào)用 Observer 構(gòu)造函數(shù),然后首次更新視圖,并觸發(fā) getter 將觀察者對象都對應(yīng)放到 options.data 的每個(gè)屬性主題中,然后按屬性類型添加不同的事件監(jiān)聽。
const el = document.querySelector(options.el);
el.childNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.hasAttribute('v-model')) {
const name = node.getAttribute('v-model');
if (node.type === 'checkbox') node.checked = this[name].includes(node.value);
const eventType = (node.tagName === 'INPUT' && node.type === 'text') || node.tagName == 'TEXTAREA' ? 'input' : 'change';
node.addEventListener(eventType, e => {
// 這里 this[name] 寫入操作會(huì)觸發(fā) setter
if (node.type === 'checkbox') {
if (node.checked) this[name] = this[name].concat(node.value).sort();
else this[name] = this[name].filter(v => v !== node.value).sort();
} else this[name] = node.value;
});
new Observer(this, node, name, 'value');
} else if (node.hasAttribute('v-bind')) {
new Observer(this, node, node.getAttribute('v-bind'), 'textContent');
}
} else if (node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.nodeValue)) {
new Observer(this, node, RegExp.$1.trim(), 'nodeValue');
}
});<div id="app">
<input type="radio" name="hello" id="hello" value="hello" v-model="title">
<label for="hello">hello</label>
<input type="radio" name="hello" id="hello2" value="hello2" v-model="title">
<label for="hello2">hello2</label>
<p v-bind="title"></p>
<input v-model="content">
<select v-model="content">
<option>world</option>
<option>world1</option>
<option>world2</option>
</select>
<p v-bind="content"></p>
<input type="checkbox" id="hobby1" value="hobby1" v-model="hobby">
<label for="hobby1">hobby1</label>
<input type="checkbox" id="hobby2" value="hobby2" v-model="hobby">
<label for="hobby2">hobby2</label>
<input type="checkbox" id="hobby3" value="hobby3" v-model="hobby">
<label for="hobby3">hobby3</label>
<br>
{{ hobby }}
</div>運(yùn)行:

Proxy
語法:
new Proxy(target, handler)
- target:被代理的對象
- handler:被代理對象上的自定義行為,和 Reflect 對象的所有靜態(tài)方法對應(yīng),所以可以在其中調(diào)用對應(yīng)的 Reflect 方法,完成默認(rèn)行為,然后再部署額外的功能。
第一步定義觀察者構(gòu)造函數(shù),和 Object.defineProperty 方式相同。
第二步也是定義 Vue 構(gòu)造函數(shù),不同的是使用 Proxy 劫持屬性的讀取和寫入操作,不需要為 options.data 對象每個(gè)屬性都添加主題了。其他和 Object.defineProperty 方式相同。
function Vue(options) {
const subjects = [];
this.proxy = new Proxy(options.data, {
get(obj, key, receiver) {
if (Observer.target) subjects.push(Observer.target);
const value = Reflect.get(...arguments);
return value;
},
set(obj, key, value, receiver) {
if (value === obj[key]) return;
const result = Reflect.set(...arguments);
subjects.forEach(observer => observer.update());
return result;
}
});
}第三步遍歷根節(jié)點(diǎn),觸發(fā) getter 將觀察者對象都放到主題的數(shù)組中,然后添加事件監(jiān)聽時(shí),要觸發(fā) Proxy 的寫入操作,而不是原對象。
const el = document.querySelector(options.el);
el.childNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.hasAttribute('v-model')) {
const name = node.getAttribute('v-model');
if (node.type === 'checkbox') node.checked = this.proxy[name].includes(node.value);
const eventType = (node.tagName === 'INPUT' && node.type === 'text') || node.tagName == 'TEXTAREA' ? 'input' : 'change';
node.addEventListener(eventType, e => {
// 這里 this.proxy[name] 寫入操作會(huì)觸發(fā) setter
if (node.type === 'checkbox') {
let value = this.proxy[name];
if (node.checked) {
this.proxy[name] = value.concat(node.value).sort();
} else this.proxy[name] = value.filter(v => v !== node.value).sort();
} else this.proxy[name] = node.value;
});
new Observer(this.proxy, node, name, 'value');
} else if (node.hasAttribute('v-bind')) {
new Observer(this.proxy, node, node.getAttribute('v-bind'), 'textContent');
}
} else if (node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.nodeValue)) {
new Observer(this.proxy, node, RegExp.$1.trim(), 'nodeValue');
}
});<div id="app">
<input type="radio" name="hello" id="hello" value="hello" v-model="title">
<label for="hello">hello</label>
<input type="radio" name="hello" id="hello2" value="hello2" v-model="title">
<label for="hello2">hello2</label>
<p v-bind="title"></p>
<input v-model="content">
<select v-model="content">
<option>world</option>
<option>world1</option>
<option>world2</option>
</select>
<p v-bind="content"></p>
<input type="checkbox" id="hobby1" value="hobby1" v-model="hobby">
<label for="hobby1">hobby1</label>
<input type="checkbox" id="hobby2" value="hobby2" v-model="hobby">
<label for="hobby2">hobby2</label>
<input type="checkbox" id="hobby3" value="hobby3" v-model="hobby">
<label for="hobby3">hobby3</label>
<br>
{{ hobby }}
</div>運(yùn)行:

到此這篇關(guān)于Vue2 與 Vue3 的數(shù)據(jù)綁定原理及實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Vue數(shù)據(jù)綁定內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue.js項(xiàng)目實(shí)戰(zhàn)之多語種網(wǎng)站的功能實(shí)現(xiàn)(租車)
這篇文章主要介紹了Vue.js項(xiàng)目實(shí)戰(zhàn)之多語種網(wǎng)站(租車)的功能實(shí)現(xiàn) ,需要的朋友可以參考下2019-08-08
vue選項(xiàng)卡組件的實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了vue選項(xiàng)卡組件的實(shí)現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
vue實(shí)現(xiàn)虛擬滾動(dòng)渲染成千上萬條數(shù)據(jù)
本文主要介紹了vue實(shí)現(xiàn)虛擬滾動(dòng)渲染成千上萬條數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02

