一、DOM JavaScript語言核心。變數的定義、變數的類型、運算符、表達式、函數、if語句、for迴圈、演算法等等。這些東西都屬於語言核心,下次繼續學習語言核心就是面向對象了。JavaScript能做非常多的事情:DOM開發、Ajax開發、Canvas開發、NodeJS開發、前端框架(React ...
一、DOM
JavaScript語言核心。變數的定義、變數的類型、運算符、表達式、函數、if語句、for迴圈、演算法等等。這些東西都屬於語言核心,下次繼續學習語言核心就是面向對象了。JavaScript能做非常多的事情:DOM開發、Ajax開發、Canvas開發、NodeJS開發、前端框架(React、Vue、Angular等等)、HTML5開發。這些都需要語言核心的知識。
DOM開發說白了就是瀏覽器中的頁面效果開發,在2011年之前,DOM開發占據了前端開發工程師的90%的工作;但是現在,DOM開發的工作比重已經降到了10%以下。換句話說,2011年之前,前端 = 做特效的;2011年之後,前端要負責得到後臺的數據介面,用前端MVC邏輯分層開發前端組建、界面、功能,還要寫HTML5,還要做canvas動畫!
上層的框架屏蔽了下層的語言的一些麻煩、不方便的東西,並且提供更方便的API。
jQuery就是乾這個事情的,把JS中的不方便封裝起來,暴露的API都是非常簡便的。
jQuery的哲學就是DOM編程領域的霸主,操作DOM節點、綁定監聽、運動、css樣式、Ajax等等都有封裝。
工作上都是用jQuery,如果不用jQuery也是用類似的東西。沒有人會不用輪子去開發頁面效果。
JavaScript中Library表示“庫”,如果這個庫的功能很強大,甚至顛覆了傳統編程的語法、行文習慣,我們就可以叫做“框架”。
1.1 DOM是什麼
文檔對象模型 (DOM,Document Object Model) 是 HTML 和 XML 文檔的編程介面。它給文檔(結構樹)提供了一個結構化的表述並且定義了一種方式—程式可以對結構樹進行訪問,以改變文檔的結構,樣式和內容。 DOM 提供了一種表述形式— 將文檔作為一個結構化的節點組以及包含屬性和方法的對象。從本質上說,它將 web 頁面和腳本或編程語言連接起來了。
到底什麼是DOM?就是你可以像操作對象一樣操作HTML頁面,而不是操作字元串。
DOM將 web 頁面和腳本或編程語言連接起來了。
回看一下我們之前學習的DOM操作,都在幹嘛?我們在開發特效,但是微觀的看,實際上在進行:
1) 得到HTML節點
2) 改變節點的屬性
3) 改變節點的樣式
4) 修改節點、刪除節點、增加節點
5) 節點之間的關係
1.2原生JavaScript得到節點
document.getElementById('box'); document.getElementsByTagName('p'); |
以上兩是全線瀏覽器都相容的得到元素方法。
以下這些得到元素的方法都不相容IE678。
document.getElementsByName('aaa')[0] //通過name屬性得到元素們 document.getElementsByClassName('pp') //通過類名得到元素們 document.querySelector('#box'); document.querySelectorAll('#box p'); //通過選擇器得到元素們
jQuery是DOM開發的王者!幫我們解決了元素選擇的相容問題。
jQuery底層很強大,比如$('.par1')機制不是getElementsByClassName(),而是在遍歷所有節點,選擇類名有par1的項。
二、原生JavaScript節點關係
在jQuery中學習parent()、children() 、siblings()、next()、prev()等等節點關係,JS中也有對應的屬性。
原生JS提供的節點關係很少:
childNodes、firstChild、lastChild、parentNode、nextSibling、previousSibling
常見的nodeType值:
1-普通元素節點、比如div、p等等 2-屬性節點 3-文本節點 8-註釋節點 9-document節點 10-文檔DTD |
想要查看某一個元素的節點類型,直接讀取它的nodeType屬性即可。
改變nodeType為3的文本節點的內容,要改變他的nodeValue屬性
2.1 childNodes兒子節點
childNodes在IE6、7、8和高級瀏覽器不一致,高級瀏覽器認為所有的換行為空文本節點,而IE678無視這個空文本節點。
div中沒有文本節點,此時應該是4個節點,但是IE9、Chrome、火狐會認為有9個節點、IE8認為有4個節點。
高級瀏覽器會把空文本當做一個節點,標簽前後的空文本也被算作一個。
註釋的前後算不算空文本節點,各個瀏覽器有不同的解釋。所以用節點的時候,一定要去過濾、判斷節點的nodeType是不是1。
<div id="box"> <p></p> <p></p> <p></p> <p></p> </div>
oBox.childNodes.length; //Chrome數值9、IE678是4 |
為解決相容性問題(到底空文本算不算兒子,所以要封裝函數來解決):
可以利用nodeType是不是1來過濾文本節點、註釋節點等,編寫一個函數,得到一個標簽真正的子節點。
jQuery也有這層過濾:
//封裝一個children函數,這個函數能返回obj對象的所有真正兒子節點 function chidlren(obj,num){ var arr = []; //存儲所有兒子 //遍歷所有的節點 for(var i = 0; i < obj.childNodes.length;i++){ //遍歷的過程,尋找真正的HTML兒子節點、過濾文本、註釋等節點 //判斷節點類型是不是1 if(obj.childNodes[i].nodeType == 1){ arr.push(obj.childNodes[i]); //如果是兒子節點就插入數組中 } } //return arr; //返回的是:如果用戶傳入了num,返回某一個兒子,如果沒有num,返回所有兒子 return num ? arr[num] : arr; } chidlren(oBox)[2].style.backgroundColor = 'red'; chidlren(oBox,3).style.backgroundColor = 'red';
2.2 parentNode父親節點
parentNode屬性表示父親節點。任何節點的parentNode的nodeType一定是1,也就是說父親節點一定是標簽節點。文本節點、註釋節點沒有兒子。
var input = document.getElementsByTagName('input'); for(var i = 0;i < input.length;i++){ //當點擊某個input時,如果自己被選中,此時改變父親的顏色為綠色,否則為白色 input[i].onclick = function(){ if(this.checked){ this.parentNode.style.backgroundColor = 'green'; }else{ this.parentNode.style.backgroundColor = 'white'; } } }
2.3 previousSibling和nextSibling兄弟節點
上一個兄弟previousSibling、下一個兄弟nextSibling。同樣的,文本節點也屬於節點,註釋也是節點,所以一個節點的上一個兄弟可能是文本、註釋節點。原生JS中沒有提供類似nextAll()、prevAll()、siblings()方法,如果節點沒有上一個兄弟或下一個兄弟、返回null。
console.log(pp.previousSibling.nodeType) console.log(pp.nextSibling.nodeType) |
var pp = document.getElementById("pp"); //返回obj的前面一個兄弟 function prevSibling(obj){ //開始遍歷obj節點的前面,直到遇見一個nodeType為1的節點 var prev = obj; //迴圈遍歷。註意while的條件是一個賦值語句!賦值語句也有表達式的 while(prev = prev.previousSibling){ if(prev.nodeType == 1){ return prev; } } return null; } //得到真正的後面兄弟 function nextSibling(obj){ //開始遍歷obj節點的前面,直到遇見一個nodeType為1的節點 var next = obj; while(next = next.nextSibling){ if(next.nodeType == 1){ return next; } } return null; } //返回obj的前面所有兄弟 function prevAll(obj){ //開始遍歷obj節點的前面,直到遇見一個nodeType為1的節點 var prev = obj; var arr = []; while(prev = prev.previousSibling){ if(prev.nodeType == 1){ arr.push(prev); } } return arr; } prevSibling(pp).style.background = "red"; nextSibling(pp).style.background = "green"; prevAll(pp)[1].style.background = "green";
三、原生JavaScript DOM節點操作
HTML節點我們原來最多就是改改HTML屬性,比如改改src屬性;或者改改css樣式,比如.style或者.css()。
現在的問題是,我們要增加節點、刪除節點、移動節點、替換節點。
3.1 createElement()創建和appendChild()添加
創建節點的方法: create創建,Element元素。接收一個參數,就是創建的標簽是什麼。
document.createElement() |
追加節點的方法:創建出來的節點不在DOM樹上,所以就應該用appendChild()來添加到DOM樹上:
父親.appendChild(新兒子); |
var btn = document.getElementById('btn'); var txt = document.getElementById('txt'); var ul = document.getElementsByTagName('ul')[0]; btn.onclick = function(){ //創建一個li標簽,用變數oLi 來表示,創建除了的節點不是任何節點的兒子(沒有在DOM樹上) var oLi = document.createElement('li'); oLi.innerHTML = txt.value; //改變這個節點的內容 //把新創建的節點,追加到DOM樹上 ul.appendChild(oLi); }
appendChild()一般來說就是用來追加新創建的節點,如果試圖把頁面上已經有的節點,appendChild()到別的地方,那麼這個節點將移動,也就是說,同一個節點不可能在頁面上兩個地方出現。
比如:
<div id="box1"> <p id="xiaoming">我是小明</p> </div> <div id="box2"> </div> <script type="text/javascript"> var box2 = document.getElementById('box2'); var xiaoming = document.getElementById('xiaoming'); box2.appendChild(xiaoming); </script>
以上將把xioaming移動到box2裡面。
用innerHTML創建節點:
事實上,工作的時候很少用createElement。因為innerHTML足夠好用,innerHTML也可以用來創建節點,甚至效率createElement還高。
var year = document.getElementById('year'); for(var i = 1950; i <= 2018;i++){ //創建節點 var op = document.createElement('option'); //改變創建出來的節點內容 op.innerHTML = i; //上DOM樹 year.appendChild(op); // 父親.appendChild(新兒子); }
innerHTML創建:
for(var i = 1950; i <= 2018; i++) { year.innerHTML += '<option>'+i+'</option>'; }
JavaScript是動態變數:
var oBox = document.getElementById('box'); //得到box裡面所有的p,現在box沒有p,所以ops是一個空數組 var ops = oBox.getElementsByTagName('p'); var np = document.createElement('p'); //創建節點 oBox.appendChild(np); //追加節點 var np = document.createElement('p'); //創建節點 oBox.appendChild(np); //追加節點 var np = document.createElement('p'); //創建節點 oBox.appendChild(np); //追加節點 var np = document.createElement('p'); //創建節點 oBox.appendChild(np); //追加節點 var np = document.createElement('p'); //創建節點 oBox.appendChild(np); //追加節點 //這裡彈出多少?初學者認為彈出0,因為先得到數組p,然後創建節點,節點又沒有往數組裡面push,所以應該ops數不變才對。 //但是JS中存儲DOM節點的變數是動態,不是一個瞬時照片,而是一個有生命的動態對象,當oBox裡面的p標簽變化時,ps也變化 console.log(ops.length)
3.2 insertBefore()添加
appendChild是把新節點插入在父親的所有子節點的後面,也就是說添加的節點就是父親的最後一個兒子。
我們可以在任意一個位置添加子節點:會在原有標桿兒子之前插入
父親.insertBefore(新兒子,原有標桿兒子) |
var btn = document.getElementById('btn'); var txt = document.getElementById('txt'); var ul = document.getElementsByTagName('ul')[0]; var lis = document.getElementsByTagName('li'); btn.onclick = function(){ //創建一個li標簽,用變數oLi 來表示,創建除了的節點不是任何節點的兒子(沒有在DOM樹上) var oLi = document.createElement('li'); oLi.innerHTML = txt.value; //改變這個節點的內容 //把新創建的節點,追加到DOM樹上 //在lis[0]的前面插入 ul.insertBefore(oLi,lis[0]); }
如果想每次都在開頭添加,那麼就是:
ul.insertBefore(oLi,lis[0]); |
lis這個變數是動態的,這次添加的li,下回就是lis[0]。
3.3 removeChild()刪除
父親.removeChild(兒子); <ul> <li>吃飯 <a href="###">刪除</a></li> <li>睡覺 <a href="###">刪除</a></li> <li>打豆豆 <a href="###">刪除</a></li> </ul> <script type="text/javascript"> var ul = document.getElementsByTagName('ul')[0]; var lis = document.getElementsByTagName('li'); var as = document.getElementsByTagName('a'); for(var i = 0;i< as.length;i++){ as[i].onclick = function(){ ul.removeChild(this.parentNode); //如果要自殺,也要找到爸爸 //this.parentNode.removeChild(this); } } </script>
如果要自殺,也要找到爸爸:
this.parentNode.removeChild(this); |
3.4 replaceChild()替換
替換節點:
父親.replaceChild(新兒子,舊兒子) |
<div> <p>趙麗穎</p> <p id="xh">小黑</p> <p>迪麗熱巴</p> </div> <script type="text/javascript"> var oBox = document.getElementsByTagName('div')[0] var xh = document.getElementById('xh'); //創建節點,孤兒節點 var op = document.createElement('p'); op.innerHTML = '朱老師'; //更改op的內容 oBox.replaceChild(op,xh); </script>
3.5 clone()克隆
克隆節點,參數true表示深度克隆,節點裡面的所有內容和事件一同複製。
複製之後的節點是個孤兒節點,所以也要使用appendChild()等方法來添加上DOM樹。
克隆對象.cloneNode(true) |
<div id="box1"> <ul> <li><span>趙麗穎</span></li> <li><span>迪麗熱巴</span></li> <li><span>柳岩</span></li> <li><span>志玲姐姐</span></li> </ul> </div> <div id="box2"> </div> <script type="text/javascript"> var box1 = document.getElementById('box1'); var box2 = document.getElementById('box2'); var ul = document.getElementsByTagName('ul')[0]; var lis = document.getElementsByTagName('li'); //克隆li和li的所有後代(要加true),然後追加到ul中 //ul.appendChild(lis[0].cloneNode()); //克隆第0個li box2.appendChild(ul.cloneNode(true)); //克隆ul追加到box2中 </script>
四、事件監聽
一堆理論知識正要來襲。
4.1事件流
我們考慮一個結構,三個div嵌套,點擊最內層的div,我們點擊了誰?僅僅點擊了最內層div嗎?不是就像手指放在到一個同心圓中,實際上手指觸碰到了任何一個圓。
點擊最內層的div,實際上瀏覽器會認為我們點擊了所有的盒子,甚至於body、document、window。
為了描述事件的傳播,人為規定了一個事件的傳播方向,稱為“事件流”。兩個階段:事件捕獲階段,事件冒泡階段。
“事件流”描述的是頁面上各個元素接收事件的順序。
4.2 DOM0級事件監聽
DOM分級別,DOM0級、1級、2級、3級,是不同的標準,標準一直在升級。
之前學習的on開頭的語法添加事件,稱為“DOM0級事件”。
事件的觸發一定是按照事件流的順序,由於DOM0級只能監聽冒泡階段,所以順序是:box3→box2→box1→body→document→window 如果改變監聽順序,彈出順序不變。
box1.onclick = function(){ alert('我是box1');} box2.onclick = function(){ alert('我是box2');} box3.onclick = function(){ alert('我是box3');} document.body.onclick = function(){alert('我是body')} document.onclick = function(){alert('我是document')} window.onclick = function(){alert('我是window')}
這種監聽寫法,就是DOM0級,就是把onclick當做屬性添加給了div元素。
這種事件添加方法,只能監聽冒泡過程,不能監聽事件捕獲階段。
DOM0級事件處理函數中,this指的是觸發事件的DOM元素,就是事件傳播到的這個元素。
DOM0級事件處理函數中,如果同一個對象,同一個事件名,綁定多個監聽,後面寫的覆蓋前面寫的。
box1.onclick = function(){ alert('我是box1');} box1.onclick = function(){ alert('我是box1,後面寫的');} |
DOM0級事件,IE6、7事件只能冒泡到document,IE8只能冒泡body,不能繼續冒泡到window。也就是說不能給window對象添加事件。
4.3 DOM2級事件監聽
DOM1級規範中,沒有對事件進行改動,所以沒有DOM1級的事情
DOM2級做了新的規範,不用on**來綁定監聽了,而是用一個方法
W3C推出了addEventListener()函數,add添加、event事件,listener監聽
它接收三個參數:事件,函數,是否監聽捕獲階段
元素.addEventListener(事件,事件處理函數,是否添加到捕獲階段) |
第一個參數:事件名不用謝on。(click、mouseover)
第二個參數:函數可以是匿名函數,也可以是有名函數
第三個參數:布爾值,true表示監聽