提升对前端的认知,不得不了解Web API的DOM和BOM
了解Web API的DOM和BOM
- 引言
- 正文
- 一、DOM操作
- 1、DOM的本質
- 2、DOM節點操作
- (1)property形式
- (2)attribute形式
- 3、DOM結構操作
- (1)新增/插入節點
- (2)獲取子元素列表,獲取父元素
- (3)刪除子元素
- 4、DOM性能
- (1)對DOM查詢做緩存
- (2)將頻繁操作改為一次性操作
- 5、回顧
- 二、BOM操作
- 1、navigator
- 2、screen
- 3、location
- 4、history
- 結束語
引言
在現代的開發中,vue和react都是很流行的開發框架,框架雖好用,但是框架的原理還是基于 DOM 操作去實現。如果一個前端工程師只會框架,不會 DOM ,那基本上是很容易被淘汰的。因為框架的存活時間我們誰也說不準,且技術更新迭代也特別快,說不定三五年就會被淘汰了都有可能。所以,扎實的學會 js 的基礎原理,不要被框架和一些外部事件所迷惑,對自己會有一個更好的競爭力提升。
本文將講解 JS 中 Web API 的 DOM 和 BOM 操作。
正文
一、DOM操作
DOM操作,即Document Object Model文檔對象模型。下面通過幾個知識點來分析DOM的本質、節點操作、結構操作以及 DOM 的性能。
1、DOM的本質
DOM的本質就是一棵 樹 ,是樹結構。
我們從早起xml說起,xml是一種可擴展性的標準語言,早期基本是用xml來對DOM進行編寫。具體形式如下:
<?xml version = "1.0" encoding = "UTF-8"?> <note><to>Tony</to><from>Abby</from><heading>London</heading><body>Have a nice day!</body><other><a></a><b></b></other> </note>我們可以看到,一層一層的下來,層層遞進,很像一棵樹。
回到現在,我們基本上用的是 html 來進行編寫。 html 也是一種特定的 xml ,只不過它是有自己的一套規范,比如說我們常見的 p 標簽, span 標簽, li 標簽等等。引用百度來做一個解釋:
大家可以看到,上面一層一層遞進的關系,把整個 html 渲染出來形成我們看到的頁面,這一層層遞進的關系,其實就是 DOM 樹,所以也可以說, DOM 是從 html 文件解析出來的一棵樹。
2、DOM節點操作
DOM節點主要有兩種操作,一種是property操作,另一種是attribute操作。下面讓我們來看看這兩種操作。
(1)property形式
html代碼:
<div id="div1" class="container"><p>文段 1</p><p>文段 2</p><p>文段 3</p> </div> <div id="div2"><img src="https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58" alt=""> </div>JS代碼:
/*** property形式*/const pList = document.querySelectorAll('p');const p1 = pList[0];p1.style.width = '100px'; //修改樣式console.log(p1.style.width); //獲取樣式p1.className = 'red'; //修改class名稱console.log(p1.className); //獲取class名稱// 獲取nodeName和nodeTypeconsole.log(p1.nodeName); //打印節點名稱,pconsole.log(p1.nodeType); //打印節點類型,普通的DOM節點元素為1,文本類型是3控制臺打印結果如下:
從上面的結果中可以看到,通過修改 DOM 的 JS 變量,從而操作 DOM ,最終得到我們想要的結果。
(2)attribute形式
html代碼:
<div id="div1" class="container"><p>文段 1</p><p>文段 2</p><p>文段 3</p> </div> <div id="div2"><img src="https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58" alt=""> </div>JS代碼:
/*** attribute形式*/const pList = document.querySelectorAll('p');const p1 = pList[0];p1.setAttribute('data-name', 'immoc');console.log(p1.getAttribute('data-name'));p1.setAttribute('style', 'font-size:50px;');console.log(p1.getAttribute('style'));控制臺打印結果如下:
從上面的結果中可以看到,通過修改 DOM 結構的節點屬性,最終得到我們想要的結果。
綜上所述, porperty 和 attribute 這兩種操作類型,主要有以下區別:
- property:修改對象屬性,不會體現到 html 結構中;
- attribute:修改 html 屬性,會改變 html 結構,即標簽結構;
- 兩者都有可能引起DOM重新渲染,但 attribute 引起 DOM 重新渲染的可能性更大,因為它會改動 html 的結構。所以在實際開發中,可以選擇的話盡量渲染 property 去操作 DOM 。
3、DOM結構操作
通過上面的了解,我們都明白了DOM是一種樹結構。那既然是樹結構,就應該可以對節點進行增刪改的操作。
因此,DOM結構操作主要有以下三種類型:
- 新增/插入節點;
- 獲取子元素列表,獲取父元素;
- 刪除子元素。
接下來對這三種類型進行講解。
(1)新增/插入節點
先附上html代碼。
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><div id="div1" class="container"><p id = "p1">文段 1</p><p>文段 2</p><p>文段 3</p></div><div id="div2"> <img src="https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58" alt=""></div><script src="./你的文件路徑.js"></script> </body> </html>此時執行狀態如下。
如果此時要給 div1 新增一個子節點 p ,代碼如下。
// 新建節點 const newP = document.createElement('p'); newP.innerHTML = 'this is newP'; // 插入節點 div1.appendChild(newP);此時瀏覽器執行狀態如下。
大家可以看到, div1 成功新增了一個節點,這就是關于新增節點操作的具體流程。
那如果在此基礎上,要移動一個節點呢?具體操作如下。
// 移動節點 -> 把p1移動到div2中來 const p1 = document.getElementById('p1'); div2.appendChild(p1);此時瀏覽器執行狀態如下。
(2)獲取子元素列表,獲取父元素
在上面的基礎上,我們來看看獲取父元素和子元素列表的流程。
// 獲取父元素 console.log(p1.parentNode);// 獲取子元素列表 const div1ChildNodes = div1.childNodes; console.log(div1ChildNodes); //此處獲取的包含p標簽以及p標簽下面的文本,因此需要一層過濾 const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {if(child.nodeType === 1){return true;}else{return false;} }); console.log('div1ChildNodesP', div1ChildNodesP);此時瀏覽器執行狀態如下。
(3)刪除子元素
在上面操作的基礎上,我們現在對 div1 中的第一個 p 節點進行移除。
// 移除元素 div1.removeChild(div1ChildNodesP[0]);此時瀏覽器執行狀態如下。
以上就是對DOM結構的新增、移動、獲取子父元素以及刪除子元素的一個操作,相信大家對DOM結構的增刪改有了一個新的了解。接下來我們講解DOM性能。
4、DOM性能
為什么要對DOM做性能優化呢,原因在于 DOM 操作是非常“昂貴”的,每一次操作都很有可能引發瀏覽器的重排和重繪,因此要避免頻繁的 DOM 操作。那如何做到避免頻繁的 DOM 操作,給 DOM 進行性能優化呢?主要有以下兩個方面給 DOM 操作進行性能優化:
- 對 DOM 查詢做緩存
- 將頻繁操作改為一次性操作
接下來將對這兩種方法進行講解。
(1)對DOM查詢做緩存
// 不緩存 DOM 查詢結果 for(let i = 0; i < document.getElementsByTagName('p').length; i++){// 每次循環,都會計算length,頻繁進行 DOM 查詢 } // 緩存 DOM 查詢結果 const pList = document.getElementsByTagName('p'); const length = pList.length; for(let i = 0; i < length; i++){// 緩存length,只進行一次 DOM 查詢 }分析以上兩段代碼,在第一段當中,每次循環的時候,都會計算 length ,頻繁的對 DOM 進行查詢,如此頻繁的操作,可想對程序都不是不太好的。
因此,通過優化,我們寫出第二段代碼。在第二段代碼中,把 length 放在外部進行緩存,等到每次循環的時候,只需要進行一次 DOM 查詢,而不用像第一段一樣頻繁操作,極大提高了性能。
(2)將頻繁操作改為一次性操作
我們來看一個例子。比如說,我們先在要通過操作 DOM 來一次性插入10個列表。
在正常情況下我們想象的操作如下:
html代碼
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><div id="div1" class="container"><p id = "p1">文段 1</p><p>文段 2</p><p>文段 3</p></div><div id="div2"> <img src="https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2144980717,2336175712&fm=58" alt=""></div><ul id="list"></ul><script src="./你的路徑.js"></script> </body> </html>js代碼
const list = document.getElementById('list');for(let i = 0; i < 10; i++){const li = document.createElement('li');li.innerHTML = `List item ${i}`;list.appendChild(li); }此時瀏覽器執行狀態如下。
按照預想的,呈現出了我們想要的結果,且看著好像也沒什么問題。但是呢,這就違反了我們所說的一次性操作 DOM 的原則,因為它在頻繁的操作 DOM ,一直在頻繁操作中修改。因此,我們可以通過以下方法來對性能做一個優化:
/*** 頻繁操作改為一次性操作*/ const list = document.getElementById('list');// 創建一個文檔片段,此時還沒有插入到 DOM 結構中 const frag = document.createDocumentFragment();for(let i = 0; i < 15; i++){const li = document.createElement('li');li.innerHTML = `List item ${i}`;//先插入文檔片段frag.appendChild(li); }// 都完成之后,再統一插入到 DOM 結構中 list.appendChild(frag);執行后,瀏覽器也同樣效果呈現出來了。
通過代碼我們可以發現,通過創建一個文檔片段,來對節點進行一個緩存,等到全部節點操作都完成以后,再統一插入到 DOM 結構中。這種方法,也極大提高了程序中的性能。
5、回顧
最后,我們用幾個題目來回顧DOM的知識點。
(1)DOM是哪一種數據結構?
DOM是一種樹的數據結構,DOM也常被稱為DOM樹。
(2)DOM操作的常用API
(3)attribute和property的區別
兩者主要有以下區別:
- property:修改對象屬性,不會體現到 html 結構中;
- attribute:修改 html 屬性,會改變 html 結構,即標簽結構;
- 兩者都有可能引起DOM重新渲染,但 attribute 引起 DOM 重新渲染的可能性更大,因為它會改動 html 的結構。所以在實際開發中,可以選擇的話盡量渲染 property 去操作 DOM 。
(4)一次性插入多個DOM節點,需考慮性能,怎么操作?
可以通過創建一個Fragment的文檔片段,來對節點進行一個緩存,等到 全部節點操作 都完成以后,再統一插入到 DOM 結構中,從頻繁執行改為一次執行。
二、BOM操作
BOM,即Brouse Object Model瀏覽器對象模型。下面通過幾個知識點來了解瀏覽器的BOM操作。
1、navigator
navigator 主要用到 userAgent 屬性, navigator.userAgent 表示獲取瀏覽器的用戶代理字符串。如以下代碼操作:
//navigator const ua = navigator.userAgent; console.log(ua); const isChrome = ua.indexOf('Chrome'); console.log(isChrome);此時瀏覽器打印如下。
從上圖中可以看到,通過 userAgent 可以獲取到當前所使用瀏覽器的內核信息。
2、screen
screen 主要用到width和height屬性,screen.width表示獲取當前屏幕的寬度,sceen.height表示獲取當前屏幕的高度。如以下代碼操作:
// screen console.log(screen.width); //獲取屏幕寬度 console.log(screen.height); //獲取屏幕高度此時瀏覽器打印如下。
從上圖中可以看到,通過 screen.width 和 screen.height 可以獲取到當前屏幕的寬度和高度。
3、location
location 主要用到 href/protocol/pathname/search/hash 屬性,具體含義如下代碼所示。
// location console.log(location.href); //獲取整個網址 console.log(location.protocol); //獲取網址協議 console.log(location.pathname); //獲取網址域名 console.log(location.search); //獲取網址中的一些參數 console.log(location.hash); //獲取哈希值,即#號后面的值此時瀏覽器打印如下。
相信從上圖的演示之后,大家對 location 屬性的應用有了有一定的了解。
4、history
history 主要用到 back 和 forward 屬性,當網頁執行history.back()代碼時,會將當前網頁向后退一頁;當網頁執行history.forward()代碼時,會將當前網頁向前進一頁。如以下代碼操作:
// history history.back(); //對網頁進行后退 history.forward(); //對網頁進行前進此時瀏覽器打印如下。
從上圖中可以看到,通過 history.back() 和 history.forward() 可以讓當前瀏覽頁面進行前進或者后退操作。
結束語
JS 的基礎知識規定了 ECMA 的語法標準,而 Web API 則是網頁操作的 API ,是 W3C 的標準。如果要說兩者的關系,那自然是 ES 標準是 Web API 的基礎。在實際應用開發中,只有兩者結合才能真正做到實際應用。所以,不管是 ES 標準還是 Web API 中的 DOM 和 BOM 操作,在實際開發中都是至關重要的內容。
關于 DOM 和 BOM 的操作就講到這里啦!如有疑問歡迎評論區評論或私信我交流~
-
關注公眾號 星期一研究室 ,不定期分享學習干貨
-
如果這篇文章對你有用,記得點個贊加個關注哦~
總結
以上是生活随笔為你收集整理的提升对前端的认知,不得不了解Web API的DOM和BOM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么清理手机里的大文件?
- 下一篇: 你真的理解事件绑定、事件冒泡和事件委托吗