摘要:無論是雙向綁定還是單向綁定,都是符合思想的。看了的源碼后不難發(fā)現(xiàn)的雙向綁定的實現(xiàn)也就是在表單元素上添加了事件,可以說雙向綁定是單向綁定的一個語法糖。
前言
本文會帶大家手動實現(xiàn)一個雙向綁定過程(僅僅涵蓋一些簡單的指令解析,如:v-text,v-model,插值),當然借鑒的是Vue1的源碼,相信大家在閱讀完本文后對Vue1會有一個更好的理解,源代碼放到了github,由于本人水平有限,理解不到位的地方還請大家指出。
MVVMMVVM使開發(fā)可以更加關(guān)注于數(shù)據(jù),減少了很大的工作量,也使代碼可讀性,可維護性更高,MVVM核心的思想就是視圖是狀態(tài)的函數(shù):View = ViewModel(Model),所以當Model發(fā)生改變時,ViewModel會來操作View來怎么做,而非是自己寫代碼來做。無論是雙向綁定還是單向綁定,都是符合MVVM思想的。Vue提倡的是雙向綁定,也就是允許View到Model的變化,其實這個場景出現(xiàn)在的也就是表單操作上,看個例子,例子中分別利用了Vue和React實現(xiàn)了一下表單value變化,影響頁面與其相關(guān)的dom節(jié)點發(fā)生變化,可以發(fā)現(xiàn)的是雙向綁定的Vue是input的value發(fā)生變化則h1的innerText就發(fā)生了變化,變化是由View->Model,而提倡單向數(shù)據(jù)流的React需要手動監(jiān)聽事件,事件觸發(fā)后,更改Model的值,從而使input的value發(fā)生了變化??戳薞ue的源碼后不難發(fā)現(xiàn)Vue的雙向綁定的實現(xiàn)也就是在表單元素上添加了input事件,可以說雙向綁定是單向綁定的一個語法糖。
實現(xiàn)思路上圖是一個大體的流程,下面按照流程來實現(xiàn)下:
利用observer對data進行了監(jiān)聽,并且提供訂閱某個數(shù)據(jù)項的變化的能力
這點的實現(xiàn),需要借助的是Object.defineProperty()來為對象的屬性綁定get/set特性(由于利用了Object.defineProperty(),所以Vue不支持ie8),observer需要將data的所有屬性都綁定get/set,很容易想到的就是利用遞歸來實現(xiàn),具體代碼就不貼出,請參見這里。
利用Compile對模板進行解析
這點實現(xiàn)的是將我們的模板轉(zhuǎn)化為html,過程中會將數(shù)據(jù)與View中的節(jié)點相關(guān)聯(lián)起來,最終會將編譯好的html頁面替換到頁面上。首先來看解析,首先從根節(jié)點開始,根據(jù)不同的節(jié)點類型采用不同的解析方式:
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; } }
對于文本節(jié)點來說,可能存在情況只有兩種:
與數(shù)據(jù)不相關(guān)不用操作
含有插值,需要與數(shù)據(jù)進行關(guān)聯(lián)
{{}}文本插值
{{{}}}純html插值
利用下面正就可以將插值找出:
/{{{(.*?)}}}|{{(.*?)}}/g
采用下面函數(shù)來對文本節(jié)點的內(nèi)容解析:
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,里面存儲了每一個塊內(nèi)容,一個插值or一個普通文本,tag來標記是否為插值,html來標記是否為純html插值。遍歷返回的tokens,根據(jù)不同的類型,來采用不同的方式將其添加到其父節(jié)點上:
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是一個指令集合,也就是決定了如何初始化以及如何更新該節(jié)點。對于nodeType為1的節(jié)點來說,指令全部存儲在其屬性中,遍歷屬性,假若指令中含有v-html,v-model,v-text,則停止遍歷其子樹,直接將調(diào)用相應(yīng)指令即可,否則,則需要遍歷其子節(jié)點,對其子節(jié)點應(yīng)用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中還會做的就是將數(shù)據(jù)與View的dom節(jié)點相關(guān)聯(lián),利用的就是Dep與Watcher,頁面上每一個與數(shù)據(jù)相關(guān)聯(lián)的節(jié)點都含有一個Watcher,當數(shù)據(jù)發(fā)生變化是Watcher用于計算,是否需要更新該節(jié)點;數(shù)據(jù)的每一個屬性都有一個Dep,當該屬性發(fā)生變化時,Dep會通知與該數(shù)據(jù)相關(guān)聯(lián)的Watcher來進行計算是否需要更新對應(yīng)頁面。Dep代碼,Watcher代碼。
異步更新隊列
異步更新隊列,是一個優(yōu)化,將更新dom的操作變?yōu)楫惒降模诺较乱粋€事件循環(huán)來做,這樣做可以減少不必要的dom更新,看下面情況:
vm.value++; vm.value++; vm.value++;
三次數(shù)據(jù)改變,假若同步更新的話,則每次數(shù)據(jù)改變會立即更新dom,而異步更新的話,可以先將更新推入一個隊列中,由于是異步,也可以保證每一個Watcher只被推入到一次,這樣就避免了不必要的更新,異步更新主要利用的是nextTick,這個函數(shù)會優(yōu)先使用Promise,不兼容則利用MutationObserver,再不兼容的話會利用setTimeout。
寫在后面看過了Vue的源碼不得不感嘆Vue的優(yōu)美,而Vue2又增加了虛擬dom,這樣就可以做到服務(wù)端渲染,給了我們更多的可能!
這篇博客最好配合著源碼來看,關(guān)于源碼歡迎star
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/91287.html
摘要:原文博客地址如何理解如何實現(xiàn)是否解讀過的源碼與框架的區(qū)別實現(xiàn)實現(xiàn)獨立初始化實例兩者的區(qū)別數(shù)據(jù)和視圖的分離,解耦開放封閉原則,對擴展開放,對修改封閉在中在代碼中操作視圖和數(shù)據(jù),混在一塊了以數(shù)據(jù)驅(qū)動視圖,只關(guān)心數(shù)據(jù)變化, 原文博客地址:https://finget.github.io/2018/05/31/mvvm-vue/ MVVM 如何理解 MVVM 如何實現(xiàn) MVVM 是否解讀過 ...
摘要:所以無需太過介懷是實現(xiàn)的單向或雙向綁定。監(jiān)聽數(shù)據(jù)綁定更新函數(shù)的處理是在這個方法中,通過添加回調(diào)來接收數(shù)據(jù)變化的通知至此,一個簡單的就完成了,完整代碼。 本文能幫你做什么?1、了解vue的雙向數(shù)據(jù)綁定原理以及核心代碼模塊2、緩解好奇心的同時了解如何實現(xiàn)雙向綁定為了便于說明原理與實現(xiàn),本文相關(guān)代碼主要摘自vue源碼, 并進行了簡化改造,相對較簡陋,并未考慮到數(shù)組的處理、數(shù)據(jù)的循環(huán)依賴等,也...
摘要:當我們的視圖和數(shù)據(jù)任何一方發(fā)生變化的時候,我們希望能夠通知對方也更新,這就是所謂的數(shù)據(jù)雙向綁定。返回值返回傳入函數(shù)的對象,即第一個參數(shù)該方法重點是描述,對象里目前存在的屬性描述符有兩種主要形式數(shù)據(jù)描述符和存取描述符。 前言 談起當前前端最熱門的 js 框架,必少不了 Vue、React、Angular,對于大多數(shù)人來說,我們更多的是在使用框架,對于框架解決痛點背后使用的基本原理往往關(guān)注...
摘要:接下來要看看這個訂閱者的具體實現(xiàn)了實現(xiàn)訂閱者作為和之間通信的橋梁,主要做的事情是在自身實例化時往屬性訂閱器里面添加自己自身必須有一個方法待屬性變動通知時,能調(diào)用自身的方法,并觸發(fā)中綁定的回調(diào),則功成身退。 本文能幫你做什么?1、了解vue的雙向數(shù)據(jù)綁定原理以及核心代碼模塊2、緩解好奇心的同時了解如何實現(xiàn)雙向綁定為了便于說明原理與實現(xiàn),本文相關(guān)代碼主要摘自vue源碼, 并進行了簡化改造,...
閱讀 3286·2021-11-24 09:38
閱讀 2158·2021-11-23 09:51
閱讀 1750·2021-10-13 09:39
閱讀 2624·2021-09-23 11:53
閱讀 1408·2021-09-02 15:40
閱讀 3660·2019-08-30 15:54
閱讀 1135·2019-08-30 13:04
閱讀 2566·2019-08-30 11:01