JavaScript权威指南--脚本化文档

知识要点   

脚本化web页面内容是javascript的核心目标。javascript

第13章和14章解释了每个web浏览器窗口、标签也和框架由一个window对象所示。每一个window对象有一个document对象,document对象表示窗口的内容,它就是本章的主题。尽管如此,Document对象并不是独立的,它是一个巨大的API的核心对象,叫作文档对象模型(Document Object Model ,DOM),它表明和操做文档的内容。php

本章开始部分解释DOM的基本框架,而后进一步解释如下内容:css

  • 如何在文档中查询或选取单独元素
  • 如何将文档做为节点树来遍历,如何找到文档元素的祖先、兄弟和后代元素
  • 如何查询和设置文档的元素的属性
  • 如何查询、设置和修改文档内容
  • 如何经过建立、插入和删除节点来修改文档结构
  • 如何与html表单一块儿工做

本章最后一节涵盖其它各类文档特性,包括referrer属性、write()方法和查询当前文档中选取的文档文本的技术等。html

1.DOM概览

文档对象模型(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处理指令等类型。程序员

2.选取文档元素

大多数客户端javascript程序运行时老是在操做一个或多个文档元素,这些程序启动时,可使用全局变量document来引用Document对象。可是,为了操做文档中的元素,必须经过某种方式来得到或选取这些文档元素的Element对象,DOM定义了不少方式来选取元素,查询文档的一个或多个元素有以下的方法:

2.1.经过id选取元素

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属性的元素。

2.2.经过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()来查找它们。

2.3.经过标签名选取元素

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)

2.4.经过css类选取元素

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()使用样式表的匹配算法。当一个文档在怪异模式下运行时是大小写不敏感的,不然,该比较区分大小写。

2.5.经过css选择器选取元素

选择器用来描述文档中的若干或多组元素。本书不会详细介绍。

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选择器规范

与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和其它一些客户端库所采纳.

2.6.document.all[]

在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>元素

3.文档结构和遍历

一旦从文档中选取了一个元素,有时须要查找文档中与之在结构上相关的部分(父亲、兄弟和子女)。

3.1.做为节点树的文档

Document对象、它的Element对象和文档中表示文本的Text对象都是Node对象。Node定义了如下重要属性:

  • parentNode:该节点父节点,或者针对相似Document对象的应该是null,由于它没有父节点
  • childNodes:只读的类数组对象(NodeList对象),它是该节点的子节点的实时表示
  • firstChildlastChild:该节点子节点的第一个和最后一个,若是该节点没有子字节点则为null
  • nextSiblingpreviousSibling:该节点的兄弟节点的前一个和下一个。具备相同父节点的两个节点为兄弟节点。节点的顺序反映了它们在文档中出现的顺序。这两个属性将节点之间的以双向链表的形式链接起来。
  • nodeType:该节点的类型,9表明Document节点,1表明Element节点,3表明Text节点,8表明Comment节点,11表明DocumentFragment节点
  • nodeValue:Text节点或Comment节点的文本内容
  • nodeName:元素的标签名,以大写形式表示

使用这些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。

3.2.做为元素树的文档

当将主要的兴趣点集中在文档中的元素上而并不是它们之间的文本(和它们之间的空白)上时,咱们可使用另一个更有用的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对象的子属性和兄弟属性:

  • firstElementChild,lastElementChild:相似firstChild和lastChild,但只表明Element
  • nextElementSibling,previousElementSibing:相似nextSibing和previousSibling,但只表明兄弟Element
  • childElementCount:子元素的数量。返回的值和chilren.length值相等

子元素和兄弟元素的属性是标准属性,并在除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对象),但它们有原型,能够用自定义方法扩展它。

。。。。。。

4.属性

HTML元素包含一个标签和一组称为属性(Attribute)的名/值对组成。例如:<a>元素定义了一个超连接,经过使用href属性来指向连接的目的地址。HTML元素的属性值在表明这些元素的HtmlElement对象的属性(property)中是可用的。DOM一样定义了一些API来设置和获取XML属性和非标准化的HTML属性。下面将详细介绍这些特性。

4.1.HTML属性做为Element属性

表示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操做符也没法完成此项目。下一节描述能够实现此目的方法。

4.2.取得和设置非标准的HTML属性

像上面描述的那样,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()地第二个参数应该是属性的有效名字,它包含命名空间的前缀。能够在本书的第四部分中阅读更多关于命名空间的属性的方法。

4.3.数据集属性

有时在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>绘制相似火花线的标记代码。

4.4.做为Attr节点的属性

还有一种使用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属性返回属性的名字和值。

5.元素的内容

看上面树状图的列子,并问本身一个问题<p>元素的“内容”是什么?回答这个问题有三个方法

  • 内容是HTML字符串"This is a <i>simle</i> document"
  • 内容是纯文本字符串“This is a simple document”.
  • 内容是一个Text节点、一包含了一个Text子节点的Element节点和另一个Text节点

每一种回到都有效,后面几节咱们将解释如何使用HTML表示、纯文本和元素内容树状表示

5.1.做为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、.这些值对于以下

后续有相关内容。

5.2.做为纯文本的元素内容

另外一种方法处理元素的内容是当作一个子节点列表,每一个子节点均可能有它本身一组子节点,当考虑元素的内容时,一般感兴趣的是它的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]);
}

 6.建立、插入、和删除节点

咱们已经看到用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);
}

6.1.建立节点

建立新的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为第二参数,此方法将递归导入全部的后代节点。

6.2.插入节点

一旦有了有了一个新的节点,就能够用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
    }
}

6.3.删除和替换节点

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()的实际用法

。。。。。。

6.4.使用DocumentFragment

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。

。。。。。。

7.例子:生成目录表

本例子说明了如何为文档动态的建立一个目录表。它展现了上一节所描述的文档脚本化的不少概念、元素选取、文档遍历、元素属性设置、innerHTML属性设置和在文档中建立于插入新节点等。本例子的注释也比较详尽。

。。。。。。

8.文档和元素的几何形状和滚动

但有时候。判断一个元素精确的几个形状也是很是有必要的。例如:在16章咱们看到利用css元素指定位置。若是想要css动态定位一个元素(如工具的提示或插图)到某个已经由浏览器定位后的普通元素的旁边,首先要判断那个元素的当前位置。

本节阐述了在浏览器窗口中完成文档的布局后,怎么才能在抽象的基于树的文档模型与几何形状的基于坐标的视图之间来回变换。本节描述的属性和方法以及在浏览器中实现有很长一段时间了,有些是IE特有的,有些到IE9才实现。你们能够参考W3C的标准化流程,做为CSSOM-View模块www.w3.org/TR/cssom-view/

8.1.文档坐标和视口坐标

元素的位置是以像素度量的,向右表明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};
}

8.2.查询元素的几何尺寸

断定一个元素尺寸和位置最简单的方法是调用它的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()所返回的矩形对象(和矩形列表)并非实时的,他们只是调用方法时视觉状态的静态快照。用户在滚回或改变浏览器大小的时候并不会更新他们。

8.3.判断元素在某点

getBoundingClientRect()方法使咱们能在视口中断定元素的位置,但有时咱们反过来想,断定在视口中指定的位置有什么元素。这时咱们能够用Document对象的elementFormPoint()方法来断定传递(x,y)坐标(视口坐标而非文档坐标),该方法返回指定位置的一个元素。因为该方法的算法尚未具体定下来,可是这个方法意图就是它返回在那个点里和最上面(z-index属性)的元素若是指定的点在视口之外,elementFromPoint()返回null.即便改点在转换为文档坐标后是完美有效的,返回值也同样。

8.4.滚动

例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为一个命名锚点的名字后浏览器产生的行为相似。

8.5.关于元素尺寸、位置和溢出的更多信息

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很好地解决这方面的问题。

9.HTML表单

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、请参考下面。

9.1.选取表单和表单元素

表单和它们所包含的元素能够用如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;

9.2.表单和元素的属性

上面描述的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()方法来重置表单元素的值。

全部表单元素通常都会有一下属性:

  • type:标识表单元素类型的只读的字符串。针对<input>标签订义的表单元素而言,就是其type属性的值。其它表单元素(如<textarea>和<select>)定义type属性是为了轻松标识它们。与input元素在类型检测时相互区别。
  • form:对包含元素Form对象的只读引用,或者若是元素没有包含在一个<form>元素中的值则为null
  • name:只读的字符串,由HTML属性name指定
  • value:可读/写的字符串,指定了表单元素包含或表明的“值”。它就是当提交表单时发送到web服务器的字符串,也是javascript程序有时候会感兴趣的内容。针对Text和Textarea元素,该属性值包含了用户输入的文本。针对用<input>标签建立的按钮元素(除了用<button>标签建立的按钮),该属性值指定了按钮显示的文本。可是,针对单选和复选按钮,该属性用户不可见也不能编辑。它仅是用HTML的value属性来设置一个字符串。它在表单提交的时候使用,但在关联表单元素的额外数据时也颇有用。在本章后面有关不一样类目的表单元素小节中将深刻讨论value属性。

 9.3.表单和元素的事件处理程序

每一个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对象的引用。

9.4.按钮

按钮是常见的表单元素之一。按钮的自己没有默认的行为,除非它有onclick实际处理程序。不然它没有什么用处。以<input>定义的按钮会将value属性的值以纯文本显示。注意,超级连接与按钮同样提供了onclick事件处理程序。当onclick事件所触发的动做能够概念化为“跟随此连接”是就用一个连接;不然,用按钮。

提交和重置元素本就是按钮,不一样的是它们之间有关联的默认动做(提交和重置)。

本书的第四部分末包含input按钮,关于按钮表单元素详细内容参看input项,它包含了用button元素建立的按钮。

9.5.开关按钮

复选框和单选元素都是开关按钮,或有两种视觉状态的按钮:选中或未选中HTML属性name的值都相同。注意,当复选框利用做为表单属性的名字来选中这些元素时,返回的是一个类数组对象而不是单个元素。

单选和复选框都定义了它的checked属性该属性是可读/写的布尔值,它指定了元素当前是否选中defaultChecked属性也是布尔值,它是HTML属性的checked值;它指定了元素第一次加载页面时是否被选中。

单选和复选框元素自己不显示任何文本,它们一般和相邻的HTML文本一块儿显示(或与<label>元素相关联)。这意味着设置复选框或单选元素的value属性不改变元素的视觉表现,设置value只改变提交表单时发送到web服务器的字符串。

当用户单击或复选开关按钮,单选或复选框元素触发onclick事件。若是因为单击开关按钮改变了它的状态,它也触发onchange事件。注意,当用户单击其余单选按钮而致使这个单选按钮状态的改变,后者不触发onchange事件。

9.6.文本域

“text”和“textarea”文本输入域是常见经常使用的元素,用户能够输入简短的文本字符串。value属性表示用户输入的内容。经过设置该属性值能够显示地指定应该在输入域中显示的文本。

在HTML5中,placeholder属性表示用户在输入框中提示的信息。

文本输入域的onchange事件处理程序表面用户完成编辑并将焦点移出文本域

“password”输入时显示星号,但这只是防止眼睛看见而已,网络上传输仍是有可能被看到的。

“file”上传文件到服务器。有onchange事件处理程序。跟普通的输入域不一样的是它的value是只读,这个防止恶意的JavaScript程序欺骗用户上传本意不想共享的文件。

不一样的文本输入元素定义onkeypress、onkeydown和onkeyup事件处理程序。能够设置为返回false来防止记录用户的按键。

9.7.选择框和选项元素

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()等

10.其它文档特性

10.1.Document的属性

除了body、documentElement、forms外,还有其余有趣的属性:

  • cookie:容许javascript程序读、写HTTP cookie的特殊的属性。20章涵盖该属性
  • domain:该属性容许当前web页面之间的交互,相同域名下相互信任的web服务器之间协做放宽同源策略的安全限制(13.6.2节)
  • lastModified:包含文档修改的时间的字符串
  • location:与window对象的location属性引用同一个Location对象
  • referrer:若是有,它表示浏览器导航到当前连接的上一个文档。该属性和HTTP的Referer头部信息相同,只是拼写有两个r.
  • title:文档的<title></title>标签之间的内容
  • URL:文档的URL,只读字符串而不是Location对象。该属性与Location.href初始值相同,只是不包含Location对象的动态变化。例如,若是用户在文档中导向一个新的片断,Location.href会发生变化,但document.URL则不会。

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;
        }
    }
}

10.2.document.write()方法

已经较少使用了。

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 = "";
};

10.3.查询和选取的文本

有时断定用户在文档中选取了那些文本很是有用,能够用相似以下的函数达到目的。

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属性。

10.4.可编辑的内容

全部当今的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章的计算在本地保存用户的编辑文本。

相关文章
相关标签/搜索