1. 多個對象響應式
-
- 當前存在的問題:當前實現僅針對某個固定對象(obj)進行依賴收集,實際開發中需要處理多個不同對象
-
- 將對象響應式處理邏輯抽取為通用函數,支持任意對象
-
- 代碼如下:
// 方案一:Object.defineProperty -> Vue2// 多個對象響應式抽取成通用函數function reactive(obj) {Object.keys(obj).forEach(key => {let value = obj[key];Object.defineProperty(obj, key, {set: function(newValue) {value = newValue;const dep = getDepend(obj, key)dep.notify()},get: function() {// 找到對應的obj對象的key對應的dep對象const dep = getDepend(obj, key)// dep.addDepend(reactiveFn)dep.depend()return value;}})})return obj}
- 代碼如下:
-
- Vue2實現:
- 基于Object.defineProperty的響應式系統
- data返回的對象會被reactive包裹處理
- 模板編譯生成的render函數自動收集依賴
2. Vue3響應式原理(監聽對象-Proxy)
-
2.1. 核心:使用
Proxy
替代Vue2的Object.defineProperty
實現響應式 -
2.2 與
Vue2
對比:Vue2
需要遍歷對象所有屬性進行監聽,Vue3
的Proxy
可以自動監聽整個對象Proxy
能捕獲更多操作類型(如新增屬性、刪除屬性等)
-
2.3. 完整代碼:
// 方案二:new Proxy() -> Vue3 function reactive(obj) {const objProxy = new Proxy(obj, {// receiver作用:1. 可以改變操作中的this指向 2. 確保getter/setter中的this指向代理對象set: function(target, key, newValue, receive) {// target[key] = newValueReflect.set(target, key, newValue, receive)const dep = getDepend(target, key)dep.notify()},get: function (target, key, receive) {const dep = getDepend(target, key)dep.depend()return Reflect.get(target, key, receive)}})return objProxy }
3. Depend類的重構
-
- 重構的點:
- 使用
Set替代數組存儲依賴函數,避免重復收集
- 添加depend方法專門處理依賴收集邏輯
-
- 代碼如下:
class Depend {constructor() {// 使用Set替代數組存儲依賴函數,避免重復收集this.reactiveFns = new Set();}addDepend (fn) {if(fn) {this.reactiveFns.add(fn);}}depend () {if(reactiveFn) {this.reactiveFns.add(reactiveFn)}}notify () {this.reactiveFns.forEach(fn => {fn()})}}
- 代碼如下:
4. 完整代碼如下:
class Depend {constructor() {// 使用Set替代數組存儲依賴函數,避免重復收集this.reactiveFns = new Set();}addDepend (fn) {if(fn) {this.reactiveFns.add(fn);}}depend () {if(reactiveFn) {this.reactiveFns.add(reactiveFn)}}notify () {this.reactiveFns.forEach(fn => {fn()})}
}// 封裝一個函數:負責通過obj的key獲取對應的Depend對象
const objMap = new WeakMap() // WeakMap弱引用
function getDepend (obj, key) {// 1.根據對象obj,找到對應的map對象let map = objMap.get(obj)if(!map) {map = new Map()objMap.set(obj, map)}// 2.根據key,找到對應的depend對象let dep = map.get(key)if(!dep) {dep = new Depend();map.set(key, dep)}return dep
}// 監聽屬性變化數據劫持
// 方案一:Object.defineProperty -> Vue2
// 多個對象響應式抽取成通用函數
// function reactive(obj) {
// Object.keys(obj).forEach(key => {
// let value = obj[key];
// Object.defineProperty(obj, key, {
// set: function(newValue) {
// value = newValue;
// const dep = getDepend(obj, key)
// dep.notify()
// },
// get: function() {
// // 找到對應的obj對象的key對應的dep對象
// const dep = getDepend(obj, key)
// // dep.addDepend(reactiveFn)
// dep.depend()
// return value;
// }
// })
// })
// return obj
// }// 方案二:new Proxy() -> Vue3
function reactive(obj) {const objProxy = new Proxy(obj, {// receiver作用:1. 可以改變操作中的this指向 2. 確保getter/setter中的this指向代理對象set: function(target, key, newValue, receive) {// target[key] = newValueReflect.set(target, key, newValue, receive)const dep = getDepend(target, key)dep.notify()},get: function (target, key, receive) {const dep = getDepend(target, key)dep.depend()return Reflect.get(target, key, receive)}})return objProxy
}// 設置一個專門執行響應式函數的一個函數
let reactiveFn = null // 自由變量
function watchFn (fn) {reactiveFn = fnfn()reactiveFn = null
}// =================================== 業務代碼 =====================================
const obj = reactive({name: 'why',age: 18,address: '長沙市'
})watchFn(function () {console.log(obj.name);console.log(obj.age);
})// 修改obj的屬性
console.log('age發生變化時----------------------------------------');
obj.age = 21console.log('user對象----------------------------------------');const user = reactive({nickName: 'abc',level: 100
})watchFn(function () {console.log('nickName: ' ,user.nickName);console.log('level: ' ,user.level);
})user.nickName = 'cba'