從設計模式理解Vue響應式(多圖警告)

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"序言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"近日公司開發一個拖拽表單項目,用到了 Vue,部門老大便開始研讀 Vue 源碼,並且傳授給我們,老大說,讀源碼不能僅僅只看懂源碼,還得讀懂他的設計思想,他爲什麼要這麼設計,把自己當做設計者來讀,這樣才能真正理解,本文中,我會按照老大的指引方向,和自己的理解,來談一談 Vue 響應式原理,及其設計思想","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"什麼是Vue響應式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"官方解釋: Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的 JavaScript 對象。而當你修改它們時,視圖會進行更新 ,簡單說就是數據發生改變視圖會做出相應的更新,視圖發生變化,例如 input 輸入,數據也會做出對應的變化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來看一個實例圖:圖片來源於阿寶哥的響應式原理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是一個Excel表格的加法表達式,我們會發現當加數發生改變,他的和就會會自動發生改變,不需要人爲操作讓他重新運算結果。在Vue中數據和視圖的關係,就像這裏的加數與和的關係。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/90/900fe7a55ad574fb412ad8b1ec09563a.gif","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼Vue響應式是如何實現的呢,想要知道的話我們就得了解一下 他的設計模式了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"響應式的設計模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Vue 響應式所使用的設計模式,是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"觀察者模式","attrs":{}},{"type":"text","text":",觀察者模式通俗的說就是。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如:郭老師每天上班帶一個橙子,我想在他喫橙子的時候蹭一口,但是我又不想一直盯着郭老師看他什麼時候喫,於是我和郭老師約定,你喫橙子的時候通知我。然後小郭老師看到了,也想要喫,就也和郭老師約定,郭老師喫橙子的時候也通知她,然後等郭老師喫橙子的時候,想起和他做好約定的我和小郭老師,於是發送通知,我和小郭老師收到消息,立馬就做出我想要做的事情(湊過去和他一起喫)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在編程中,這樣做可以省去了反覆檢索的資源消耗,也得到更高的反饋速度。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/37/377c2541198ea0255b586aa4cbcb0469.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"觀察對象 (Subject):擁有兩個必要標識,通知當前實例所擁有的觀察者的方法。給當前實例添加觀察者的方法,這樣你才知道到時候要通知誰。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"觀察者(Observer):擁有一個必要標識,通知實例更新狀態的方法。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聯繫方式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"觀察對象 (Subject) 通過自己內部的通知函數,調用所有觀察者列表中所有觀察者對應的回調函數,達到通知觀察者的目的。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"觀察者(Observer)通過調用觀察者對象(Subject)中的添加方法,把自己回調函數傳入他的觀察者列表中。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"響應式和觀察者模式的融合","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們先來看一下實現一個基礎的Vue需要哪些文件,我們以尤大的","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/yzycool/miniVue","title":null},"content":[{"type":"text","text":"miniVue","attrs":{}}]},{"type":"text","text":"作爲探討的demo。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f9/f9bf6daa63381cf5a0b48c2d9ff2ceb1.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們先來一一介紹一下這些文件分別做了什麼功能;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Vue 是事件總線文件,他會將對象描述文本中的 data 數據拿出來,然後通過 _proxyData 函數,將 data 所有數據進行劫持代理,方便後續屬性的訪問 。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"observer 文件,這裏的 walk 方法,通過遞歸遍歷 data 中的每一個屬性。然後在 defineReactive 中給每個屬性創建一個新的 Dep ,用於存儲自身的依賴(觀察者),Object.defineProperty 響應式的根本,如果是觸發的get,就把他的 Dep.target 添加到 Dep 列表,這一步也就是收集依賴,你取了我的值說明你對我感興趣,所以我把你添加進我的觀察者倆表,如果觸發set,說明數據發生改變,觸發dep中的notify,通知所有觀察者,有數據更新了,快行動。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"dep 文件是觀察者模式中的觀察對象(Subject),裏面擁有一個儲存觀察中的容器,和添加觀察者,通知觀察者的方法。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"compiler 文件是一個處理文件,專門用來處理解析指令,差值表達式,等等的集合。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"watcher 文件是觀察者模式中的觀察者,裏面有一個更新視圖的方法,和調用dep中添加觀察者的方法。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們看到,除了事件總線文件,和處理文件,就剩下3個文件了,這三個文件就是Vue響應式的根本了,我們看個關係圖","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/16/16aec4ab02ebf516c6b058ad143bc4e2.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"小結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以發現dep文件和watcher文件,就是一個觀察者模式的實現,然後observe文件是他們之間的橋樑,通過劫持get和set操作,告訴dep什麼時候應該添加觀察者,和通知觀察者,形成了自動化,當數據被讀取,創造觀察者和被觀察者的聯繫,當數據改變,通知被觀察者發送通知消息,如此一來就實現了響應式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Vue實例初始化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"響應式三步走源碼","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"首先是進入口文件 vue.js 。通過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"defineProperty","attrs":{}}],"attrs":{}},{"type":"text","text":" 完成了 Data 中所有數據的代理。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class Vue {\n constructor (options) {\n \n this.$options = options || {} // save options\n this.$el = typeof options.el === 'string' ? document.querySelector(options.el)\n :options.el // get dom\n this.$data = options.data // get data\n this.$methods = options.methods\n // 1.data 所有數據進行劫持代理\n this._proxyData(this.$data)\n // 2.調用observe對象,監聽數據變化\n new Observer(this.$data)\n // 3.調用compiler對象,解析指令和差值表達式\n new Compiler(this)\n }\n _proxyData (data) {\n // 遍歷所有data\n Object.keys(data).forEach(key => {\n // 將每一個data通過defineProperty進行劫持\n Object.defineProperty(this, key, {\n enumerable: true,\n configurable: true,\n get () {\n return data[key]\n },\n set (newValue) {\n if (data[key] === newValue) {\n return\n }\n data[key] = newValue\n }\n })\n })\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"2","normalizeStart":"2"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"然後進入 Observe.js ,遍歷所有數據,如果是get則收集依賴,如果是set則發送通知","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\nclass Observer {\n constructor(data) {\n this.walk(data)\n }\n walk (data) { // 循環執行data\n if (!data || typeof data !== 'object') {\n return\n }\n Object.keys(data).forEach(key => {\n this.defineReactive(data, key, data[key])\n })\n }\n defineReactive (obj, key, val) { \n let that = this\n this.walk(val) // 如果val是對象,則給他綁定get和set時觸發的方法\n let dep = new Dep() // 負責收集依賴,併發送通知\n Object.defineProperty(obj, key, {\n configurable: true,\n enumerable: true,\n get() {\n Dep.target && dep.addSub(Dep.target) // 收集依賴\n return val // 如果使用obj[key],會變成死循環\n },\n set(newValue) {\n if (newValue === val) {\n return\n }\n val = newValue\n that.walk(newValue) // 修改後可能是對象,set函數內部調用,修改了this指向\n dep.notify() // 發送通知\n }\n })\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小結:這個文件是響應式自動化的實現,使用了一個 walk 方法,通過遞歸遍歷 data 中的每一個屬性,然後在放進 defineReactive 中給每個對象創建一個新的 Dep ,用於存儲自身的依賴(觀察者),然後將對象的可枚舉和可寫屬性打開,並且定義一個 set 和 get 觸發時的方法。 如果是觸發的 get ,就把他的 Dep.target 添加到 Dep 列表,這一步也就是收集依賴,你取了我的說明你對我感興趣,所以我把你添加進我的觀察者列表。如果觸發 set ,說明數據發生改變,觸發 dep 中的 notify ,通知所有觀察者,有數據更新了,快行動,以此達成響應式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"3","normalizeStart":"3"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"然後進入 watcher.js 。這裏通過一個 Dep 類的靜態屬性 target ,用來記錄當前 watcher 對象,通過使用其中的屬性,觸發 get ,使自身依賴添加進觀察者列表,形成了觀察者模式,然後復原Dep.target,方便下一個Watcher使用。在接收到觀察對象的通知後,調用自身的更新方法,完成視圖更新。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class Watcher {\n constructor (vm, key, cb) {\n this.vm = vm\n // data中的屬性名稱\n this.key = key\n // 回調函數負責更新視圖\n this.cb = cb\n // 把watcher對象記錄到Dep類的靜態屬性target\n Dep.target = this\n // 觸發get方法,在get方法中會調用addSub\n this.oldValue = vm[key]\n Dep.target = null\n }\n // 當數據發生變化的時候通知視圖更新\n update () {\n let newValue = this.vm[this.key]\n if (this.oldValue === newValue) {\n return\n }\n this.cb(newValue)\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們最後來看一下初始化流程圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f4/f40c2858f73c8335d4b32b6e4951af79.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按照箭頭得知:首先初始化階段做了三個步驟:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"調用 vue.js 中的 _proxyData 進行數據劫持。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"進入 Observe.js ,創建 Dep(觀察目標),所有他擁有一個儲存觀察者列表的容器,和添加觀察者,通知觀察者的方法。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"創建 Watcher (觀察者) ,他是觀察者模式中的觀察者,所以他擁有一個通知更新的響應方法。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小結:這三個步驟是 Vue 響應式的核心,也是觀察者模式的實現,Watcher(觀察者)通過觸發 get ,將自身添加進觀察目標的觀察者列表。Dep 通過遍歷自身觀察者列表實現通知所有觀察者,從而實現響應式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"視圖渲染和視圖更新","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來看看 compiler.js 文件的主要內容","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"compile :編輯模板","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"compileElement :編譯元素節點","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"compileText :編譯文本節點,處理差值表達式","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// 解析 v-model\n modelUpdater (node, value, key) {\n node.value = value\n new Watcher(this.vm, key, (newValue) => { // 創建watcher對象,當數據改變更新視圖\n node.value = newValue\n })\n // 雙向綁定\n node.addEventListener('input', () => {\n this.vm[key] = node.value\n })\n } \n// 編譯模板\n compile (el) {\n let childNodes = el.childNodes\n Array.from(childNodes).forEach(node => {\n if (this.isTextNode(node)) { // 處理文本節點\n this.compileText(node)\n } else if(this.isElementNode(node)) { // 處理元素節點\n this.compileElement(node)\n }\n // 如果還有子節點,遞歸調用\n if (node.childNodes && node.childNodes.length > 0) {\n this.compile(node)\n }\n })\n }\n // 編譯元素節點,處理指令\n compileElement (node) {\n // console.log(node.attributes)\n if (node.attributes.length) {\n Array.from(node.attributes).forEach(attr => { // 遍歷所有元素節點\n let attrName = attr.name\n if (this.isDirective(attrName)) { // 判斷是否是指令\n attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2) \n // 獲取 v- 後面的值\n let key = attr.value // 獲取data名稱\n this.update(node, key, attrName)\n }\n })\n }\n }\n // 編譯文本節點,處理差值表達式\n compileText (node) {\n // 獲取 {{ }} 中的值\n // console.dir(node) // console.dir => 轉成對象形式\n let reg = /\\{\\{(.+?)\\}\\}/\n let value = node.textContent\n if (reg.test(value)) {\n let key = RegExp.$1.trim() // 返回匹配到的第一個字符串,去掉空格\n node.textContent = value.replace(reg, this.vm[key])\n new Watcher(this.vm, key, (newValue) => { // 創建watcher對象,當數據改變更新視圖\n node.textContent = newValue\n })\n }\n }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們再看一幅流程圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e7/e7a45dcecc43b606f8c58bf88e9b69e5.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小結:compile 把元素轉換爲數據模型,他是普通的 JavaScript 對象,我們這叫做 vnode 對象,然後遍歷 vnode 對象,根據標識分爲元素節點,文本節點,數據三個分類,分別進入不同的處理函數,並且創建一個 Watcher 對象,然後在 Watcher 對象中觸發 get 實現響應式,同步會進行 updata 更新數據,轉換成真實 dom ,完成頁面渲染,更新就是如此反覆。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"整體響應式運行流程圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/10/10210c6f88f1474cf503106c0851c169.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,我們根據這張流程圖進行一下知識回顧。首先是初始化三步走:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"通過 _proxyData 進行數據劫持,對 Data 進行代理,","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"創建 Dep(觀察目標)對象,","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"然後創建 Watcher (觀察者)對象,","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後開始渲染階段","attrs":{}}]},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"vm.render()觸發後進行模板編譯","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"complie(template)將元素編譯成 vnode 對象,遍歷該對象,創建 Watcher ,添加依賴完成響應式。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"updata 更新數據,然後轉化成真實 dom,完成渲染。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此, Vue響應式原理及其設計模式應該很清楚啦,如有疑問歡迎留言提出。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"歡迎想要一起學習進步的朋友,加入我的學習羣,大家可以在裏面討論一下進階技巧,分享自己最新學習的內容!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果覺得文章不錯,可以點個贊,給作者一點小小的鼓勵,謝謝,可以選擇加我微信好友,我來拉你們進羣。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b8/b8ddcf1220eb2dacf4ec4b3a839bce84.png","alt":"微信二維碼.png","title":"微信二維碼.png","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章