三、脚本化文档
客户端javascript的存在使得静态的html文档变成了交互式的web应用。每一个web浏览器窗口,标签页和框架由一个window对象所表示。每个window对象都有一个document属性引用了一个Document对象。Document对象表示窗口的内容,Document对象并非独立的,它是一个巨大的api中的核心对象,叫做文档对象模型(document object model, DOM),它代表和操作文档的内容。
DOM树结构在html相关知识里有详细介绍,这里就不介绍了,树中的每个节点称为Node,每个节点都有自己的属性和方法。DOM树的根节点是Document节点,代表整个文档。代表HTML元素的节点是Element节点,代表文本的节点是Text节点,代表注释的节点是Comment节点。一切皆节点。Document,Element和Text是Node的子类。
1. 选取文档元素
1.1 通过ID选取元素 getElementById()
前面说过,任何html元素都可以有一个id属性,这个id必须唯一。可以用Document对象的getElementById()方法选取一个基于唯一ID的元素,返回唯一的Element。
Var section1 = document.getElementById(“section1”);
1.2 通过名字选取元素 getElementsByName()
Html的name属性最初打算为表单元素分配名字,在表单提交到服务器时使用该属性的值。类似id属性,name是给元素分配名字,但name不是必须唯一的。比如radio和checkbox元素通常同一组的要设置相同的name属性,方便操作。而且,和id属性不同的是name属性只在少数html元素中有效,包括表单,表单元素,<iframe>和<img>元素。
可以通过Document的getElementsByName()获取一组Element (因为name属性不是要求唯一的),返回一个NodeList, 而且在IE中,getElementsByName()也返回id属性匹配的元素。
Var radiobuttons = document.getElementsByName(“favorite_color”);
1.3 通过标签名选取元素 getElementsByTagName()
Document对象的getElementsByTagName()可以用来选取指定类型(标签名)的所有html和xml元素。
Var spans = document.getElementsByTagName(“span”);
类似于getElementsByName(), getElementsByTagName()返回一个实时的NodeList对象。在NodeList中返回的元素按照在文档中的顺序排序的。该方法对标签名不区分大小写,但建议规范都用小写。
另外: 给getElementsByTagName()传递通配符参数“*”,则会返回一个文档中所有元素的NodeList对象。
Element对象也定义了getElementsByTagName()方法,原理和Document对象的一样,但是只返回调用该方法的元素的后代元素。
由于历史原因,HTMLDocument类定义了一些快捷属性来访问各种节点。如images, forms和links等属性指向行为类似只读数组的<img>,<form>和<a>(但只包含那些有href属性的<a>标签)元素集合。这些属性指代HTMLCollection对象(很像NodeList对象)。scripts在html5中是标准属性,是<script>元素的集合。
HTMLDocument对象还定义两个属性,指代特殊的单个元素而不是元素的集合。document.body是一个html文档的<body>元素,document.head是<head>元素。Document类的documentElement属性指代文档的根元素,在html元素中总是指代<html>元素。
1.4 通过css类选取元素 getElementsByClassName()
HTML元素的class属性值是一个以空格隔开的列表,可以为空或包含多个标识符。在客户端javascript中,用className属性保存HTML的class属性值。Html定义了getElementsByClassName()方法,基于class属性值中的标识符来选取文档元素集合,返回一个实时的NodeList对象。需要匹配多个class属性时以空格隔开,多个之间是且的关系,与顺序无关。Document和Element对象都定义了该方法,跟getElementsByTagName类似。
var warning = document.getElementsByClassName(“warning”); var log = document.getElementById(“log”); var fatal = log.getElementsByClassName(“fatal error”);
1.5 通过CSS选择器选取元素(很重要)querySelectorAll(), querySelector()
CSS样式表有一个非常强大的语法,就是选择器,用来描述文档中若干或多组元素。Css功能很强大也使用很频繁,但这里不详细给出各种用法,可以参考其他资料。
比如:http://www.w3school.com.cn/css/css_selector_attribute.asp
Document和Element都定义了querySelectorAll()方法,接受一个css选择器的字符串参数,返回一个NodeList对象。与前面的选取元素的方法不同,querySelectorAll()返回的NodeList对象不是实时的,只包含在调用时刻匹配的元素,并不更新后续文档的变化。如果没有匹配的元素则返回空的NodeList。如果选择器非法,将抛出异常。
除了querySelectorAll(),文档对象还提供了querySelector()方法,它只返回第一个匹配的元素(以文档顺序),匹配不到返回null.
还可以参考:http://www.hujuntao.com/web/javascript/javascript-selector-queryselector-and-queryselectorall.html
注意:我们还注意到上面返回的有的是Element对象,有的是Node(NodeList)对象,不同的对象有不同的属性可以访问。但dom树中所有元素都可视为节点,所以返回的对象既可以使用Element的属性页可以使用Node的属性,关于属性的访问下面会有介绍。
文档结构的遍历
一旦从文档中选取了一个元素(上面提到了好几种选取方法),有时需要查找文档中与之结构上相关的部分(祖先,父亲,兄弟,子女)。上面提到的几个方法几乎都是返回NodeList对象(getElementById就不是),下面看下有了Node, NodeList可以做哪些操作。既可以遍历节点,也可以操作节点本身的属性。
Node: https://developer.mozilla.org/zh-CN/docs/DOM/Node
NodeList: https://developer.mozilla.org/zh-CN/docs/DOM/NodeList
1. 作为节点树的文档
整个html文档就是一个节点树,所有元素皆节点,Document对象,Element对象,Text对象等都是Node对象,Node定义了以下重要的属性。
1) parentNode 该节点的父节点,Document对象的父节点是null.
2) childNodes 只读的类数组对象(NodeList对象),它是该节点的子节点的实时表示。
3) firstChild, lastChild 该节点的子节点中的第一个和最后一个,如果该节点没有子节点则为null.
4) nextSibling, previousSibling 该节点的兄弟节点中的前一个和后一个。这两个属性将节点之间以双向链表的形式连接起来。
5) nodeType 该节点的类型。9代表Document节点,1代表Element节点,3代表Text节点,8代表Comment节点,11代表DocumentFragment节点。
6) nodeValue Text节点或Comment节点的文本内容。
7) nodeName 元素的标签名,以大写形式表示。
2. 作为元素树的文档
当将主要的兴趣点集中在文档中的元素上,而非他们之间的文本上时,我们可以使用另一个更有用的api。它将文档看作是Element对象树,忽略部分文档:Text和Comment节点。
1) 该API的第一部分是Element对象的children属性。类似childNodes,它也是一个NodeList对象。但不同的是children列表只包含Element对象。
注意:Text和Comment节点没有children属性,没有孩子节点,它意味着上述Node.parentNode属性不可能返回Text或Comment节点。
2) 基于元素的文档遍历API第二部分是Element属性,类似Node对象的子属性和兄弟属性:
firstElementChild, lastElementChild: 类似firstChild和lastChild.
nextElementSibling, previousElementSibling: 类似nextSibling和previousSibling
childElementCount: 子元素的数量,返回的值和children.length相等。
3. 属性
HTML元素由一个标签和一组属性(attribute,名/值对)组成。
3.1 html属性作为Element的属性
表示html文档元素的HTMLElement对象定义了读写属性,他们映射了html属性。如:
Var image = document.getElementById(“myimage”); Var imgurl = image.src; // src属性时图片的url Image.id === “myimage”; // 判定要查找图片的id.
Html属性名不区分大小写,但javascript属性名则大小写敏感,从html属性名转换到javascript属性名应该采用小写。多个单词的属性名采用驼峰表示法。
表示html属性的值通常是字符串。当属性时布尔值或数值(如input元素的defaultCheckd和maxLength属性),属性页是布尔值或数值而不是字符串。
事件处理程序总是Function对象(或null)。
3.2 获取和设置非标准html属性(非dom标准定义的属性,但被大多浏览器支持)
如上所述,htmlElement和其子类型定义了一些属性,他们对应于元素的标准html属性。Element类型还定义了getAttribute()和setAttribute()方法来查询和设置非标准的html属性,可以来查询和设置xml文档中元素上的属性。
Var image = document.images[0]; Var width = parseInt(image.getAttribute(“WIDTH”)); Image.setAttribute(“class”, “thumbnail”);
1) 属性值都被看做是字符串,getAttribute不反回数值,布尔值或对象。
2) 使用标准属性名,即使属性名是javascript的保留字。属性名不区分大小写。
Element类型还定义了两个相关的方法,hasAttribute()和removeAttribute(),他们用来检测命名属性是否存在和完全删除属性。
3.3 数据集属性
有时候在html元素上绑定一些额外的信息也是有帮助的,通常借助非标准属性来完成。可以通过getAttribute()和setAttribute()来读写非标准属性。但付出的代价是文档不再是合法有效的html。
HTML5提供了一个解决方案,任意以“data-”为前缀的小写的属性名字都是合法的。这些“数据集属性”将不会对其元素的表现产生影响,他们定义了一种标准的附加额外数据的方法,并不是在文档合法性上作出让步。
Html5在Element对象上定义了dataset属性,该属性指代一个对象,它的各个属性对应于去掉前缀的data-属性,如:
dataset.xx对应于html里定义的data-xx属性。
带连字符的属性对应于驼峰命名法属性名:
dataset.jqueryTest对应于html中定义的data-jquery-test.
3.4 作为Attr节点的属性
还有一种使用Element属性的方法。Node类型定义了attributes属性,针对非Element对象的任何节点该属性为null; 对于Element对象,attributes属性时只读的类数组对象,它代表元素的所有属性。类似NodeList, attributes对象也是实时的。可以用数字索引访问,也可以用属性名索引。
Document.body.attributes[0];
Document.body.attributes.bgcolor;
Document.body.attributes[“ONLOAD”];
小结:本节介绍了两种遍历html树的方法,一种通过节点(Node, NodeList的属性)访问,一种通过Element元素访问。因为Document对象,Element对象,Text对象等都是Node对象,所以获取到一个元素后两种方式访问可以混合同时使用。另外还介绍了3中访问属性的方法,一种直接通过Element对象访问html属性,第二种通过getAttribute,setAttribute访问,第三种就是通过Attr节点的attributes属性访问。其中还提到一种通过”data-“定义自定义属性的方法。
元素的内容
例子:This is a <i>simple</i>document.
这个例子中的“内容”是什么?可以有三个角度解释:
1, 内容是html字符串“This is a <i>simple</i>document.”
2, 内容是纯文本字符串 “This is a simple document.”
3, 内容是一个Text节点,一个包含了一个Text子节点的Element节点和另外一个Text节点。
下面分别解读一下。
1. 作为HTML的元素内容
读取Element的innerHTML属性作为字符串编辑返回那个元素的内容。在元素上设置innerHTML调用了Web浏览器的解析器,用新字符串内容的解析展现形式替换元素当前内容。
Web浏览器很擅长解析HTML,通常设置innerHTML效率很高。但在进行”+=”操作时通常效率低下,因为既要序列化又要解析。
HTML5还标准化了outerHTML属性,这个属性包含被查询元素的开头和结尾标签。当设置outerHTML时,元素本身被新的内容替换。只有Element节点定义了outerHTML属性,Document节点则无。
2. 作为纯文本的元素内容
有时需要查询纯文本形式的元素内容,或者插入纯文本。标准的做法是用Node的textContent属性来实现:
Var p = document.getElementByTagName(“p”)[0]; Var text = p.textContent; p.textContent = “hello world.”;
textContent在除了IE的所有浏览器中都支持。在IE中可以用Element的innerText属性来代替。
3. 作为Text节点的元素内容
另一种方法处理元素的内容是当做一个子节点列表,每个子节点可能有它自己的一组子节点。当考虑元素的内容时通常感兴趣的是它的text节点。在xml文档中,也必须准备好处理CDATASection节点(它是text的子类型,代表了CDATA的内容)。
获取了text节点就可以通过nodeValue属性来访问内容了。前面提到过nodeValue是Text节点或Comment节点的文本内容。
创建、插入和删除节点
前面我们看到用html和纯文本字符串如何来查询和修改文档内容,也看到我们能遍历Document来检查组成Document的每个Element和Text节点。Document类型定义了创建Element和Text对象的方法,Node类型定义了在节点树中插入,删除和替换的方法。前面我们分别从节点和Element的角度讨论过html树的结构,可以遍历和修改各个节点,属性。
1. 创建节点,复制节点 createElement(),createTextNode(),cloneNode()
创建新的Element节点可以使用Document对象的createElement()方法,给方法传递元素的标签名,对html文档来说该名字不区分大小写,对xml文档要区分。
Text节点要用createTextNode()创建,传入文本节点的内容。
Var newText = document.createTextNode(“text node”);
Document也定义了其他一些工厂方法,如不经常使用的createComment(), createDocumentFragment(), 如果使用了xml命名空间的文档可以使用createElementNS()来同时指定命名空间的uri和待创建的Element的标签名字。
另一种创建新文档节点的方法是赋值已存在的节点。每个节点都有一个cloneNode()方法来返回该节点的一个全新副本。给方法传递true参数能赋值所有的后代节点,或传递false只是执行一个浅复制。
2. 插入节点 appendChild(), insertBefore()
一旦有了新节点,就可以用Node的方法appendChild()或insertBefore()将他们插入到文档中。appendChild()是在需要插入的Element节点上调用的,它插入指定的节点使其成为自己的最后一个子节点。
insertBefore()就想appendChild()一样,除了它接收两个参数。第一个参数是待插入的节点,第二个参数是已存在的节点,新节点将插入该节点的前面。该方法应该在新节点的父节点上调用,也就是这个方法的两个参数应该是兄弟关系。如果传递null作为第二个参数,insertBefore()的行为类似appendChild(),它将节点插入到最后。
如果调用appendChild()或insertBefore()将已存在的文档中的一个节点再次插入,那个节点将自动从当前的位置删除并在新的位置插入。
3. 删除和替换节点 removeChild(), replaceChild()
RemoveChild()方法是从文档树中删除一个节点。但是请小心:该方法不是在待删除的节点上调用,而是在其父节点上调用。是删除父节点的子节点。
replaceChild()方法删除一个子节点并用一个新的节点取而代之。在父节点上调用该方法,第一个参数是新节点,第二个参数是需要代替的节点。
4. 使用DocumentFragment
DocumentFragment是一个特殊的Node,它作为其他节点的一个临时的容器:
Var frag = document.createDocumentFragment();
像Document节点一样,DocumentFragment是独立的,而不是任何其他文档的一部分。它的parentNode总是null.但类似Element,它可以有任意多的子节点,可以用appendChild(),insertBefore()等方法来错做他们。
DocumentFragment的特殊之处在于它使得一组节点被当做一个节点看待,如果给appendChild(),insertBefore()或replaceChild()传递一个DocumentFragment,其实是将该文档片段的所有子节点插入到文档中,而非片段本身。
这里可以看到Element所有的属性和方法:
http://www.w3schools.com/jsref/dom_obj_all.asp
html表单
html的<form>元素和各种各样的表单输入元素(如<input>,<select>,<button>)在客户端编程中有着重要的地位。用户的输入从表单元素来收集,表单将这些输入递交给服务器,服务器处理输入并生成一个新的html页面显示在客户端。
W3school中定义的form:
<form> 标签用于为用户输入创建 HTML 表单。
表单能够包含 input 元素,比如文本字段、复选框、单选框、提交按钮等等。
表单还可以包含 menus、textarea、fieldset、legend 和 label 元素。
表单用于向服务器传输数据。
即使当整个表单数据都是由客户端javascript来处理并不会提交到服务器时,html表单仍是收集用户数据很好的方法。
在服务端程序中,表单必须要有一个“提交”按钮,否则它就没有用处。另一方面,在客户端编程中,“提交”按钮不是必须的。服务端程序是基于表单提交动作的,他们按表单大小的块处理数据。客户端程序时基于事件的,他们可以对单独的表单元素上的事件作出相应。
下面是最常使用的表单元素:
1. 选取表单和表单元素
表单和他们所包含的元素可以用如getElementsByTagName(),getElementById()等标准方法从文档获取,在支持querySelectorAll()的浏览器中,从一个表单中选取所有的单选按钮或所有同名的元素代码如下:
Document.querySelectorAll(‘#shipping input[type=”radio”]’); Document.querySelectorAll(‘#shipping input[type=”radio”][name=”method”]’);
一般来说指定文档元素的方法用id属性比name属性更佳,但是,name属性在html表单提交中有特殊的目的,它在表单中较为常用,其他元素很少使用。
2. 表单和元素的属性
Elements[]数组是form对象中最重要的属性:
elements 集合可返回包含表单中所有元素的数组。
元素在数组中出现的顺序和它们在表单的HTML 源代码中出现的顺序相同。
每个元素都有一个 type 属性,其字符串值说明了元素的类型。
提示:如果 elements[] 数组具有名称(input 标签的 id 或 name 属性),那么该元素的名称就是 formObject 的一个属性,因此可以使用名称而不是数字来引用 input 对象。
举例,假设 x 是一个 form 对象,其中的一个 input 对象的名称是 fname,则可以使用 x.fname 来引用该对象。
action,encoding,method和target属性直接对应于<form>元素的html属性。这些属性都控制了表单如何提交数据到web服务器。
在javascript产生之前,要用一个专门的提交按钮来提交表单,重置按钮重置表单元素的值。Javascript的form对象支持两个方法:submit()和reset(),他们完成同样的目的,但是调用这两个方法不会触发form元素的onsubmit和onreset事件。
所有(或多数)表单元素都有以下属性。如果一些元素由其他专用的属性,会在后面单独描述:
type:标识表单元素的类型的只读字符串,如type=”input”, type=”select”等。
form:对包含元素的form对象的只读引用,或者如果元素没有包含在一个form元素中则其值为Null
name:只读的字符串,由html属性name指定
value:可读写的字符串,指定了表单元素包含或代表的值,针对text和textarea表示用户输入的文本。针对input标签(除了button类型)表示按钮显示的文本。但是针对单选和复选按钮,该属性用户不可见也不可编辑。
3. 表单和元素的事件处理程序
每个form元素都有一个onsubmit事件和onreset事件处理程序来侦测表单。表单提交前调用onsubmit,它通过返回false能取消提交动作,如可以在提交前做输入校验,注意,onsubmit事件只能通过单击提交按钮触发,调用表单的submit方法是不能触发的。Onreset几乎类似。
当用户与表单元素交互时往往会触发click或change事件,通过定义onclick或onchange事件处理程序可以处理这些事件。
一般,当按钮表单元素激活时会触发click事件,当用户改变其他表单元素所代表的值时会触发change事件。注意并不是每次用户输入一个键值都会触发change,仅当用户改变了元素的值后焦点移到其他元素上时才触发。
表单元素收到键盘的焦点也会触发focus事件,失去焦点会触发blur事件。
其他文档属性
1. Document的属性
前面已经介绍的Document属性有body,documentElement和forms等特殊的文档元素,文档还定义了一些其他有趣的属性:
Cookie: 允许javascript程序读写http cookie的特殊的属性。
Domain:该属性允许当web页面之间交互时,相同域名下互相信任的web服务器之间协作放宽同源策略安全限制。
lastModified: 包含文档修改时间的字符串。
Location:与window对象的location属性引用同一个Location对象。
2. document.write()方法
这是一个由Netscape2浏览器实现的非常早期的脚本化api, 现在的代码中已经不再需要它了。它能在解析文档时动态的向当前文档输出html,但一定不要放在函数的定义中,否则会冲掉当前文档已有的所有内容。
3. 可编辑的内容 contenteditable
我们已经知道html表单元素包含了文本字段和文本域元素。但这里提到的是另外一种方法。
有两种方法启用编辑功能。一、设置任何html标签的contenteditable属性,二、使用javascript设置contenteditable属性。这都将使得任何元素的内容变得可编辑。
contenteditable 属性是 HTML5 中的新全局属性。可以设置:contenteditable="value" 其中value可以取值false和true.
如:
<div id=”editor” contenteditable=”true”> Click to edit. </div>