Vue響應(yīng)式原理初探

一筹我、前言

“響應(yīng)式”是 Vue 的一大特點(diǎn),當(dāng)我們?cè)诟鶎?shí)例的 data 中定義了數(shù)據(jù)后,在模板中使用 {{}} 的語法便可以使用其中的數(shù)據(jù)了九孩,且在之后的編碼中我們無需關(guān)心其視圖層面的表現(xiàn)名段,可以將精力集中處理業(yè)務(wù)邏輯與數(shù)據(jù)阱扬。因?yàn)?Vue 會(huì)代替我們監(jiān)聽數(shù)據(jù)的變化,并在視圖層同步我們更新的數(shù)據(jù)伸辟。那 Vue 是如何實(shí)現(xiàn)對(duì)數(shù)據(jù)的監(jiān)聽與使用的呢麻惶?例如對(duì)象的屬性,數(shù)組的項(xiàng)信夫。

二窃蹋、數(shù)據(jù)偵測(cè)

(一)偵測(cè) Object

1. Object.defineProperty(obj, prop, descriptor)

該方法用于設(shè)置對(duì)象屬性,其接受 3 個(gè)參數(shù)静稻,第一個(gè)參數(shù)是需要定義的對(duì)象警没,第二個(gè)是需要定義的屬性,第三個(gè)則被稱為描述符振湾。JS 中的對(duì)象身上有兩種描述符杀迹,第一種是數(shù)據(jù)描述符,具有以下鍵值:

  1. configurable押搪,用于描述數(shù)據(jù)的可配置性树酪,是否可通過 delete 操作符刪除,默認(rèn)值為 false大州;
  2. enumerable续语,表示該屬性是否可枚舉,即可通過 for...in 訪問其屬性摧茴,默認(rèn)值為 false绵载;
  3. value,表示該屬性的值苛白,可以是任何有效的 JS 值娃豹,默認(rèn)值為 undefined;
  4. writable购裙,表示該屬性的值是否可寫懂版,默認(rèn)值為 false,當(dāng)值為 true 時(shí)其值才可被賦值運(yùn)算符改變躏率。

第二種是訪問器描述符躯畴,具有以下鍵值:

  1. configurable民鼓,用于描述數(shù)據(jù)的可配置性,是否可通過 delete 操作符刪除蓬抄,默認(rèn)值為 false丰嘉;
  2. enumerable,表示該屬性是否可枚舉嚷缭,即可通過 for...in 訪問其屬性饮亏,默認(rèn)值為 false;
  3. getter阅爽,在讀取屬性時(shí)調(diào)用的函數(shù)路幸,默認(rèn)值為 undefined;
  4. setter付翁,在寫入屬性時(shí)調(diào)用的函數(shù)简肴,默認(rèn)值為 undefined。

這兩種描述符不可以同時(shí)使用百侧。同時(shí)砰识,請(qǐng)注意 getter 與 setter 兩個(gè)方法,一個(gè)是讀取對(duì)象屬性時(shí)調(diào)用移层,一個(gè)是寫入時(shí)調(diào)用仍翰。這意味著對(duì)象的屬性是可“偵測(cè)”的。

2. 數(shù)據(jù)響應(yīng)

現(xiàn)在观话,對(duì)象屬性已經(jīng)是可以偵測(cè)的了予借,但實(shí)現(xiàn)視圖層的同步更新是一個(gè)問題。
其實(shí)频蛔,數(shù)據(jù)雖多灵迫,但只需在使用數(shù)據(jù)的位置進(jìn)行更新即可。前面也說到晦溪,getter 是一個(gè)屬性被讀取時(shí)調(diào)用的函數(shù)瀑粥,通過它,Vue 知道了誰讀取了該屬性三圆,順即將其存在getter中狞换。同樣的,誰觸發(fā)了 setter 方法舟肉,由 setter 去通知依賴了該屬性的元素更新就好修噪。但Vue中的實(shí)現(xiàn)并非這么簡(jiǎn)單,Vue 建立了一個(gè) Watcher路媚,其代替 getter 行使收集那些依賴了數(shù)據(jù)的元素的職能黄琼,誰使用了數(shù)據(jù),便為其建立一個(gè) watcher 實(shí)例整慎,當(dāng)數(shù)據(jù)更新時(shí)脏款,由這個(gè) watcher 通知元素更新數(shù)據(jù)围苫。但這個(gè)通知不是直接通知,Vue是通過虛擬 Dom 實(shí)現(xiàn)真實(shí) Dom 的更新撤师,這個(gè) watcher 通知渲染函數(shù)剂府,再由渲染函數(shù)操作虛擬 Dom 實(shí)現(xiàn)視圖的更新。

3. 屬性的使用

Vue 無法檢測(cè) property 的添加或移除剃盾。由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì) property 執(zhí)行 getter周循、setter 轉(zhuǎn)化,所以 property 必須在 data 對(duì)象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的万俗。

(二)偵測(cè) Array

1. 利用對(duì)象的 getter 與 setter

Array 類型并不同于 Object 類型,并沒有 getter 與 setter,其并不能使用這兩種方法饮怯。但巧妙的是闰歪,data 里 return 的就是一個(gè)對(duì)象,數(shù)組存放在其中蓖墅,恰作為 data 的一個(gè)屬性库倘,誰讀取它,便可以被偵測(cè)到了论矾。但這僅僅是偵測(cè)到 array 的使用者教翩,array 的自身的變化如何偵測(cè)?

2. 操作即變化

對(duì)于數(shù)組而言贪壳,數(shù)組的變化便表明使用者調(diào)用了數(shù)組方法饱亿,例如 push(),pop() 等闰靴。明確了這個(gè)概念彪笼,Vue 便可以實(shí)現(xiàn) Array 的偵測(cè)。其將可以改變數(shù)組的方法(push蚂且,pop配猫,shift,unshift杏死,splice泵肄,sort,reverse)進(jìn)行了從新封裝淑翼,在不改變這些方法的原始功能的基礎(chǔ)上安插了偵測(cè)器(攔截器)腐巢,一旦調(diào)用這些方法,意味著數(shù)組的改變窒舟,這些偵聽器便會(huì)通知Vue數(shù)組發(fā)生了變化系忙。通過實(shí)現(xiàn)一個(gè)存儲(chǔ)那些使用了數(shù)組的依賴存儲(chǔ)器,再實(shí)現(xiàn)一個(gè)訪問通知這些依賴的方法惠豺,便實(shí)現(xiàn)了數(shù)組的偵測(cè)银还。

3. 彌補(bǔ)不足

由于數(shù)組的檢測(cè)通過重寫數(shù)組方法實(shí)現(xiàn)风宁,所以 Vue 并不能直接通過索引操作數(shù)組,也不能使用其 length 屬性操作數(shù)組蛹疯。于是 Vue 實(shí)現(xiàn)了兩個(gè) API戒财。以下兩個(gè)方法實(shí)現(xiàn)第一種功能:

1. Vue.set(vm.items, indexOfItem, newValue)或vm.$set(vm.items, indexOfItem, newValue)
2. vm.items.splice(indexOfItem, 1, newValue)

以下方法實(shí)現(xiàn)第二種功能:

1. vm.items.splice(newLength)

這兩個(gè)方法有效彌補(bǔ)了上述不足。

二捺弦、聲明響應(yīng)式property

由于 Vue 不允許動(dòng)態(tài)添加根級(jí)響應(yīng)式 property饮寞,所以你必須在初始化實(shí)例前聲明所有根級(jí)響應(yīng)式 property,哪怕只是一個(gè)空值:

var vm = new Vue({
  data: {
    // 聲明 message 為一個(gè)空值字符串
    message: ''
  },
  template: '<div>{{ message }}</div>'
})
// 之后設(shè)置 `message`
vm.message = 'Hello!'

如果你未在 data 選項(xiàng)中聲明 message列吼,Vue 將警告你渲染函數(shù)正在試圖訪問不存在的 property幽崩。

三、異步更新隊(duì)列

Vue 在觀察數(shù)據(jù)變化時(shí)并不是直接更新 DOM寞钥,而是開啟一個(gè)隊(duì)列慌申,并緩存同一個(gè)事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。在緩存時(shí)會(huì)除去重復(fù)數(shù)據(jù)理郑,從而避免不必要的計(jì)算和 DOM 操作蹄溉。然后,在下一個(gè)事件循環(huán) tick 中您炉,Vue 刷新隊(duì)列并執(zhí)行實(shí)際(已去重)的工作柒爵。
Vue 這種去重機(jī)制減少了開銷,如果一個(gè)for循環(huán)來動(dòng)態(tài)改變數(shù)據(jù) 100 次赚爵,其實(shí)它只會(huì)應(yīng)用最后一次改變棉胀,如果沒有這種機(jī)制,DOM 就要重繪 100 次冀膝。Vue會(huì)根據(jù)當(dāng)前瀏覽器環(huán)境優(yōu)先使用原生的Promise.then 和MutationObserve膏蚓。如果都不支持就會(huì)采用setTimeout代替。為了在數(shù)據(jù)變化之后等待 Vue 完成更新 DOM畸写,可以在數(shù)據(jù)變化之后立即使用 Vue.nextTick(callback)驮瞧。這樣回調(diào)函數(shù)將在 DOM 更新完成后被調(diào)用。
說人話就是 Vue 中的 Dom 更新并不是立即執(zhí)行的枯芬,在觀察數(shù)據(jù)變化后會(huì)將 Dom 更新存放起來论笔,并對(duì)其進(jìn)行一些去重工作,比如某個(gè)數(shù)據(jù)被循環(huán)了千所,不會(huì)直接循環(huán)N次狂魔,而是存起來一次性更新完以實(shí)現(xiàn)更高的性能。也就是說數(shù)據(jù)雖然已經(jīng)改變淫痰,但Dom的更新不是實(shí)時(shí)的最楷,要想針對(duì)Dom做一些操作,就得用到 Vue.nextTick(callback) 方法,在其回調(diào)函數(shù)里實(shí)現(xiàn)自己想要進(jìn)行的操作籽孙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末烈评,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子犯建,更是在濱河造成了極大的恐慌讲冠,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件适瓦,死亡現(xiàn)場(chǎng)離奇詭異竿开,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)玻熙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門否彩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嗦随,你說我怎么就攤上這事胳搞。” “怎么了称杨?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)筷转。 經(jīng)常有香客問我姑原,道長(zhǎng),這世上最難降的妖魔是什么呜舒? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任锭汛,我火速辦了婚禮,結(jié)果婚禮上袭蝗,老公的妹妹穿的比我還像新娘唤殴。我一直安慰自己,他們只是感情好到腥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布朵逝。 她就那樣靜靜地躺著,像睡著了一般乡范。 火紅的嫁衣襯著肌膚如雪配名。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天晋辆,我揣著相機(jī)與錄音渠脉,去河邊找鬼。 笑死瓶佳,一個(gè)胖子當(dāng)著我的面吹牛芋膘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼为朋,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼臂拓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起潜腻,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤埃儿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后融涣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體童番,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年威鹿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了剃斧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忽你。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幼东,死狀恐怖科雳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糟秘,我是刑警寧澤简逮,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站尿赚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏凌净。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一冰寻、第九天 我趴在偏房一處隱蔽的房頂上張望须教。 院中可真熱鬧,春花似錦斩芭、人聲如沸没卸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迁筛。三九已至煤蚌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尉桩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工翰苫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人这橙。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓屈扎,卻偏偏與公主長(zhǎng)得像埃唯,于是被迫代替她去往敵國(guó)和親鹰晨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容