一根暑、什么是響應(yīng)式?
在了解什么是響應(yīng)式之前我們現(xiàn)來看一段代碼演示
let x;
let y;
let f = n => n * 100
x = 1;
y = f(x);
console.log(y); // 100
x = 2;
y = f(x);
console.log(y); // 200
x = 3;
y = f(x);
console.log(y); // 300
代碼示例中徙邻,變量y依賴變量x進(jìn)行求值排嫌,但是我們會(huì)發(fā)現(xiàn)每一次變量x重新賦值時(shí)都要手動(dòng)對(duì)y進(jìn)行求值,存在大量的重復(fù)模板缰犁,因此淳地,指導(dǎo)我們進(jìn)行程序設(shè)計(jì)的DRY原則就發(fā)揮價(jià)值了
DRY 全稱:Don't Repeat Yourself (摘自wikipedia)怖糊,是指編程過程中不寫重復(fù)代碼,將能夠公共的部分抽象出來颇象,封裝成工具類或者用“abstraction”類來抽象公有的東西蓬抄,降低代碼的耦合性,這樣不僅提高代碼的靈活性夯到、健壯性以及可讀性嚷缭,也方便后期的維護(hù)或者修改。
那么我們需要有一個(gè)方法耍贾,實(shí)現(xiàn)自動(dòng)監(jiān)聽x的變化并且自動(dòng)對(duì)y進(jìn)行求值阅爽,以減少重復(fù)代碼
假設(shè)我們有一個(gè)onXChange函數(shù),使得每次x重新賦值時(shí)都會(huì)觸發(fā)onXChange中的回調(diào)函數(shù)荐开,你的代碼看起來應(yīng)該像下面這樣:
let x;
let y;
let onXChange = function(cb) {
// ...
}
onXChange(() => {
y = f(x);
console.log(y);
})
x = 1; // 100
x = 2; // 200
x = 3; // 300
如果將y換成dom模板付翁,根據(jù)x的變化自動(dòng)渲染不同的模板也是同理。
現(xiàn)在我們可以來解釋什么是響應(yīng)式了(其實(shí)都不用我解釋晃听,看到這你自己也有答案了)百侧,響應(yīng)式只是一種編程方式,它的目的是為了簡(jiǎn)化編程能扒,特點(diǎn)是自動(dòng)對(duì)變化進(jìn)行響應(yīng)佣渴。
以下是摘自wikipedia的解釋:
響應(yīng)式(Reactive Programming)
是一種面向數(shù)據(jù)流和變化傳播的編程范式。這意味著可以在編程語(yǔ)言中很方便的表達(dá)靜態(tài)或動(dòng)態(tài)的數(shù)據(jù)流初斑,而相關(guān)的計(jì)算模型會(huì)自動(dòng)將變化的值通過數(shù)據(jù)流進(jìn)行傳播辛润。
二、Vue中的響應(yīng)式分析
我們來看看Vue中是怎么實(shí)現(xiàn)響應(yīng)式的
首先第一步需要監(jiān)聽數(shù)據(jù)變化见秤,知道變量什么時(shí)候進(jìn)行了修改砂竖,JS提供的能夠監(jiān)聽數(shù)據(jù)變化的API有Object.defineProperty以及ES6新增的Proxy,本節(jié)我們只探討Object.defineProperty
關(guān)于Object.defineProperty的使用如果你還不了解的話請(qǐng)閱讀如下文檔:
Vue2.0中使用了Object.defineProperty來遍歷data中的數(shù)據(jù)鹃答,在getter中將使用到這個(gè)數(shù)據(jù)的上下文進(jìn)行收集乎澄,這個(gè)過程稱之為【依賴收集】,在setter中修改這個(gè)數(shù)據(jù)時(shí)則會(huì)觸發(fā)【通知依賴更新】的操作测摔,如下圖所示:
什么是依賴收集置济?
所謂依賴收集,就是把一個(gè)數(shù)據(jù)用到的地方收集起來避咆,在這個(gè)數(shù)據(jù)發(fā)生改變的時(shí)候舟肉,統(tǒng)一去通知各個(gè)地方做對(duì)應(yīng)的操作。
為什么這里需要依賴收集查库?
考慮到一個(gè)變量的修改可能會(huì)引起多處變化路媚,因此需要將依賴這個(gè)變量的所有地方都收集起來,等到變量更新時(shí)再進(jìn)行批量操作樊销。
Vue關(guān)于響應(yīng)式原理的介紹官網(wǎng)已經(jīng)說的很清楚整慎,這里貼出鏈接不再贅述:
https://cn.vuejs.org/v2/guide/reactivity.html#ad
三脏款、實(shí)現(xiàn)一個(gè)簡(jiǎn)單的數(shù)據(jù)響應(yīng)
以上面的代碼為例,我們需要通過onXChange函數(shù)來監(jiān)聽x的修改裤园,由于上面代碼中x的值是基礎(chǔ)類型撤师,我們需要將x更改為引用類型才可以使用Object.defineProperty,因此我們可以創(chuàng)建一個(gè)函數(shù)來做這件事情拧揽,假設(shè)這個(gè)函數(shù)為ref
ref函數(shù)接收一個(gè)初始值剃盾,函數(shù)內(nèi)閉包一個(gè)value變量賦值為傳入的初始值,通過Object.defineProperty返回一個(gè)帶有value屬性的對(duì)象淤袜,在get中返回value痒谴,并在set中將value賦值,這樣在我們修改了x.value之后會(huì)自動(dòng)觸發(fā)set來更新閉包的value變量
let ref = initValue => {
let value = initValue;
return Object.defineProperty({}, 'value', {
get() {
return value;
},
set(newValue) {
value = newValue;
}
})
}
對(duì)應(yīng)的代碼也需要修改一下:
- 修改x為ref函數(shù)調(diào)用的返回值
- 將對(duì)應(yīng)的x賦值更改為x.value賦值
let x;
let y;
let f = n => n * 100
let onXChange = function(cb) {
// ...
}
let ref = initValue => {
let value = initValue;
return Object.defineProperty({}, 'value', {
get() {
return value;
},
set(newValue) {
value = newValue;
}
})
}
// 創(chuàng)建x對(duì)象铡羡,初始value傳入1
x = ref(1);
// 監(jiān)聽x
onXChange(() => {
y = f(x.value);
console.log(y);
})
x.value = 2;
x.value = 3;
到這一步已經(jīng)可以自動(dòng)獲取到x.value改變后的值了积蔚,我們可以在set方法中打印newValue
set(newValue) {
console.log('x: ', newValue)
value = newValue;
}
既然已經(jīng)監(jiān)聽到x.value的修改了,接下來我們只需要拿到onXChange中的回調(diào)函數(shù)烦周,在set方法中調(diào)用它就可以同步修改y的值
怎么拿這個(gè)回調(diào)函數(shù)尽爆?
我們可以創(chuàng)建一個(gè)變量將這個(gè)回調(diào)函數(shù)存起來,假設(shè)變量名為active读慎,然后在set方法中調(diào)用active函數(shù)即可
// 省略部分代碼...
// 創(chuàng)建active變量
let active;
let onXChange = function(cb) {
// 將回調(diào)賦值給active
active = cb;
}
let ref = initValue => {
let value = initValue;
return Object.defineProperty({}, 'value', {
get() {
return value;
},
set(newValue) {
value = newValue;
active(); // 調(diào)用active函數(shù)
}
})
}
可以看到y(tǒng)的打印結(jié)果出來了漱贱,但少了x.value初始為1時(shí)的結(jié)果,我們還需要在初始的時(shí)候調(diào)用一次active
// 執(zhí)行onXChange時(shí)就調(diào)用一次回調(diào)函數(shù)
let onXChange = function(cb) {
active = cb;
active();
active = null; // 銷毀active贪壳,避免修改x.value時(shí)重復(fù)添加依賴
}
四饱亿、結(jié)合Vue源碼來看響應(yīng)式
到這里一個(gè)簡(jiǎn)單的響應(yīng)式其實(shí)已經(jīng)完成了,但還不夠闰靴,如果我們不僅有onXChange,還有onYChange钻注、onZChange呢蚂且,這些函數(shù)都依賴了x變量怎么辦?
Vue的解決辦法是通過一個(gè)Dep對(duì)象將這些依賴都收集起來幅恋,在變量發(fā)生改變時(shí)進(jìn)行批量通知更新杏死。
那么Dep對(duì)象至少應(yīng)該具有一個(gè)存儲(chǔ)依賴的列表、一個(gè)添加依賴的方法和一個(gè)通知依賴更新的方法
我們先來簡(jiǎn)單實(shí)現(xiàn)一下捆交,再結(jié)合Vue源碼驗(yàn)證
Dep代碼如下:
class Dep {
deps = new Set();
// 收集依賴
depend() {
if (active) {
this.deps.add(active);
}
}
// 批量更新
// 將所有的依賴都執(zhí)行一遍
notify() {
this.deps.forEach(dep => dep());
}
}
然后我們需要在ref函數(shù)中獲取dep實(shí)例淑翼,在get時(shí)調(diào)用dep.depend()添加依賴,在set時(shí)調(diào)用dep.notify來通知依賴更新
let ref = (initValue) => {
let value = initValue;
// 獲取dep實(shí)例
let dep = new Dep();
return Object.defineProperty({}, "value", {
get() {
// 添加依賴
dep.depend();
return value;
},
set(newValue) {
value = newValue;
// 通知依賴更新
dep.notify();
},
});
};
現(xiàn)在來驗(yàn)證一下品追,我們添加任意個(gè)依賴x變量的函數(shù):
let onYChange = function(cb) {
active = cb;
active();
}
onYChange(() => {
console.log('onYChange', f(x.value));
})
// ......
可以看到所有依賴x變量的地方都打印了結(jié)果玄括,一切都沒有問題。
那么Vue的源碼是不是這么實(shí)現(xiàn)的呢肉瓦?
以vue2.6.11版本為例:
defineReactive$$1函數(shù)的作用就是通過Object.defineProperty來將普通的數(shù)據(jù)處理成響應(yīng)式數(shù)據(jù)遭京,完整代碼如下:
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
去除源碼中影響閱讀的代碼:
function defineReactive$$1 (
obj,
key,
val,
) {
var dep = new Dep();
// 省略部分代碼...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
if (Dep.target) {
dep.depend();
}
return value
},
set: function reactiveSetter (newVal) {
val = newVal;
dep.notify();
}
});
}
與我們自己實(shí)現(xiàn)的ref函數(shù)對(duì)比一下胃惜,你Get到了嗎?
再看源碼中的Dep是怎么實(shí)現(xiàn)的:
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (!config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
先不用管別的代碼哪雕,至少我們?cè)贒ep源碼中找到了一個(gè)存儲(chǔ)依賴的列表subs船殉、添加依賴的方法depend、通知依賴更新的方法notify斯嚎,看到這里我想你對(duì)Vue的響應(yīng)式原理已經(jīng)有自己的理解了利虫。
那么我們?cè)賮砜偨Y(jié)一下Vue的響應(yīng)式原理:
- 將data中的數(shù)據(jù)通過Object.defineProperty處理成響應(yīng)式數(shù)據(jù)
- 數(shù)據(jù)被【讀】的時(shí)候會(huì)觸發(fā)getter,將使用到這個(gè)數(shù)據(jù)的上下文進(jìn)行依賴收集堡僻,存放到Dep類中
- 數(shù)據(jù)被【寫】的時(shí)候會(huì)觸發(fā)setter糠惫,調(diào)用Dep.notify方法通知依賴更新
不對(duì)的地方請(qǐng)指正,但不要批評(píng)我苦始,不聽哈哈哈寞钥!以上演示代碼已上傳github:
https://github.com/Mr-Jemp/VueStudy/blob/main/vue-reactive-demo/src/assets/js/demo2.js
后面要學(xué)習(xí)的內(nèi)容在這里:
Vue—關(guān)于響應(yīng)式(二、異步更新隊(duì)列原理分析)
Vue—關(guān)于響應(yīng)式(三陌选、Diff Patch原理分析)
Vue—關(guān)于響應(yīng)式(四理郑、深入學(xué)習(xí)Vue響應(yīng)式源碼)
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!