摘要:無論是雙向綁定還是單向綁定,都是符合思想的。看了的源碼后不難發現的雙向綁定的實現也就是在表單元素上添加了事件,可以說雙向綁定是單向綁定的一個語法糖。
前言
本文會帶大家手動實現一個雙向綁定過程(僅僅涵蓋一些簡單的指令解析,如:v-text,v-model,插值),當然借鑒的是Vue1的源碼,相信大家在閱讀完本文后對Vue1會有一個更好的理解,源代碼放到了github,由于本人水平有限,理解不到位的地方還請大家指出。
MVVMMVVM使開發可以更加關注于數據,減少了很大的工作量,也使代碼可讀性,可維護性更高,MVVM核心的思想就是視圖是狀態的函數:View = ViewModel(Model),所以當Model發生改變時,ViewModel會來操作View來怎么做,而非是自己寫代碼來做。無論是雙向綁定還是單向綁定,都是符合MVVM思想的。Vue提倡的是雙向綁定,也就是允許View到Model的變化,其實這個場景出現在的也就是表單操作上,看個例子,例子中分別利用了Vue和React實現了一下表單value變化,影響頁面與其相關的dom節點發生變化,可以發現的是雙向綁定的Vue是input的value發生變化則h1的innerText就發生了變化,變化是由View->Model,而提倡單向數據流的React需要手動監聽事件,事件觸發后,更改Model的值,從而使input的value發生了變化。看了Vue的源碼后不難發現Vue的雙向綁定的實現也就是在表單元素上添加了input事件,可以說雙向綁定是單向綁定的一個語法糖。
實現思路上圖是一個大體的流程,下面按照流程來實現下:
利用observer對data進行了監聽,并且提供訂閱某個數據項的變化的能力
這點的實現,需要借助的是Object.defineProperty()來為對象的屬性綁定get/set特性(由于利用了Object.defineProperty(),所以Vue不支持ie8),observer需要將data的所有屬性都綁定get/set,很容易想到的就是利用遞歸來實現,具體代碼就不貼出,請參見這里。
利用Compile對模板進行解析
這點實現的是將我們的模板轉化為html,過程中會將數據與View中的節點相關聯起來,最終會將編譯好的html頁面替換到頁面上。首先來看解析,首先從根節點開始,根據不同的節點類型采用不同的解析方式:
function compileNode(node, vm) { const type = node.nodeType; if (type === 1 && !isScript(node)) { compileElement(node, vm); } else if (type === 3 && node.data.trim()) { compileTextNode(node, vm); } else { return null; } }
對于文本節點來說,可能存在情況只有兩種:
與數據不相關不用操作
含有插值,需要與數據進行關聯
{{}}文本插值
{{{}}}純html插值
利用下面正就可以將插值找出:
/{{{(.*?)}}}|{{(.*?)}}/g
采用下面函數來對文本節點的內容解析:
function parseText(node) { var text = node.wholeText; if (!tagRE.test(text)) { return void 0; } const tokens = []; var lastIndex = tagRE.lastIndex = 0, match, index, html, value; while (match = tagRE.exec(text)) { index = match.index; if (index > lastIndex) { tokens.push({ value: text.slice(lastIndex, index) }) } html = htmlRE.test(match[0]); value = html ? match[1] : match[2]; tokens.push({ value: value, tag: true, html: html }); lastIndex = index + match[0].length; } if (lastIndex < text.length) { tokens.push({ value: text.slice(lastIndex) }) } return tokens; }
返回了tokens,里面存儲了每一個塊內容,一個插值or一個普通文本,tag來標記是否為插值,html來標記是否為純html插值。遍歷返回的tokens,根據不同的類型,來采用不同的方式將其添加到其父節點上:
function compileTextNode(node, vm) { const tokens = parseText(node); if (tokens == null) return void 0; var frag = document.createDocumentFragment(); tokens.forEach(token => { var el; if (token.tag) { if (token.html) { el = document.createDocumentFragment(); el.$parent = node.parentNode; el.$oneTime = true; dirCollection["html"](el, vm, token.value); } else { el = document.createTextNode(" "); dirCollection["text"](el, vm, token.value); } } else { el = document.createTextNode(token.value); } el && frag.appendChild(el); }); return replace(node, frag); }
dirCollection是一個指令集合,也就是決定了如何初始化以及如何更新該節點。對于nodeType為1的節點來說,指令全部存儲在其屬性中,遍歷屬性,假若指令中含有v-html,v-model,v-text,則停止遍歷其子樹,直接將調用相應指令即可,否則,則需要遍歷其子節點,對其子節點應用compileNode進行解析:
function compileNodeList(nodes, vm) { for (let val of nodes) { compileNode(val, vm); } } function compileElement(node, vm) { var flag = false; const attrs = Array.prototype.slice.call(node.attributes); attrs.forEach((val) => { const name = val.name, value = val.value; if (dirRE.test(name)) { var dir; // 事件指令 if ( (dir = name.match(eventRE)) && (dir = dir[1]) ) { dirCollection["eventDir"](node, dir, vm, value); } else { dir = name.match(dirRE)[1]; dirCollection[dir](node, vm, value); } // 指令中為v-html or v-text or v-model終止遞歸 flag = flag || name === vhtml || name === vtext; node.removeAttribute(name); } }); const childs = node.childNodes; if (!flag && childs && childs.length) { compileNodeList(childs, vm); } }
在dirCollections中還會做的就是將數據與View的dom節點相關聯,利用的就是Dep與Watcher,頁面上每一個與數據相關聯的節點都含有一個Watcher,當數據發生變化是Watcher用于計算,是否需要更新該節點;數據的每一個屬性都有一個Dep,當該屬性發生變化時,Dep會通知與該數據相關聯的Watcher來進行計算是否需要更新對應頁面。Dep代碼,Watcher代碼。
異步更新隊列
異步更新隊列,是一個優化,將更新dom的操作變為異步的,放到下一個事件循環來做,這樣做可以減少不必要的dom更新,看下面情況:
vm.value++; vm.value++; vm.value++;
三次數據改變,假若同步更新的話,則每次數據改變會立即更新dom,而異步更新的話,可以先將更新推入一個隊列中,由于是異步,也可以保證每一個Watcher只被推入到一次,這樣就避免了不必要的更新,異步更新主要利用的是nextTick,這個函數會優先使用Promise,不兼容則利用MutationObserver,再不兼容的話會利用setTimeout。
寫在后面看過了Vue的源碼不得不感嘆Vue的優美,而Vue2又增加了虛擬dom,這樣就可以做到服務端渲染,給了我們更多的可能!
這篇博客最好配合著源碼來看,關于源碼歡迎star
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91287.html
摘要:原文博客地址如何理解如何實現是否解讀過的源碼與框架的區別實現實現獨立初始化實例兩者的區別數據和視圖的分離,解耦開放封閉原則,對擴展開放,對修改封閉在中在代碼中操作視圖和數據,混在一塊了以數據驅動視圖,只關心數據變化, 原文博客地址:https://finget.github.io/2018/05/31/mvvm-vue/ MVVM 如何理解 MVVM 如何實現 MVVM 是否解讀過 ...
摘要:所以無需太過介懷是實現的單向或雙向綁定。監聽數據綁定更新函數的處理是在這個方法中,通過添加回調來接收數據變化的通知至此,一個簡單的就完成了,完整代碼。 本文能幫你做什么?1、了解vue的雙向數據綁定原理以及核心代碼模塊2、緩解好奇心的同時了解如何實現雙向綁定為了便于說明原理與實現,本文相關代碼主要摘自vue源碼, 并進行了簡化改造,相對較簡陋,并未考慮到數組的處理、數據的循環依賴等,也...
摘要:當我們的視圖和數據任何一方發生變化的時候,我們希望能夠通知對方也更新,這就是所謂的數據雙向綁定。返回值返回傳入函數的對象,即第一個參數該方法重點是描述,對象里目前存在的屬性描述符有兩種主要形式數據描述符和存取描述符。 前言 談起當前前端最熱門的 js 框架,必少不了 Vue、React、Angular,對于大多數人來說,我們更多的是在使用框架,對于框架解決痛點背后使用的基本原理往往關注...
摘要:接下來要看看這個訂閱者的具體實現了實現訂閱者作為和之間通信的橋梁,主要做的事情是在自身實例化時往屬性訂閱器里面添加自己自身必須有一個方法待屬性變動通知時,能調用自身的方法,并觸發中綁定的回調,則功成身退。 本文能幫你做什么?1、了解vue的雙向數據綁定原理以及核心代碼模塊2、緩解好奇心的同時了解如何實現雙向綁定為了便于說明原理與實現,本文相關代碼主要摘自vue源碼, 并進行了簡化改造,...
閱讀 3267·2021-11-24 09:38
閱讀 2148·2021-11-23 09:51
閱讀 1738·2021-10-13 09:39
閱讀 2610·2021-09-23 11:53
閱讀 1394·2021-09-02 15:40
閱讀 3648·2019-08-30 15:54
閱讀 1121·2019-08-30 13:04
閱讀 2552·2019-08-30 11:01