近日公司開發一個拖拽表單項目,用到了 Vue,部門老大便開始研讀 Vue 源碼,而且傳授給咱們,老大說,讀源碼不能僅僅只看懂源碼,還得讀懂他的設計思想,他爲何要這麼設計,把本身當作設計者來讀,這樣才能真正理解,本文中,我會按照老大的指引方向,和本身的理解,來談一談 Vue 響應式原理,及其設計思想javascript
官方解釋: Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的 JavaScript 對象。而當你修改它們時,視圖會進行更新 ,簡單說就是數據發生改變視圖會作出相應的更新,視圖發生變化。vue
例如 input 輸入,數據也會作出對應的變化。java
咱們來看一個響應式的實例圖,這是一個Excel表格的加法表達式,咱們會發現當加數發生改變,他的和就會會自動發生改變,不須要人爲操做讓他從新運算結果。在Vue中數據和視圖的關係,就像這裏的加數與和的關係。 那麼Vue響應式是如何實現的呢,想要知道的話咱們就得了解一下 他的設計模式了。node
Vue 響應式所使用的設計模式,是觀察者模式,觀察者模式通俗的說就是。git
例如:郭老師天天上班帶一個橙子,我想在他吃橙子的時候蹭一口,可是我又不想一直盯着郭老師看他何時吃,因而我和郭老師約定,你吃橙子的時候通知我。而後小郭老師看到了,也想要吃,就也和郭老師約定,郭老師吃橙子的時候也通知她,而後等郭老師吃橙子的時候,想起和他作好約定的我和小郭老師,因而發送通知,我和小郭老師收到消息,立馬就作出我想要作的事情(湊過去和他一塊兒吃)。github
在編程中,這樣作能夠省去了反覆檢索的資源消耗,也獲得更高的反饋速度。web
聯繫方式:編程
咱們先來看一下實現一個基礎的Vue須要哪些文件,咱們以尤大的miniVue做爲探討的demo。 設計模式
咱們先來一一介紹一下這些文件分別作了什麼功能;微信
咱們看到,除了事件總線文件,和處理文件,就剩下3個文件了,這三個文件就是Vue響應式的根本了,咱們看個關係圖
咱們能夠發現dep文件和watcher文件,就是一個觀察者模式的實現,而後observe文件是他們之間的橋樑,經過劫持get和set操做,告訴dep何時應該添加觀察者,和通知觀察者,造成了自動化,當數據被讀取,創造觀察者和被觀察者的聯繫,當數據改變,通知被觀察者發送通知消息,如此一來就實現了響應式。
defineProperty
完成了 Data 中全部數據的代理。class Vue {
constructor (options) {
this.$options = options || {} // save options
this.$el = typeof options.el === 'string' ? document.querySelector(options.el)
:options.el // get dom
this.$data = options.data // get data
this.$methods = options.methods
// 1.data 全部數據進行劫持代理
this._proxyData(this.$data)
// 2.調用observe對象,監聽數據變化
new Observer(this.$data)
// 3.調用compiler對象,解析指令和差值表達式
new Compiler(this)
}
_proxyData (data) {
// 遍歷全部data
Object.keys(data).forEach(key => {
// 將每個data經過defineProperty進行劫持
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get () {
return data[key]
},
set (newValue) {
if (data[key] === newValue) {
return
}
data[key] = newValue
}
})
})
}
}
複製代碼
class Observer {
constructor(data) {
this.walk(data)
}
walk (data) { // 循環執行data
if (!data || typeof data !== 'object') {
return
}
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive (obj, key, val) {
let that = this
this.walk(val) // 若是val是對象,則給他綁定get和set時觸發的方法
let dep = new Dep() // 負責收集依賴,併發送通知
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get() {
Dep.target && dep.addSub(Dep.target) // 收集依賴
return val // 若是使用obj[key],會變成死循環
},
set(newValue) {
if (newValue === val) {
return
}
val = newValue
that.walk(newValue) // 修改後多是對象,set函數內部調用,修改了this指向
dep.notify() // 發送通知
}
})
}
}
複製代碼
小結:這個文件是響應式自動化的實現,使用了一個 walk 方法,經過遞歸遍歷 data 中的每個屬性,而後在放進 defineReactive 中給每一個對象建立一個新的 Dep ,用於存儲自身的依賴(觀察者),而後將對象的可枚舉和可寫屬性打開,而且定義一個 set 和 get 觸發時的方法。
若是是觸發的 get ,就把他的 Dep.target 添加到 Dep 列表,這一步也就是收集依賴,你取了個人說明你對我感興趣,因此我把你添加進個人觀察者列表。若是觸發 set ,說明數據發生改變,觸發 dep 中的 notify ,通知全部觀察者,有數據更新了,快行動,以此達成響應式。
class Watcher {
constructor (vm, key, cb) {
this.vm = vm
// data中的屬性名稱
this.key = key
// 回調函數負責更新視圖
this.cb = cb
// 把watcher對象記錄到Dep類的靜態屬性target
Dep.target = this
// 觸發get方法,在get方法中會調用addSub
this.oldValue = vm[key]
Dep.target = null
}
// 當數據發生變化的時候通知視圖更新
update () {
let newValue = this.vm[this.key]
if (this.oldValue === newValue) {
return
}
this.cb(newValue)
}
}
複製代碼
咱們最後來看一下初始化流程圖
按照箭頭得知:首先初始化階段作了三個步驟:
小結:這三個步驟是 Vue 響應式的核心,也是觀察者模式的實現,Watcher(觀察者)經過觸發 get ,將自身添加進觀察目標的觀察者列表。Dep 經過遍歷自身觀察者列表實現通知全部觀察者,從而實現響應式。
咱們來看看 compiler.js 文件的主要內容
// 解析 v-model
modelUpdater (node, value, key) {
node.value = value
new Watcher(this.vm, key, (newValue) => { // 建立watcher對象,當數據改變動新視圖
node.value = newValue
})
// 雙向綁定
node.addEventListener('input', () => {
this.vm[key] = node.value
})
}
// 編譯模板
compile (el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
if (this.isTextNode(node)) { // 處理文本節點
this.compileText(node)
} else if(this.isElementNode(node)) { // 處理元素節點
this.compileElement(node)
}
// 若是還有子節點,遞歸調用
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node)
}
})
}
// 編譯元素節點,處理指令
compileElement (node) {
// console.log(node.attributes)
if (node.attributes.length) {
Array.from(node.attributes).forEach(attr => { // 遍歷全部元素節點
let attrName = attr.name
if (this.isDirective(attrName)) { // 判斷是不是指令
attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2) // 獲取 v- 後面的值
let key = attr.value // 獲取data名稱
this.update(node, key, attrName)
}
})
}
}
// 編譯文本節點,處理差值表達式
compileText (node) {
// 獲取 {{ }} 中的值
// console.dir(node) // console.dir => 轉成對象形式
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim() // 返回匹配到的第一個字符串,去掉空格
node.textContent = value.replace(reg, this.vm[key])
new Watcher(this.vm, key, (newValue) => { // 建立watcher對象,當數據改變動新視圖
node.textContent = newValue
})
}
}
複製代碼
咱們再看一幅流程圖
小結:compile 把元素轉換爲數據模型,他是普通的 JavaScript 對象,咱們這叫作 vnode 對象,而後遍歷 vnode 對象,根據標識分爲元素節點,文本節點,數據三個分類,分別進入不一樣的處理函數,而且建立一個 Watcher 對象,而後在 Watcher 對象中觸發 get 實現響應式,同步會進行 updata 更新數據,轉換成真實 dom ,完成頁面渲染,更新就是如此反覆。
最後,咱們根據這張流程圖進行一下知識回顧。 首先是初始化三步走:
而後開始渲染階段
至此, Vue響應式原理及其設計模式應該很清楚啦,若有疑問歡迎留言提出。
歡迎想要一塊兒學習進步的朋友,加入個人學習羣,你們能夠在裏面討論一下進階技巧,分享本身最新學習的內容!
若是以爲文章不錯,能夠點個贊,給做者一點小小的鼓勵,謝謝,能夠選擇加我微信好友,我來拉大家進羣。