給后端團隊封裝定制一套jQuery UI組件,然后他們直接嵌數(shù)據(jù)渲染頁面。可能你會想,前后分離為什么不讓前端直接寫頁面接數(shù)據(jù),我也很迷惑啊....說干就干。
先封裝個基礎的modal彈框組件:

彈框
HTML代碼:
<div class="ahm-modal-base-con">
<div class="ahm-modal-base-cover"></div>
<div class="ahm-modal-base-wrap">
<div class="ahm-modal-base-top">
<span class="ahm-modal-base-title">標題</span>
<span class="ahm-modal-base-icon"><i class="iconfont icon-close"></i></span>
</div>
<div class="ahm-modal-base-center">
內(nèi)容
</div>
<div class="ahm-modal-base-bottom">
<div class="modal-base-bottom-btn-wrap">
<button class="btn btn-site">確定</button>
<button class="btn">取消</button>
</div>
</div>
</div>
</div>
用js把它封裝下
// 先創(chuàng)建外層的div
dom = document.createElement('div')
dom.classList.add('ahm-modal-base-con')
document.querySelector('body').appendChild(dom)
// 遮罩
let coverDom = document.createElement('div')
coverDom.classList.add('ahm-modal-base-cover')
dom.appendChild(coverDom)
// 主布局部分
let baseDom = document.createElement('div')
baseDom.classList.add('ahm-modal-base-wrap')
dom.appendChild(baseDom)
// 省去下面代碼....
一層一層的寫下去,這代碼有點多啊,我們得寫個方法讓它們?nèi)プ约轰秩旧蒬om。
實現(xiàn)思路:布局好的div字符串生成真實dom ? 將真實dom轉(zhuǎn)為虛擬dom ? 對虛擬dom數(shù)據(jù)進行處理 ? 將虛擬dom轉(zhuǎn)為真實dom ? 渲染頁面 ? 操作虛擬dom數(shù)據(jù)更新頁面。
將dom字符串處理生成真實dom
// dom字符串
let domStr = `<div class="ahm-modal-base-con">
<div class="ahm-modal-base-cover" style="z-index: 1001;"></div>
<div class="ahm-modal-base-wrap" style="z-index: 1002; width: 600px;">
<div class="ahm-modal-base-top">
<span class="ahm-modal-base-title">標題</span>
<span class="ahm-modal-base-icon"><i class="iconfont icon-close"></i></span>
</div>
<div class="ahm-modal-base-center" ahm-ondata = 'maskData'></div>
<div class="ahm-modal-base-bottom">
<div class="ahm-modal-base-btn-wrap">
<button class="btn btn-site" ahm-onclick = 'onOk'>確定</button>
<button class="btn" ahm-onclick = 'onCancel'>取消</button>
</div>
</div>
</div>
</div>`
let objEle = document.createElement('div')
objEle.innerHTML = domStr
let domDir = objEle.childNodes[0] // 獲得真實dom
let vnode = getVnode(domDir) // 轉(zhuǎn)虛擬dom
真實dom轉(zhuǎn)為虛擬dom方法
// 真實dom轉(zhuǎn)虛擬dom
let __KEY = 0
function getVnode(domDir, parentVnode) {
let vnode = null
__KEY += 1
if (domDir.nodeType == 1) {
vnode = {
nodeName: '',
attr: {},
value: '',
_key: __KEY,
children: [],
}
vnode['nodeName'] = domDir.localName
for (let h of domDir.attributes) {
vnode['attr'][h.name] = h.value
}
if (domDir.childNodes) {
for (let k of domDir.childNodes) {
let childNodes = getVnode(k, vnode)
if (childNodes) {
vnode.children.push(childNodes)
}
}
}
} else if (domDir.nodeType == 3) {
parentVnode['value'] = domDir.nodeValue.trim()
}
return vnode
}
得到虛擬dom數(shù)據(jù)

在dom字符串中會發(fā)現(xiàn)ahm-onclick,ahm-ondata等字段,這是預留的事件和數(shù)據(jù)渲染的處理,解析虛擬dom的時候?qū)⑺\用上。
// 解析虛擬dom
function parseVnode(vnode) {
let element = document.createElement(vnode.nodeName)
vnode.element = element // 保留dom節(jié)點后續(xù)方便dom操作
for (let k in vnode.attr) {
if (k.indexOf('ahm-') > -1) { // 事件和數(shù)據(jù)的標記處理
let str = k.substring(4, k.length)
if (typeof this[vnode.attr[k]] === 'function') {
element[str] = this[vnode.attr[k]].bind(this)
} else {
element.innerHTML = this[vnode.attr[k]]
}
} else {
element.setAttribute(k, vnode.attr[k])
}
}
if (vnode.value) {
element.innerHTML = vnode.value
}
if (vnode.children && vnode.children.length) {
vnode.children.map(item => {
element.appendChild(parseVnode.call(this, item))
})
}
return element
}
拿到dom了,掛載到頁面去,掛載的事件記得補上。
let eventObj = {
maskData: '內(nèi)容',
onOk: (e) => {},
onCancel: (e) => {}
}
let dom = parseVnode.call(eventObj, vnode)
document.querySelector('body').appendChild(dom)
渲染完美結(jié)束,更新才是重點。
實現(xiàn)思路:新的虛擬dom和舊的虛擬dom對比差別,把它們的所有變化記錄下來 ? 記錄下的補丁去遍歷樹更新dom。
// 省去新dom和舊dom的對比,我們直接得到需要更新的記錄patches
/*
* 更新虛擬dom
* @param vnode: 虛擬dom
* @param patches: 需更新的補丁
:patches = [{
_key: 16,
type: 'TEXT',
attr: {
style: "color: red"
},
value: this.num
}]
*/
// 更新dom
function updateVnode(vnode, patches = []) {
patches.map(patch => {
_walk(vnode, patch)
})
}
// 遍歷樹更新
function _walk(vnode = {}, patch) {
if (vnode._key === patch._key) {
if (patch.type === "TEXT") {
vnode.value = patch.value
Object.keys(patch.attr).map(d => {
vnode.attr[d] = patch.attr[d]
})
if (vnode.attr && vnode.attr.style) {
vnode.element.style = vnode.attr.style
}
vnode.element.innerHTML = patch.value
}
return
}
if (vnode.children) {
vnode.children.map(item => {
_walk(item, patch)
})
}
}
測試,對onOk事件完善下。
onOk: (e) => {
let patches = [{
_key: 16,
type: 'TEXT',
attr: {
style: "color: red"
},
value: '內(nèi)容和顏色變了'
}]
updateVnode(vnode, patches)
}
簡單的實現(xiàn)虛擬dom和更新的理解,當然更新方法和許多的事件,方法還有很多的情況,就此不一一判斷了。可以參考react和vue的虛擬dom和diff算法的實現(xiàn)。