腳本化web頁面內容是javascript的核心目標。javascript
第13章和14章解釋了每個web瀏覽器窗口、標籤也和框架由一個window對象所示。每一個window對象有一個document對象,document對象表示窗口的內容,它就是本章的主題。儘管如此,Document對象並不是獨立的,它是一個巨大的API的核心對象,叫作文檔對象模型(Document Object Model ,DOM),它表明和操做文檔的內容。php
本章開始部分解釋DOM的基本框架,而後進一步解釋如下內容:css
本章最後一節涵蓋其它各類文檔特性,包括referrer屬性、write()方法和查詢當前文檔中選取的文檔文本的技術等。html
文檔對象模型(DOM)是表示和操做HTML和XML文檔內容的基礎API。API不是特別複雜,可是須要了解大量的構架細節。首先:應該理解HTML或XML文檔的嵌套元素在DOM樹對象中的表示。HTML文檔樹的結構包含表示HTML標籤或元素(如body,<p>)和表示文本字符串的字節,它也可能包含HTML註釋的節點。考慮一下簡單的HTML文檔:html5
<html> <head> <title>title name</title> </head> <body> <h1>an html Document</h1> <p>this is a <i>simple</i>docuent</p> </body> </html>
在一個節點之上的直接節點是其父節點,在其下一層的直接節點是其子節點。在同一層上具備相同父節點的是兄弟及誒單。在一個節點之下的全部層級的一組節點是其後代節點。一個節點任何父節點和其上層的全部節點是祖先節點。java
上圖的每一個方框是文檔的一個節點,它表示一個Node對象,後續和第四部分會講。上圖中包含三種類型不一樣的節點。樹形的根部是Document節點,它表明整個文檔。 表明HTML元素的節點是Element節點,表明文本的節點是Text節點。Document、Element和Text是Node的子類,在第四部分中它們有本身的條目。Document和Element是兩個重要的DOM類,本章大部份內容將刪除它們的屬性和方法。node
下圖展現了Node及其在類型層次結構中的子類型。注意,通用的Document和Element類型與HTMLDocument和HTMLElement類型之間有嚴格的區別。Document類型表明一個HTML或XML文檔 ,Element類型表明了該文檔中的一個元素。HTMLDocument和HTMLElement子類只是針對於HTML文檔和元素。jquery
值得注意的是,上圖中HTMLElement的不少子類型表明HTML元素的具體類型。每一個類型定義多個javascript屬性,它們對應具體的元素或元素組,(本章15.4.1節)的HTML屬性。有些具體元素也定義額外的屬性和方法,它們並非簡單地映射HTML語法。第四部分涵蓋這些類型及其額外的特性。css3
最後,上圖還展現了到目前還未接觸的一些節點類型,Comment節點表明HTML或XML的註釋。因爲註釋基本上是文本字符串,所以它們很像表示文檔中顯式文本的Text節點。CharacterData一般是Text和Conmment的祖先,它定義這兩種節點所共享的方法。Attr節點類型表明XML或HTML屬性,但它幾乎從不使用,由於和文檔節點不一樣,Element類型定義了將屬性當作「名/值」對使用方法。DocumentFragment類(未在上圖顯式)在實際文檔中並不存在的一種節點:它表明一系列沒有常規父節點的節點,對一些文檔操做來講DocumentFragment很是有用,15.6.4節將涵蓋這部份內容。DOM也定義了一些不常使用的類型,如像表明Doctype聲明和xml處理指令等類型。程序員
大多數客戶端javascript程序運行時老是在操做一個或多個文檔元素,這些程序啓動時,可使用全局變量document來引用Document對象。可是,爲了操做文檔中的元素,必須經過某種方式來得到或選取這些文檔元素的Element對象,DOM定義了不少方式來選取元素,查詢文檔的一個或多個元素有以下的方法:
id屬性在文檔中值必須惟一,能夠用Document對象的getElementById()方法選取一個基於惟一ID的元素。
若是須要經過ID查找多個元素,可用下面的getElements()函數:
/** * 函數接受任意多的字符串參數 * 每一個參數將當作元素的id傳給document.getElementById() * 返回一個對象,它把這些id映射到對應的Element對象 * 如任何一個id對應的元素未定義,則拋出一個Error對象 **/ function getElement( /*ID(s)*/ ) { var elements = {}; //開始是一個map映射對象 for (var i = 0; i<arguments.length; i++) { //循環每一個參數 var id = arguments[i]; //參數是元素的id var elt = document.getElementById(id); //查找元素 if (elt == null) throw new Error("No element with id: " + id); //拋出異常 elements[id] = elt; //id和元素之間的映射 } return elements; //對於元素映射返回id }
在低於IE8版本的瀏覽器中,getElementById()對匹配元素不區分大小寫,並且也返回匹配name屬性的元素。
HTML的name屬性最初打算爲表單元素分配名字,在表單數據提交到服務器時使用該屬性的值。相似id屬性,name是給元素分配的名字,可是區別id,name的屬性值不是必須惟一:多個元素可能有一樣的名字,在表單中,單選和複選按鈕一般是這樣的狀況。並且和id不同的是name屬性值是在少數HTML元素中有效,包括表單、表單元素、<iframe>和<img>元素。
基於name屬性的值選取html元素,可使用Document對象的getElementByName()方法。
getElementsByName()定義在HTMLDocument類中,而不在Document類中,全部它只徵對HTML文檔可用,在xml文檔中不可用。它返回一個NodeList對象,後者的行爲相似若干Element對象的只讀數組。在IE中,getElementByname()也返回id屬性匹配中只讀的元素。爲了兼容,應該當心謹慎,不要將ID和name同名。
在14章7節中咱們看到,爲某些HTML元素設置name屬性將自動爲window對象中建立對於的屬性,對Document對象也相似。爲<form><img><iframe><applet><embed><object>元素(其中只有<object>元素沒有後背對象)設置name屬性值, 即在Document對象中建立以此name屬性值爲名字的屬性。
若是給定的名字只有一個元素,自動建立的文檔屬性對於的值該是元素自己。若是有多個元素,該文檔屬性的值是一個NodeList對象,它表現爲一個包含這些元素的數組。如14章7節所示,爲若干命名<iframes>元素建立的文檔屬性比較特殊:它們指代這些框架的window對象而不是Element對象。這就意味這有些元素能夠做爲Document屬性僅經過名字來選取:
//徵對<form name="shipping">元素,獲得Element對象 var form = document.shipping;
在14章7節介紹了爲何不要用爲窗口對象自動建立的屬性,這一樣適用用爲文檔對象自動建立的屬性。若是須要查找命名的元素,最好顯式地調用getElementByName()來查找它們。
Document對象的getElementsByTagName()方法可用來選取指定類型(標籤名)的全部HTML或XML元素。元素不區分大小寫。
相似於getElementByName(),getElementByTagName()返回一個NodeList對象(關於NodeList類,見本節補充的信息)。在NodeList中返回的元素按照在文檔中的順序排序的。
給getElementByTagName()傳遞通配符參數"*"將得到一個表明文檔中所願元素的NodeList對象。
Element定義getElementByTagName()方法,其原理和Document版本同樣,可是它只選取調用該方法的元素的後代元素。所以,要查找文檔的第一個<p>元素裏全部<span>元素,代碼以下:
var firstpara = document.getElementsByTagName("p")[0]; var firstParaSpan = firstpara.getElementsByTagName("span");
因爲歷史的緣由,HTMLDocument類定義的一些快捷屬性來訪問各類各樣的節點。例如images、forms和links等屬性行爲執行相似只讀數組<img>、<from>和><a>(但只包含哪些有href屬性的<a>標籤)元素結合(能夠在控制檯輸入document.images試試)。這些屬性指代HTMLCollection對象,它們很像NodeList對象,可是除此以外它們能夠用元素的id或名字來索引,咱們已經看到用法以下的表達式來索引一個命名的<form>元素:
document.shiping;
用document.forms屬性也能夠更具體地引用命名(或有ID)表單,以下:
document.forms.shipping
還定義embeds和plugins屬性,它們是同義詞,是HTMLCollection類型的<embed>元素的集合。anchors是非標準屬性,指代有一個name屬性的<a>元素而不是一個href屬性。scripts在HTML5中是標準屬性,是<script>元素的集合。
HTMLDocument對象還定義兩個屬性,它們指代包含特殊的單個元素而不是元素的集合。document.body是一個HTML文檔的 <body>元素,document.head是<head>元素。這些屬性老是會定義,若是文檔沒有顯式包含它們,瀏覽器將隱式地建立它們。Document類的documentElement屬性指代文檔的根元素,在HTML文檔中,它總指代<HTML>元素。
節點列表和HTML集合
節點列表和HTML集合 getElementByName()和getElementByTagName()都返回NodeList對象,相似document.images和document.forms的屬性爲HTMLCollection對象 這些對象都是隻讀的類數組對象(7章11節)
for (var i = 0; i < document.images.length; i++) //循環全部的圖片 document.images[i].style.display = "none";
能夠間接地使用調用Array的方法。
因爲歷史元素,NodeList和HTMLCollection對象也都能當作函數:以數字或字符串爲參數調用它就如同使用數字或字符串索引它們通常,不鼓勵這種怪異的方法。
NodeList和HTMLCollection對象都定義了item()方法,指望輸入一個整數,並返回此處索引的元素。相似的,HtmlCollection定義了namedItem()方法,它返回指定屬性名的值。但這些都不必,由於能夠直接用索引。
NodeList和HTMLCollection對象不是歷史文檔狀態的一個靜態快照,而一般是實時的,而且當文檔變化時它們所包含的元素能隨之改變,這是其中一個重要和使人驚訝的特性。假設在一個沒有<div>元素的文檔中調用getElementByTagName("div"),此時返回值是一個length爲0的NodeList對象。若是再在此文檔中插入一個新的<div>,元素將自動成爲NodeList的一個成員,而且它的length屬性變成1。
一般,NodeList和HTMLCollection的實時性很是有用,若是要迭代一個NodeList對象時再文檔中添加或刪除元素,首先要對NodeList對象生成一個靜態的副本:
var snapshot = Array.prototype.slice.call(nodelist,0)
HTML的class屬性是用空格分開的有0個或者多個標識符的列表。class是javascript的保留詞,所以客戶端JavaScript使用className屬性來得到HTML中class的屬性。class屬性一般和CSS樣式表一同使用將一樣的樣式做用在一系列元素之上,咱們將在16章繼續瞭解它。另外,HTML5定義了一個方法,getElementsByClassName(),容許咱們經過class屬性的標識符選擇一系列文檔中的元素。
相似getElementsByTagName(),能夠做用在HTML文檔和HTML元素上調用getElementsByClassName() ,返回一個實時的NodeList對象,包含全部文檔或元素節點中匹配的後代節點。getElementByClassName()只須要傳入一個字符串參數,字符串參數可使用空格隔開的標識符。只有當元素的class屬性徹底包含標識符的元素纔會被匹配,可是標識符的順序可有可無。注意class屬性和getElementsByClassName()方法都是用空格進行分隔,而不是逗號。下面是使用getElementsByClassName()的一些例子:
// 找到全部class屬性值爲"warning"的元素 var warnings = document.getElementsByClassName("warning"); // 查找奕log命名且包含有"error"和"fatal"類的元素的全部後端 var log = document.getElementById("log"); var total = log.getElementsByClassName("error fatal");
其中一個怪異行爲就是在class屬性中和CSS樣式表中的類標識符是不區分大小寫的。getElementsByClassName()使用樣式表的匹配算法。當一個文檔在怪異模式下運行時是大小寫不敏感的,不然,該比較區分大小寫。
選擇器用來描述文檔中的若干或多組元素。本書不會詳細介紹。
1.元素能夠用ID、標籤名或類描述;
2.元素能夠給予屬性值來選取:
p[lang = "fr"] //全部使用語法段落,如:<p lang="fr"> * [name = "x"] //全部包含name = "x"屬性的元素
3.這些基本的選擇器能夠組合使用
span.fatal.error //其class中 包含"fatal"和"error"的全部<span>元素 span[lang = "fr"].warning //全部使用語法且class中包含"warning"的<span>元素
4.選擇器能夠指定文檔結構
#log span //id="log"元素中全部的<span>元素 #log>span //id="log"元素的子元素中的全部<span>元素 body>h1:first-child //<body>的子元素中的第一個<h1>元素
5.選擇器能夠組合起來選取多個或多組元素
div,#log //全部<div>元素,以及id="log"的元素
與css3選擇器的標準化一塊兒的另外一個稱做「選擇器API」的w3c標準定義了獲取匹配一個給定選擇器元素的javascript方法(選擇器API標準不是HTML5的一部分,但與之有相關緊密聯繫,更多http://www.w3.org/TR/selectors-api/)該API的關鍵是Document方法querySelectorAll()。它接受包含一個css選擇器的字符串參數,返回一個表示文檔中匹配選擇器的全部元素的NodeList對象。與前面描述的選取元素的方法是不一樣的,querySelectorAll()返回的nodeList對象並非實時的:它包含在調用時刻選擇器所匹配的元素,但它並不更新後續文檔的變化。若是沒有匹配的元素,querySelectorAll()將返回一個空的NodeList對象。若是選擇器字符串非法,querySelectorAll()將拋出一個異常。
document.querySelector("#doc") //選擇id="doc"
除了querySelectorAll(),文檔對象還定義了querySelector()方法。與querySelectorAll()工做原理類似,但它只返回第一個匹配的元素(以文檔順序)。或者若是沒有匹配的元素則返回null。
css還定義了一些僞元素,":first-line"和"first-letter"等。在css中它們匹配的文本節點一部分不是實際元素。若是和querySelectorAll()或querySelector()一塊兒使用它們是不匹配的。並且不少瀏覽器會拒絕返回「:link」和":visited"等僞類匹配結果,由於這會泄露用戶的瀏覽歷史記錄。
當期全部的瀏覽器都支持querySelectorAll()和querySelector()方法。可是注意,這些方法的規範並不要求支持css3瀏覽器:鼓勵瀏覽器和在樣式表中同樣的選擇器集合。當前的瀏覽器除IE都支持css3選擇器,IE7和IE8支持css2選擇器。
querySelectorAll()是終級的選取元素的方法:它是一種強大的技術 ,經過讓客戶端javascript程序可以選擇它們想要操做的元素。幸運的是,甚至在沒有querySelectorAll()的原生支持的瀏覽器均可以使用css3選擇器。jQuery庫使用這種基於css選擇器的查找做爲它的核心編程規範式。基於jQuery的web應用程序使用一個輕便的、跨瀏覽器的、和querySelectorAll()等效的方法,命名爲$().
jQuery的css3選擇器匹配代碼以及做爲一個獨立的標準庫併發布了,命名爲Sizzle(http://sizzlejs.com/)。它已經被Dojo和其它一些客戶端庫所採納.
在DOM標準化以前,IE4引入了document.all[]集合來表示全部文檔中的元素(除了Text節點)。document.all[]已經成被標準的方法,由於有上述方法能夠得到元素,如今已經廢棄不使用了,可是引入它是革命性的,它在以各類方式使用的已有代碼中仍然能看到:
document.all[0] //文檔中的第一個元素 document.all["nav"] //id或name爲"nav"的元素(或多個元素) document.all.nav //同上 document.all.tags("div") //文檔中的全部div元素 document.all.tags("p")[0] //文檔中的第一個<p>元素
一旦從文檔中選取了一個元素,有時須要查找文檔中與之在結構上相關的部分(父親、兄弟和子女)。
Document對象、它的Element對象和文檔中表示文本的Text對象都是Node對象。Node定義瞭如下重要屬性:
使用這些Node屬性,能夠用下面相似的表達式獲得文檔的第一個節點下面的第二個子節點的引用:
document.childNodes[0].childNodes[1];
document.firstChild.firstChild.nextSibling;
<html> <head> <title>test</title> </head> <body> hello world! </body> </html>
那麼第一個字節點下的第二個元素就是BODY,它的nodeType爲1,nodeName爲BODY。
當將主要的興趣點集中在文檔中的元素上而並不是它們之間的文本(和它們之間的空白)上時,咱們可使用另一個更有用的API.它將文檔看作是Element對象樹,忽略部分文檔:Text和Comment節點。
該API的第一部分是Element對象的children屬性。相似ChildNodes,它是一個NodeList對象,但不一樣是的children列表只包含Element對象。Children非標準屬性,可是在全部當前瀏覽器裏都能工做。IE也實現好一段時間了,大多數瀏覽器也如法炮製,最後採納的是Firefox3.5。
注意:Text和Comment節點沒有children屬性,它意味着上述Node.parentNode屬性不可能返回Text或Comment節點。任何Element的parentNode老是另外一個Element,或者追溯到樹根的Document或DocumentFragment節點
基於元素的文檔遍歷API的第二部分是Element屬性,後者相似Node對象的子屬性和兄弟屬性:
子元素和兄弟元素的屬性是標準屬性,並在除IE以外的瀏覽器已經實現http://www.w3.org/TR/ElementTraversal/。
因爲逐個元素的文檔遍歷的API並未徹底標準化,咱們任然能夠經過像下面的例子可移植的遍歷函數那樣實現這種功能:
/*****可移植的遍歷函數******/ /** * 返回元素e的第n層祖先元素,若是不存在此類祖先或祖先不是Element,例如(Document或者DocumentFragment)則返回null * 若是n爲0,則返回e自己,若是n爲1(或省略),則返回父元素。若是n爲2,則返回祖父元素,依次類推 **/ function parent(e, n) { if (n === undefined) n = 1; while (n-- && e) e = e.parentNode; if (!e || e.nodeType !== 1) return null; return e; } /** *返回元素e的第n個兄弟元素,若是n爲正,返回後續的第n個兄弟元素; * 若是n爲負,返回前面n個兄弟元素,若是n爲零,返回e自己 **/ function sibling(e, n) { while (e && n !== 0) { //若是e未定義,馬上返回它 if (n > 0) { //查找後續的兄弟元素 if (e.nextElementSibling) e = e.nextElementSibling; else { for (e = e.nextSibling; e && e.nodeType !== 1; e = e.nextSibling) /*空循環*/ ; } n--; } else { //查找前邊的兄弟元素 if (e.previousElementSibling) e = e.previousElementSibling; else { for (e = e.previousElementSibling; e && e.nodeType !== 1; e = e.previousElementSibling) /*空循環*/ ; } n++ } } return e; } /** * 返回元素e的第n代子元素,若是不存在則爲null * 負值n表明從後往前計數,0表示第一個子元素,而-1表明最後一個,-2表明倒數第二,依次類推 **/ function child(e, n) { if (e.children) { //若是children數組存在 if (n < 0) n += e.children.length; //轉換負的n爲數組索引 if (n < 0) return null; //若是它仍爲負,說明沒有子元素 return e.children[n]; //返回值指定的子元素 } //若是e沒有children數組,找到第一個字元素並向前數,或找到最後一個子元素並往回鼠 if (n >= 0) { //非負,從第一個元素向前數 //找到e的第一個子元素 if (e.firstElementChild) e = e.firstElementChild; else { for (e = e.firstElementChild; e && e.nodeType !== 1; e = e.nextSibling) /*空循環*/ ; } return sibling(e, n); //返回第一個子元素的第n個兄弟嚴肅 } else { //n爲負數,從第一個元素往回數 if (e.lastElementChild) e = e.lastElementChild; else { for (e.lastElementChild; e && e.nodeType !== 1; e = e.previousElementSibling) /*空循環*/ ; } return sibling(e, n + 1); //+1來轉化最後1個子元素的最後1個兄弟元素 } }
自定義Element的方法
全部當前瀏覽器,都實現了DOM,故相似Element和HTMLDocument(IE8支持Element、HTMLDocument和Text的可擴展屬性,但不支持Node,Document、HTMLDocument或HTMLElement更具體的子類型的可擴展屬性)等類型都是想String和Array都是類。它們不是構造函數(將在本章後面看到如何建立新的Element對象),但它們有原型,能夠用自定義方法擴展它。
。。。。。。
HTML元素包含一個標籤和一組稱爲屬性(Attribute)的名/值對組成。例如:<a>元素定義了一個超連接,經過使用href屬性來指向連接的目的地址。HTML元素的屬性值在表明這些元素的HtmlElement對象的屬性(property)中是可用的。DOM一樣定義了一些API來設置和獲取XML屬性和非標準化的HTML屬性。下面將詳細介紹這些特性。
表示HTML文檔中元素的HTMLElement對象定義了讀/寫對的屬性,它們映射了元素的HTML屬性。HTMLElement定義了通用的HTTP屬性(id,title ,lang,dir)和像onclick這樣的事件處理屬性,特定Element的子類型定義了這些元素的特定屬性。爲了查詢一個圖片的URL地址,你可使用表示<img>元素的HTMLElement的src屬性:
var image = document.getElementById("myimage"); var imgurl = image.src;//src是圖片的url image.id === "myimage"; //true
類似地,你經過下面這樣的代碼設置<form>元素提交的屬性:
var f = document.forms[0]; // First <form> in the document f.action = "http://www.example.com/submit.php"; // Set URL to submit it to. f.method = "POST";
HTML屬性名是大小寫不敏感的,可是JavaScript的屬性名是大小寫敏感的。從HTML屬性名轉化爲JavaScript屬性名應該採用小寫。可是若是一個屬性名包含不止一個單詞,以駝峯方式命名:如defaultChecked和tabIndex。
有些HTML屬性名在JavaScript中是保留字,因此通常都須要加"html"前綴。例如HTML中的for屬性(<label元素>)在JavaScript中變成htmlFor屬性。有一個例外:class屬性在JavaScript用className屬性,16章會講。
有些HTML的屬性的值一般是字符串。當屬性爲布爾值或數值(例如,<input>元素的defalultChecked和maxLength屬性,)屬性也是布爾值或數字,而不是字符串。事件處理程序屬性值老是Function對象(或null)。HTML5規範定義了一個新的屬性(如<input>和相關元素的form屬性)用以ID轉換爲實際的Element對象。最後,任何元素的style屬性值是CSSStyleDeclaration對象,而不是字符串,咱們將在16章看到這個重要的屬性值的更多信息。
注意:這個基於屬性的API用來獲取和設置屬性,但沒有定義任何從元素刪除屬性的方法。奇怪的是,delete操做符也沒法完成此項目。下一節描述能夠實現此目的方法。
像上面描述的那樣,HTMLElement及其子類型定義了一些屬性,它們對應於元素的標準HTML屬性。Element類型一樣定義了getAttribute()和setAttribute()方法來查詢和設置非標準的HTML屬性,固然也能夠用來查詢和設置XML文檔中元素上的屬性。
var image = document.images[0]; var width = parseInt(image.getAttribute("width")); image.setAttribute("class","firstImage");
從上面看出這些方法和前面的基於屬性的API方法兩個很重要的區別。首先,屬性值都被視做字符串。getAttribute()不會返回數值、布爾值或者對象。其次,這些方法是用標準的屬性名,簡基於屬性的API,甚至這些名稱爲javascript保留字時都不例外。對於HTML元素來講,屬性名不區分大小寫。
Element還定義了兩個相關方法,hasAttribute()和removeAttribute(),檢查命名屬性是否存在和徹底地刪除這個屬性。當屬性爲布爾值時這些方法特別有用:有些屬性(如HTML的表單元素的disabled屬性)在一個元素中是否存在是重點關鍵,而其餘值可有可無。
若是操做包含來自其餘命名空間中屬性的XML文檔,你可使用四種方法的命名空間版本:getAttributeNS(),setAttributeNS,hasAttributeNS()和removeAttributeNS()。這些方法須要兩個屬性名字符串做爲參數。第一個是標識命名空間的URI,第二個一般是屬性的本地名字,在命名空間中是無效的。但特別的,setAttributeNS()地第二個參數應該是屬性的有效名字,它包含命名空間的前綴。能夠在本書的第四部分中閱讀更多關於命名空間的屬性的方法。
有時在HTML元素上綁定上額外的信息是頗有用的,特別是當JavaScript代碼須要選擇這些元素並按必定方法操縱他們的時候。有時能夠經過爲class屬性添加特定的標識符來完成一些工做。其餘時候,對於某些複雜的數據而言,客戶端程序員會藉助非標準的屬性。如上文提到的,你可使用getAttribute()和setAttribute()方法來讀取和改寫非標準屬性的值。固然你所付出的代價是文檔將再也不是合法有效的HTML。
HTML5提供了一個解決方法,任意以小寫data-做爲前綴的屬性名字都是合法的。這些「數據集屬性」不會影響元素的表現,它們定義了標準的、附加額外數據的方法,並非在文檔合法性上作出讓步。
HTML5也在Element對象上定義了dataset屬性。這個屬性指代一個對象,它的各個屬性對應於去掉前綴data-屬性。所以dataset.x應該保持data-x屬性的值。帶連字符的屬性對應駝峯命名的屬性名:data-jquery-test屬性變成dataset.jqueryTest屬性。
<span class="sparkline" data-ymin="0" data-ymax="10"> 1 1 1 2 2 3 4 5 5 4 3 5 6 7 7 4 2 1 </span>
火花線是一個圖像——一般是一條線——設計用來在文本流中顯示。爲了建立一個條火花線,也許能夠以下代碼提取上述的dataset屬性的值 。
//假設ES5 Array.map() 方法 (或相似的方法)有定義 var sparklines = document.getElementsByClassName("sparkline"); for (var i = 0; i < sparklines.length; i++) { var elt = sparklines[i]; var ymin = parseFloat(elt.getAttribute("data-ymin")); var ymin = parseFloat(elt.getAttribute("data-ymax")); var points = elt.getAttribute("data-points"); var data = elt.textContent.split(" ").map(parseFloat); drawSparkline(elt, ymin, ymax, data); // 此方法未實現 }
或者能夠用上述提到的獲取屬性的方法。
注意:dataset屬性是(實現的時候會這樣)元素的data-屬性的實時、雙向接口。設置或刪除dataset的一個屬性就等同於設置或移除對應元素的data-屬性。
上面例子中的drawSparkline()函數是虛構的,21章會給出<canvas>繪製相似火花線的標記代碼。
還有一種使用Element的屬性的方法。Node類型定義了attributes屬性。針對非Element對象的任何節點,該屬性爲null。對於Element對象,attributes屬性是隻讀的類數組對象,它表明元素的全部屬性。相似NodeLists,attributes對象是實時的。它能夠用數字索引訪問,這意味着可枚舉元素的全部屬性。而且,它也能夠用屬性名索引:
document.body.attributes[0]; //<body>元素的第一個屬性 document.body.attributes.bgColor //<body>元素的bgColor屬性 document.body.attributes["ONLOAD"] //<body>元素的onload屬性
當索引attributes對象時獲得的值是Attr對象。Attr對象的一類特殊的Node,但歷來不會像Node同樣去用。Attr的name和value屬性返回屬性的名字和值。
看上面樹狀圖的列子,並問本身一個問題<p>元素的「內容」是什麼?回答這個問題有三個方法:
每一種回到都有效,後面幾節咱們將解釋如何使用HTML表示、純文本和元素內容樹狀表示
讀取Element的innerHTML屬性做爲字符串標記返回那個元素的內容。在元素上設置該屬性調用了web瀏覽器的解釋器,用新的字符串內容的解析展示形式替換元素當前內容。
web瀏覽器很擅長解析HTML,一般設置innerHTML效率很是高,甚至在指定的值須要解析時效率也至關不錯,注意對innerHTML屬性用「+=」操做符重複追加一小段文本的效率很是低下,由於它即要序列化又要解析。
html5到來它才使得innerHTML變得標準化。HTML5還標準化了outerHTML屬性。當查詢outerHTML時,返回HTML或XML標記字符串包含被查詢元素的開頭和結尾標籤。當設置元素的outerHTML時,元素自己被新內容所替換。只有Element節點定義了outerHTML屬性,Document節點則無。
IE引入的另一個特性是insertAdjacentHTML()方法,在HTML5標準化,它將任意HTML標記字符串插入到指定的元素「相鄰」的位置。標記是該方法的第二個參數。而且相鄰的精確含義依賴於第一個參數的值,第一個參數爲具備如下值之一的字符串:"beforebegin"、"afterbegin"、"beforeend"、afterend、.這些值對於以下:
後續有相關內容。
另外一種方法處理元素的內容是當作一個子節點列表,每一個子節點均可能有它本身一組子節點,當考慮元素的內容時,一般感興趣的是它的Text節點。在XML文檔中,你必須準備好處理CDATSection節點——它是Text的子類型,表明CDATA段的內容。
下面的例子展現了一個textContent()函數,它遞歸地遍歷元素的子節點,而後鏈接後代節點中全部的Text節點文本,爲了理解代碼,必須回想一下nodeValue屬性(定義在Node類型中),它保存Text節點的內容。
/**查找元素的後代節點中全部的Text節點**/ //返回元素e的純文本內容,遞歸進入其子元素 //該方法的效果相似於textContent屬性 function textContent(e) { var child, type, s = ""; //s保存全部字節點文本 for (child = e.firstChild; child != null; child = child.nextSibling) { type = child.nodeType; if (type === 3 || type === 4) //text和CDATASection節點 s += child.nodeValue; else if (type === 1) s += textContent(child); } return s; }
nodeValue屬性能夠讀/寫,設置它能夠改變Text或CDATASection節點所顯示的內容。Text和CDATA都是CharacterData子類型,能夠在第四部分查看相關信息。CharacterData定義了data屬性,它和nodeValue的文本相同,如下函數經過設置data屬性將Text節點的內容轉換爲大寫。
//遞歸把n的後代子節點中的全部Text節點內容轉換爲大寫形式 cd = document.getElementsByTagName("p")[0]; function upCase(n) { if (n.nodeType == 3 || n.nodeType == 4) //若是n是Text或CDATA節點 n.data = n.data.toUpperCase(); //轉換爲大寫 else for (var i = 0; i < n.childNodes.length; i++) upCase(n.childNodes[i]); }
咱們已經看到用HTML和純文本字符串如何來查詢和修改文檔內容,也已經看到咱們可以遍歷Document來檢查組成Document的每一個Element和Text節點,在每一個節點基本修改文檔也是有可能的。Document類型定義了建立Element和Text對象的方法,Node類定義了在節點樹中插入、刪除和替換的方法。
function loadsync(url){ var hand = document.getElementsByTagName("head")[0]; var s = document.createElement("script"); s.src = url; hand.appendChild(s); }
建立新的Element節點可使用Document對象的createElement()方法。給方法傳遞元素的標籤名:對HTML文檔來講名字不容易區分大小寫,對XML文檔則區分大小寫。
Text節點用相似的方法建立
var newnode = document.createTextNode("Text node");
Document也定義了一些其餘的工廠方法,如不常用的createComment(),15.6.4節使用了createDocumentFragment()方法。在使用了XML命名空間的文檔中,可使用createElementNS()來同時指定命名空間的URI和待建立的Element標籤的名字。
另外一種建立新文檔的方法是複製已經存在的節點。每一個節點上有一個cloneNode()方法來返回該節點的一個全新副本。給方法傳遞參數true也可以遞歸地複製全部後代及節點,或傳遞參數false只是執行一個淺複製。在除了IE的其它瀏覽器中,Document對象還定義了一個相似的方法叫importNode().若是給它傳遞另外一個文檔的一個節點,它將返回一個適合本文檔插入的節點的副本。傳遞true爲第二參數,此方法將遞歸導入全部的後代節點。
一旦有了有了一個新的節點,就能夠用Node的方法appendChild()或insertBefore()將它插入到文檔中.appendChild()是在須要插入的Element節點上調用的,它插入指定的節點將其成爲那個節點的最後一個節點。
insertBefore()就像appendChild()同樣,除了它接受兩個參數,第一個是待插入的節點,第二個參數是已經存在的節點,新節點將插入該節點的前邊。該方法應該是在新節點的父節點上調用,第二個參數必須是該父節點的子節點。若是傳遞null做爲第二個參數,insertBefore()的行爲相似appendChild(),它將節點插入在最後。
這是一個在數字索引的位置插入節點的簡單函數。同時展現了appendChild()和insertBefore()方法
//將child節點插入到parent中,使其成爲第n個子節點 function inserAt(parent, child, n){ if (n < 0 || n > parent.childNodes.length) throw new Error("invalid index"); else if (n == parent.childNodes.length) parent.appendChild(child); else parent.insertBefore(child, parent.childNodes[n]); }
若是調用appendChild或insertBefore()將已存在的文檔中的一個節點再次插入。那個節點將自動從它當前的位置刪除並在新的位置從新插入:不必顯式刪除改節點。下面的的例子展現了一個函數,基於表格指定列中單元格的值來進行行排序。它沒有建立任何新的節點。只是用appendChild()改變已存在的節點。
/**表格的行排序**/ //根據指定表格每行第n個單元格的值,對第一個<tbody>中的進行排序 //若是存在comparator函數則使用它,不然按字母表順序比較 function sortrows(table, n, comparator) { var tbody = table.tBodies[0]; //第一個<tbody>,多是隱式窗口的 var rows = tbody.getElementsByTagName("tr"); //tbody中全部行 rows = Array.prototype.slice.call(rows, 0); //真實的數組 //基於第n個<td>元素的值對行排序 rows.sort(function(row1, row2) { var cell1 = row1.getElementsByTagName("td")[n]; //得到第n個單元格 var cell2 = row2.getElementsByTagName("td")[n]; //兩行都是 var val1 = cell1.textContent || cell1.innerText; //得到文本內容 var val2 = cell2.textContent || cell2.innerText; //同上,兩單格都是 if (comparator) return comparator(val1, val2); // 進行比較 if (val1 < val2) return -1; else if (val1 > val2) return 1; else return 0; }); //在tobody中按他們的順序把行添加到最後 //這將自動把它們從當前位置移走,故不必預先刪除它們 //若是<tbody>還包含除了<tr>的任何其餘元素,這些節點都將會懸浮到頂部位置 for (var i = 0; i < rows.length; i++) tbody.appendChild(rows[i]); } //查找表格的<th>元素,假設只有一行,它們能夠單擊 //以便單擊列標題,按列對行排序。 function makeSortable(table) { var headers = table.getElementsByTagName("th"); for (var i = 0; i < headers.length; i++) { (function(n) { //嵌套函數來建立本地域 headers[i].onclick = function() { sortrows(table, n); }; }(i)); //將i的全局變量賦值給局部變量n } }
removeChild()就是從文檔樹中刪除一個節點,可是請當心:該方法不是在待刪除的節點上調用,而是(就像其名字的一部分「child」所暗示的同樣)在其父節點上調用。在父節點上調用該方法,並將須要刪除子節點做爲方法參數傳遞給它。在文檔中刪除n節點。代碼能夠這樣寫:
n.parentNode.removeChild(n);
replaceChild()方法刪除一個子節點並用一個新的節點取而代之。在父節點上調用該方法,第一個參數是新節點,第二個參數是要替代的節點,例如用一個文本字符串來代替節點n,能夠這樣寫:
n.parentNode.replaceChild(document.createTextNode("[redactd]"),n);
如下的函數展現了replaceChild()的另外一種用法
//用一個新的<b>元素替換n節點,並使n成爲該元素的子節點 function embolden(n) { //假設參數爲字符串而不是節點,將其當作元素的id if (typeof n == "string") n = document.getElementById(n); var parent = n.parentNode; // 得到n的父節點 var b = document.createElement("b"); //建立一個b元素 parent.replaceChild(b, n); //使用<b>元素替換節點n b.appendChild(n); //使你成爲<b>元素的子節點 }
本章5.1介紹過元素的outerHTML屬性,也解釋了一些版本的firefox還未實現它。下面的例子將展現在firefox(和其它任何支持innerHTML的瀏覽器,要有一個可擴展的Element.prototype對象,還有一些方法來定義屬性的getter和setter)如何來實現該屬性。同時,代碼還展現了removeChild()和cloneNode()的實際用法。
。。。。。。
DocumentFragment是一種特殊的Node,它爲其它節點建立一個臨時的容器。像這樣建立一個DocumentFragment:
var frag = document.createDocumentFragment();
像Document節點同樣,DocumentFragment是獨立的,而不是任何其它文檔的一部分。它的parentNode總爲null。但相似Element,它能夠有任意多的子節點,能夠用appendChild(),insertBefore()等方法來操做他們。
DocumentFragment的特殊之處在於它使得一組節點被當作一個節點來看待:若是給appendChild()、insertBefore()或replaceChild()傳遞一個DocumentFragment,實際上是將該文檔片斷的全部子節點插入到文檔中,而非片斷自己。(文檔片斷的子節點從片斷移動到文檔中,文檔片斷清空以便重用)。下面的例子是函數使用DocumentFragment來倒序排列的一個節點的子節點。
//倒序排列節點n的子節點 function reverseDome(n) { //建立一個DocumentFragment() var f = document.createDocumentFragment(); //從後至前循環子節點,將每個字節點移動到文檔片斷中 //n的最後一個子節點變成第一個子節點 //注意,給f添加一個節點,該節點自動會從n中刪除 while (n.lastChild) f.appendChild(n.lastChild); //最後將全部的子節點一次移動回n中 n.appendChild(f); }
下面的例子使用innerHTML屬性和DocumentFragment實現insertAdjacentHTML()方法(5.1節)。還定義了一些名字更符合邏輯的HTML插入函數,能夠替換讓人迷惑的insertAdjacentHTML()API。內部工具函數fragment()代碼中最有用的部分:它反映對一個指定的HTML字符串文本解析後的DocumentFragment。
。。。。。。
本例子說明了如何爲文檔動態的建立一個目錄表。它展現了上一節所描述的文檔腳本化的不少概念、元素選取、文檔遍歷、元素屬性設置、innerHTML屬性設置和在文檔中建立於插入新節點等。本例子的註釋也比較詳盡。
。。。。。。
但有時候。判斷一個元素精確的幾個形狀也是很是有必要的。例如:在16章咱們看到利用css元素指定位置。若是想要css動態定位一個元素(如工具的提示或插圖)到某個已經由瀏覽器定位後的普通元素的旁邊,首先要判斷那個元素的當前位置。
本節闡述了在瀏覽器窗口中完成文檔的佈局後,怎麼才能在抽象的基於樹的文檔模型與幾何形狀的基於座標的視圖之間來回變換。本節描述的屬性和方法以及在瀏覽器中實現有很長一段時間了,有些是IE特有的,有些到IE9才實現。你們能夠參考W3C的標準化流程,做爲CSSOM-View模塊www.w3.org/TR/cssom-view/
元素的位置是以像素度量的,向右表明X座標的增長,向下表明Y座標的增長,可是,有兩個不一樣的點做爲座標系的原點:元素的X和Y座標能夠相對於文檔的左上角或者相對於在其中顯示文檔的視口的左上角。在頂級窗口和標籤頁中,」視口「只是實際顯示文檔內容的瀏覽器的一部分:它不包括瀏覽器的「外殼」(如菜單、工具條和標籤頁)。針對框架頁中顯示的文檔,視口是定義了框架頁的<iframe>元素。不管在何種狀況下,當討論元素的位置是,必須弄清楚所使用的座標是文檔座標仍是視口座標。(注意,視口座標有時也叫做窗口座標)
若是文檔比視口要小,或者說還未出現滾動,則文檔的左上角就是視口的左上角,文檔和視口座標系統是同一個。可是通常來講,要在兩種座標系之間互相轉換,必須加上或減去滾動的偏移量(scroll offset)。例如,在文檔座標中若是一個元素的Y座標是200像素,而且用戶已經把瀏覽器向下滾動了75像素,那麼視口座標中元素的Y座標就是125像素。一樣,在視口座標中若是一個元素的X座標是400像素,而且用戶已經水平滾動了視口200像素,那麼文檔座標中像素的X座標中元素的X座標就是600像素。
文檔座標比視口座標更加基礎,而且在用戶滾動是他們不會發生變化。不過,在客戶端編程中使用視口座標是很是常見的。當使用CSS指定元素的位置時運用了文檔座標(16章)。可是最簡單的查詢元素位置的方法(15.8.2節)返回視口座標中的位置。相似的,當爲鼠標事件註冊事件處理程序函數時,報告的鼠標指針的座標是在視口座標系中。
爲了在座標系中轉換,咱們須要斷定瀏覽器窗口的滾動條的位置。Window對象的pageXoffset和pageYOffset屬性在全部的瀏覽器中提供這些值,除了IE8及更早的版本之外。IE(和全部現代瀏覽器)也能夠經過scrollLeft和scrollTop屬性來得到滾動條的位置。使人迷惑的是,正常的狀況下經過查找文檔的根節點(document.documentElement)來獲取這些屬性,可是在怪異模式下,必須在文檔的<body>元素(documeng.body)上查詢它們。如下顯示瞭如何簡便的查詢滾動條的位置。
/*查詢窗口滾動條的位置*/ //以一個對象的x和y屬性的方法返回滾動條的偏移量 function getScrollOffsets(w) { //使用指定才窗口,若是不帶參數則使用當前窗口 w = w || window; //除了IE8及更早的版本之外,其它的瀏覽器都能用 if (w.pageXOffset != null) return {x: w.pageXOffset, y:w.pageYOffset}; //對標準模式下的IE,或任何瀏覽器 var d = w.document; if (document.compatMode == "CSS1Compat") return {x:d.documentElement.scrollLeft, y:d.documentElement.scrollTop}; //怪異模式下的瀏覽器 return { x: d.body.scrollLeft, y: d.body.scrollTop }; }
有的時候判斷視口的尺寸也是很是有用的,例如:爲了肯定文檔的那些部分是當前可見的,利用滾動偏移量查詢視口的簡單方法在IE8及更早的版本中沒法工做。並且該技術在IE中的運行方式還要取決於瀏覽器的模式。下面的例子便捷的查詢視口尺寸,注意,它和上面的代碼十分類似。
/*查詢窗口的視口尺寸*/ //做爲一個對象的w和h屬性返回視口的尺寸 function getViewportSize(w) { //使用指定的窗口,若是不帶參數則使用當前窗口 w = w || window; //除了ie8和更早的版本,其它瀏覽器都能用 if (w.innerWidth != null) return { w: w.innerWidth, h: w.innerHeight }; //對於標準模式下的IE或其任何瀏覽器 var d = w.document; if (document.compatMode == "CSS1Compat") return { w: d.documentElement.clientWidth, h: d.documentElement.clientHeight }; //對於怪異模式下的瀏覽器 return{w:d.body.clientWidth,h:d.body.clientHeight}; }
斷定一個元素尺寸和位置最簡單的方法是調用它的getBoundingClientRect()方法。這個方法是在IE5中引入的。而如今全部的瀏覽器都實現 了,它不須要參數,返回一個有left,right,top,bottom的屬性對象。left和top表示左上角的x和y座標。right和bottom屬性表示元素右下角的x和y座標。
這個方法返回元素在窗口座標的位置,爲了轉換甚至用戶滾動瀏覽器窗口之後仍然有效的文檔座標,須要加上滾動的偏移量:
var box = e.getBoundingClientRect(); //得到視口在座標中的位置 var offsets = getScrollOffsets(); //上面定義的工具函數 var x = box.left + offsets.x; var y = box.top + offsets.y;
在不少瀏覽器(和w3c標準中),getBoundingClientRect()對象還包含width和height屬性,但在原始的ie中未實現,爲了簡便,能夠這樣計算width和height屬性
var box = e.getBoundingClientRect(); //得到視口在座標中的位置 var w = box.width || (box.right - box.left); var h = box.height || (box.bottom - box.top);
getBoundingClientRect()所返回的座標包含元素的邊框和內邊距,但不包含元素的外邊距。
"Client"指定了返回的矩形的座標系,對於"Bounding"?瀏覽器在佈局時塊狀元素(如圖片)老是爲矩形。可是,內聯元素可能跨了多行,所以可能由多個矩形組成。好比一個被斷成兩行的斜體文本<i>,它的形狀是由第一行的右邊部分和第二行的左邊部分兩個矩形組成。若是內聯元素調用getBoundingClientRect(),它返回"邊界矩形"。如上述的<i>元素,邊界矩形會包含兩行的寬度。
若是想查詢內聯元素每一個獨立的矩形,調用getClientRects()方法來得到一個只讀的類數組對象,它的每一個元素相似於getBoundingClientRect()返回的矩形對象。
咱們知道,例如getElementByTagName()這樣的DOM方法返回的結果是「實時的」,當文檔變化時這些結果變化時能自動更新,可是getBoundingClientRect()和getClientRects()所返回的矩形對象(和矩形列表)並非實時的,他們只是調用方法時視覺狀態的靜態快照。用戶在滾回或改變瀏覽器大小的時候並不會更新他們。
getBoundingClientRect()方法使咱們能在視口中斷定元素的位置,但有時咱們反過來想,斷定在視口中指定的位置有什麼元素。這時咱們能夠用Document對象的elementFormPoint()方法來斷定。傳遞(x,y)座標(視口座標而非文檔座標),該方法返回指定位置的一個元素。因爲該方法的算法尚未具體定下來,可是這個方法意圖就是它返回在那個點裏和最上面(z-index屬性)的元素。若是指定的點在視口之外,elementFromPoint()返回null.即便改點在轉換爲文檔座標後是完美有效的,返回值也同樣。
例15-8展現了關於滾動條的位置查詢,還有一種更簡單的方法。Window對象的scrollTo()(和其同義詞scroll())方法接受一個點的X和Y座標(文檔座標),並做爲滾動條的偏移量設置它們。也就是,窗口滾動到指定的點出如今視口的左上角。若是指定的點太接近文檔的下邊緣或右邊緣,瀏覽器將盡可能保證它和視口的左上角之間最近。但沒法達到一致
window的scrollBy()和scroll和scrollTo相似,可是它的參數是相對的,並在當前滾動條的偏移量上增長
//每200毫秒向下滾動10像素,注意,他沒法關閉 javascript:void setInterval(function(){scrollBy(0,10)},200);
一般,除了滾動到文檔中用數字表示的位置,咱們只是想它滾動使得文檔中的某個元素可見。能夠利用getBoundingClientRect計算元素的位置,並轉換爲文檔座標,而後用scrollTo方法達到目的。可是在須要顯示的HTML元素上調用scrollIntoView方法更加方便。該方法保證了元素在視口中可見。默認狀況下,它試圖將元素的上邊緣放在或儘可能接近視口的上邊緣。若是隻傳遞false做爲參數,它將試圖將元素的下邊緣放在或儘可能接近視口的下邊緣。只要有助於元素在視口內可見。瀏覽器也會水平滾動視口。
scrollIntoView()的行爲與設置window.location.hash爲一個命名錨點的名字後瀏覽器產生的行爲相似。
getBoundingClientRect()方法在全部當前瀏覽器都有定義,面對老式瀏覽器只能使用更老的技術來斷定元素的尺寸和位置。元素的尺寸比較簡單:任何HTML元素的只讀屬性offsetWidth和offsetHeight以css像素返回它的屏幕尺寸。返回的尺寸包含元素的邊框和內邊距,除去了外邊距。
全部HTML元素擁有offsetLeft和offsetTop屬性來返回元素的x座標和y座標。對於不少元素,這些值是文檔座標,並直接指定元素的位置。但對於已定義元素的後代元素和一些其餘元素(如表格單元),這些屬性返回的座標是相對於祖先元素的而不是文檔。offsetParent屬性指定這些屬性所相對的父元素。若是offsetParent爲null,這些屬性都是文檔座標,所以,通常來講,用offsetLeft和offsetTop來計算元素e的位置須要一個循環:
function getElementPosition(e){ var x=0,y=0; while(e!=null){ x+=e.offsetLeft; y+=e.offsetTop; e+=e.offsetParent; } return {x:x,y:y}; }
經過循環offsetParent對象鏈來累加偏移量,該函數計算指定元素的文檔座標。可是這個函數也不老是返回正確的值,下面看看如何修復。
除了這些名字以offset開頭的屬性之外,全部的文檔元素定義了其餘兩組屬性,其名稱一組以client開頭,另外一組以scroll開頭。即,每一個HTML元素都有一些這些屬性:
爲了理解這些client和scroll屬性,你須要知道HTML元素的實際內容有可能比分配用來容納內容的盒子更大,所以單個元素可能有滾動條。內容區域是視口,就像瀏覽器的窗口,當實際比視口更大時,須要把元素的滾動條位置考慮進去。
clientWidth和clientHeight相似offsetWidth和offsetHeight,不一樣的是它們不包含邊框大小,只包含內容和它的內邊距。同時,若是瀏覽器咋內邊距和邊框之間添加了滾動條,clientWidth和clientHeight在其返回值中也不包含滾動條。注意對於相似<i> <code> <span>這些內聯元素,clientWidth和clientHeight老是返回0。例15-9有用的。有一個特殊的案例,在文檔的根元素上查詢這些屬性時,它們的返回值和窗口的innerWidth和innerHeight屬性值相等。
clientLft和clientTop屬性沒什麼做用:它們返回元素的內邊距的外邊緣和它的邊框的外邊緣之間額水平距離和垂直距離,一般這些值就等於左邊和上邊的邊框寬度。若是元素有滾動條,而且瀏覽器將這些滾動條放置在左側或頂部,這兩屬性也就包含滾動條的寬度。對於內聯元素,它們老是爲0。
scrollWidth和scollHeight是元素的內容區域加上它內邊距再加上任何溢出內容的尺寸。當內容正好和內容區域匹配而沒有溢出時,這些屬性和clientWidth和cliengHeight是相等的。但當溢出時,它們就包含溢出的內容,返回值比clientWidth和clientHeight要大
最後,scrollLeft和scrollTop指定元素的滾動條的位置。注意,scrollLeft和scrollTop是可寫的屬性,經過設置它們來讓元素中的內容滾動。(HTML元素並無相似Window對象的scrollTo方法)
當文檔包含可滾動的且有溢出內容的元素時,須要修改上面的getElementPosition()方法爲getElementPos:從累計的偏移量中減去滾動條的位置,返回的位置從文檔座標轉換爲視口座標:
。。。。。。
getElementPos()方法和getBoundingClientRect()的返回值同樣。可是仍是有較多兼容性問題,jQuery很好地解決這方面的問題。
HTML的<form>元素和各類各樣的表單輸入元素,如input,seclect和button。他們在客戶端編程中有着重要的地位。這些html元素能夠追溯到web最開始,比javascript更早。html表單就是第一代web應用程序背後的運行機制,它根不須要javascript。用戶的輸入從表單元素來收集;表單這些輸入遞交給服務器,服務器處理並生成一個新的HTML頁面(一般有一個新的表單元素)並顯示在客戶端。
即便當整個表單數據都是由客戶端javascript來處理並不會提交到服務器時,html表單還是收集用戶數據很好的方法。 在服務端程序中,表單必需要有一個「提交」按鈕,不然它就沒有用處。另外一方面,在客戶端編程中,「提交」按鈕不是必須的。服務端程序是基於表單提交動做的——他們按表單大小的塊處理數據——這限制了它們的交互性。客戶端程序時基於事件的——他們能夠對單獨的表單元素上的事件做出相應——這使得它們有更好的響應度。好比用戶打字時客戶端程序能校驗有效性。
表單由HTML元素組成就像HTML文檔的其它部分同樣,能夠用文章中介紹的DOM來操做它們。可是表單是第一批腳本化的元素,在早期的客戶端編程中它們還支持比DOM更早的其它API。
注意,本節是關於腳本化的HTML表單,而不是HTML自己。下文的列出經常使用的表單元素,更詳細的內容參考第四部分的表單和表單元素的API,在form、input、option、select、textarea、請參考下面。
表單和它們所包含的元素能夠用如getElementById()和getElementByTagName()等標準方法從文檔中來選取。
在支持querySelectorAll()的瀏覽器中,從一個表單中選取全部的單選按鈕或全部同名的元素代碼以下
//id爲"shipping"的表單中的單選按鈕 document.querySelectorAll('#shipping input[type="radio"]'); //id爲"shipping"的表單中全部name爲"method"的單選按鈕 document.qierySelectorAll('#shipping input[type="radio"][name="method"]')
儘管如此,如同在14.7節,15.2.2節和15.2.3所描述的,有name或id屬性的<form>元素可以經過不少方法來選取。name="address"屬性的form能夠用下面任何的方法來選取
window.address //不可靠,不要使用 document.address //僅當表單有name屬性時可用 document.forms.address //僅當方法有name或id的表單 document.forms[n] //不可靠:n是表單的序號
15.2.3節闡述了document.forms是一個HTMLCollection對象,能夠經過數字序號或id或name來選取表單元素。Form對象的自己行爲相似多個表單元素組成的HTMLCollection集合,也能夠經過那麼或數字來索引。若是名爲"address"的表單的第一個元素的name是"street",可使用如下任何的一種表達式來引用該元素
document.forms.address[0]; document.forms.address.street; document.address.street //當有name = "address",而不是id="address"
若是要明確選取一個表單元素,能夠索引表單對象的elements屬性:
document.forms.address.elements[0] document.forms.address.elements.street
通常來講指定文檔元素的方法用id屬性要比name屬性更佳。可是,name屬性在html表單提交中有特殊的目的,它在表單中較爲經常使用,在其它元素中較少使用。它應用於相關的複選按鈕組和強制共享name屬性值的、互斥的單選按鈕組。請記住,當用name來索引一個HTMLCollection對象並而且包含多個元素來共享name時,返回值爲一個類數組對象,它包含全部匹配的元素。考慮如下表單,它包含多個單選按鈕來選擇運輸方式。
<form name="shipping"> <fieldset> <legend>shipping method</legend> <label><input type="radio" name="method" value="1st">第一次</label> <label><input type="radio" name="method" value="2day">第二次</label> <label><input type="radio" name="method" value="3rd">第三次</label> </fieldset> </form>
對於該表單,用以下代碼來引用單選按鈕的元素數組
var methods = document.forms.shipping.elements.method;
注意,<form>元素自己有一個HTML屬性和對於的javascript屬性叫"method",全部在此案例中,必需要用該表單的elements屬性而非method屬性。爲了斷定用戶選取哪一種運輸方式,須要遍歷數組中的表單元素,並檢測他們的checked屬性:
var shipping_method; for (var i = 0; i < methods.length; i++) if (methods[i].checked) shipping_method = method[i].value;
上面描述的elements[]數組是Form對象中最有趣的屬性 。Form對象中其餘屬性相對沒有如此重要。action、encoding、method和target屬性(property)直接對應於<form>元素的action、encoding、method個target等HTML屬性(attribute)。這些屬性都控制了表單是如何來提交數據倒web服務器並如何顯示的。客戶端javascript可以設置這些屬性值,不過僅當表單真的會將數據提交到一個服務端程序時它們纔有用。
在javascript產生以前,要有一個轉義的「提交」按鈕來提交表單,用一個專用的「重置」按鈕來重置各元素的值。javascript的Form對象支持兩個方法:submit()和reset(),它們完成一樣的目的。調用Form對象的submit()方法來提交表單,調用reset()方法來重置表單元素的值。
全部表單元素通常都會有一下屬性:
每一個form元素都有一個onsubmit事件處理來完成偵測表單提交,還有一個onreset事件處理程序偵測表單重置。表單提交前調用onsubmit程序;它經過返回false能取消提交動做。這給javascript程序一個機會檢查用戶的輸入錯誤。目的是爲了不或不完整或無效的數據提交到服務端程序。注意,onsubmit只能經過單擊「提交」按鈕來觸發,直接調用表單的submit()方法不觸發onsubmit事件處理程序。
onreset事件處理程序和onsubmit是相似的。它在表單重置以前調用,經過返回false可以阻止表單元素被重置。在表單中不多須要「重置」按鈕,但若是有,你可能須要提醒用戶來確認是否重置。
<form... onreset = "return confirm('你肯定重置全部的輸入重新輸入碼?')"> ... <button type="reset">清楚並從新輸入</button> </form>
相似onsubmit事件處理程序,onreset它只能經過單擊重置按鈕來觸發,直接調用表單的reset方法不能觸發onreset事件處理程序。
當用戶在表單元素交互時每每會觸發click或change事件。經過定義onclick或onchange事件處理程序能夠處理這些事件。表15-1有給出各個元素主要的事件處理程序。當用戶改變其餘表單元素所表明的值時會觸發change事件。注意,在一個文本域中改事件不是每次輸入一個值就會觸發,它僅當用戶改變元素的值而後將焦點移到其餘元素時纔會觸發。
表單元素在收到鍵盤的焦點時也會觸發 focus 事件,失去焦點時會觸發blur事件。
重要的一點:this的引用(17章)。它是觸發該事件的文檔元素的一個引用。form元素的事件處理程序能經過this.form來獲得Form對象的引用。
按鈕是常見的表單元素之一。按鈕的自己沒有默認的行爲,除非它有onclick實際處理程序。不然它沒有什麼用處。以<input>定義的按鈕會將value屬性的值以純文本顯示。注意,超級連接與按鈕同樣提供了onclick事件處理程序。當onclick事件所觸發的動做能夠概念化爲「跟隨此連接」是就用一個連接;不然,用按鈕。
提交和重置元素本就是按鈕,不一樣的是它們之間有關聯的默認動做(提交和重置)。
本書的第四部分末包含input按鈕,關於按鈕表單元素詳細內容參看input項,它包含了用button元素建立的按鈕。
複選框和單選元素都是開關按鈕,或有兩種視覺狀態的按鈕:選中或未選中。HTML屬性name的值都相同。注意,當複選框利用做爲表單屬性的名字來選中這些元素時,返回的是一個類數組對象而不是單個元素。
單選和複選框都定義了它的checked屬性,該屬性是可讀/寫的布爾值,它指定了元素當前是否選中。defaultChecked屬性也是布爾值,它是HTML屬性的checked值;它指定了元素第一次加載頁面時是否被選中。
單選和複選框元素自己不顯示任何文本,它們一般和相鄰的HTML文本一塊兒顯示(或與<label>元素相關聯)。這意味着設置複選框或單選元素的value屬性不改變元素的視覺表現,設置value只改變提交表單時發送到web服務器的字符串。
當用戶單擊或複選開關按鈕,單選或複選框元素觸發onclick事件。若是因爲單擊開關按鈕改變了它的狀態,它也觸發onchange事件。注意,當用戶單擊其餘單選按鈕而致使這個單選按鈕狀態的改變,後者不觸發onchange事件。
「text」和「textarea」文本輸入域是常見經常使用的元素,用戶能夠輸入簡短的文本字符串。value屬性表示用戶輸入的內容。經過設置該屬性值能夠顯示地指定應該在輸入域中顯示的文本。
在HTML5中,placeholder屬性表示用戶在輸入框中提示的信息。
文本輸入域的onchange事件處理程序表面用戶完成編輯並將焦點移出文本域。
「password」輸入時顯示星號,但這只是防止眼睛看見而已,網絡上傳輸仍是有可能被看到的。
「file」上傳文件到服務器。有onchange事件處理程序。跟普通的輸入域不一樣的是它的value是隻讀,這個防止惡意的JavaScript程序欺騙用戶上傳本意不想共享的文件。
不一樣的文本輸入元素定義onkeypress、onkeydown和onkeyup事件處理程序。能夠設置爲返回false來防止記錄用戶的按鍵。
select元素可讓用戶作出一組選擇(Option元素表示),瀏覽器一般將其渲染爲下拉菜單的形式。但當指定size屬性大於1時,它將顯式爲列表中的選項(可能有滾動)。<select>元素可能有兩種不一樣的方式運做,這取決於type的屬性值是如何設置的。若是<select>元素有multiple屬性,也就是select對象的type屬性值爲"select-multiple",那就容許用戶有多個選項。不然,若是沒有多選項屬性,那隻能選擇單個項目,它的type屬性值爲「select-one」。
某種程度上「select-multiple」元素與一組複選框元素類似,「select-one」元素與一組單選框元素類似。可是select元素不是開關按鈕:它們有<option>元素定義。Select元素定義了options屬性,它是一個包含多個option元素的類數組對象。
有onchange事件處理程序。
針對「select-one」,它的可讀/寫屬性selectedIndex指定了哪一個選項當前被選中。針對「select-multiple」元素,selectedIndex不足以表示一組選項。這時要斷定哪些選項被選中,就必須遍歷options[]數組的元素,並檢測每一個option對象的selected屬性值。
除了其select屬性,每一個option對象都有一個text屬性,它指定了select元素中的選項所顯示的純文本字符串。設置該屬性能夠改變顯示給用戶的文本。value屬性指定了在提交表單時仿到服務器的文本字符串,它可讀寫。甚至在寫純客戶端程序而且不可能有表單提交時,value屬性(或它所在的HTML屬性value)是用來保存任何數據的好地方,在用戶選取特定的選項時可使用這些數據。
注意:Option元素並無與表單相關的事件處理程序:用包含Select元素的onchange事件處理程序來代替。
經過設置options.length爲一個但願的值能夠截斷option元素數組,而設置options。length爲0能夠從select元素移除全部的選項。設置options[]數組中某點的值爲null能夠從Select元素中移除某個option對象。這將刪除該option對象,options[]數組中高端的元素自動移下來填補空缺.
爲select元素增長一個新的選項,首先用Option()構造函數建立一個Option對象,而後將其添加到options[]屬性中.
//建立一個新的屬性 var zaire = new Option("Zaire", // text屬性 "zaire", //value屬性 false, //defaultSelected屬性 false); //selected屬性 //經過添加到options數組中,在Select元素中如今改選項 var countries = document.address.country;//獲得Select對象 countries.options[countries.options.length] = zaire;
請牢記這一點,這些專用的Select元素的API已經很老了。能夠用那行標準的調用更明確的插入和移除選項元素:Document.createElement(),Node.insertBefore(),Node.removeChild()等。
除了body、documentElement、forms外,還有其餘有趣的屬性:
referrer是這些屬性中最有趣的屬性之一:它包含用戶鏈接到當前文檔的上一個文檔的URL,能夠用以下代碼使用該屬性:
if (document.referrer.indexOf("http://www.google.com/search?") == 0) { var args = document.referrer.substring(ref.indexOf("?") + 1).split("&"); for (var i = 0; i < args.length; i++) { if (args[i].substring(0, 2) == "q=") { document.write("<p>welcome google User."); document.write("You searched for:" + unescape(args[i].substring(2)).replace('+', '')) break; } } }
已經較少使用了。
document.write()方法會將其的字符串鏈接起來,而後讓結果字符串插入到文檔中調用它的腳本元素的位置。當腳本執行結束,瀏覽器解析生成的輸出並顯示它。以下
<script> document.write("<p>document title :" + document.title); document.write("<p>URL :" + document.URL); document.write("<p>Referred:" + document.referrer); document.write("<p>Modified on :" + document.lastModified); document.write("<p>Accessed :" + new Date()); </script>
只有在解析文檔的時候才能使用write()方法輸出HTML到文檔中,理解這點很是重要,也就是說能在<script>元素的頂層代碼中調用document.write(),就是由於這些腳本是文檔解析流程的一部分。若是將其放在一個函數的定義中,而該函數的調用是從一個事件處理程序中發起的,它會擦除當前文檔和它包含的腳本。同理,在設置了defer或async屬性的腳本中不要使用。
還可使用write()方法在其餘的窗口或框架頁中來建立整個全新文檔。第一次調用其餘文檔的write()方法即會擦除該文檔的全部內容。能夠屢次調用來逐步創建新文檔的內容,會緩存起來知道對象的close()方法結束它。值得一提的是,Document對象還有支持writeln()方法。除了在其它參數輸出以後加一個換行符之外它和wirte()方法徹底同樣。例如在<pre>元素內輸出預格式化的文本時很是有用。
在當今的代碼中document.wirte()方法並不經常使用,innerHTML屬性和其它DOM技術提供了更好的方法增長內容。另外一方面,某些算法的確可以使得他們自己稱爲很好的I/O API。如同write()方法提供的API同樣。若是你正在書寫在運行時計算和輸出文本代碼,可能會下面的例子感興趣,它利用指定元素的innerHTML書寫包裝了簡單的write()和close()方法。
/*徵對innerHTML屬性的流式API*/ //設置元素的innerHTML定義簡單的「流式」API function ElementStream(elt) { if (typeof elt === "string") elt = document.getElementById(elt); this.elt = elt; this.buffer = ""; } //鏈接全部的參數,添加到緩存中 ElementStream.prototype.wirte = function() { this.buffer += Array.prototype.join.call(arguments, ""); }; //相似write(),只是添加了換行符 ElementStream.prototype.writeln = function() { this.buffer += Array.prototype.join.call(arguments, "") + "\n"; }; //從緩存中設置元素的內容,而後清空緩存 ElementStream.prototype.close = function() { this.elt.innerHTML = this.buffer; this.buffer = ""; };
有時斷定用戶在文檔中選取了那些文本很是有用,能夠用相似以下的函數達到目的。
function getSelectedText() { if (window.getSelection) //HTML5標準API return window.getSelection().toString(); else if (document.selection) //IE獨有技術 return document.selection.createRange().text; }
標準的window.getSelection()方法返回一個Selection對象,後者描述了當前選取的一系列一個或多個Range對象。Selection和Range定義了一個不經常使用的的較爲複雜的API.本書並無記錄。toString()方法是Selection對象中的最重要的普遍實現了(除了IE)特性,他返回選取的純文本。
IE定義了一個不一樣的API,它在本書中也沒有文檔記錄。Document.selection對象表明了用戶的選擇。該對象的createRange()方法返回IE獨有的TextRange對象,它的text屬性包含了選取的文本。
如上的代碼在書籤工具(13.2.5節)中特別有用,它選取操做的文本,而後利用搜索引擎或參考某個單詞,例如,以下的HTML連接在Wikipedia上查找選取的文本,收藏書籤後,該連接和它包含的javascript URL就編成了一個書籤工具
<a href="javascript: var q;
if (window.getSelection) q = window.getSelection().toString();
else if (document.selection) q = document.selection.createRange().text;
void window.open('https://www.baidu.com/s?wd=' + q)">選取後點擊連接查找</a>
上述的代碼兼容性不佳:window對象的getSelection()方法沒法返回那些表單元素input或textarea內部的文本,它只返回在文檔主體自己中選取的文本。另外一方面,iE的Document.selection屬性能夠返回任意地方上選取的文本。
從文本輸入域或textarea輸入的元素能夠獲取的文本可使用以下代碼
elt.value.substring(elt.selectionStart,elt.selectionEnd);
IE8以及更早的版本瀏覽器不支持selectionStart,selectionEnd屬性。
全部當今的web瀏覽器支持簡單的HTML編輯功能;你已經看到在這個頁面上使用了,如博客評論。它嵌入了一個文本編輯器,包含了一系列的按鈕工具欄來設置排版樣式等。
有兩種辦法啓用編輯功能。其一,設置任何標籤的HTML contentEditable屬性;其二,設置對於元素的javascript contentEditable屬性,這都是使元素的內容變得可編輯,當用戶點擊該元素的內容就會出現插入光標。用戶敲擊鍵盤就能夠將內容插入其中。以下,一個HTML元素建立一個可編輯的區域:
<div id="editor" contenteditable>
click to edit
</div>
瀏覽器可能爲表單字段和contenteditable元素支持自動拼寫檢查。在支持該功能的瀏覽器中,檢查可能默認的開啓或關閉。爲元素添加spellcheck元素來顯式開啓拼寫檢查,而使用spellcheck=false來顯式關閉該功能(例如在一個textarea將顯式源代碼或其它內容包含了字典找不到的標識符時。)
將Document對象的designMode屬性設置爲字符串"on"是的整個文檔可編輯。(設置爲off將恢復爲只讀文檔),designMode屬性並無對應的HTML屬性。以下代碼使得<iframe>內部文檔可編輯
<iframe id="editor" src="about:blank"></iframe> //空iframe <script> onload = function() { var editor = document.getElementById("editor"); editor.contentDocument.designMode = "on"; //開啓編輯 } </script>
全部當今瀏覽器都支持contenteditable和designMode屬性。但仍是不太兼容,好比enter有些另起一行有些開啓新段落。
瀏覽器定義了多項文本編輯器命令,大部分沒有鍵盤快捷。爲了執行這些命令,應該使用Document對象execCommand()方法。(注意,這是Document的方法,而不是設置了contenteditable屬性的元素方法。若是文檔的元素中有多個可編輯的元素,命令將自動應用到選區或插入光標所在那個元素上),用execCommand()執行的命令名字都是如"bold","subscript","justifycenter","或"insertimage"之類的字符串。命令名是execCommand()第一個參數。有些命令還須要一個值的參數,例如:"createlink"須要一個超級連接URL。理論上,execCommand()第二個參數爲true,瀏覽器會自動提示用戶輸入的所需值。但爲了提升可移植性,你應該提示用戶輸入,並傳遞false爲二個參數,傳遞用戶輸入的值做爲第三個參數。
function bold() { document.execCommand("blod", false, url); } function link() { var url = prompt("輸入link描述"); if (url) document.execCommand("createlink", false, url) }
execCommand()所支持的命令一般是工具欄上的按鈕觸發的。當要觸發的命令不可用時,良好的UI會使對應的按鈕無效。能夠給document.queryCommandSupport()傳遞命令查詢瀏覽器是否支持該命令。調用document.queryCommanEnabled()來查詢當前所使用的命令。(例如,一條須要文本選擇區域的命令在無選區的狀況下,多是無效的)有一些 命令,如"bold"和"italic"有一個布爾值狀態,開關取決於當前選區或可使用document.queryCommandState()。最後,有些命令,(如「fontname」)有一個相關的值(字體系列名)。用document.queryCommandValue()查詢該值。若是當前文本選區了兩種不一樣的字體,"fontname"是不肯定的。使用document.querycpmmandIndeterm()來檢測這種狀況。
不一樣的瀏覽器實現了不一樣的編輯命令組合,只有一少部分獲得了很好的支持。如「bold」,「italic」,「createlink」,「undo」和「redo」(互操做命令表,請參考http://www.quirksmode.org/dom/execCommand.html),HTML5草案定義瞭如下命令,但他們尚未沒被廣泛的支持,就不作詳細的文檔記錄:
若是web須要一個富文本編輯器,就須要採納一個預先構建的解決瀏覽器之間各類差別的解決方法。
一旦用戶編輯了某元素,該元素的設置了conteneditable屬性,就可使用innerHTML屬性獲得已編輯內容的HTML標記。如何處理富文本本身決定(YUI和Dojo框架包含了編輯器組件,還有一些可選的方案,參考:http://en.wikipedia.org/wiki/online_rich-text_editor)。能夠把它存儲在隱藏的表單字段中。並經過提交表單發送到服務器。可使用18章描述的技術直接把已編輯的文本發送到服務器。或者使用20章的計算在本地保存用戶的編輯文本。