dom

一、DOM概述

  • D: Document 文檔 一份文檔就是一棵節點樹,每個節點都是一個對象
  • O:Object 對象
    JavaScript語言里對象可以分為三種類型:
    (1)用戶定義的對象(user-defined object) 有程序員自己創建的對象
    (2)內建對象(native object) 內建在JavaScript語言里的對象,如Array、Math、Date等
    (3)宿主對象(host object) 由瀏覽器提供的對象,如 window對象
  • M: 模型,文檔的表現形式。

節點:元素[標簽]節點、文本節點和屬性節點

  • 標簽節點 就是html里的標簽,如<body>、<p>之類的標簽。標簽的名字就是元素的名字
  • 文本節點 就是文本,如<p>標簽包含this is content文本,那么這個文本就是文本節點
  • 屬性節點 被放在起始標簽里,如<p title="title">xxxx</p>,那么title=”title”就是屬性節點

二、CSS(層疊樣式表)告訴瀏覽器應該如何顯示一份文檔的內容

如何引用CSS樣式:

(1)直接在html里寫

<head>
    <style type="text/css">
        body {
            background-color: red;
        }
        p {
            margin-left: 20px
        }
    </style>
<head>

(2)引入CSS文件

<head>
    <link rel="stylesheet" type="text/css" href="mystyle.css">
</head>

元素樣式:

body{
    color:yellow;
    background-color: black;
}

元素樣式還有繼承的功能,類似DOM,CSS也罷文檔的內容視為一棵節點樹,節點樹上的各個元素將繼承其父元素的樣式屬性,比如上面的我們為body元素定義了一些字體和背景顏色,這些顏色不僅作用于那些直接包含在標簽里的內容, 還將作用于嵌套在body元素內部的所有元素。

class樣式(給帶有class屬性的元素設置樣式)

<p class"special">element p has special class</p>
<h2 class="special">element h2 has special class</p>

如下面的樣式,就會對上面的兩個標簽起作用,內容都變成了斜體。

.special{
    font-style: italic;
}

如果只想給h2標簽設置斜體,p標簽不受影響,可以這樣設置樣式:

h2.special{
    font-style: italic;
}

如果即存在.special又存在h2.special,.special樣式還是會影響到所有h2標簽。

id樣式 (給具有唯一id標識的元素設置樣式)

<ul id="purchases">
    <li>支付寶</li>
    <li>微信</li>
    <li>信用卡</li>
</ul>

通過如下方式設置【#】:

#purchases{
    border: 1px solid white;
    background-color:  #333;
    color: #ccc;
    padding: 2em;
}

如果你想給id為purchases的節點的子節點設置單獨設置樣式,可以這樣:

#purchases li{
    font-weight: bold;
}

三、DOM操作節點的方法:

1.getElementById(id);

這個方法返回一個對象,這個對象對應著document對象里一個獨一無二的元素。【實際上,document中的每個元素都是一個對象。利用DOM提供的方法能得到任何一個對象】
節點上設置id屬性,id應該設置為獨一無二的,但是你非得在html節點上加上兩個相同的id,chrome上只會返回第一個。
一般說來,用不著為文檔里的每個元素都定義一個獨一無二的對象。DOM提供了另一個方法來獲取那些沒有id屬性的對象。

2.getElementsByTagName(tag);

getElementsByTagName方法會返回一個對象數組,每個對象分別對應著文檔里有著給定標簽的一個元素。參數tag就是文檔的標簽,比如body、ul、ui等標簽。調用可以參考demo里的getChildNodes()方法。
getElementsByTagName還可以傳遞通配符* ,它會返回文檔里的所有元素節點,document.getElementsByTagName(“*”)。
getElementsByTagName還可以被其他元素節點調用,比如

    var payWays = document.getElementById("purchases");
    var items = payWays.getElementsByTagName("*");

因為在例子中id為purchases元素節點是ul,那么payWays.getElementsByTagName(“*”);返回的是ul里面有多少個ui節點。

3.getElementsByClassName(class);

HTML5 DOM中新增的一個方法。這個方法能夠通過class屬性來訪問元素。該方法返回的也是數組。
如果你想輸入多個className可以用空格隔開。如:

document.getElementsByClassName("name1 name2")

和getElementsByTagName(tag)一樣,getElementsByClassName也可以被其他元素調用,但是不支持*通配符:

var payWays = document.getElementById("purchases");
var items = payWays.getElementsByClassName("sale");

因為getElementsByClassName方法HTML5才出來的,老點的瀏覽器可能不支持,下面方法兼容老版本:

    function getElementsByClassName(node, classname){
    //如果支持,使用新方法
        if(node.getElementsByClassName){
            return node.getElementsByClassName(classname);
        } else{
            var results = new Array();
            var es = node.getElementsByTagName("*");
            for(var i=0;i<es.length;i++){
                if(es[i].className.index(classname)!=-1){
                    results[results.length] = es[i];
                }
            }
        }
    }

4.getAttribute()、setAttribute();

這個兩個方法都是用來獲取和設置節點上的屬性。不屬于document對象。

5.childNodes獲取節點下的所有類型的子元素,該屬性返回一個數組

文檔里的節點不止元素節點、文本節點,屬性節點三個類型的節點。文檔里幾乎每一樣東西都是一個節點。甚至連空格換行都會被解釋為節點,而他們也會全部包含在childNodes屬性返回的數組當中。
上面我們通過getElementsByTagName(““)也可以獲取某個節點下子元素, 注意getElementsByTagName(““)返回的不僅僅是直接子節點,如果子節點包含了節點,也會計算在內的。
雖然childNodes返回所有類型的節點,我們可以通過nodeType獲取節點的類型,但是該屬性只返回整形

6.nodeType 節點的類型

元素節點的nodeType屬性值是1.
屬性節點的nodeType屬性值是2.
文本節點的nodeType屬性值是3.
也就這是三個節點有使用價值。

7.nodeValue 屬性返回文本節點的值。

比如要獲取<p id="xxx">我是標簽p里的值</p>里面的文本的值,其實這里包含了兩個節點,一個元素節點p和里面的文本節點,可以通過下面方式獲取里面的文本:

document.getElementById("xxx").childNodes[0].nodeValue; //document.getElementById("xxx").firstNode.nodeValue;

nodeValue除了可以獲取文本節點的值,還可以修改文本節點的值,如:
p1.firstChild.nodeValue = "通過nodeValue設置新的值"

8.firstChild 和 lastChild 獲取節點數組中首尾節點

var e = document.getElementById(“xxx”);
e.firstChild 相當于 e.childNodes[0]
e.lastChild 相當于 e.childNodes[e.childNodes.length-1]

四、JavaScript最佳實踐

例如我們想點擊一個url然后在新的窗口彈出(這個窗口的大小我們可以設定),可以通過JavaScript偽協議內嵌的事件處理函數來實現。

什么是JavaScript偽協議?

協議就是英特網上的如http、ftp等,協議則是一種非標準化的協議。javascript: 偽協議讓我們可以通過一個鏈接來調用JavaScript函數。如:

<a href="javascript:openWindow()">openWindow</a>

這條語句在支持javascript:偽協議的瀏覽器中運行正常,較老的瀏覽器則會去嘗試打開那個鏈接但失敗。支持這種偽協議但禁用JavaScript功能的瀏覽器將什么都不做。

總之,在HTML文檔中通過javascript:偽協議調用JavaScript代碼的做法非常不好。

內嵌的事件處理函數

<a href="#" onclick="openWindow();return false">openWindow</a>

在HTML指令里使用了return false 語句,瀏覽器不會試圖去打開這個鏈接。

使用內嵌的事件處理函數和使用JavaScript偽協議的做法很糟糕。因為他們都不能平穩的退化,原因有兩點:

(1)因為他們都不能平穩退化,如果用戶已經禁用了瀏覽器的JavaScript功能,這樣的鏈接將毫無用處。
(2)href屬性值不是合法的鏈接,可能會影響搜索引擎上的排名
所以最好的方式就是href設置為合法的url,修改一下openwindow,把url作為參數傳遞進去而不是在方法里寫死。

<a  onclick="openwindow('http://www.baidu.com');return false">openWindow</a>

可以共用前面設置的url

<a  onclick="openwindow(this.getAtrribute('href'));return false">openWindow</a>

看起來比較多,還可以簡化下(this可以代表任何一種當前元素)

<a  onclick="openwindow(this.href);return false">openWindow</a>

JavaScript分離

如何把JavaScript代碼調用行為與HTML文檔的結構和內容分離開,這樣的話,網頁就會健壯多。那么,能否使用下面的語句來實現點擊事件(里面并沒有onclick屬性):

<a  class="popup">open window</a>

JavaScript語言不要求事件必須在HTML文檔里處理,我們可以在外部JavaScript文件里把一個事件添加到HTML文檔中的某個元素上,如下所示:
element.event = action…
如何獲取element上面我們已經講了很多操作方法比如:getElementById(id)、getElementsByTagName(tag)…
那么,如何實現上面的點擊效果呢?
(1)獲取html的a標簽
(2)遍歷a標簽數組
(3)如果某個a標簽的class屬性等于popup,就表示需要給它設置處理點擊事件的函數

var as = document.getElementsByTagName("a");
for(var i = 0; i < as.length; i++){
    if(as[i].getAttribute("class") == 'popup'){
        as[i].onclick = function(){
            openwindow(this.getAttribute("href"));
            return false;
        }
    }
}

測試的時候,發現并沒有用。因為上面的js代碼的第一行是:

var as = document.getElementsByTagName("a");

這行語句將在JavaScript文件被加載時立刻執行,如果引用外部js的代碼<script>標簽調用是在放在標簽<head>里,它將在HTML文檔之前加載到瀏覽器里;如果是</body>標簽之前,就不能保證那個文件先結束加載(瀏覽器可能一次加載多個)。因為文檔加載時文檔可能不完整,所以模型也不完整。沒有完整的DOM,getElementsByTagName函數就不能正常工作。
所以必須讓這些代碼在HTML文檔全部加載到瀏覽器之后馬上開始執行。還在,HTML文檔全部加載完畢時將觸發一個事件 window.onload,當觸發onload事件時,document對象已經存在了。所以把外部js改成如下形式就可以了:

window.onload = preLinks;
function preLinks(){
    var as = document.getElementsByTagName("a");
    for(var i = 0; i < as.length; i++){
        if(as[i].getAttribute("class") == 'popup'){
            as[i].onclick = function(){
                openwindow2(this.getAttribute("href"));
                return false;
            }
        }
    }
}
function openwindow2(url){
    window.open(url,"openwindow","width=600,height=600");
}

這樣我們就成功的把行為和結構分離了

問題又來了,如果瀏覽器沒有啟用JavaScript功能。這樣的話可以確保那些“古老的”瀏覽器不會因為我們的腳本代碼而出問題,這樣做是為了讓腳本有良好的向后兼容性。那些只支持一部分JavaScript功能但不支持DOM的瀏覽器認可訪問我們的網頁。
(我覺得,現在瀏覽器都非常新了,而且沒有不支持DOM的吧。 但是還是記錄下這個知識點。可能這本書比較老,這也體現了這本書作者的思維嚴謹性,值得學習)
判斷某個js方法是否可用可以通過if(functionName),如:

if(!document.getElementById(id)) return false

性能考慮:

(1)盡量減少DOM的操作,比如document.getElementById(id),回去搜索整個DOM樹。所以如果能夠復用操作盡量復用,減少DOM操作。
(2)如果引入多個外部js文件,如果可以的話,把多個js合并到一個js文件,減少加載頁面時發出請求的次數。
(3)壓縮腳本,所謂壓縮腳本,指的是把腳本文件不要的字節,如空格和注釋等統統刪除,達到壓縮文件的目的。很多工具都可以替你來做這件事。有的壓縮工具甚至會重寫你的部分代碼,使用更短的變量名,從而減少整體的文件大小。壓縮后的代碼版本雖然不容易看懂,卻能大幅減少文件的大小。所以要保存兩個版本,一個一個是工作副本(可以修改代碼并添加注釋);另一個是精簡副本,用于存放站點。通常為了與非精簡版本區分開,最好在精簡副本的文件名中加上min字樣。如:

<script src="scriptName.min.js">

下面推薦幾個代表性的壓縮工具:

五、DOM Core 和 HTML-DOM

至此,我們在編寫JavaScript代碼時只用到了一下幾個DOM方法:
getElementById();
getElementsByTagName();
getAttribute();
setAttribute();
這些方法都是DOM Core的組成部分,它們并不專屬于JavaScript,支持DOM的任何一種程序設計語言都可以使用它們。它們的用途也并非僅限于處理網頁。
在使用JS語言和DOM為HTML編寫腳本的時候,還有很多屬性可供選擇,例如我們使用onclick,這是屬于HTML-DOM。例如HTML-DOM提供了一個forms對象:document.getElementsByTagName(“form”),可以簡化為:document.forms。類似的還有element.getAttribute(“src”)簡化為element.src,還有href屬性等。所以你要能夠看懂別人的HTML,明白這兩種寫法。

動態創建DOM節點

上面我們講了通過一系列的方法可以找到DOM的節點、改變節點的屬性(element.setAttribute)。那么如何創建節點呢?

1、使用傳統方法:

(1)document.write() 該方法必須放在body標簽里面,且必須是script標簽里。如:

<body id="content">
    <script type="text/javascript">
        document.write("<p>document write something</p>");
    </script>
</body>

document.write的最大缺點是它違背了“行為應該和表現分離”的原則。所以盡量避免使用。

(2)innerHTML屬性
現如今的瀏覽器幾乎都支持該屬性,但這個屬性并不是W3C DOM標準的組成部分,但現在已經在HTML5的規范中。最早見于IE4瀏覽器中,
該屬性可以用來讀、寫某個給定元素的HTML內容。比如你可以把某個標簽里的所有HTML代碼全部替換成某個文本。但是這樣就沒有細節可言了,如果想要精準的控制還是必須使用DOM的方法和屬性。

2、使用DOM方法

案例一:比如我們想在一個id為container的div節點添加一個p節點,p節點里有個文本“this dynamic node”。怎么實現?
需要用到的DOM方法:createElement、appendChild、createTextNode方法。
首先我們要創建一個element,可以通過document.createElement(nodeName);
所以創建p節點很簡單:
var p = document.createElement(“p”);
并且需要在p節點里添加一個文本,這就要用到創建文本節點方法createTextNode.
vat txt = document.createTextNode(“this dynamic node”);
此時txt節點還是個孤零零的節點,和p節點沒有任何關系,需要通過appendChild為p節點添加txt節點。
p.appendChild(txt);
同樣的,p節點也是孤零零的存在,和div節點沒有任何關系,所以也需要appendChild();

var container = document.getElementById("container");
container.appendChild(p);

案例二:在已有元素前插入一個新元素
DOM提供了名為insertBefore(newElement,targetElement)方法。該方法將把一個新元素插入到一個現有元素的前面。調用該方法需要知道三件事:
(1)新元素:你想插入的元素(newElement)
(2)目標元素:你想把這個新元素插入到哪個元素(targetElement)之前。
(3)父元素:目標元素的父元素(parentElement)。可以通過node.parentNode屬性獲得parentElement

parentElement.inertBefore(newElement,targetElement)

具體的例子可以參考源碼里的testInsertBefore方法,往input之前加入文本

案例三:在已有元素前插入一個新元素
很遺憾,DOM并沒有提供這個方法。所以需要我們自己實現。
結合insertBefore方法和element.nextSibling屬性可以實現。
具體的例子可以參考源碼里的insertAfter方法。源碼鏈接在文章最后給出。

六、CSS DOM

我們在瀏覽器里看到的網頁是由一下三層信息構成的共同體:
結構體 (HTML)
表示層 (CSS)
行為層 (Javascript、DOM)

style屬性

文檔中的每個元素都是一個對象,每個對象又有著各種各樣的屬性。有些屬性告訴我們元素節點樹上的位置節點,比如,parentNode、nextSibling、previousSibling、childNodes、firstChild和lastChild這些屬性,告訴我們文檔中各節點之間關系。
除此之外,文檔的每個元素都有一個style屬性,style屬性包含著元素的樣式,比如給p節點加上一個樣式:

    <p id="localStyle" style="color:grey;font-family: 'Arial',sans-serif;"> this is local style</p>

如果想要獲取p節點的樣式里的顏色怎么辦?
查詢style屬性將返回一個對象,而不是一個簡單的字符串。樣式都存放在這個style對象的屬性里:

element.style.propertyName

所以獲取里面的顏色很簡單:

p.style.color;

需要注意的是在p標簽里我們設置樣式里的顏色是grey,如果我設置的是十六進制的值“#999999”,p.style.color返回的值是RGB格式rgb(153,153,153)
但是獲取font-family,要使用駝峰的方式,CSS是使用-來分隔兩個單詞的,我們通過DOM來獲取有-的style屬性必須使用去掉-然后第二個單詞首字母大寫。如CSS屬性background-color對應的DOM屬性為backgroundColor;CSS屬性font-weight對應這DOM屬性fontWeight;CSS屬性margin-top-width對應著DOM屬性marginTopWidth。

獲取font-family:
p.style.fontFamily
DOM可以獲取style屬性,同樣可以修改樣式:
p.style.color = “black”;

以上我們是通過DOM來操作內嵌樣式,但是這樣的內嵌樣式有很大的局限性。只有把CSS style屬性插入到標記里(標簽),才可以用DOM style屬性去查詢那些信息:

    <p id="localStyle" style="color:grey;font-family: 'Arial',sans-serif;"> this is local style</p>

這樣不是使用CSS的好辦法(表現信息與結構混雜在一起了)。更好的辦法是用一個外部樣式去設置樣式:

    p#localStyle{
        color: grey;
        font-family: 'Arial',sans-serif;
    }

把這段CSS代碼放到一個單獨的文件,然后在HTML引入即可:

    <link href="my.css" rel="stylesheet" type="text/css" />

但是這樣就無法通過DOM獲取style里的屬性了,因為p標簽沒有style屬性節點了。

className屬性

在前面的例子,我們通過DOM直接設置或修改樣式,這種做法讓“行為層”干“表示層”的活,并不是理想的工作方式。
這里有一種簡明的解決方案:與其使用DOM直接修改某個元素的樣式,不同通過JavaScript代碼去更新這個元素的class屬性。
比如我們已經有了下面的樣式:

.special{
    font-style: italic;
}

我們只需要給某個節點設置class屬性為special即可。設置節點的className屬性可以兩種方式:
element.setAttribute(“class”,”special”);
element.className = “special”;
如果想要設置多個className,請空格隔開:
element.className = “special special2”;

七、總結

本博客的內容主要來自《JavaScript DOM編程藝術》。本博客主要總結了如下知識點:

DOM的獲取Element的方法和屬性:

getElementById(id)
getElementsByTagName(tag)
getElementsByClassName(class)
parentNode
childNodes
firstChild
lastChild
nextSibling
previousSibling
getAttribute()
setAttribute()

DOM 動態創建Element

createElement
appendChild
createTextNode
insertBefore

Element屬性

nodeType
nodeValue
style
className

DOM 操作style里CSS屬性以及JavaScript的最佳實踐

代碼可以參考我的githubhttps://github.com/chiclaim/html-javascript-css

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容