碼字不易,有幫助的同學希望能關注一下我的微信公眾號:Code程式人生,感謝!代碼自用自取,

vue實作資料回應式,是通過資料劫持偵測資料變化,發布訂閱模式進行依賴收集與視圖更新,換句話說是Observe,Watcher以及Compile三者相互配合,
- Observe實作資料劫持,遞回給物件屬性,系結setter和getter函式,屬性改變時,通知訂閱者
- Compile決議模板,把模板中
變數換成資料,系結更新函式,添加訂閱者,收到通知就執行更新函式 - Watcher作為Observe和Compile中間的橋梁,訂閱Observe屬性變化的訊息,觸發Compile更新函式
資料劫持/代理 Observer
實作回應式的第一步就是能偵測數r據的變化,在Vue2.x是通過ES5的方法Object.defineProperty()實作物件屬性的偵聽,在Vue3.x中使用了ES6提供的Proxy對物件進行代理,
Object.defineProperty
function observe(obj) {
if (!obj || typeof obj !== "object") {
return;
}
Object.keys(obj).forEach((key) => {
defineReactive(obj, key, obj[key]);
});
function defineReactive(obj, key, value) {
//遞回子屬性
observe(value);
//訂閱器
const dp = new Dep();
Object.defineProperty(obj, key, {
configurable: true, //可洗掉
enumerable: true, //可列舉遍歷
get: function () {
/* 將Dep.target(即當前的Watcher物件存入dep的subs中) */
dp.addSub(Dep.target);
return value;
},
set: function (newValue) {
//遞回新的子屬性
observe(newValue);
if (value !== newValue) {
value = newValue;
/* 在set的時候觸發dep的notify來通知所有的Watcher物件更新視圖 */
dp.notify();
}
},
});
}
}
Proxy實作代理
let target = { name: " xiao" };
let handler = {
get(target, key) {
if (typeof target[key] === "object" && target[key] !== "null") {
return new Proxy(target[key], handler);
}
return target[key];
},
set: function (target, key, value) {
target[key] = value;
},
};
target = new Proxy(target, handler);
依賴收集Dep
//Dep訂閱者,依賴收集器
class Dep {
constructor() {
/* 用來存放Watcher物件的陣列 */
this.subs = [];
}
/* 在subs中添加一個Watcher物件 */
addSub(sub) {
this.subs.push(sub);
}
/* 在subs中添加一個Watcher物件 */
notify() {
this.subs.forEach((sub) => {
sub.update();
});
}
}
//用 addSub 方法可以在目前的 Dep 物件中增加一個 Watcher 的訂閱操作;
//用 notify 方法通知目前 Dep 物件的 subs 中的所有 Watcher 物件觸發更新操作,
Watcher訂閱者
class Watcher {
constructor(obj, key, cb) {
/* 在new一個Watcher物件時將該物件賦值給Dep.target,在observe get中會用到 */
Dep.target = this;
this.obj = obj;
this.key = key;
this.cb = cb;
//觸發getter,依賴收集
this.value = obj[key];
//收集完置空Dep.target,防止重復收集
Dep.target = null;
}
update() {
//獲得新值
this.value = obj[this.key];
console.log("視圖更新");
}
}
Compile模板編譯
- 正則匹配決議vue指令、運算式
- 把變數替換成資料初始化渲染
- 創建Watcher訂閱更新函式
//指令處理類
const compileUtile = {
getVal(expr,vm){
//reduce用的好啊
return expr.split('.').reduce((data,curentval)=>{
return data[curentval];
},vm.$data)
},
html(node,expr,vm){
new Watcher(vm,expr,(newVal)=>{
this.updater.htmlUpdate(node,newVal);
})
const value = this.getVal(expr,vm);
this.updater.htmlUpdate(node,value);
},
//更新函式
updater:{
htmlUpdate(node,value){
node.innerHTML= value;
},
}
}
//Compile指令決議器
class Compile{
//各種正則匹配vue指令和運算式,替換資料
}
Object.defineProperty與Proxy的區別?
- Proxy可以直接監聽物件,而非屬性,可以監聽屬性的增加
- Proxy可以監聽陣列
- Proxy有很多Object.defineProperty不具備的攔截方法
- Proxy回傳一個新物件,可以直接操作新物件達到目的,Object.defineProperty只能遍歷物件屬性修改
為什么要依賴收集?
資料劫持的目的是在屬性變化的時候觸發視圖更新,依賴收集可以收集到哪些地方使用到了相關屬性,屬性變化時,就可以通知到所有的地方去更新視圖,對于沒有使用的屬性,也可以避免無用的資料比對更新
Dep和Watcher的關系(多對多)
- data中一個key對應一個Dep實體, 一個Dep實體對應多個Watcher實體(一個屬性在多個運算式中使用)
- 一個運算式對應一個Watcher實體,一個Watcher對用多個Dep實體(一個運算式中有多個屬性)
watcher和Dep何時創建
- Dep在初始化data的屬性進行資料劫持時創建的
- Watcher是在初始化時決議大括號運算式/一般指令時創建
如何實作對陣列的監聽
因為Object.defineProperty不能監聽陣列長度變化,所以Vue使用了函式劫持的方式,重寫了陣列的方法,Vue將data中的陣列進行了原型鏈重寫,指向了自己定義的陣列原型方法,這樣當呼叫陣列api時,可以通知依賴更新,如果陣列中包含著參考型別,會對陣列中的參考型別再次遞回遍歷進行監控,這樣就實作了監測陣列變化,
1 // src/core/observer/array.js
2
3 // 獲取陣列的原型Array.prototype,上面有我們常用的陣列方法
4 const arrayProto = Array.prototype
5 // 創建一個空物件arrayMethods,并將arrayMethods的原型指向Array.prototype
6 export const arrayMethods = Object.create(arrayProto)
7
8 // 列出需要重寫的陣列方法名
9 const methodsToPatch = [
10 'push',
11 'pop',
12 'shift',
13 'unshift',
14 'splice',
15 'sort',
16 'reverse'
17 ]
18 // 遍歷上述陣列方法名,依次將上述重寫后的陣列方法添加到arrayMethods物件上
19 methodsToPatch.forEach(function (method) {
20 // 保存一份當前的方法名對應的陣列原始方法
21 const original = arrayProto[method]
22 // 將重寫后的方法定義到arrayMethods物件上,function mutator() {}就是重寫后的方法
23 def(arrayMethods, method, function mutator (...args) {
24 // 呼叫陣列原始方法,并傳入引數args,并將執行結果賦給result
25 const result = original.apply(this, args)
26 // 當陣列呼叫重寫后的方法時,this指向該陣列,當該陣列為回應式時,就可以獲取到其__ob__屬性
27 const ob = this.__ob__
28 let inserted
29 switch (method) {
30 case 'push':
31 case 'unshift':
32 inserted = args
33 break
34 case 'splice':
35 inserted = args.slice(2)
36 break
37 }
38 if (inserted) ob.observeArray(inserted)
39 // 將當前陣列的變更通知給其訂閱者
40 ob.dep.notify()
41 // 最后回傳執行結果result
42 return result
43 })
44 })
def就是通過Object.defineProperty重寫value,也就是自定義的幾個陣列方法
function def(obj,key,val,enumble){
Object.defineProperty(obj,key,{
enumble:!!enumble,
configrable:true,
writeble:true,
val:val
})
}
observe方法里面加入陣列的處理,
- 能獲取到
__proto__屬性,就把__protp__屬性指向重寫的方法 - 獲取不到
__proto__屬性,就把重寫的方法定義到物件上實體上
// src/core/observer/index.js
export class Observer {
...
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
...
}
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
有微信小程式課設、畢設需求聯系個人QQ:505417246
關注下面微信公眾號,可以領取微信小程式、Vue、TypeScript、前端、uni-app、全堆疊、Nodejs、Python等實戰學習資料
最新最全的前端知識總結和專案原始碼都會第一時間發布到微信公眾號,請大家多多關注,謝謝

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/259696.html
標籤:其他
