JS红皮书读书笔记-10-DOM

TOC
  1. 1. 节点层次
    1. 1.1. Node类型
    2. 1.2. Document类型
    3. 1.3. Element类型
    4. 1.4. Text类型
    5. 1.5. Comment类型
    6. 1.6. CDATASection类型
    7. 1.7. DocumentType类型
    8. 1.8. DocumentFragment类型
    9. 1.9. Attr类型
  2. 2. DOM操作技术
    1. 2.1. 动态脚本
    2. 2.2. 动态样式
    3. 2.3. 操作表格
    4. 2.4. 使用NodeList

DOM即文档对象模型, 是针对HTML和XML的一个编程API, 主要功能是允许开发人员添加、移除和修改页面的某一部分。

节点层次

DOM以节点树的形式反映:

<html>
<head>
<title>Sample Page</title>
</head>
<body>
<p>Hello World!</p>
</body>
</html>

以上就是一个最简单的DOM树结构.

文档节点是每个文档的根节点。 在这个例子中,文档节点只有一个子节点,即元素. 称之为文档元素。文档元素是文档的最外层元素,文档中的其他所有元素都包含在文档元素中。每个文档只能有一个文档元素。在 HTML 页面中,文档元素始终都是元素。在 XML 中,没有预定义的元素,因此任何元素都可能成为文档元素。

每一段标记都可以通过树中的一个节点来表示:HTML 元素通过元素节点表示,特性(attribute) 通过特性节点表示,文档类型通过文档类型节点表示,而注释则通过注释节点表示。总共有 12 种节点类型,这些类型都继承自一个基类型。

Node类型

DOM1 级定义了一个 Node 接口,该接口将由 DOM 中的所有节点类型实现。这个 Node 接口在JavaScript 中是作为 Node 类型实现的;除了 IE 之外,在其他所有浏览器中都可以访问到这个类型。JavaScript 中的所有节点类型都继承自 Node 类型,因此所有节点类型都共享若相同的基本属性和方法。每个节点都有一个 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)。

通过比较上面这些常量,可以很容易地确定节点的类型,例如:

if (someNode.nodeType == Node.ELEMENT_NODE){  //在 IE 中无效
alert("Node is an element.");
}

这个例子比较了 someNode.nodeType 与 Node.ELEMENT_NODE 常量。如果二者相等,则意味若someNode 确实是一个元素。然而,由于 IE 没有公开 Node 类型的构造函数,因此上面的代码在 IE 中会导致错误。为了确保跨浏览器兼容,最好还是将 nodeType 属性与数字值进行比较,如下所示:

if (someNode.nodeType == 1){  //适用于所有浏览器
alert("Node is an element.");
}

并不是所有节点类型都受到 Web 浏览器的支持。开发人员最常用的就是元素和文本节点。

  1. nodeName和nodeValue
    对于元素节点, nodeName保存的是元素的标签名, 如”DIV”,对于元素节点它们的nodeValue始终是null

  2. 节点关系
    每个节点都有一个childNodes属性.其中保存若一个 NodeList 对象。NodeList 是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。请注意,虽然可以通过方括号语法来访问 NodeList 的值,而且这个对象也有 length 属性,但它并不是 Array 的实例。NodeList 对象的独特之处在于,它实际上是基于 DOM 结构动态执行查询的结果,因此 DOM 结构的变化能够自动反映在 NodeList 对象中。

var body = document.body;
body.childNodes instanceof Array; // false
var firstChild = body.childNodes[0];
var secondChild = body.childNodes.item(1);
var count = body.childNodes.length;

前面我们讲了引用类型中数组, 有几个方法是可以操作数组实例并返回数组的, 比如slice, 如果要把类数组对象转换成熟组, 我们可以:

// 推荐方式
var arr = ([]).slice.call(arrayLikeObj);

// 或者新建一个空数组, 循环arrayLikeObj把其中的项添加进空数组

每个节点都有一个 parentNode 属性,该属性指向文档树中的父节点。包含在 childNodes 列表中的所有节点都具有相同的父节点,因此它们的 parentNode 属性都指向同一个节点。此外,包含在childNodes 列表中的每个节点相互之间都是同胞节点。通过使用列表中每个节点的 previousSibling 和 nextSibling 属性,可以访问同一列表中的其他节点。列表中第一个节点的 previousSibling 属性值为 null,而列表中最后一个节点的 nextSibling 属性的值同样也为 null .

如果列表中只有一个节点,那么该节点的 nextSibling 和 previousSibling 都为 null。

someNode.firstChild == someNode.childNodes[0]
someNode.lastChild == someNode.childNodes[someNode.childNodes.length-1]

如果没有子节点,那么 firstChild 和 lastChild 的值均为 null.

另外,someNode.hasChildNodes()可以查询是否包含子节点. 所有节点都包含一个属性ownerDocument, 指向当前文档对象.

  1. 操作节点
    前面的关系指针都是只读的, DOM还提供了一些列操作节点的方法:
  • appendChild
    • 向childNodes里面添加最后一个节点, 如果要被添加的节点本身已经存在于文档当中, 那就会将原本的位置转移到新的位置 . 要记住, DOM树可以看做是一些列指针链接起来的文档模型. 只不过特殊的地方在于DOM节点不能出现在多个位置上
  • insertBefore
    • 用法: someNode.insertBefore(newNode, 参照节点)
  • replaceChild
    • 用法: someNode.replaceChild(newNode, 被替换的节点)
  • removeChild
    • 删除某个节点, 被删除的节点仍为文档所有, 只不过没有了位置
  1. 其他操作
  • cloneNode(bool)
    • 参数为非true的时候, 执行浅复制,反之深复制, 此方法返回一个新生成的副本, 如果不插入DOM树, 它相当于一个’孤儿’, 没有自己的位置, 但仍属于文档.
  • normalize

Document类型

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

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

基本上最常用到:

  • document.documentElement
  • document.body
  1. 文档信息
  • document.title
  • document.URL
  • document.domain: 跨域方面能用到
  • document.referrer
  1. 查找元素
    由于HTML的容错性, 与标准不同, 实际上元素的签名是忽略大小写的, 但是我们依然要按照区别大小写的方式来开发.
  • document.getElementById
  • document.getElementsByTagName
  • document.getElementsByName
  • document.getElementsByClassName
  1. 特殊集合
  • document.anchors: 遍历带有name属性的a标签
  • document.applets: 已废弃
  • document.forms: 页面所有表单
  • document.links: 所有带href的标签
  1. DOM一致性检测
    document.hasFeature用于检测DOM是否支持某些功能, 但是实际开发上我们很少用这个API(直接用特征检测更好),故不再讲解.

  2. 文档写入
    有以下几个方法:

  • document.wirte: 这个方法通常用来写入外部脚本, 不建议使用这个API, 会阻塞网页和带来性能问题
  • document.wirteLn: 和wirte类似, 但行末会添加换行符(\n)
  • document.open: 使用document.wirte的时候会自动执行这个方法
  • document.close: 如果不使用这个方法, document.wirte能继续在原有文档上添加内容, 否侧document.wirte会重新清空文档

Element类型

除了document类型, element类型就是开放人员最常用的了.

  1. HTML元素
    HTML元素基本具备以下属性:
  • id
  • title
  • lang
  • dir
  • class
  • align
  1. 读写属性(特性)
    主要是以下三个方法:
  • ele.getAttribute(属性名): 通常开发者用点运算符访问或者方括号访问
  • ele.setAttribute(属性名, 值)
  • ele.removeAttribute(属性名)
  1. attributes属性
    ele. attributes返回一个类数组对象, 罗列该元素有的属性, 但是也能使用ele. attributes[属性名]代替ele.getAttribute(‘属性名’)

  2. 创建元素
    document.createElement

  3. 元素的子节点

Text类型

略, 通常开发者用innerHTML或者innerText代替这个功能

Comment类型

注释类型, 略

CDATASection类型

此类型针对XML, 略

DocumentType类型

几乎不用, 略

DocumentFragment类型

documentFragment类型在文档中没有对应的标记, 你可以理解成它是文档之外的一个”独立仓库”, 不占用额外的资源.

假如我们要给一个ul元素插入多个li元素, 按照正常思路:

function buildLi(){
var li = document.createElement('li');
li.innerText = 'new li';
return li;
}

for (var i = 0; i < 10; i++) {
Jul.appendChild(buildLi())
}

虽然上面的代码功能上没有问题, 但是导致浏览器的反复渲染. 为了尽可能地减少这种渲染开销, 我们可以利用documentFragment来实现:

function buildLi(){
var li = document.createElement('li');
li.innerText = 'new li';
return li;
}

var frag = document.createDocumentFragment();
for (var i = 0; i < 10; i++) {
frag.appendChild(buildLi())
}
Jul.appendChild(frag); //只渲染了一次

Attr类型

略, 它不被认为是DOM文档树的一部分

DOM操作技术

动态脚本

即动态创建script元素, 再将script元素插到html中, 此为异步加载

动态样式

即动态创建link元素, 再将link元素插到html中,此为异步加载

操作表格

操作表格的API不十分常用, 略

使用NodeList

除了nodeList(childNodes产生), 还有nodeNameMap(例如document.links产生), HTMLCollection(例如获取dom元素产生), 使用它们的时候尽可能缓存起来, 因为它们都是动态的, 实时更新. 这意味着开销的变大.

如果你将nodeList长度作为循环判断的截止条件, 那有可能会陷入无限循环.

var divs = document.getElementsByTagName("div"),
i,
div;

for (i=0; i < divs.length; i++){
div = document.createElement("div");
document.body.appendChild(div);
}

本章完

访客评论