什么是DOM???
DOM(Document Object Model 文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口)。
注意,IE中的所有DOM对象都是以COM(组件对象模型)对象的形式实现的。IE中的DOM对象与原生JavaScript对象的行为或活动特点并不一致。
COM对象是遵循COM规范编写、以Win32动态链接库(DLL)或可执行文件(EXE)形式发布的可执行二进制代码,能够满足对组件架构的所有需求。DOM定义了一个Node接口,这个接口在JavaScript中是作为Node类型实现的,而在IE8-浏览器中的所有DOM对象都是以COM对象的形式实现的。所以,IE8-浏览器并不支持Node对象的写法。
1.节点层次
首先,大家知道 “在Java的世界里,一切皆对象“,同样,Js 也是。但是,JavaScript并不具备传统的面向对象语言所支持的类和接口等基本结构。DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。
DOM可以将任何HTML或XML文档描绘成一个由多层节点构成的结构。节点分为几种不同的类型,每种类型分别表示文档中不同的信息及(或)标记。每个节点都拥有各自的特点、数据和方法,另外也与其他节点存在某种关系。节点之间的关系构成了层次,而所有页面标记则表现为一个一特定节点为根结点的树形结构。
以HTML为例:
文档节点是每个文档的根结点。在这个例子中,文档节点只有一个子节点,即<html>元素,我们称之为文档元素。
文档元素是文档的最外元素,文档中的其他所有元素都包含在文档元素中。每个文档只能有一个文档元素。在HTML页面中,文档元素始终都是<html>元素。
每一段 标记都可以通过树中的一个节点来表示:HTML元素通过元素节点表示,特性通过特性节点表示,文档类型通过文档类型节点表示,而注释则通过注释节点表示。
每个方框是文档的节点,它表示一个Node对象。树形的根部Document节点,它代表整个文档。
2.节点类型
元素节点 Node.ELEMENT_NODE(1)
属性节点 Node.ATTRIBUTE_NODE(2)
文本节点 Node.TEXT_NODE(3)
CDATA节点 Node.CDATA_SECTION_NODE(4)
实体引用名称节点 Node.ENTRY_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)
DTD声明节点 Node.NOTATION_NODE(12)
Document
Document表示文档,在浏览器中,document对象是HTMLDocument的一个实例,表示整个页面,它同时也是window对象的一个属性。Document有下面的特性:
(1)nodeType为9
(2)nodeName为#document
(3)nodeValue为null
(4)parentNode为null
(5)子节点可能是一个DocumentType或Element
Element
Element提供了对元素标签名,子节点和特性的访问,我们常用HTML元素比如div,span,a等标签就是element中的一种。
Element有下面几条特性:
(1)nodeType为1
(2)nodeName为元素标签名,tagName也是返回标签名
(3)nodeValue为null
(4)parentNode可能是Document或Element
(5)子节点可能是Element,Text,Comment,Processing_Instruction,CDATASection或EntityReference
Text
Text表示文本节点,它包含的是纯文本内容,不能包含html代码,但可以包含转义后的html代码。Text有下面的特性:
(1)nodeType为3
(2)nodeName为#text
(3)nodeValue为文本内容
(4)parentNode是一个Element
(5)没有子节点
Attr
Attr类型表示元素的特性,相当于元素的attributes属性中的节点,它有下面的特性:
(1)nodeType值为2
(2)nodeName是特性的名称
(3)nodeValue是特性的值
(4)parentNode为null
Comment
Comment表示HTML文档中的注释,它有下面的几种特征:
(1)nodeType为8
(2)nodeName为#comment
(3)nodeValue为注释的内容
(4)parentNode可能是Document或Element
(5)没有子节点
DocumentFragment类型
DocumentFragment是所有节点中唯一一个没有对应标记的类型,它表示一种轻量级的文档,可能当作一个临时的仓库用来保存可能会添加到文档中的节点。DocumentFragment有下面的特性:
(1)nodeType为11
(2)nodeName为#document-fragment
(3)nodeValue为null
(4)parentNode为null
节点创建型
createElement
createElement通过传入指定的一个标签名来创建一个元素,如果传入的标签名是一个未知的,则会创建一个自定义的标签,注意:IE8以下浏览器不支持自定义标签。
使用如下:
var div = document.createElement("div");
使用createElement要注意:通过createElement创建的元素并不属于html文档,它只是创建出来,并未添加到html文档中,要调用appendChild或insertBefore等方法将其添加到HTML文档树中。
createTextNode大致同上
createTextNode接收一个参数,这个参数就是文本节点中的文本,和createElement一样,创建后的文本节点也只是独立的一个节点,同样需要appendChild将其添加到HTML文档树中
cloneNode
cloneNode是用来返回调用方法的节点的一个副本,它接收一个boolean参数,用来表示是否复制子元素,使用如下:
var parent = document.getElementById("parentElement");
var parent2 = parent.cloneNode(true);// 传入true
parent2.id = "parent2";
这段代码通过cloneNode复制了一份parent元素,其中cloneNode的参数为true,表示parent的子节点也被复制,如果传入false,则表示只复制了parent节点。
这段代码很简单,主要是绑定button事件,事件内容是复制了一个parent,修改其id,然后添加到文档中。这里有几点要注意:
(1)和createElement一样,cloneNode创建的节点只是游离有html文档外的节点,要调用appendChild方法才能添加到文档树中
(2)如果复制的元素有id,则其副本同样会包含该id,由于id具有唯一性,所以在复制节点后必须要修改其id
(3)调用接收的bool参数最好传入,如果不传入该参数,不同浏览器对其默认值的处理可能不同
除此之外,我们还有一个需要注意的点:如果被复制的节点绑定了事件,则副本也会跟着绑定该事件吗?这里要分情况讨论:(1)如果是通过addEventListener或者比如onclick进行绑定事件,则副本节点不会绑定该事件(2)如果是内联方式绑定比如<div onclick="showParent()"></div>这样的话,副本节点同样会触发事件。
createDocumentFragment
createDocumentFragment方法用来创建一个DocumentFragment。前面说到的DocumentFragment表示一种轻量级的文档,它的作用主要是存储临时的节点用来准备添加到文档中。
创建型 总结
创建型API主要包括createElement、createTextNode、cloneNode、和createDocumentFragment四个方法,需要注意下面几点:
(1)它们创建的节点只是一个孤立的节点,要通过appendChild添加到文档中。
(2)cloneNode要注意如果被复制的节点是否包含子节点以及事件绑定等问题
(3)使用createDocumentFragment来解决添加大量节点时的性能问题
修改型
修改页面内容的API主要包括:appendChild,insertBefore,removeChild,replaceChild。
appendChild
appendChild我们在前面已经用到多次,就是将指定的节点添加到调用该方法的节点的子元素的末尾。调用方法如下:parent.appendChild(child);child节点将会作为parent节点的最后一个子节点�这个方法很简单,但是有一点需要注意:如果被添加的节点是一个页面中存在的节点,则执行后这个节点将会添加到指定位置,其原本所在的位置将移除该节点,也就是说不会同时存在两个该节点在页面上,相当于把这个节点移动到另一个地方。
insertBefore
insertBefore用来添加一个节点到一个参照节点之前,用法如下:
parentNode.insertBefore(newNode,refNode);
parentNode表示新节点被添加后的父节点
newNode表示要添加的节点
refNode表示参照节点,新节点会添加到这个节点之前
关于第二个参数参照节点还有几个注意的地方:
(1)refNode是必传的,如果不传该参数会报错
(2)如果refNode是undefined或null,则insertBefore会将节点添加到子元素的末尾
removeChild
removeChild顾名思义,就是删除指定的子节点并返回,用法如下:
var deletedChild = parent.removeChild(node);
deletedChild指向被删除节点的引用,它等于node,被删除的节点仍然存在于内存中,可以对其进行下一步操作。
注意:如果被删除的节点不是其子节点,则程序将会报错。我们可以通过下面的方式来确保可以删除:
if(node.parentNode){
node.parentNode.removeChild(node);
}
通过节点自己获取节点的父节点,然后将自身删除。
replaceChild
replaceChild用于使用一个节点替换另一个节点,用法如下:parent.replaceChild(newChild,oldChild);
newChild是替换的节点,可以是新的节点,也可以是页面上的节点,如果是页面上的节点,则其将被转移到新的位置,oldChild是被替换的节点。
页面修改型api主要是这四个接口,要注意几个特点:
(1)不管是新增还是替换节点,如果新增或替换的节点是原本存在页面上的,则其原来位置的节点将被移除,也就是说同一个节点不能存在于页面的多个位置
(2)节点本身绑定的事件不会消失,会一直保留着。
节点查询型API
document.getElementById
这个接口很简单,根据元素id返回元素,返回值是Element类型,如果不存在该元素,则返回null。�使用这个接口有几点要注意:
(1)元素的Id是大小写敏感的,一定要写对元素的id
(2)HTML文档中可能存在多个id相同的元素,则返回第一个元素
(3)只从文档中进行搜索元素,如果创建了一个元素并指定id,但并没有添加到文档中,则这个元素是不会被查找到的
document.getElementsByTagName
这个接口根据元素标签名获取元素,返回一个即时的HTMLCollection类型,什么是即时的HTMLCollection类型呢?
这个方法有几点要注意:
(1)如果要对HTMLCollection集合进行循环操作,最好将其长度缓存起来,因为每次循环都会去计算长度,暂时缓存起来可以提高效率
(2)如果没有存在指定的标签,该接口返回的不是null,而是一个空的HTMLCollection
(3)“ * ”表示所有标签
document.getElementsByName
getElementsByName主要是通过指定的name属性来获取元素,它返回一个即时的NodeList对象。使用这个接口主要要注意几点:
(1)返回对象是一个即时的NodeList,它是随时变化的
(2)在HTML元素中,并不是所有元素都有name属性,比如div是没有name属性的,但是如果强制设置div的name属性,它也是可以被查找到的
(3)在IE中,如果id设置成某个值,然后传入getElementsByName的参数值和id值一样,则这个元素是会被找到的,所以最好不好设置同样的值给id和name
扩展
一.选择符API
1.querySelector()方法
该方法接收一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null。
通过Document类型调用querySelector()方法时,会在文档元素的范围内查找匹配的元素。而通过Element类型调用querySelector()方法时,只会在该元素后代元素的范围内查找匹配的元素。
注:众多JavaScript库中最常用的一项功能,就是根据CSS选择符选择与某个模式匹配的DOM元素。实际上,jQuery的核心就是通过CSS选择符查询DOM文档取得元素的引用,从而抛开了getElementById()和getElementByTagName()。
CSS选择符可以简单也可以复杂,视情况而定。如果传入了不被支持的选择符,querySelector()会抛出错误。
2.querySelectorAll()
querySelectorAll()方法接受的参数与querySelector()方法一样,都是一个CSS选择符,但是返回的不仅不仅是一个元素,而是一个NodeList的实例。
与querySlector()一样能够调querySlectorAll()方法的有document,element,DocumentFragment。
要取得返回的NodeList中的每一个元素,可以使用item()方法,也可以使用方括号语法,比如:
getElementsByClassName()
1)使用方法:element.getElementsByClassName("classNames"),其中,element是有效的DOM元素(包括document)
classNames是CSS类名称的组合(多个类名之间用空格,可以是多个空格隔开),如
element.getElementsByClassName("class2 class1")
将选取elements后代元素中同时应用了class1和class2样式的元素(样式名称不区分先后顺序) 2)说明:a. 返回值是一个nodeList集合(区别于Array 有明确的定义与概念)
b. 该方法只能选取调用该方法的元素的后代元素。
3)兼容性:IE8及其以下版本的浏览器未实现getElementsByClassName方法
HTML5添加的getElementsByClassName()方法,可以通过Document对象及所有HTML元素调用该方法
事件
所谓事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间
一、事件流
在页面上,单击某个元素的同时,也单击了它的包含容器。事件流就是描述的从页面中接收事件的顺序。IE是事件冒泡流,Netscape是事件捕获流。
事件冒泡
事件开始时,由最具体的元素(文档中嵌套最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档);(所有现代浏览器都支持事件冒泡) 如果单击了页面中的<div>元素,那么这个click事件会按照如下顺序传播:
(1)<div>
(2)<body>
(3)<html>
(4)document
事件捕获
不太具体的节点最早接收到事件,最具体的节点最后接收到事件。(老版本浏览器不支持)如果单击了页面中的<div>元素,那么这个click事件会按照如下顺序传播:
(1)document
(2)<html>
(3)<body>
(4)<div>
二、DOM事件流
DOM2级事件规定的事件流包括三个阶段:
1、事件捕获阶段
2、处于目标阶段
3、事件冒泡阶段
在DOM事件流中,实际的目标(div元素)在捕获阶段不会接受到事件。这意味着在捕获阶段,事件从document到<html>再到<body>后就停止了。下一阶段是”处于目标”阶段,于是事件在<div>上发生,并在事件处理中被看成冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回文档。
多数支持DOM事件流的浏览器都实现了一种特定的行为:即使”DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操作事件。
浏览器的内核
主要分成两部分:渲染引擎(layout engineer或Rendering Engine)和JS引擎。�渲染引擎:负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。�JS引擎则:解析和执行javascript来实现网页的动态效果。�最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。
解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树。
当浏览器获得一个html文件时,会“自上而下”加载,并在加载过程中进行解析渲染。
解析:
1. 浏览器会将HTML解析成一个DOM树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。
2. 将CSS解析成 CSS Rule Tree 。
3. 根据DOM树和CSSOM来构造 Rendering Tree。
注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像 Header 或 display:none 的东西就没必要放在渲染树中了。
4.有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为Layout,顾名思义就是计算出每个节点在屏幕中的位置。
5.再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。
上述过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
(1)Reflow(回流):浏览器要花时间去渲染,当它发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染。
(2)Repaint(重绘):如果只是改变了某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的repaint,重画某一部分。
Reflow要比Repaint更花费时间,也就更影响性能。所以在写代码的时候,要尽量避免过多的Reflow。
reflow的原因:
(1)页面初始化的时候;
(2)操作DOM时;
(3)某些元素的尺寸变了;
(4)如果 CSS 的属性发生变化了。
减少 reflow/repaint
(1)不要一条一条地修改 DOM 的样式。与其这样,还不如预先定义好 css 的 class,然后修改 DOM 的 className。
(2)不要把 DOM 结点的属性值放在一个循环里当成循环里的变量。
(3)为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。
(4)千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。
编写CSS时应该注意:
CSS选择符是从右到左进行匹配的。从右到左,所以,#nav li 我们以为这是一条很简单的规则,秒秒钟就能匹配到想要的元素,但是,但是,但是,是从右往左匹配啊,所以,会去找所有的li,然后再去确定它的父元素是不是#nav。,因此,写css的时候需要注意:
(1)dom深度尽量浅。
(2)减少inline javascript、css的数量。
(3)使用现代合法的css属性。
(4)不要为id选择器指定类名或是标签,因为id可以唯一确定一个元素。
(5)避免后代选择符,尽量使用子选择符。
原因:子元素匹配符的概率要大于后代元素匹配符。后代选择符;#tp p{} 子选择符:#tp>p{}
(6)避免使用通配符,举一个例子:
.mod .hd *{font-size:14px;}
根据匹配顺序,将首先匹配通配符,也就是说先匹配出通配符,然后匹配.hd(就是要对dom树上的所有节点进行遍历他的父级元素),然后匹配.mod,这样的性能耗费可想而知.