脚本化文档(1)

DOM概览

HTML文档的树状表示

上图的每个方框是文档的一个节点,它表示一个Node对象。

Node节点的继承关系

注意,通用的Document和Element类型与HTMLDocument和HTMLElement类型之间是有严格的区别的。Document类型代表一个HTML或XML文档,Element类型代表该文档中的一个元素。HTMLDocument和HTMLElement子类只是针对于HTML文档和元素

选择文档元素

获取文档的一个或多个元素有如下方法:

  • 用指定的id属性
  • 用指定的name属性
  • 用指定的标签名字
  • 用指定的CSS类(class属性)
  • 用指定的CSS选择器

通过ID选取元素

可以用Document对象的getElementById()方法选取一个基于唯一ID的元素,返回包含单个Element的Node对象

// 选择id为"section1"的唯一元素
var section1 = document.getElementById("section1");

通过name选择元素

区别于id,name属性的值不是必须唯一,多个元素可以有同样的名字。
getElementsByName()定义在HTMLDocument类中,而不是在Document类中,所以它只针对HTML文档可用,返回包含多个Elements的NodeList对象
注意:对于<iframe>元素,返回值不是元素自身的Element对象,而是表示<iframe>元素创建的嵌套浏览器窗体的Window对象。

// 选择name为"favorite"的所有元素
var radiobuttons = document.getElementsByName("favorite");

通过标签名选择元素

Document对象的getElementsByTagName()方法可用来选取指定标签的所有HTML或XML元素,返回包含多个Elements的NodeList对象

// 选择第1个<p>元素
var firstpara = document.getElementByTagName("p")[0];

由于历史原因,HTMLDocument类定义了一些快捷属性来访问各种各样的节点:

  • images、forms和links属性指向<img><form><a>元素集合。这些属性指代HTMLCollection对象,很像NodeList对象。
// 引用id为"shipping_address"的form元素
document.forms.shipping_address;
  • head、body属性与上面不同,指向单个元素而不是元素的集合。
// 引用body元素
document.body;

通过CSS类(class)选择元素

类似getElementsByTagName(),在HTML文档和HTML元素上都可以调用getElementsByClassName(),它的返回值是一个实时的的NodeList对象,包含文档或元素所有匹配的后代节点
getElementsByClassName()只需要一个字符串参数,但是该字符串可以由多个空格隔开的标识符组成,只有当元素的class属性值包含所有的标识符时才匹配,但是标识符的顺序是无关紧要的。

// 查看id为"log"的元素的所有后代中,类名中包含"fatal"和"error"的元素集合
var log = document.getElementById("log");
var fatal = log.getElementsByClassName("fatal error");

通过CSS选择器选择元素

  • 用ID、标签名或类名选择
#nav        // id="nav"的元素
div         // 所有<div>元素
.warning    // 所有在class属性中包含"warning"的元素
  • 基于属性值来选取
p[lang="fr"]    // 所有使用法语的段落,如:<p lang="fr">
*[name="x"]     // 所有包含name="x"属性的元素
  • 组合使用
span.fatal.error            // class中包含"fatal"和"error"的所有<span>元素
span[lang="fr"].warning     // 所有使用法语且class中包含"warning"的<span>元素
  • 基于文档结构选取
#log span               // id="log"元素的 *后代元素* 中的所有<span>元素
#log>span               // id="log"元素的 *子元素* 中的所有<span>元素
body>h1:first-child     // <body>的子元素中的第一个<h1>元素

Document对象的querySelectorAll()方法,接收一个包含CSS选择器的字符串参数,返回匹配选择器的所有元素的NodeList对象,但是NodeList对象并不是实时的
同时,还有个方法querySelector(),但它只返回第1个匹配的元素(以文档顺序)或者没有匹配就返回null。
注意:CSS定义了":first-line"和":first-letter"等伪元素,在CSS中,它们匹配文本节点的一部分而不是实际元素。如果和querySelectorAll()和querySelector()一起使用它们是不匹配的。

文档结构和遍历

文档的节点(Node)树

Document对象、它的Element对象和文档中表示文本的Text对象都是Node对象。
Node有以下一些属性:

属性 意义
parentNode 该节点的父节点,Document对象的父节点是null。
childNodes 该节点的子节点的实时表示(NodeList对象)。
firstChild、lastChild 该节点的第1个和最后1个子节点,如果没有子节点则为null。
nextSibling、previoursSibling 该节点的兄弟节点的下一个和前一个。
nodeType 该节点的类型。1代表Element节点,3代表Text节点,8代表Comment节点,9代表Document节点,11代表DocumentFragment节点。
nodeValue Text节点或Comment节点的文本内容。
nodeName 元素的标签名,以大写形式表示。

文档的元素(Element)树

如果将文档Element对象树,将忽略Text和Comment节点。
Element对象的有以下属性:

属性 意义
parentNode 该节点的父节点,任何Element的parentNode总是另一个Element,或者追溯到树根的Document或DocumentFragment节点。
children 类似ChildNodes,它也是一个NodeList对象,但不同的是children列表只包含Element对象。
firstElementChild, lastElementChild 类似firstChild和lastChild,但只代表子Element
nextElementSibling, previousElementSibling 类似nextSibling和previousSibling,但只代表兄弟Element
childElementCount 子元素的数量。返回的值和children.length值相等。

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<!-- 注释 -->
<div class="fox" id="box">123</div>
<script>
//【1】元素节点
var nodeElement = document.body;
console.log(nodeElement.nodeName, nodeElement.nodeValue, nodeElement.nodeType, nodeElement.nodeType==Node.ELEMENT_NODE);//BODY null 1 true

//【2】元素特性在DOM中以Attr类型表示,是存在于元素的attributes属性中的节点,但却不是DOM文档树的一部分。
var nodeAttribute = document.getElementById("box").attributes[0] ;
console.log(nodeAttribute.nodeName, nodeAttribute.nodeValue, nodeAttribute.nodeType,nodeAttribute.nodeType == Node.ATTRIBUTE_NODE)//id box 2 true (示例中包含2个属性,如果是attributes[1]是输出"class fox 2 true")

//【1】元素节点
var nodeChildElement = document.body.firstElementChild;
console.log(nodeChildElement.nodeName, nodeChildElement.nodeValue, nodeChildElement.nodeType,nodeChildElement.nodeType == Node.ELEMENT_NODE)//DIV null 1 true

//【3】文本节点
var nodeText = document.body.firstElementChild.firstChild;
console.log(nodeText.nodeName, nodeText.nodeValue, nodeText.nodeType,nodeText.nodeType == Node.TEXT_NODE)//#text 123 3 true

//【4】CDATASection类型只针对基于XML的文档,只出现在XML文档中,表示的是CDATA区域
//【5】ENTITY_REFERENCE_NODE 实体引用名称节点
//【6】ENTITY_NODE 实体名称节点
//【7】PROCESSING_INSTRUCTION_NODE 处理指令节点

//【8】注释节点
var nodeComment = document.body.childNodes[1];
console.log(nodeComment.nodeName, nodeComment.nodeValue, nodeComment.nodeType,nodeComment.nodeType == Node.COMMENT_NODE)//#comment 注释 8 true

//【9】文档节点
var nodeDocument = document;
console.log(nodeDocument.nodeName, nodeDocument.nodeValue, nodeDocument.nodeType,nodeDocument.nodeType==Node.DOCUMENT_NODE);//#document null 9 true

//【10】文档类型节点
var nodeDocumentType = document.firstChild;
console.log(nodeDocumentType.nodeName, nodeDocumentType.nodeValue, nodeDocumentType.nodeType,nodeDocumentType.nodeType==Node.DOCUMENT_TYPE_NODE);//html null 10 true

//【11】DocumentFragment文档片段类型在文档中没有对应的标记,是一种轻量级的文档。
var nodeDocumentFragment = document.createDocumentFragment();
console.log(nodeDocumentFragment.nodeName, nodeDocumentFragment.nodeValue, nodeDocumentFragment.nodeType,nodeDocumentFragment.nodeType == Node.DOCUMENT_FRAGMENT_NODE)//#document-fragment null 11 true

//【12】NOTATION_NODE DTD中声明的符号
</script>
</body>
</html>

可参考链接

元素属性

HTML元素由一个标签和一组称为属性(attribute)的名/值对组成。

HTML标准属性

HTMLElement定义了通用的HTTP属性,如id、lang、dir,以及事件处理程序,如onclick。
标准属性有以下特点:

  • HTML属性名不区分大小写,但JavaScript属性名则对大小写敏感。从HTML属性名转换到JavaScript属性名时应该采用小写,如果包含多个单词,则除第一个以外的单词的首字母大写,如:defatultCheked.
  • 有些HTML属性在JavaScript中是保留字。对于这些属性,一般的规则是为属性名加前缀"html"。如for属性在JavaScript中变为htmlFor;但class属性是一个例外,在JavaScript中它为className。
  • 表示HTML属性的值通常是字符串,但也有布尔值或数值的属性,如defaultChecked和maxLength。事件处理程序的属性则是Function对象(或null)。HTML元素的style属性值是CSSStyleDeclaration对象。

HTML非标准属性

Element类型定义了getAttribute()和setAttribute()方法来查询和设置非标准的HTML属性,也可用于查询和设置XML文档的属性。
非标准属性有以下特点:

  • 属性值都被看做是字符串。
  • 方法使用标准属性名,甚至当这些名称是JavaScript保留字时也不例外。
var image = document.images[0];
var width = parseInt(image.getAttribute("WIDTH"));  // 需要调用parseInt()将字符串转换成int
image.setAttribute("class", "thumbnail");           // "class"属性

数据集属性(dataset)

有时候我们需要在HTML元素上绑定一些额外的信息,可以使用getAttribute()和setAttribute()来读和写非标准属性的值,但为此付出的代价是文档将不再是合法有效的HTML。
HTML5提供了一个解决方案。在HTML5文档中,任意以"data-"为前缀的小写的属性名字都是合法的。
HTML5还在Element对象上定义了dataset属性。该属性指代一个对象,它的各个属性对应于去掉前缀的data-属性。带连字符的属性对应于驼峰命名法属性名:data-jquery-test属性就变成dataset.jqueryTest属性。

attributes属性

Node类型定义了attributes属性。针对非Element对象的节点,该属性为null。对于Element对象,attributes属性是实时只读的类数组对象,它代表元素的所有属性。Attr对象是一类特殊的Node.

document.body.attributes[0];        // <body>元素的第1个属性
document.body.attributes.bgcolor;   // <body>元素的bgcolor属性
document.body.attributes["ONLOAD"]; // <body>元素的onload属性

元素内容

innerHTML&outerHTML属性

  • innerHTML属性返回元素的内容(可能包含其他element元素)。
  • outerHTML属性返回元素的标签与内容。

如,对于<p>元素:<p>This is a <i>simple</i> document</p>
innerHTML属性的值为:This is a <i>simple</i> document
outerHTML属性的值为:<p>This is a <i>simple</i> document</p>

textContent属性

有时需要查询纯文本形式的内容,或在文档中插入纯文本,则可以使用Node的textContent属性来实现。
textContent属性就是将指定元素的所有后代Text节点简单地串联在一起
如,对于<p>元素:<p>This is a <i>simple</i> document</p>
textContent属性的值为:This is a simple document
注意:在IE中要使用innerText属性来代替。

// 实现textContent
function textContent(e) {
    var child, type, 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)             // 递归Element节点
            s += textContent(child);
    }
    return s;
}

创建、插入和删除节点

创建节点

  • 创建新的Element节点可以使用document对象的createElement()方法。
// 从指定的URL,异步加载和执行脚本
function loadasync(url) {
    var head = document.getElementsByTagName("head")[0];
    var s    = document.createElement("script");
    s.src    = url;
    head.appendChild(s);
}
  • 还可通过cloneNode()方法来创建一个节点,新创建的节点以现有的节点为模板:
<!DOCTYPE html>
<html>

<script>
function myFunction()
{
    var itm=document.getElementById("myList2");
    var cln=itm.cloneNode(true);    // 深拷贝
    document.body.appendChild(cln);
}
</script>

<body>

<ul id="myList1"><li>Coffee</li><li>Tea</li></ul>
<ul id="myList2"><li>Water</li><li>Milk</li></ul>

<button onclick="myFunction()">添加一个列表</button>

</body>
</html>

注意:拷贝出来的元素id与原始的相同,但通过getElementById()获取到的将还是原始的元素,而不是拷贝出来的元素。

插入节点

Node的方法appendChild()或insertBefore()方法可以实现将一个节点插入到已知文档中。
appendChild()将新节点插入到最后,作为该节点的最后一个子节点。
insertBefore()则将新节点插入到指定的子节点之前。
注意:如果使用上述方法将已存在的一个节点再次插入,那么节点将自动从它原有的位置删除并在新的位置重新插入(类似于先删除后新增)。

删除和替换节点

  • removeChild()方法可以从文档树中删除一个节点。
node.parentNode.removeChild(node);  // 删除自身节点
  • replaceChild()方法可以用一个新节点替换已存在的节点。
node.parentNode.replaceChild(document.createTextNode("[ REPLACED ]"), node);    // 替换自身节点
// 使用innerHTML实现outerHTML属性
( function() {
    // 如果outerHTML存在,则直接返回
    if(document.createElement("div").outerHTML) return;
    
    // get方法
    function outerHTMLGetter() {
        var container = document.createElement("div");  // 创建一个虚拟节点
        container.appendChild(this.cloneNode(true));
        return container.innerHTML;
    }
    
    // set方法
    function outerHTMLSetter(value) {
        var container = document.createElement("div");  // 创建一个虚拟节点
        container.innerHTML = value;
        // 将value的所有节点插入到this节点之前
        // NOTE: 由于同一个documnet中,firstChild插入后,原始位置上的节点将被删除
        while(container.firstChild) 
            this.parentNode.insertBefore(container.firstChild, this);
        // 删除当前节点
        this.parentNode.removeChild(this);
    }
    
    // 设置outerHTML属性的getter和setter
    if(Object.defineProperty) {
        Object.defineProperty(Element.prototype, "outerHTML", {
                                get: outerHTMLGetter,
                                set: outerHTMLGetter,
                                enumerable: false,
                                configurable: true
                              });
    } else {
        Element.prototype._defineGetter_("outerHTML", outerHTMLGetter);
        Element.prototype._defineSetter_("outerHTML", outerHTMLSetter);
    }
}());

DocumentFragment节点

  • DocumentFragment是一个特殊的Node,它作为其他节点的一个临时的容器。
  • 像Document节点一样,DocumentFragment节点是独立的,而不是任何其他文档的一部分,它的parentNode总是null。
  • 类似Element节点,它可以有任意多的子节点,可以使用appendChild()、insertBefore()等方法来操作它们。
  • 将DocumnetFragment节点插入到文档中,其实是将DocumentFragment的所有子节点插入到文档,而不是DocumentFragment本身。
// 倒序排列节点node的子节点
function reverse(node) {
    var f = document.createDocumentFragment();
    // NOTE: 给f添加一个节点,该节点会自动从node中删除
    while(node.lastChild) f.appendChild(node.lastChild);
    
    // 将临时节点f的所有子节点全部移回到node中
    node.appendChild(f);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容