DOM(Document Object Model)

参考书:《JavaScript高级程序设计》


知识点前提:什么是节点


Node类型

DOM1级定义了一个Node接口,该接口将DOM中所有节点类型实现。Node接口在JavaScript中是作为Node类型实现的;除了IE之外,在其他所有浏览器都可以访问到这个类型。

nodeType

只读

每个节点都有一个nodeType属性,有用于表明节点的类型。节点类型由在Node类型中定义的下列12个数值常量表示,任何节点类型必占据一种。

  • Node.ELEMENT_NODE(1);
  • Node.ATTRIBUTE_NODE(2);
  • Node.TEXT_NODE(3);
  • Node.CDATA_SECTION_NODE(4);
  • Node.ENTITY_REFERENCE_NODE(5);
  • Node.ENTITY_NODE(6);
  • Node.PROCESSING_INSTRUCTION_NODE(7);
  • Node.COMMENT_NODE(8);
  • Node.DOCUMENT_NODE(9);
  • Node.DOCUMENT_TYPE_NODE(10);
  • Node.DOCUMENT_FRAGMENT_NODE(11);
  • Node.NOTATION_NODE(12);

test1

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <h1 id="h">i'm h1</h1>
</body>
<script>
    var h = document.getElementById("h");
    console.log(h.nodeType);                                    // 1
    if (h.nodeType === Node.ELEMENT_NODE) console.log(true);    // true
</script>
</html>

使用Node类型常量来比较。
由于IE没有公开Node类型函数,因此上面代码在IE中会导致错误。为确保浏览器兼容,最好将nodeType属性与数字进行比较

test2

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <h1 id="h">i'm h1</h1>
</body>
<script>
    var h = document.getElementById("h");
    if (h.nodeType === 1) console.log(true);    // true
</script>
</html>

不是所有节点都会受到浏览器支持,最常用的就是元素和文本节点。

MDN —— NodeType


nodaName | nodeValue

nodaName
只读

nodaValue
可写可读

使用nodeName和nodeValue可以具体了解节点信息,这两个属性的值完全取决于节点的类型

可以检测节点类型后在使用其属性,下图列出了不同节点类型的nodeValue的返回值。


from MDN

所以,对于element这样的节点,返回值都为null。

test3

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <h1 id="h">i'm h1</h1>
</body>
<script>
    var h = document.getElementById("h");
    console.log(h.nodeName+" | "+h.nodeValue);
    // H1 | null
</script>
</html>

可以这样

test4

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <h1 id="h">i'm h1</h1>
</body>
<script>
    var h = document.getElementById("h").getAttributeNode("id");
    console.log(h);             //  id  [object attr]
    console.log(h.nodeValue);   //  h
</script>
</html>

使用getAttributeNode方法,输入属性名,返回其attribute节点对象,然后使用NodeValue方法,返回值就为实际属性值。


节点关系

文档中所有的节点之间都存在这样或者那样的关系。节点间的各种关系可以用传统的家族关系来描述,每个节点存在childNodes属性,其中保存着一个Nodelist对象,是一种类数组对象,保存一组有序的节点,可以通过位置来访问这些节点。
虽然可以使用方括号语法来访问Nodelist的值,而且这个对象也有length属性,但它并不是Array的实例。Nodelist对象的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在Nodelist对象中。

我们常说,Nodelist是有生命、有呼吸的对象,而不是在我们第一次访问它们的某个瞬间拍摄下来的一张快照。

test5*

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <div id="div">
        <p>i'm P</p>
        <h1>i'm H1</h1>
    </div>
</body>
<script>
    var html = document.getElementsByTagName("html")[0];
    console.log(html);
</script>
</html>

可以看到其中childNodes属性里,Nodelist对象的传承,包含了从根目录下的所有节点。
其中,#div的Nodelist里,是否发现了有5个节点,但实际上源代码只有2个元素啊,其实[0],[2],[4]所代表的#text是我们源代码换行时留下的空位,在Nodelist里就用#text来表示了。
<div id="div"><p>i'm P</p><h1>i'm H1</h1></div>
修改后,将只有两个element。

注意,length属性表示的是访问Nodelist的那一刻,其中包含的节点数量。
对于JS函数内部类数组对象arguments对象使用Array.prototype.slice()方法可以将其转换为数组。而采用同样的方法,也可以将Nodelist对象转换为数组。

test6

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <div id="div">
        <p>i'm P</p>
        <h1>i'm H1</h1>
    </div>
</body>
<script>
    var div = document.getElementById("div");

    // 将Nodelist对象转换为数组
    var arrayOfNodes = Array.prototype.slice.call(div.childNodes,0);
    
    // 修改数组不会反映到Nodelist
    arrayOfNodes.splice(1,0,arrayOfNodes[3]);
    console.log(div.childNodes);
</script>
</html>

为方便演示,缩小范围,只抽取了#div做演示,
白箭头即为转换后的真数组,对转换后数组进行了一次数组splice增添节点操作,那么,是否会映射到Nodelis上呢?
红箭头是Nodelist对象,看看它,依然不变。

当然,通过Nodelist对象方法添加是可以,后续~

对于刚才的Nodelist对象转换数组方法,由于在IE8及更早版本将Nodelist实现为一个COM对象,而我们不能像使用JavaScript对象那样使用这种对象,因此原方法会导致错误,要想在IE中Nodelist转换为数组,必须手动枚举所有成员。
下列代码在所有浏览器均可运行:

test7

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <div id="div">
        <p>i'm P</p>
        <h1>i'm H1</h1>
    </div>
</body>
<script>
    function convertToArray(nodes) {
        var array = null;
        try {
            array = Array.prototype.slice.call(nodes,0);    // 针对非IE浏览器
        } catch (ex) {
            array = new Array();
            for (var i=0,len=nodes.length;i<len;i++) {
                array.push(nodes[i]);
            }
        }e
        return array;
    }
    var div = document.getElementById("div");
    console.log(convertToArray(div.childNodes));
</script>
</html>

Array(5) [#text, p, #text, h1, #text]

函数首先尝试了创建数组的最简单的方式。如果导致错误(说明在IE8及更早版本中),则通过try-catch来捕获错误,然后手动创建数组。
每个节点上都有一个parentNode属性,该属性指向文档树中的父节点。包含在childNodes列表中的所有节点都具有相同的父节点,因此它们的parentNode属性都指向同一个节点。此外,包含在childNodes列表中的每个节点相互之间都是同胞节点(siblingNode)。通过使用列表中的每个节点的previousSibling和nextSibling属性,可以访问同一列表中的其他节点。

关于节点关系,主要就是子节点、节点、父节点之间的定位关系。


test8

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <div id="div"><p id="p">i'm P</p><h1 id="h1">i'm H1</h1></div>
</body>
<script>
    var div = document.getElementById("div");
    var p = document.getElementById("p");
    var h = document.getElementById("h1"); 


    console.log(div.firstChild);        // 选中#div的第一个子元素:p#p
    console.log(div.lastChild);         // 选中#div的最后一个子元素:h1#h1

    console.log(p.nextSibling);         // 选中#p的下一个兄弟元素:h1#h1
    console.log(h.previousSibling);     // 选中#h1的上一个兄弟元素:p#p

    console.log(p.parentNode);          // 选中#p的父元素:div#div
</script>
</html>

在反映这些关系的所有属性当中,childNodes属性与其他属性相比更方便一些,因为只须使用简单的关系指针,就可以通过它访问文档树中的任何节点。另外,hasChildNodes()也是一个非常有用的方法,这个方法在节点包含一或多个子节点的情况下返回true;应该说,这是比查询childNodes列表的length属性更简单的方法。
所有节点都有的最后一个属性时ownerDocument,该属性指向表示整个文档的文档节点。这种关系表示的是任何节点都属于它所在的文档,任何节点都不能同时存在与两个或多个文档中。通过这个属性,我们可以不必在节点层次中通过层层回溯到达顶端,而是可以直接访问文档节点。


操作节点

因为关系指针都是只读的,所以DOM提供了一些操作节点的方法。其中,最常用的方法时appendChild(),用于向childNodes列表的末尾添加一个节点。添加节点后,childNodes的新增节点、父节点及以前的最后一个子节点的关系指针都会相应的得到更新(因为childNodes属性里的Nodelist对象更新了)。更新完成后,appendChild()返回新增的节点。

test9

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <div id="div">
        <p id="p">i'm P</p>
        <h1 id="h1">i'm H1</h1>
    </div>
</body>
<script>
    var h6 = document.createElement("h6");
    var div = document.getElementById("div");

    var returnNode = div.appendChild(h6);
    console.log(returnNode === h6);         // true
    console.log(div.lastChild === h6);      // true
</script>
</html>

如果传入到appendChild()中的节点已经是文档中的一部分了,那结果就是将该节点从原来的位置转移到新位置。即使可以将DOM数看成是由一系列指针连接起来的 ,但任何DOM节点也不能同时出现在文档中的多个位置上。因此,如果在调用appenChild()时传入了父节点的第一个子节点,那么该节点就会成为父节点的最后一个子节点。

test10

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <div id="div">
        <p id="p">i'm P</p>
    </div>
</body>
<script>
    var div = document.getElementById("div");
    var returnNode = div.appendChild(div.firstChild);

    console.log(returnNode === div.firstChild);     // false
    console.log(returnNode === div.lastChild);      // true

</script>
</html>

如果需要把节点放在childNodes列表中某个特定的位置上,而不是放在末尾,那么可以使用inserBefore()方法。这个方法接受两个参数:要插入的节点和作为参照的节点。插入节点后,被插入的节点会变成参照节点的前一个同胞节点(preivousSibling),同时被方法返回,如果参照节点是null,则insertBefore()与appendChild()执行相同的操作。

test11

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <div id="div"><p id="p">i'm P</p><h1 id="h1">i'm H1</h1></div>
</body>
<script>
    var div = document.getElementById("div");
    var h1 = document.getElementById("h1");
    var p = document.getElementById("p");
    console.log(div);

    // 创建的新节点h6
    var h6 = document.createElement("h6");
    var text = document.createTextNode("i'm h6");
    h6.appendChild(text);

    // 将新创建的h6节点插入到h1之前
    div.insertBefore(h6,h1);
    console.log(h1.previousSibling === h6);     // true
    // p > h6 > h1
    
    // 将h1插入到p之前,同时h6位置为最后一个子节点
    div.insertBefore(h6,p);
    console.log(p.previousSibling === h6);      // true 
    console.log(div.firstChild === h6);         // true
    console.log(div.lastChild === h1);          // true
     
</script>
</html>
注意,如果将代码换行,保证可读性的同时,会使Nodelist对象增加若干的#text节点,这是换行导致的。

如若要替换节点可使用replaceChild()方法
方法接受两个参数:要插入的节点,要替换的节点。要替换的节点将由这个方法返回并从文档树中移除,同时插入的节点占据其位置。

test12

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <div id="div"><p id="p">i'm P</p><h1 id="h1">i'm H1</h1>
    </div>
</body>
<script>
    var div = document.getElementById("div");
    var h1 = document.getElementById("h1");
    var p = document.getElementById("p");

    // 创建一个新节点h3
    var h3 = document.createElement("h3");
    h3.appendChild(document.createTextNode("i'm h3"));

    // 替换p节点
    var returnNode = div.replaceChild(h3,p);
    console.log(returnNode);            // p#p
    
</script>
</html>

使用replaceChild()插入一个节点时,该节点所有关系指针都会从被它替换的节点赋值过来,尽管从技术上讲,被替换的节点还在文档中,但它在文档中已经没有了自己的位置。

如果只想移除而非替换节点,可以使用removeChild()方法。
方法接受一个参数,即要移除的节点。被移除的节点将成为方法的返回值。
test13

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <div id="div"><p id="p">i'm P</p><h1 id="h1">i'm H1</h1>
    </div>
</body>
<script>
    var div = document.getElementById("div");
    var h1 = document.getElementById("h1");
    var p = document.getElementById("p");

    // 创建一个新节点h3
    var h3 = document.createElement("h3");
    h3.appendChild(document.createTextNode("i'm h3"));

    // 替换p节点
    var returnNode = div.replaceChild(h3,p);
    console.log(returnNode);            // p#p

    // 移除第一个节点
    var formerFirstChild = div.removeChild(div.firstChild);
    console.log(formerFirstChild);      // h3
    console.log(div.firstChild);        // h1#h1
</script>
</html>

有两个方法是所有类型节点都有的。

  • cloneNode()

  • normalize()

cloneNode()方法接受一个布尔值参数,表示是否执行深复制。参数为true情况下,执行深复制,也就是复制节及其整个子节点树;在参数为false情况下,执行潜复制,即只复制节点本身。复制后返回的节点副本属于文档所有,但并没有为它指定父节点。因此这个节点副本就成为了一个“孤儿”,除非通过appedChild,insertBefore,replaceChild将它添加到文档中。

test14

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Fuck!</title>
</head>
<body>
    <ul>
        <li>item 1</li>
        <li>item 2</li>
        <li>item 3</li>
    </ul>
</body>
<script>
    var ul = document.getElementsByTagName("ul")[0];

    var deepList = ul.cloneNode(true);
    var shallow = ul.cloneNode(false);

    console.log(deepList.childNodes.length);    //  7 4个空隙,三个元素,长度为7
    console.log(shallow.childNodes.length);     //  0 
</script>
</html>

cloneNode()不会赋值添加到DOM节点中的JavaScript属性,例如事件处理程序等,这个方法只复制特性、子节点(true),其他一切都不会复制。


normalize()

这个方法唯一作用就是处理文档树中的文本节点。
由于解析器的实现或者DOM操作等原因,可能会出现文本节点或者不包含文本,或者接连出现两个文本节点的情况。
当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到空白文本节点,则删除它,如果找到相邻的文本节点,则将它们合并为一个文本节点。


Document类型

JavaScript通过Document类型表示文档。在浏览器汇总,document对象是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面。而且,document对象是window对象的一个属性,因此可以将其作为全局对象来访问。

window.document === document // true

Document节点具有下列特征:

  • nodeType的值为:9
  • nodeName的值为:"#document"
  • nodeValue的值为:null
  • parentNode的值为:null
  • 其子节点可能是一个DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment。

Document类型可以表示HTML页面或者其他基于XML的文档。不过,最常见的应用还是作为HTMLDocument实例的document对象。通过这个文档对象,不仅可以取得与页面有关的信息,而且还能操作页面的外观及其底层结构。

1.文档的子节点

虽然DOM标准规定Document节点的子节点可以是DocumentType、Element、ProcessingIn-struction或Comment,但还有两个内置的访问其子节点的快捷方式。第一个就是documentElement属性,该属性始终指向HTML页面中的<html>元素。另一个就是通过childNodes列表访问文档元素,但通过documentElement属性则能更快捷、直接的访问元素。

<html>
    <body>
        
    </body>
</html>

这个页面经浏览器解析后,其文档中只包含一个子节点,即<html>元素。可以通过documentElement或childNodes列表来访问这个元素。

        var html = document.documentElement;            // 取得对<html>的引用
        console.log(html === document.childNodes[0]);   // true
        console.log(html === document.firstChild);      // true

documentElement、firstChild和childNodes[0]的值相同。,都指向<html>元素。

作为HTMLDocument的实例,document对象还有一个body属性,直接指向<body>元素。因为开发人员经常要使用这个元素,所以document.body在JS代码汇总出现的频率很高。
var body = document.body;
所有浏览器都支持document.documentElement和document.body属性。
Document另一个可能的子节点就是DocumentType。通常将<!DOCTYPE>标签看成一个与文档其他部分不同的实体,可以通过doctype属性来访问它的信息。
var doctype = document.doctype;

类似的,还有如下功能方法,取得文档信息。

取得文档标题
var originalTitle = document.title;
设置文档标题
document.title = "New page ttile";

网页请求相关方法;
  • URL:属性中包含页面完整的URL(即地址栏中显示的URL)
  • domain:只包含页面的域名
  • referrer:保存链接到当前页面的页面的URL(从哪到这的),referrer属性可能包含空字符串。所有信息都存在请求HTTP头部,只不过通过这些属性让我们能够在JavaScript中访问它们而已。
//取得完整URL
var url = document.URL;

//取得域名
var domain = document.domain;

//取得来源页面的URL
var referrer = document.referrer;

URL和domain属性是相互关联的。例如,如果document.URL为http://www.wrox.com/WileyCDA/,那么document.domain就是www.wrox.com

这三个属性中,只有domain是可以设置的,但由于安全方面限制,并非可以给domain设置任何值。如果URL包含一个子域名,例如p2p.wrox.com,那么就只能将domain设置为wrox.com。不能将这个属性设置为URL中不包含的域。

        // 假设页面来自:p2p.wrox.com
        document.domain = "wrox.com";       // 成功
        document.domain = "nczonline.net";  // 出粗!

当页面中包含来自其他子域的框架或内嵌框架时,能够设置domain就非常方便了。由于跨域安全限制,来自不同子域的页面无法通过JavaScript通信。而通过将每个页面的document.domain设置为相同的值,这些页面就可以互相访问对方包含的JavaScript对象了。例如,假设有一个页面加载自www.wrox.com,其中包含一个内嵌框架,框架内的页面加载来自p2p.wrox.com。由于document.domain字符串不一样,内外两个页面之间无法相互访问对方的JavaScript对象。但如果将两个页面的document.domain值设置为wrox.com,他们之间就可以通信了。

浏览器对domain属性还有一个限制,即如果域名一开始是松散的,那么不能将它设置为紧绷的。换句话说,在将document.domain设置为wrox.com之后,不能再将其设置回p2p.wrox.com,否认导致错误。

// 假设页面来自于p2p.wrox.com域
document.domain = "wrox.com";    // 松散的(成功)
document.domain = "p2p.wrox.com"    // 紧绷的(出错!)

查找元素

Document类型提供的两个方法:

  • getElementById()
  • getElementsByTagName()

第一个方法,接受一个参数:要取得的元素ID,如果找到相应的元素则返回该元素,如果不存在带有相应的ID的元素,则返回null。注意,这里的ID必须与页面中的元素的ID特性严格匹配,包括大小写。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv"></div>
    <script>
        var div = document.getElementById("myDiv");     // 取得对<div>元素的引用
    </script>
</body>
</html>

但是,在IE7下和更早版本的浏览器都将返回null。
var div = document.getElementById("mydiv");
IE8及较早版本不区分ID的大小写,因此"myDiv"和"mydiv"会被当作相同的元素ID。
如果页面中多个元素的ID值相同,getElmentById()只返回文档中第一次出现的元素。

document.getElementsByTagName()
接受一个参数,既要取得元素的标签名,返回的是包含零或多个元素的Nodelist。在HTML文档中,这个方法会返回一个HTMLCollection对象,作为一个动态集合,该对象与Nodelist对象非常类似。例如,下列代码回取得页面中所有的<img>元素,并返回一个HTMLCollection。
var images = document.getElementsByTagName("img");
这行代码会将一个HTMLCollection对象保存在images变量中。与Nodelist对象类似,可以使用方括号语法或item()方法来访问对象中的项。而这个对象中元素的数量则可以通过其length属性取得。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv"></div>
    <script>
        var divs = document.getElementsByTagName("div");
        console.log(divs.length);       // 1    输出div的数量
        console.log(divs[0].id);            // myDiv    输出第一个div元素的ID特性
        console.log(divs.item(0).id);   // myDiv    输出第一个div元素的ID特性
    </script>
</body>
</html>

HTMLCollection对象还有一个方法,叫做namedItem(),使用这个方法可以通过元素的那么name特性取得集合中的项。例如,假设上面提到的页面中包含如下<div>元素
<div id="myDiv" name="myDiv">
那么可以通过如下方式从divs变量中取得这个<div>元素。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv" name="myDiv"></div>
    <script>
        var divs = document.getElementsByTagName("div");
        var div = divs.namedItem("myDiv");
        console.log(div);       // div#myDiv
    </script>
</body>
</html>

咋提供按索引访问项的基础上,HTMLCollection还支持按名称访问项,这就为我们取得实际想要的元素提供了便利。而且,对命名的项也可以使用方括号语法来访问。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv"></div>
    <script>
        var divs = document.getElementsByTagName("div");
        var div = divs["myDiv"];
        console.log(div);   // div#myDiv

    </script>
</body>
</html>

第三个方法,也就是只有HTMLDocument类型才有的方法,是getElementsByName(),顾名思义,方法会返回带有给定name特性的所有元素。最常使用getElementsByName()方法的情况是取得单选按钮。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <fieldset>
        <legend>Which color do you prefer?</legend>
        <ul>
            <li>
                <input type="radio" value="red" name="color" id="colorRed">
                <label for="colorRed">red</label>
            </li>
            <li>
                <input type="radio" value="green" name="color" id="colorGreen">
                <label for="colorGreen">Green</label>
            </li>
            <li>
                <input type="radio" value="blue" name="color" id="colorBlue">
                <label for="colorBlue">Blue</label>
            </li>
        </ul>
    </fieldset>

    <script>
        var radios = document.getElementsByName("color");
    </script>
</body>
</html>

所有的单选按钮的name特性值相同都是color,但它们的ID不同,ID作用在于将label元素应用到每个单选按钮,而name特性则用以确保三个值中只有一个被发送给浏览器。这样,我们取得所有单选按钮。

特殊集合

除了属性和方法,document对象还有一些特殊的集合,这些集合都是HTMLCollection对象,为访问文档常用的部分提供了快捷方式,包括:

  • document.anchors,包含文档中所有带name特性的<a>元素
  • document.applets,包含文档中所有的<applet>元素,很少使用。
  • document.forms,包含所有的<form>元素
  • document.images,包含所有的<img>元素
  • document.links,包含文档中所有的带href特性的<a>元素。

Element类型

访问元素的标签名,使用nodeName属性,也可以使用tagName属性;这两个属性会返回相同的值,使用后者主要是为了清晰可见。
<div id="myDiv"></div>
可以先这样取得元素及其标签名:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="myDiv"></div>
    <script>
        var div = document.getElementById("myDiv");
        console.log(div.tagName);           // "DIV"
        console.log(div.tagName === div.nodeName);  // true
    </script>
</body>
</html>

所有的HTML元素都由HTMLElement类型表示,不是直接通过这个类型,也是通过它的子类型表示。HTMLElement类型直接继承自Element并添加了一些属性。添加的这些属性分别对应于每个HTML元素中都存在的下列标准特性。

  • id,元素在文档中的唯一标识符
  • title,元素附加说明信息,通过工具提示条显示
  • lang,元素语言代码
  • dir,语言方向,ltr(left to right),rtl(right to left),很少使用。
  • className,与元素class属性对应。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

    <div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>

    <script>
        var div = document.getElementById("myDiv");

        console.log(div.id);            // myDiv
        console.log(div.className);     // bd
        console.log(div.title);         // Body text
        console.log(div.lang);          // en
        console.log(div.dir);           // ltr
    </script>
</body>
</html>

也可以为每个属性赋值,修改对应属性的特性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

    <div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>

    <script>
        var div = document.getElementById("myDiv");

        div.id = "someOtherId";
        div.className = "ft";
        div.title = "Some other text";
        div.lang = "fr";
        div.dir = "rtl";

        console.log(div.id);        
        console.log(div.className); 
        console.log(div.title);     
        console.log(div.lang);      
        console.log(div.dir);           
        /*
        someOtherId
        ft
        Some other text
        fr
        rtl
        */
    </script>
</body>
</html>

*取得特性

getAttribute()

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

    <div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>

    <script>
        var div = document.getElementById("myDiv");

        console.log(div.getAttribute("id"));    
        console.log(div.getAttribute("class")); 
        console.log(div.getAttribute("title"));     
        console.log(div.getAttribute("lang"));      
        console.log(div.getAttribute("dir"));           
        /*
        myDiv
        bd
        Body text
        en
        ltr
        */
    </script>
</body>
</html>

传递给方法的特性名与实际的特性名相同,因此要想得到class的特性值,应该传入class而不是className,后者只有在通过对象属性访问特性时采用。如果给定名称特性名不存在,则返回null。

当然,你可以添加自定义特性,标准HTML中没有的特性。
<div id="myDiv" my_special_attrbute="hello!"></div>
这个元素包含一个名为my_special_attrbute的自定义特性,值为hello!,可以像取得其他特性一样取得这个值。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

    <div id="myDiv" my_special_attrbute="hello!"></div>

    <script>
        var div = document.getElementById("myDiv");
        var value = div.getAttribute("my_special_attrbute");
        console.log(value);     // hello!
    </script>
</body>
</html>

不过,只有公认的特性才会以属性的形式添加到DOM对象中
<div id="myDiv" align="left" my_special_attribute="hello!"></div>
因为id和align在HTML中是<div>的公认特性,因此该元素的DOM对象中也将存在对应的属性。不过,自定义特性my_special_attribute在Safari、Opera、Chrome及Firefox是不存在的;但IE却会为自定义特性也创建属性。

        console.log(div.id);                    // "myDiv"
        console.log(div.my_special_attribute);  // undefined(IE除外)
        console.log(div.align);                 // "left"

有两类特殊的特性,它们虽然有对应的属性名,但属性值与通过getAttribute()返回的值并不相同,第一类是style,通过CSS为元素指定形式。通过getAttribute()访问时,返回的style特性值中包含的是CSS文本,而通过属性来访问它则返回一个对象。由于style属性用于以编程方式访问元素样式的,因此并没有映射到style属性。

第二类与众不同的特性时onclick这样的事件处理程序,当在元素上使用时,onclick特性中包含的是JavaScript代码,如果通过getAttribute()访问,则会返回相应的代码的字符串。而在访问onclick属性时,则会返回一个JavaScript函数(如果未指定,返回null)这是因为onclick及其他事件处理程序属性本身就应该被赋予函数值。

由于这些差别,通过JavaScript编程方式操作DOM时,开发者经常不使用getAttribute(),而是只使用对象属性,只有在取得自定义特性值情况下,才会使用getAttribute()方法。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<style>

</style>
<body>

    <div id="myDiv" style="background-color:red;width:100px;height:100px"></div>

    <script>
        var div = document.getElementById("myDiv");
        console.log(div.getAttribute("style"));     // background-color:red;width:100px;height:100px
        console.log(div.style);// CSSStyleDeclaration {0: "background-color", 1: "width", 2: "height", alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "", …}
    </script>
</body>
</html>

设置特性
与getAttribute()对应的方法是setAttribute(),这个方法接受两个参数:要设置的特性名和值。如果特性已经存在,setAttribute()会以指定的值替换现有的值;如果特性不存在,setAttribute()则创建该属性并设置相应的值。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<style>

</style>
<body>

    <div></div>

    <script>
        var div = document.getElementsByTagName("div")[0];

        div.setAttribute("id","someOtherId");
        div.setAttribute("class","ft");
        div.setAttribute("title","Some other text");        
        div.setAttribute("lang","fr");
        div.setAttribute("dir","rtl");
    </script>
</body>
</html>

通过方法既可以操作HTML特性,也可以操作自定义特性。通过这个方法设置的特性名会统一转换为小写形式,即ID最终会成为id。

不过,添加自定义属性,该属性不会自动成为元素的特性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<style>

</style>
<body>

    <div></div>

    <script>
        var div = document.getElementsByTagName("div")[0];
        
        div.mycolor = "red";
        console.log(div.getAttribute("mycolor"));       // null
    </script>
</body>
</html>

attributes属性

Element类型是使用attributes属性的唯一一个DOM节点类型。attributes属性中包含一个NamedNodeMap,与Nodelist类似,也是一个动态集合。元素的每一个特性都由一个Attr节点表示,每个节点都保存在NamedNodeMap对象中。NamedNodeMap拥有下列方法:

  • getNamedItem(name):返回nodeName属性等于name的节点;
  • removeNamedItem(name):从列表中移除nodeName属性等于name的节点;
  • setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引;
  • item(pos):返回位于数字pos位置处的节点。

attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,而节点的nodeValue就是特性的值。要取得元素的id特性,可以:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<style>

</style>
<body>

    <div id="myDiv"></div>

    <script>
        var div = document.getElementById("myDiv");
        var id = div.attributes.getNamedItem("id").nodeValue;
        console.log(id);    // myDiv
    </script>
</body>
</html>

或者使用方括号语法通过特性名称访问节点的简写方式。
var id = div.attributes["id"].nodeValue;
也可以通过以上的方法设置特性的值。
div.attributes["id"].nodeValue = "someOtherId";

一般来说,attributes的方法不够方便,更多的会使用getAttribute()、removeAttribute()和setAttribute()方法。
不过,如果想要遍历元素特性,attributes属性可以派上用场。在需要将DOM结构序列化为XML或者HTML字符串时,多数都会涉及遍历元素特性。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<style>
</style>

<body>

    <div id="myDiv" class="div_class" align="left" name="DIV"></div>

    <script>

        var div = document.getElementById("myDiv");

        function outputAttributes(element) {
            var attrName;
            var attrValue;
            var pairs = [];
            var len = element.attributes.length;

            for (var i = 0; i < len; i++) {
                attrName = element.attributes[i].nodeName;
                attrValue = element.attributes[i].nodeValue;
                pairs.push(attrName + '=\"' + attrValue + '\"');
            }
            return pairs;
        }
        
        console.log(outputAttributes(div));
    </script>
</body>
</html>

针对attributes对象中的特性,不同浏览器返回的顺序不同。这些特性在XML和HTML代码中出现的先后顺序,不一定与它们出现在attributes对象中的顺序一致。
IE7及更早版本会返回HTML元素中所有可能的特性,包括没有指定的特性。换句话说,返回100多个特性的情况会很常见。

创建元素
var div = document.createElement("div");
使用createElement()方法创建新元素的同时,也为新元素设置了ownerDocument属性。此时,还可以操作元素的特性,为它添加更多子节点,以及执行其他操作。

        div.id = "myNewDiv";
        div.className = "box";

然后添加到文档数中。可以使用appendChild()、insertBefore()、replaceChild()方法。

document.body.appendChild(div);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  •   DOM(文档对象模型)是针对 HTML 和 XML 文档的一个 API(应用程序编程接口)。   DOM 描绘...
    霜天晓阅读 3,550评论 0 7
  • 本章内容 理解包含不同层次节点的 DOM 使用不同的节点类型 克服浏览器兼容性问题及各种陷阱 DOM 是针对 HT...
    闷油瓶小张阅读 606评论 0 1
  • DOM(文档对象模型)是针对 HTML 和 XML 文档的一个 API。DOM 描绘了一个层次化的节点树,允许开发...
    劼哥stone阅读 706评论 8 6
  • 这让我想起了,现在倍受他人排挤又或者说受到他人保护的野生稀有非主流物种:杀马特 在木文小的时候,其实一直向往着的就...
    PTT演讲阅读 1,318评论 2 2
  • 对我们来说,沉迷于游戏,沉迷于小说,沉迷于刷朋友圈微博,沉迷于吃喝玩乐,好像太容易了。这些事情都是马上可以...
    飞花_毛毛阅读 457评论 0 0