接下來(lái)重點(diǎn)來(lái)看Vue的數(shù)據(jù)響應(yīng)系統(tǒng)。我看很多文章在講數(shù)據(jù)響應(yīng)的時(shí)候先用一個(gè)簡(jiǎn)單的例子介紹了數(shù)據(jù)雙向綁定的思路,然后再看源碼。這里也借鑒了這種方式,感覺這樣的確更有利于理解。
數(shù)據(jù)雙向綁定的思路
1. 對(duì)象
先來(lái)看元素是對(duì)象的情況。假設(shè)我們有一個(gè)對(duì)象和一個(gè)監(jiān)測(cè)方法:
const data = { a: 1};/*** exp[String, Function]: 被觀測(cè)的字段* fn[Function]: 被觀測(cè)對(duì)象改變后執(zhí)行的方法*/function watch (exp, fn) {}我們可以調(diào)用watch方法,當(dāng)a的值改變后打印一句話:
watch('a', () => { console.log('a 改變了')})要實(shí)現(xiàn)這個(gè)功能,我們首先要能知道屬性a被修改了。這時(shí)候就需要使用Object.defineProperty函數(shù)把屬性a變成訪問(wèn)器屬性:
Object.defineProperty(data, 'a', { set () { console.log('設(shè)置了 a') }, get () { console.log('讀取了 a') }})這樣當(dāng)我們修改a的值:data.a = 2時(shí),就會(huì)打印出設(shè)置了 a, 當(dāng)我們獲取a的值時(shí):data.a, 就會(huì)打印出讀取了 a.
在屬性的讀取和設(shè)置中我們已經(jīng)能夠進(jìn)行攔截并做一些操作了。可是在屬性修改時(shí)我們并不想總打印設(shè)置了 a這句話,而是有一個(gè)監(jiān)聽方法watch,不同的屬性有不同的操作,對(duì)同一個(gè)屬性也可能監(jiān)聽多次。
這就需要一個(gè)容器,把對(duì)同一個(gè)屬性的監(jiān)聽依賴收集起來(lái),在屬性改變時(shí)再取出依次觸發(fā)。既然是在屬性改變時(shí)觸發(fā)依賴,我們就可以放在setter里面,在getter中收集依賴。這里我們先不考慮依賴被重復(fù)收集等一些情況
const dep = [];Object.defineProperty(data, 'a', { set () { dep.forEach(fn => fn()); }, get () { dep.push(fn); }})我們定義了容器dep, 在讀取a屬性時(shí)觸發(fā)get函數(shù)把依賴存入dep中;在設(shè)置a屬性時(shí)觸發(fā)set函數(shù)把容器內(nèi)的依賴挨個(gè)執(zhí)行。
那fn從何而來(lái)呢?再看一些我們的監(jiān)測(cè)函數(shù)watch
watch('a', () => { console.log('a 改變了')})該函數(shù)有兩個(gè)參數(shù),第一個(gè)是被觀測(cè)的字段,第二個(gè)是被觀測(cè)字段的值改變后需要觸發(fā)的操作。其實(shí)第二個(gè)參數(shù)就是我們要收集的依賴fn。
const data = { a: 1};const dep = [];Object.defineProperty(data, 'a', { set () { dep.forEach(fn => fn()); }, get () { // Target就是該變量的依賴函數(shù) dep.push(Target); }})let Target = null;function watch (exp, fn) { // 將fn賦值給Target Target = fn; // 讀取屬性,觸發(fā)get函數(shù),收集依賴 data[exp];}現(xiàn)在僅能夠觀測(cè)a一個(gè)屬性,為了能夠觀測(cè)對(duì)象data上面的所有屬性,我們將定義訪問(wèn)器屬性的那段代碼封裝一下:
新聞熱點(diǎn)
疑難解答
圖片精選