藝龍網(wǎng)王海濱:前端渲染優(yōu)化-domdiff
原創(chuàng)對(duì)于不斷發(fā)展的web應(yīng)用,性能的優(yōu)化,用戶的體驗(yàn)從來都沒有間斷過,如何逆水行舟,不進(jìn)則退。隨著通訊技術(shù)的高速發(fā)展,web應(yīng)用在近幾年快速增加及普及,已經(jīng)成為人們必不可少的工具,充斥著生活的方方面面,商務(wù),娛樂,旅游,工作。
隨著用戶規(guī)模的日益增大,web應(yīng)用的內(nèi)容和功能也變得越來越豐富,各大應(yīng)用對(duì)于用戶的體驗(yàn),流量,內(nèi)存,性能優(yōu)化也越來越高,人們不僅僅要看到自己需要的內(nèi)容,還對(duì)響應(yīng)速度,動(dòng)畫的流暢性,瀏覽網(wǎng)頁的等待時(shí)間都提出了非常大的要求。
在網(wǎng)頁首屏優(yōu)化上,我們盡量采用異步加載頁面數(shù)據(jù)的方式來提升用戶的流暢性,也增加了一些離線模板的技術(shù)規(guī)劃,而在代碼的底層組件,我們引入了一下新的方向,去減少用戶點(diǎn)擊事件之后對(duì)頁面DOM節(jié)點(diǎn)的操作,從而提升用戶體驗(yàn)。
我們希望slarkjs是一個(gè)簡單的,通用的,易了解和使用的框架,而我們的組員也保持著平常心的心態(tài)去豐富我們的框架,我們希望slarkjs是很多初級(jí)的h5開發(fā)希望去了解的,去熟悉的,以下我會(huì)用很多非常白話文的概念思路去解析我們的框架組件,給一些對(duì)h5有興趣,對(duì)slarkjs有興趣的前端開發(fā)童靴去了解組件化的開發(fā)思路與框架的理念。
回到dom優(yōu)化上,最開始我們打算是引用domdiff的理念,來進(jìn)行數(shù)據(jù)對(duì)比,而這些數(shù)據(jù)對(duì)比完全是在js中去實(shí)現(xiàn),然后精簡之后來進(jìn)行dom的操作。舉個(gè)簡單的例子,一個(gè)dom節(jié)點(diǎn)可能是這樣的:
- <ul>
- <li>1</li>
- <li>2</li>
- <li>3</li>
- <li>4</li>
- </ul>
而我們想把它變成這樣
- <ul>
- <li>1</li>
- <li>2</li>
- <li>3</li>
- <li>5</li>
- <li>6</li>
- </ul>
正常情況我們只有兩種方式,***種,替換整個(gè)ul節(jié)點(diǎn),第二種,將你想要變成的數(shù)據(jù)循環(huán)inner進(jìn)去,這樣我們就有了4次的刪除和5次的添加,但是我們覺得這些dom操作太多了。
其實(shí)真實(shí)的情況,我們最需要把第四個(gè)li中的數(shù)據(jù)替換,并且在后面添加一個(gè)<li>6/li>就能達(dá)到我們需要的結(jié)果,我們需要一個(gè)組件來幫助我們對(duì)dom節(jié)點(diǎn)的操作進(jìn)行分析。一般的domdiff應(yīng)用都存在于大多數(shù)的聊天室,評(píng)論區(qū),一些頻繁的dom替換的場(chǎng)所,我們希望他是一個(gè)小型的,方便應(yīng)用的,適合框架的一個(gè)小應(yīng)用。
在開發(fā)期間,我們還花費(fèi)了將近兩周的時(shí)間對(duì)現(xiàn)在非常流行的react及react-native進(jìn)行了詳細(xì)的技術(shù)調(diào)研,我不得不說,react的開發(fā)效率是我目前所見最快速的框架,他的模塊化開發(fā)思路,虛擬dom的理念都是我非常喜歡的一種方式,并且我們嘗試了將它合并進(jìn)slarkjs框架,開始我們只希望讓它來負(fù)責(zé)view層的重繪工作,但是在實(shí)踐中我們其實(shí)更希望它能負(fù)責(zé)更多的內(nèi)容,可惜的是,react來web層面的使用,還有一定局限性,并且需要大量的開發(fā)時(shí)間來修改一些組件,很遺憾我們暫時(shí)停滯了這個(gè)項(xiàng)目的開發(fā)進(jìn)度,但react-native在app上的開發(fā),卻是一個(gè)潛能***的壯舉,在之后的文章中,我們會(huì)持續(xù)的給大家?guī)韘larkjs框架是如何吸收react-native并融入到app的開發(fā)?,F(xiàn)在我們先回到domdiff的思路邏輯中。首先,我們?cè)跇?gòu)建domdiff中,想法是很簡單的,
1. 我們需要它來接收2個(gè)參數(shù),1.現(xiàn)在頁面上的節(jié)點(diǎn),2.我們需要讓它變成什么樣子。
- var domdiff = function(oldid,newid) {
- var a1 = document.getElementById(oldid);
- var a2 = document.getElementById(newid);
- var dd = new diffDOM();
- dd.apply(a2, dd.diff(a2, a1));
- };
- var tdomdiff = function(oldid,newid) {
- var a2 = document.getElementById(oldid);
- var a3 = document.createElement('div');
- a3.innerHTML = newid;
- var dd = new diffDOM();
- dd.apply(a2, dd.diff(a2, a3));
- };
2. 我們需要它來對(duì)2個(gè)參數(shù)進(jìn)行數(shù)據(jù)對(duì)比,并放回一個(gè)list,里面包含最少量級(jí)的dom操作
- if (!tree1 || !tree2) {
- return false;
- }
- if (tree1.nodeType !== tree2.nodeType) {
- return false;
- }
- if (tree1.nodeType === 3) {
- if (tree2.nodeType !== 3) {
- return false;
- }
- return preventRecursion ? true : tree1.data === tree2.data;
- }
- if (tree1.nodeName !== tree2.nodeName) {
- return false;
- }
- if (tree1.tagName === tree2.tagName) {
- ....
- }
- if (tree1.childNodes.length !== tree2.childNodes.length) {
- return false;
- }
3. 去實(shí)現(xiàn)list
- Object.keys(options).forEach(function(t) {
- diff[t] = options[t];
- });
從開發(fā)的角度來講,1,3都非常好實(shí)現(xiàn),而第二步,會(huì)讓大多數(shù)的前端開發(fā)覺得頭疼,這時(shí)候我們需要介紹兩個(gè)很容易被遺忘,并且不會(huì)經(jīng)常用到的屬性nodeTpye和childNodes,其實(shí)JS有很多屬性我們是很少會(huì)用到或者說,在我們的業(yè)務(wù)開發(fā)中和技術(shù)實(shí)現(xiàn)中很少去涉及的,相對(duì)來說,這也影響了我們對(duì)更深入的技術(shù)開發(fā)的方向,所以很多時(shí)候,我們提倡去看一些開發(fā)大拿的代碼,其實(shí)是去看他們都用到了哪些屬性,他們的開發(fā)邏輯思維,而并不是去copy他們的代碼。
NodeType,它會(huì)讓我們獲得body元素的節(jié)點(diǎn)類型。說得簡單一些,就是讓我們知道當(dāng)前節(jié)點(diǎn)是元素,屬性,文本內(nèi)容等等
ChildNodes會(huì)讓我們獲得body元素的子節(jié)點(diǎn)集合,以NodeList對(duì)象。簡單解釋就是返回一個(gè)list,里面是當(dāng)前節(jié)點(diǎn)下所有的子節(jié)點(diǎn),包括class,文本,select,option等等。
之后就很好去分析我們的構(gòu)想了,通過NodeType去獲取節(jié)點(diǎn)并判斷節(jié)點(diǎn)屬性,當(dāng)然還要去判斷當(dāng)前頁面的節(jié)點(diǎn)是否唯一,然后通過ChildNodes去對(duì)比節(jié)點(diǎn)下屬性之間的差異,并且需要增加一些屬性作為標(biāo)記,比如判斷當(dāng)前是否應(yīng)該修改,修改的順序等等。OK,說干就干,于是我們有了以下這個(gè)邏輯圖
(點(diǎn)擊圖片查看大圖)
我們?cè)赿iff中傳建了一個(gè)空的list數(shù)組,然后將2個(gè)nodeType傳到finddiff中,finddiff會(huì)做兩件事情,在finddiff-out中判斷在body中是否唯一,然后分離其中的數(shù)據(jù)并在list中增加***個(gè)修改項(xiàng),也就是最外層的修改項(xiàng)。然后再Finddiff-inner中通過ChildNodes分析內(nèi)部結(jié)構(gòu),并且循環(huán)去判斷2組數(shù)據(jù)中是否重疊,這里有個(gè)小問題,就是你需要用用距離值去填充匹配獲相同的內(nèi)容量,舉個(gè)例子:
- <ul>
- <li>1</li>
- <li>2</li>
- <li>3</li>
- <li>4</li>
- </ul>
而我們想把它變成這樣
- <ul>
- <li>1</li>
- <li>5</li>
- <li>6</li>
- <li>3</li>
- <li>4</li>
- </ul>
如果你僅僅是去循環(huán)判斷重復(fù),那你會(huì)在第二步的時(shí)候,將5變成2,第三步將6變成3,這樣是一個(gè)很浪費(fèi)資源的,所以我們需要用距離值去填充,當(dāng)我們用新數(shù)據(jù)去循環(huán)的時(shí)候,我們需要在***次循環(huán)中判斷參數(shù)是否重復(fù),對(duì)重復(fù)的參數(shù)判斷修改值為false,再第二次循環(huán)中對(duì)非重復(fù)的參數(shù)用距離值去填充數(shù)據(jù),***得出最簡單的list來覆蓋。
這樣的方式,減少了頁面對(duì)dom的操作次數(shù),提升頁面的加載速率和二次加載速率,但是也是有一些坑的,比如:如果頁面dom修改量巨大,在循環(huán)中會(huì)浪費(fèi)非常多的時(shí)間去循環(huán)判斷重復(fù)項(xiàng),可能會(huì)比單獨(dú)替換整體dom節(jié)點(diǎn)花費(fèi)更多的時(shí)間,所以在domdiff中,需要增加一些判斷,去適應(yīng)大多數(shù)的方式。比如:減少循環(huán),如果只是單純的文本替換,我們并不需要去循環(huán)判斷它的其他屬性,又或者增加閥值,如果運(yùn)行時(shí)間或者數(shù)據(jù)量超過標(biāo)準(zhǔn)時(shí)間進(jìn)行部分的dom替換,這些都是組件級(jí)對(duì)代碼的嚴(yán)謹(jǐn)性。
結(jié)尾,domdiff其實(shí)是為了瀏覽器的優(yōu)化而做,但是也要適應(yīng)當(dāng)前的環(huán)境而用,它更像是react虛擬dom理念的前身,有好處也有壞處,使用時(shí)候還需謹(jǐn)慎,我們會(huì)在今后的1,2個(gè)月中,對(duì)react-native進(jìn)行詳細(xì)的分析,并且嘗試去融入到我們的框架中,也許會(huì)打出分支版,slarkjs-native來支持app的開發(fā),到時(shí)候會(huì)給大家繼續(xù)分享進(jìn)一步的技術(shù)體驗(yàn),希望對(duì)h5比較有興趣的童靴可以加入到我們的team中,體驗(yàn)既擁有Native的用戶體驗(yàn)、又保留React的開發(fā)效率。
參考文獻(xiàn)
http://www.w3school.com.cn/jsref/prop_node_nodetype.asp
http://www.w3school.com.cn/jsref/prop_node_childnodes.asp
https://github.com/Seven-wang/react