日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

如何提升JavaScript的運(yùn)行速度之DOM篇(四)

系統(tǒng) 2367 0

在Web開(kāi)發(fā)中,JavaScript的一個(gè)很重要的作用就是對(duì)DOM進(jìn)行操作,可你知道么?對(duì)DOM的操作是非常昂貴的,因?yàn)檫@會(huì)導(dǎo)致瀏 覽器執(zhí)行回流操作,而執(zhí)行了過(guò)多的回流操作,你就會(huì)發(fā)現(xiàn)自己的網(wǎng)站變得越來(lái)越慢了,我們應(yīng)該盡可能的減少DOM操作。本文是這個(gè)系列的最后一篇,給出了一 些指導(dǎo)性原則,比如在什么時(shí)候應(yīng)該對(duì)DOM可以進(jìn)行什么樣的操作等。

【原文】 Nicholas C. Zakas - Speed up your JavaScript, Part 4
【譯文】 明達(dá) - 如何提升JavaScript的運(yùn)行速度(DOM篇)

以下是對(duì)原文的翻譯:

在 過(guò)去的幾周中,我為大家介紹了幾種可以加快JavaScript腳本運(yùn)行速度的技術(shù)。第一節(jié) 介紹了如何優(yōu)化循環(huán)。第二節(jié)的重點(diǎn)放在優(yōu)化函數(shù)內(nèi)部代碼上,還介紹了隊(duì)列(queuing)和記憶化(memoization)兩種技術(shù),來(lái)減輕函數(shù)的工 作負(fù)擔(dān)。第三節(jié)就如何將遞歸轉(zhuǎn)換為迭代循環(huán)或者記憶化方式的話(huà)題,展開(kāi)了討論。第四節(jié)是這個(gè)系列的最后一篇,也就是本文,將重點(diǎn)闡述過(guò)多的DOM操作所帶 來(lái)的影響。

我們都知道,DOM操作的效率是很低的,而且不是一般的慢,而且這也是引發(fā)性能問(wèn)題的常見(jiàn)問(wèn)題之一。為什么會(huì)慢呢?因?yàn)閷?duì) DOM的修改為影響網(wǎng)頁(yè)的用戶(hù)界面,重繪頁(yè)面是一項(xiàng)昂貴的操作。太多的DOM操作會(huì)導(dǎo)致一系列的重繪操作,為了確保執(zhí)行結(jié)果的準(zhǔn)確性,所有的修改操作是按 順序同步執(zhí)行的。我們稱(chēng)這個(gè)過(guò)程叫做回流(reflow),同時(shí)這也是最昂貴的瀏覽器操作之一。回流操作主要會(huì)發(fā)生在幾種情況下:

* 當(dāng)對(duì)DOM節(jié)點(diǎn)執(zhí)行新增或者刪除操作時(shí)。
* 動(dòng)態(tài)設(shè)置一個(gè)樣式時(shí)(比如element.style.width="10px")。
* 當(dāng)獲取一個(gè)必須經(jīng)過(guò)計(jì)算的尺寸值時(shí),比如訪問(wèn)offsetWidth、clientHeight或者其他需要經(jīng)過(guò)計(jì)算的CSS值(在兼容DOM的瀏覽器中,可以通過(guò)getComputedStyle函數(shù)獲取;在IE中,可以通過(guò)currentStyle屬性獲取)。

解 決問(wèn)題的關(guān)鍵,就是限制通過(guò)DOM操作所引發(fā)回流的次數(shù)。大部分瀏覽器都不會(huì)在JavaScript的執(zhí)行過(guò)程中更新DOM。相應(yīng)的,這些瀏覽器將對(duì)對(duì) DOM的操作放進(jìn)一個(gè)隊(duì)列,并在JavaScript腳本執(zhí)行完畢以后按順序一次執(zhí)行完畢。也就是說(shuō),在JavaScript執(zhí)行的過(guò)程中,用戶(hù)不能和瀏 覽器進(jìn)行互動(dòng),直到一個(gè)回流操作被執(zhí)行。( 失控腳本對(duì)話(huà)框會(huì)觸發(fā)回流操作,因?yàn)樗麍?zhí)行了一個(gè)中止JavaScript執(zhí)行的操作,此時(shí)會(huì)對(duì)用戶(hù)界面進(jìn)行更新)

如果要減少由于DOM修改帶來(lái)的回流操作,有兩個(gè)基本的方法。第一個(gè)就是在對(duì)當(dāng)前DOM進(jìn)行操作之前,盡可能多的做一些準(zhǔn)備工作。一個(gè)經(jīng)典的例子就是向document對(duì)象中添加很多DOM節(jié)點(diǎn):

程序代碼 程序代碼
for (var i=0; i < items.length; i++){
var item = document.createElement("li");
item.appendChild(document.createTextNode("Option " + i);
list.appendChild(item);
}


這段代碼的效率是很低的,因?yàn)樗诿看窝h(huán)中都會(huì)修改當(dāng)前DOM結(jié)構(gòu)。為了提高性能,我們需要將這個(gè)次數(shù)降到最低,對(duì)于這個(gè)案例來(lái)說(shuō),最好 的辦法是建立一個(gè)文檔碎片(document fragment),作為那些已創(chuàng)建元素元素的臨時(shí)容器,最后一次將容器的內(nèi)容直接添加到父節(jié)點(diǎn)中:

程序代碼 程序代碼
var fragment = document.createDocumentFragment();
for (var i=0; i < items.length; i++){
var item = document.createElement("li");
item.appendChild(document.createTextNode("Option " + i);
fragment.appendChild(item);
}
list.appendChild(fragment);


經(jīng)過(guò)調(diào)整的代碼,只會(huì)修改一次當(dāng)前DOM的結(jié)構(gòu),就在最后一行,而在這之前,我們用文檔碎片來(lái)保存那些中間結(jié)果。因?yàn)槲臋n碎片沒(méi)有任何可見(jiàn) 內(nèi)容,所以這類(lèi)修改不會(huì)觸發(fā)回流操作。實(shí)際上,文檔碎片也不能被添加到DOM中,我們需要將它作為參數(shù)傳給appendChild函數(shù),而實(shí)際上添加的不 是文檔碎片本身,而是它下面的所有子元素。

避免不必要回流操作的另外一種方法,就是在對(duì)DOM操作之前,把要操作的元素,先從當(dāng)前DOM結(jié)構(gòu)中刪除。對(duì)于刪除一個(gè)元素,基本有兩種方法:

1.通過(guò)removeChild()或者replaceChild()實(shí)現(xiàn)真正意義上的刪除。
2.設(shè)置該元素的display樣式為“none”。

而一旦修改操作完成,上面這個(gè)過(guò)程就需要反轉(zhuǎn)過(guò)來(lái),將刪除的元素重新添加到當(dāng)前的DOM結(jié)構(gòu)中,我們還是拿上面的例子來(lái)做說(shuō)明:

程序代碼 程序代碼
list.style.display = "none";
for (var i=0; i < items.length; i++){
var item = document.createElement("li");
item.appendChild(document.createTextNode("Option " + i);
list.appendChild(item);
}
list.style.display = "";


將list的display樣式設(shè)置為“none”后,就將這個(gè)元素從當(dāng)前的DOM結(jié)構(gòu)中刪除了,因?yàn)檫@個(gè)節(jié)點(diǎn)不再可視。在將display屬性設(shè)置回之前的默認(rèn)值之前,向其下添加子元素是不會(huì)觸發(fā)回流操作的。

另外一個(gè)經(jīng)常引起回流操作的情況是通過(guò)style屬性對(duì)元素的外觀進(jìn)行修改。比如下面這個(gè)例子:

程序代碼 程序代碼
element.style.backgroundColor = "blue";
element.style.color = "red";
element.style.fontSize = "12em";


這段代碼修改了三個(gè)樣式,同時(shí)也就觸發(fā)了三次回流操作。每次修改元素的style屬性,都肯定會(huì)觸發(fā)回流操作。如果你要同時(shí)修改一個(gè)元素的 很多樣式,最好的辦法是將這些樣式放到一個(gè)class下,然后直接修改元素的class,這可比單獨(dú)修改元素的樣式要強(qiáng)得多。比如下面這個(gè)例子:

程序代碼 程序代碼
.newStyle {
background-color: blue;
color: red;
font-size: 12em;
}


這樣我們?cè)贘avaScript代碼中,只需下面這行代碼就可以修改樣式:

程序代碼 程序代碼
/*element.className = "newStyle";*/


修改元素的class屬性,會(huì)一次將所有的樣式應(yīng)用在目標(biāo)元素上,而且只會(huì)觸發(fā)一次回流操作。這樣做不止更加有效,而且還更容易維護(hù)。

既然DOM幾乎在所有情況下都很慢,就很有必要將獲取的DOM數(shù)據(jù)緩存起來(lái)。這種方法,不僅對(duì)獲取那些會(huì)觸發(fā)回流操作的屬性(比如offsetWidth等)尤為重要,就算對(duì)于一般情況,也同樣適用。下面介紹一個(gè)效率低的夸張的例子:

程序代碼 程序代碼
document.getElementById("myDiv").style.left = document.getElementById("myDiv").offsetLeft +
document.getElementById("myDiv").offsetWidth + "px";


這里對(duì)getElementById()調(diào)用了三次,是一個(gè)很大的問(wèn)題,訪問(wèn)DOM是很昂貴的,而這三個(gè)調(diào)用恰恰訪問(wèn)的是同一個(gè)元素,也許我們像下面這樣寫(xiě),會(huì)更好一些:

程序代碼 程序代碼
var myDiv = document.getElementById("myDiv");
myDiv.style.left = myDiv.offsetLeft + myDiv.offsetWidth + "px";


我們?nèi)サ袅艘恍┤哂嗖僮鳎F(xiàn)在對(duì)DOM操作的次數(shù)已經(jīng)被減小了。對(duì)于那些使用次數(shù)超過(guò)一次的DOM值,我們都應(yīng)該緩沖起來(lái),這樣可以避免無(wú)謂的性能消耗。

也 許,拖慢屬性訪問(wèn)速度的罪魁禍?zhǔn)拙褪荋TMLCollection對(duì)象。這些對(duì)象是object類(lèi)型的,只要DOM需要返回一組節(jié)點(diǎn)時(shí)就會(huì)使用這個(gè)對(duì)象, 也就是說(shuō)childNodes屬性和getElementsByTagName()的返回值都屬于這種情況。我們可能經(jīng)常會(huì)將 HTMLCollection當(dāng)作數(shù)組來(lái)使用,但實(shí)際上他是一個(gè)根據(jù)DOM結(jié)構(gòu)自動(dòng)變化的實(shí)體對(duì)象。每次你訪問(wèn)一個(gè)HTMLCollection對(duì)象的屬 性,他都會(huì)對(duì)DOM內(nèi)所有的節(jié)點(diǎn)進(jìn)行一次完整匹配,這意味著下面的代碼將導(dǎo)致一個(gè)死循環(huán):

程序代碼 程序代碼
var divs = document.getElementsByTagName("div");
for (var i=0; i < divs.length; i++){//infinite loop
document.body.appendChild(document.createElement("div"));
}


這段代碼為什么會(huì)變成死循環(huán)呢?因?yàn)樵诿看窝h(huán)中,將會(huì)向document中新增一個(gè)div元素,同時(shí)也會(huì)更新divs這個(gè)集合,也就是說(shuō) 循環(huán)的索引永遠(yuǎn)都不會(huì)超過(guò)divs.length的值,因?yàn)閐ivs.length的值是伴隨著循環(huán)而遞增的。每次訪問(wèn)divs.length,就會(huì)更新 一次集合對(duì)象,這可比訪問(wèn)一個(gè)普通數(shù)組的length屬性要付出更大的代價(jià)。當(dāng)對(duì)HTMLCollection對(duì)象進(jìn)行操作時(shí),應(yīng)該將訪問(wèn)的次數(shù)盡可能的 降至最低,最簡(jiǎn)單的,你可以將length屬性緩存在一個(gè)本地變量中,這樣就能大幅度的提高循環(huán)的效率。

程序代碼 程序代碼
var divs = document.getElementsByTagName("div");
for (var i=0, len=divs.length; i < len; i++){//not an infinite loop
document.body.appendChild(document.createElement("div"));
}


修改后的代碼已經(jīng)不是死循環(huán)了,因?yàn)樵诿看窝h(huán)時(shí),len的值都是保持固定不變的。將屬性值緩存起來(lái)除了更加有效率,還可以保證document不會(huì)執(zhí)行多于一次的查詢(xún)。

本文是“Speed up your JavaScript”這個(gè)系列的最后一篇文章,我希望你現(xiàn)在已經(jīng)知道如何避免那個(gè)腳本失控的對(duì)話(huà)框,以及如何讓你的腳本運(yùn)行的更快。我所提到的技巧很多別人已經(jīng)提過(guò)了,我只是將它們組織到一起,這樣大家可以更容易的找到這些信息。

如何提升JavaScript的運(yùn)行速度之DOM篇(四)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 荥经县| 克拉玛依市| 上高县| 定陶县| 廉江市| 高碑店市| 宁阳县| 隆尧县| 喀喇沁旗| 宁陵县| 湘阴县| 县级市| 南华县| 北碚区| 武冈市| 文山县| 芮城县| 湘潭县| 台安县| 温泉县| 东乡族自治县| 彭山县| 东乌珠穆沁旗| 枣阳市| 新巴尔虎右旗| 临夏市| 札达县| 南澳县| 嘉峪关市| 合水县| 灌南县| 绥滨县| 达孜县| 吕梁市| 来安县| 阿克陶县| 平顶山市| 沂源县| 平果县| 牙克石市| 鹤岗市|