![](https://static.vue-js.com/cef7dcc0-3ac9-11eb-85f6-6fac77c0c9b3.png)
## 一饺蚊、什么是雙向綁定
我們先從單向綁定切入單向綁定非常簡(jiǎn)單砾淌,就是把`Model`綁定到`View`,當(dāng)我們用`JavaScript`代碼更新`Model`時(shí)顾稀,`View`就會(huì)自動(dòng)更新雙向綁定就很容易聯(lián)想到了,在單向綁定的基礎(chǔ)上坝撑,用戶更新了`View`静秆,`Model`的數(shù)據(jù)也自動(dòng)被更新了,這種情況就是雙向綁定舉個(gè)栗子
?![](https://static.vue-js.com/d65738d0-3ac9-11eb-ab90-d9ae814b240d.png)
當(dāng)用戶填寫表單時(shí)巡李,`View`的狀態(tài)就被更新了抚笔,如果此時(shí)可以自動(dòng)更新`Model`的狀態(tài),那就相當(dāng)于我們把`Model`和`View`做了雙向綁定關(guān)系圖如下
?![](https://static.vue-js.com/dcc1d4a0-3ac9-11eb-ab90-d9ae814b240d.png)
## 二侨拦、雙向綁定的原理是什么
我們都知道 `Vue` 是數(shù)據(jù)雙向綁定的框架殊橙,雙向綁定由三個(gè)重要部分構(gòu)成
- 數(shù)據(jù)層(Model):應(yīng)用的數(shù)據(jù)及業(yè)務(wù)邏輯
- 視圖層(View):應(yīng)用的展示效果,各類UI組件
- 業(yè)務(wù)邏輯層(ViewModel):框架封裝的核心狱从,它負(fù)責(zé)將數(shù)據(jù)與視圖關(guān)聯(lián)起來
而上面的這個(gè)分層的架構(gòu)方案膨蛮,可以用一個(gè)專業(yè)術(shù)語(yǔ)進(jìn)行稱呼:`MVVM`這里的控制層的核心功能便是 “數(shù)據(jù)雙向綁定” 。自然季研,我們只需弄懂它是什么敞葛,便可以進(jìn)一步了解數(shù)據(jù)綁定的原理
### 理解ViewModel
它的主要職責(zé)就是:
- 數(shù)據(jù)變化后更新視圖
- 視圖變化后更新數(shù)據(jù)
當(dāng)然,它還有兩個(gè)主要部分組成
- 監(jiān)聽器(Observer):對(duì)所有數(shù)據(jù)的屬性進(jìn)行監(jiān)聽
- 解析器(Compiler):對(duì)每個(gè)元素節(jié)點(diǎn)的指令進(jìn)行掃描跟解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)
### 三训貌、實(shí)現(xiàn)雙向綁定
我們還是以`Vue`為例制肮,先來看看`Vue`中的雙向綁定流程是什么的
1. ?`new Vue()`首先執(zhí)行初始化冒窍,對(duì)`data`執(zhí)行響應(yīng)化處理,這個(gè)過程發(fā)生`Observe`中
2. ?同時(shí)對(duì)模板執(zhí)行編譯豺鼻,找到其中動(dòng)態(tài)綁定的數(shù)據(jù)综液,從`data`中獲取并初始化視圖,這個(gè)過程發(fā)生在`Compile`中
3. ?同時(shí)定義?個(gè)更新函數(shù)和`Watcher`儒飒,將來對(duì)應(yīng)數(shù)據(jù)變化時(shí)`Watcher`會(huì)調(diào)用更新函數(shù)
4. ?由于`data`的某個(gè)`key`在?個(gè)視圖中可能出現(xiàn)多次谬莹,所以每個(gè)`key`都需要?個(gè)管家`Dep`來管理多個(gè)`Watcher`
5. ?將來data中數(shù)據(jù)?旦發(fā)生變化,會(huì)首先找到對(duì)應(yīng)的`Dep`桩了,通知所有`Watcher`執(zhí)行更新函數(shù)
流程圖如下:
?![](https://static.vue-js.com/e5369850-3ac9-11eb-85f6-6fac77c0c9b3.png)
### 實(shí)現(xiàn)
先來一個(gè)構(gòu)造函數(shù):執(zhí)行初始化附帽,對(duì)`data`執(zhí)行響應(yīng)化處理
```js
class?Vue?{ ?
??constructor(options)?{ ?
????this.$options?=?options; ?
????this.$data?=?options.data; ?
????//?對(duì)data選項(xiàng)做響應(yīng)式處理 ?
????observe(this.$data); ?
????//?代理data到vm上 ?
????proxy(this); ?
????//?執(zhí)行編譯 ?
????new?Compile(options.el,?this); ?
??} ?
} ?
```
對(duì)`data`選項(xiàng)執(zhí)行響應(yīng)化具體操作
```js
function?observe(obj)?{ ?
??if?(typeof?obj?!==?"object"?||?obj?==?null)?{ ?
????return; ?
??} ?
??new?Observer(obj); ?
} ?
class?Observer?{ ?
??constructor(value)?{ ?
????this.value?=?value; ?
????this.walk(value); ?
??} ?
??walk(obj)?{ ?
????Object.keys(obj).forEach((key)?=>?{ ?
??????defineReactive(obj,?key,?obj[key]); ?
????}); ?
??} ?
} ?
```
#### 編譯`Compile`
對(duì)每個(gè)元素節(jié)點(diǎn)的指令進(jìn)行掃描跟解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)
?![](https://static.vue-js.com/f27e19c0-3ac9-11eb-85f6-6fac77c0c9b3.png)
```js
class?Compile?{ ?
??constructor(el,?vm)?{ ?
????this.$vm?=?vm; ?
????this.$el?=?document.querySelector(el);??//?獲取dom ?
????if?(this.$el)?{ ?
??????this.compile(this.$el); ?
????} ?
??} ?
??compile(el)?{ ?
????const?childNodes?=?el.childNodes;? ?
????Array.from(childNodes).forEach((node)?=>?{?//?遍歷子元素 ?
??????if?(this.isElement(node))?{???//?判斷是否為節(jié)點(diǎn) ?
????????console.log("編譯元素"?+?node.nodeName); ?
??????}?else?if?(this.isInterpolation(node))?{ ?
????????console.log("編譯插值?本"?+?node.textContent);??//?判斷是否為插值文本?{{}} ?
??????} ?
??????if?(node.childNodes?&&?node.childNodes.length?>?0)?{??//?判斷是否有子元素 ?
????????this.compile(node);??//?對(duì)子元素進(jìn)行遞歸遍歷 ?
??????} ?
????}); ?
??} ?
??isElement(node)?{ ?
????return?node.nodeType?==?1; ?
??} ?
??isInterpolation(node)?{ ?
????return?node.nodeType?==?3?&&?/\{\{(.*)\}\}/.test(node.textContent); ?
??} ?
} ?
```
#### 依賴收集
視圖中會(huì)用到`data`中某`key`,這稱為依賴井誉。同?個(gè)`key`可能出現(xiàn)多次蕉扮,每次都需要收集出來用?個(gè)`Watcher`來維護(hù)它們,此過程稱為依賴收集多個(gè)`Watcher`需要?個(gè)`Dep`來管理颗圣,需要更新時(shí)由`Dep`統(tǒng)?通知
?![](https://static.vue-js.com/fa191f40-3ac9-11eb-ab90-d9ae814b240d.png)
實(shí)現(xiàn)思路
?1. `defineReactive`時(shí)為每?個(gè)`key`創(chuàng)建?個(gè)`Dep`實(shí)例
?2. 初始化視圖時(shí)讀取某個(gè)`key`喳钟,例如`name1`,創(chuàng)建?個(gè)`watcher1`
?3. 由于觸發(fā)`name1`的`getter`方法在岂,便將`watcher1`添加到`name1`對(duì)應(yīng)的Dep中
?4. 當(dāng)`name1`更新奔则,`setter`觸發(fā)時(shí),便可通過對(duì)應(yīng)`Dep`通知其管理所有`Watcher`更新
```js
//?負(fù)責(zé)更新視圖 ?
class?Watcher?{ ?
??constructor(vm,?key,?updater)?{ ?
????this.vm?=?vm ?
????this.key?=?key ?
????this.updaterFn?=?updater ?
????//?創(chuàng)建實(shí)例時(shí)蔽午,把當(dāng)前實(shí)例指定到Dep.target靜態(tài)屬性上 ?
????Dep.target?=?this ?
????//?讀一下key易茬,觸發(fā)get ?
????vm[key] ?
????//?置空 ?
????Dep.target?=?null ?
??} ?
??//?未來執(zhí)行dom更新函數(shù),由dep調(diào)用的 ?
??update()?{ ?
????this.updaterFn.call(this.vm,?this.vm[this.key]) ?
??} ?
} ?
```
聲明`Dep`
```js
class?Dep?{ ?
??constructor()?{ ?
????this.deps?=?[];??//?依賴管理 ?
??} ?
??addDep(dep)?{ ?
????this.deps.push(dep); ?
??} ?
??notify()?{? ?
????this.deps.forEach((dep)?=>?dep.update()); ?
??} ?
} ?
```
創(chuàng)建`watcher`時(shí)觸發(fā)`getter`
```js
class?Watcher?{ ?
??constructor(vm,?key,?updateFn)?{ ?
????Dep.target?=?this; ?
????this.vm[this.key]; ?
????Dep.target?=?null; ?
??} ?
} ?
```
依賴收集及老,創(chuàng)建`Dep`實(shí)例
```js
function?defineReactive(obj,?key,?val)?{ ?
??this.observe(val); ?
??const?dep?=?new?Dep(); ?
??Object.defineProperty(obj,?key,?{ ?
????get()?{ ?
??????Dep.target?&&?dep.addDep(Dep.target);//?Dep.target也就是Watcher實(shí)例 ?
??????return?val; ?
????}, ?
????set(newVal)?{ ?
??????if?(newVal?===?val)?return; ?
??????dep.notify();?//?通知dep執(zhí)行更新方法 ?
????}, ?
??}); ?
} ?
```
## 參考文獻(xiàn)
- https://www.liaoxuefeng.com/wiki/1022910821149312/1109527162256416
- https://juejin.cn/post/6844903942254510087#heading-9
面試官VUE系列總進(jìn)度:3/33
[面試官:說說你對(duì)vue的理解\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484101&idx=1&sn=83b0983f0fca7d7c556e4cb0bff8c9b8&chksm=fc10c093cb674985ef3bd2966f66fc28c5eb70b0037e4be1af4bf54fb6fa9571985abd31d52f&scene=21#wechat_redirect) ?
[面試官:說說你對(duì)SPA(單頁(yè)應(yīng)用)的理解\?](http://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247484119&idx=1&sn=d171b28a00d42549d279498944a98519&chksm=fc10c081cb6749976814aaeda6a6433db418223cec57edda7e15b9e5a0ca69ad549655639c61&scene=21#wechat_redirect)
![](https://static.vue-js.com/821b87b0-3ac6-11eb-ab90-d9ae814b240d.png)