最近在看一些源碼學(xué)習(xí)文章,今晚上等發(fā)版有空就記錄一下吧。
1、什么是虛擬DOM
已經(jīng)9102年了,前端er或多或少都接觸過一些mvvm庫(kù),了解有虛擬DOM這么個(gè)東西,可能你還沒有深入了解過這個(gè)東西,其實(shí)很簡(jiǎn)單,來(lái)往下看。
虛擬DOM簡(jiǎn)而言之就是,用JS去按照DOM結(jié)構(gòu)來(lái)實(shí)現(xiàn)的樹形結(jié)構(gòu)對(duì)象,也就是一個(gè)JS對(duì)象而已。
2、什么樣的對(duì)象才能比較匹配的上dom結(jié)構(gòu)呢
來(lái)看一下如下圖所示的dom結(jié)構(gòu)

image.png
上圖是比較簡(jiǎn)單的一個(gè)結(jié)構(gòu),比較理想的是使用如下對(duì)象來(lái)模擬:
{
// 節(jié)點(diǎn)類型
type: 'ul',
// 節(jié)點(diǎn)的屬性,包括dom原生屬性和自定義屬性
props: {
class: 'list',
style: 'color:red;'
},
// 子節(jié)點(diǎn)數(shù)組
// 子對(duì)象結(jié)構(gòu)也是一樣,包含了type,props,children,沒有子節(jié)點(diǎn)的話就是普通文本
// 子對(duì)象擁有子節(jié)點(diǎn)時(shí),繼續(xù)往下擴(kuò)展就行
children: [
{type: 'li',props: {class: 'list'},children: ['利群']},
{type: 'li',props: {class: 'list'},children: ['玉溪']},
{type: 'li',props: {class: 'list'},children: ['黃鶴樓']}
]
}
OK,對(duì)象結(jié)構(gòu)已經(jīng)解析完成
3、批量創(chuàng)建這個(gè)對(duì)象---終于可以上代碼了
// 定義一個(gè)構(gòu)造函數(shù)來(lái)
class Element {
constructor(type, props, children) {
this.type = type;
this.props = props;
this.children = children;
}
}
//批量調(diào)用構(gòu)造函數(shù)
function createElement(type,props,children){
return new Element(type,props,children);
}
let virtualDom = createElement('ul',{class:'list',style:'color:red;'},[
createElement('li',{class:'item'},['利群']),
createElement('li',{class:'item'},['玉溪']),
createElement('li',{class:'item'},['黃鶴樓']),
]);
// 對(duì)象形勢(shì)的結(jié)構(gòu)展示
console.log(virtualDom);
OK,查看打印出來(lái)的對(duì)象是這樣的

image.png
4、將虛擬的DOM對(duì)象渲染成真實(shí)的DOM,添加兩個(gè)方法
// 把虛擬對(duì)象轉(zhuǎn)成dom對(duì)象
function render(domObj) {
// 根據(jù)元素類型來(lái)創(chuàng)建dom
let el = document.createElement(domObj.type);
// 遍歷props對(duì)象,然后給創(chuàng)建的元素el設(shè)置屬性
for (let key in domObj.props) {
// 設(shè)置屬性的方法
setAttr(el, key, domObj.props[key]);
}
// 遍歷子節(jié)點(diǎn)數(shù)組
domObj.children.forEach(child = >{
// 需要注意的是:如果child是虛擬dom,就繼續(xù)遞歸渲染
if (child instanceof Element) {
child = render(child);
} else {
// 只是普通的文本內(nèi)容
child = document.createTextNode(child);
}
// 把子節(jié)點(diǎn)添加到父節(jié)點(diǎn)中
el.appendChild(child);
});
return el;
}
// 上面用到的設(shè)置屬性
function setAttr(node, key, value) {
// 需要判斷key是什么
switch (key) {
case 'value':
// 屬性是value就要看標(biāo)簽類型了,input和textarea的value就需要做區(qū)別
if (node.tagName.toLowerCase() === 'input' || node.tagName.toLowerCase() == 'textarea') {
node.value = value;
} else {
node.setAttribute(key, value);
}
break;
case 'style':
node.style.cssText = value;
break;
default:
node.setAttribute(key, value);
break;
}
}
// 將元素插入頁(yè)面
function renderDom(el, target) {
target.appendChild(el);
}
// 定義一個(gè)構(gòu)造函數(shù)來(lái)
class Element {
constructor(type, props, children) {
this.type = type;
this.props = props;
this.children = children;
}
}
//批量調(diào)用構(gòu)造函數(shù)
function createElement(type,props,children){
return new Element(type,props,children);
}
// 調(diào)用之前的createElement方法
let virtualDom = createElement('ul',{class:'list',style:'color:red;'},[
createElement('li',{class:'item'},['利群']),
createElement('li',{class:'item'},['玉溪']),
createElement('li',{class:'item'},['黃鶴樓']),
]);
// 對(duì)象形勢(shì)的結(jié)構(gòu)展示
console.log(virtualDom);
// 渲染dom
let el = render(virtualDom); // 得到dom元素
console.log(el);
renderDom(el,document.querySelector('body'));
OK,結(jié)果如下:

image.png
寫的并不是很清晰,需要一行一行看下來(lái)啦...eemme,有待提升,我改bug去了。