武漢眼前一亮科技內(nèi)蒙古seo
什么是Vue中的響應式
Vue中的響應式,簡而言之就是當數(shù)據(jù)發(fā)生變化時,頁面跟隨變化。使用過Vue的v-model都有比較深刻的感受,我們在代碼中修改雙向綁定的數(shù)據(jù)后,頁面上的數(shù)據(jù)也會自動更新,頁面跟隨變化
我們看個例子:
<div id="app"><div>價格:¥{{ price }}</div><div>支付金額:¥{{ price * quantity }}</div><button @click="changePrice">改變價格</button>
</div>
var app = new Vue({el: '#app',data() {return {price: 5.0,quantity: 2};},methods: {changePrice() {this.price = 10;}}
})
在這個例子中,我們調(diào)用changePrice方法,修改price的值,頁面上價格 、支付金額,都會自動改變。這就是Vue中的響應式
Vue中如何實現(xiàn)響應式
當數(shù)據(jù)發(fā)生變化后,會重新對頁面渲染,這就是Vue響應式,那么這一切是怎么做到的呢?
想完成這個過程,我們需要完成這幾個步驟:
(1)偵測數(shù)據(jù)的變化(數(shù)據(jù)劫持 / 數(shù)據(jù)代理)
(2)收集視圖依賴了哪些數(shù)據(jù)(依賴收集)
(3)數(shù)據(jù)變化時,自動“通知”需要更新的視圖部分,并進行更新(發(fā)布訂閱模式)
1.偵測數(shù)據(jù)的變化(數(shù)據(jù)劫持 / 數(shù)據(jù)代理)
在Javascript中,如何偵測一個對象的變化?我們有兩種辦法可以偵測到變化:使用Object.defineProperty和ES6的Proxy,這就是進行數(shù)據(jù)劫持或數(shù)據(jù)代理。
在Vue2中,使用的是Object.defineProperty的方式偵測數(shù)據(jù)的變化,
類似如下代碼,當屬性被讀取或設(shè)置時,相應的getter或setter將被調(diào)用。修改obj.value的值時,會監(jiān)聽到obj.value,執(zhí)行set方法,執(zhí)行console.log(obj.value)時,又會調(diào)用get方法。
<script>const obj = {};// 定義屬性'value',包含getter和setterObject.defineProperty(obj, "value", {get() {console.log("get value");return this._value;},set(newValue) {console.log("set value");this._value = newValue;},// 可以通過這個屬性來控制屬性的可枚舉性configurable: true,// 可以通過這個屬性來控制屬性的可寫性enumerable: true,});obj.value = 5;console.log(obj.value); // get value ,set value, 5</script>
但在Vue3中,使用的是ES6的Proxy的方式偵測數(shù)據(jù)的變化。
Proxy對象允許你攔截并自定義對象的基本操作,包括屬性訪問和修改。這使得你可以創(chuàng)建一個代理對象,當訪問或修改目標對象的屬性時,會觸發(fā)預定義的行為。如下代碼示例:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>基礎(chǔ) ref()</title></head><body><button id="updateButton">點擊+1</button><div id="message"></div><script>const createRef = (initialValue) => {return new Proxy({ value: initialValue },{get(target, key) {return target[key];},set(target, key, value) {target[key] = value;if (key === "value") {updateDisplay(); // 當.value被設(shè)置時,更新DOM}return true;},});};// 初始化refconst numberRef = createRef(0);// 更新DOM的函數(shù)const updateDisplay = () => {document.getElementById("message").innerText = numberRef.value;};// 綁定按鈕點擊事件document.getElementById("updateButton").addEventListener("click", () => {numberRef.value++; // 點擊按鈕時計數(shù)器加一});// 初始顯示updateDisplay();</script></body>
</html>
2.收集視圖依賴了哪些數(shù)據(jù)(依賴收集)
當模板渲染或計算屬性計算時,Vue 會追蹤哪些數(shù)據(jù)被訪問了。這通過 Dep 類和 Watcher 類完成。Watcher 會在讀取數(shù)據(jù)時將自身添加到數(shù)據(jù)的依賴列表中。
訂閱者 Dep
收集依賴需要為依賴找一個存儲依賴的地方,為此我們創(chuàng)建了Dep,它用來收集依賴、刪除依賴和向依賴發(fā)送消息等。
于是我們先來實現(xiàn)一個訂閱者 Dep 類,用于解耦屬性的依賴收集和派發(fā)更新操作,說得具體點,它的主要作用是用來存放 Watcher 觀察者對象。我們可以把Watcher理解成一個中介的角色,數(shù)據(jù)發(fā)生變化時通知它,然后它再通知其他地方。
Dep的簡單實現(xiàn)
class Dep {constructor () {/* 用來存放Watcher對象的數(shù)組 */this.subs = [];}/* 在subs中添加一個Watcher對象 */addSub (sub) {this.subs.push(sub);}/* 通知所有Watcher對象更新視圖 */notify () {this.subs.forEach((sub) => {sub.update();})}
}
以上代碼主要做兩件事情:
用 addSub 方法可以在目前的 Dep 對象中增加一個 Watcher 的訂閱操作;
用 notify 方法通知目前 Dep 對象的 subs 中的所有 Watcher 對象觸發(fā)更新操作
觀察者 Watcher
Vue 中定義一個 Watcher 類來表示觀察訂閱依賴。
watcher的簡單實現(xiàn):
class Watcher {constructor(obj, key, cb) {// 將 Dep.target 指向自己// 然后觸發(fā)屬性的 getter 添加監(jiān)聽// 最后將 Dep.target 置空Dep.target = thisthis.cb = cbthis.obj = objthis.key = keythis.value = obj[key]Dep.target = null}update() {// 獲得新值this.value = this.obj[this.key]// 我們定義一個 cb 函數(shù),這個函數(shù)用來模擬視圖更新,調(diào)用它即代表更新視圖this.cb(this.value)}
}
3.數(shù)據(jù)變化時,自動“通知”需要更新的視圖部分,并進行更新(發(fā)布訂閱模式)
當數(shù)據(jù)被修改時,對應的 Watcher 會收到通知,并觸發(fā)視圖更新