關於MVVM前端框架你們都有了解,或多或少的使用過,好比Angular,React,VUE等等。那麼你是否也想本身手寫一個MVVM的前端框架呢,咱們從Virtual DOM入手,手把手教你寫基於Virtual DOM的前端框架,在整個編寫的過程當中,但願你們學習更多,理解更多。
Github代碼: https://github.com/chalecao/v...前端
真實的DOM是網頁上的文檔對象模型,由一個個HTML元素節點構成的樹形結構。node
如圖中所示,咱們用JS建立出來的節點就是虛擬節點,Virtual node,固然由這些虛擬節點vd構成的樹形結構就稱爲虛擬DOM,Virtual DOM。咱們本節課介紹的就是要如何建立這樣的虛擬DOM。git
首先咱們須要分析一個node節點的構成,好比他的節點類型type,節點屬性的集合props,子元素的集合。這樣咱們就能夠抽象一個數據模型來表示這個節點。虛擬DOM是由許多虛擬節點按照層級結構組合起來的,那麼咱們實現虛擬節點的數據模型抽象以後,就能夠構建虛擬DOM的數據模型抽象。github
手工實現DOM模型構建不太合理,咱們能夠藉助JSX的工具來完成這個轉換。本節咱們以rollup打包工具結合babel轉換插件實現數據的抽象。具體代碼配置參考:github中package.json配置和rollup.config.js算法
const vdom = ( <div id="_Q5" style="border: 1px solid red;"> <div style="text-align: center; margin: 36px auto 18px; width: 160px; line-height: 0;"> <img src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png" height="56" style="border: none; margin: 8px 0px;"></img> hello </div> </div>)
上面咱們定義的vdom片斷採用JSX處理器處理後以下面代碼:json
/* fed123.com */ 'use strict'; var vdom = vnode( "div", { id: "_Q5", style: "border: 1px solid red;" }, vnode( "div", { style: "text-align: center; margin: 36px auto 18px; width: 160px; line-height: 0;" }, vnode("img", { src: "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png", height: "56", style: "border: none; margin: 8px 0px;" }), "google" ) );
是否是很好理解,JSX編譯後會自動根據定義好的語法格式提取出元素的類型和屬性和子元素,並填入vnode方法中,咱們只須要實現vnode方法就能夠。咱們能夠編寫vnode方法用於構建虛擬節點的模型,編寫createElement方法用於根據vnode模型建立元素。而且把vnode的子元素追加到父元素上,造成樹形層級結構。前端框架
function vnode(type, props, ...children) { return { type, props, children }; } function createElement(node) { if (typeof node === 'string') { return document.createTextNode(node); } const $el = document.createElement(node.type); node.children .map(createElement) .forEach($el.appendChild.bind($el)); return $el; } document.body.appendChild(createElement(vdom));
這樣咱們就完成了虛擬節點vnode和虛擬vDOM的構建。babel
如圖展現了最簡單的一層DOM的結構變化,無非也就這麼幾種:增長元素節點、修改節點,刪除節點。咱們能夠基於DOM API來實現這些基本的操做,代碼以下:app
function updateElement($parent, newnode, oldnode) { var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; if (!newnode) { $parent.removeChild($parent.childNodes[index]); } else if (!oldnode) { $parent.appendChild(createElement(newnode)); } else if (isChange(newnode, oldnode)) { $parent.replaceChild(createElement(newnode), $parent.childNodes[index]); } else if (newnode.type) { var newL = newnode.children.length; var oldL = oldnode.children.length; for (var i = 0; i < newL || i < oldL; i++) { updateElement($parent.childNodes[index], newnode.children[i], oldnode.children[i], i); } } }
上面的代碼中咱們其實是把diff VirtualDOM 和update vdom放在一塊兒處理了,採用了深度優先遍歷的算法,從根節點優先查到子節點,判斷子節點是否變化,有變化就進行變動處理,而後再回到上級節點。框架
{ type: 「div」, props: {「style」: 」…」}, children: [ {type: 「img」, props: {「src」: 」…」} ]}
上面咱們抽取的vnode的模型中已經把props拿出來了,咱們這裏須要把這些樣式設置到對應元素上就行了。咱們先看下元素的屬性變化有哪幾種狀況:
如上,元素屬性能夠增長能夠減小,咱們經過DOM API實現屬性的更新操做,代碼以下:
//handle props function setProp($el, name, value) { if (typeof value == "boolean") { handleBoolProp($el, name, value); } else { $el.setAttribute(name, value); } } function handleBoolProp($el, name, value) { if (!!value) { $el.setAttribute(name, value); $el[name] = !!value; } else { $el[name] = !!value; } } function removeProp($el, name, value) { if (typeof value == "boolean") { $el[name] = false; } $el.removeAttribute(name, value); } function updateProp($el, name, newvalue, oldValue) { if (!newvalue) { removeProp($el, name, oldValue); } else if (!oldValue || newvalue != oldValue) { setProp($el, name, newvalue); } } function updateProps($el) { var newprops = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var oldProps = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var _props = Object.assign({}, newprops, oldProps); Object.keys(_props).forEach(function (key) { updateProp($el, key, newprops[key], oldProps[key]); }); }