DOM
原文链接:通俗易懂讲DOM;
1. DOM
DOM(文档对象模型)是针对HTML和XML文档的一个API,通过DOM可以改变文档。
DOM可以理解为一个 访问或操作HTML各种标签的实现标准。
Document类型代表一个HTML或XML文档,Element类型代表该文档中的一个元素。HTMLDocument和HTMLElement子类只是针对HTML文档和元素。
对于一个HTML来说,文档节点Document(看不见的)是它的根节点(#document),对应的对象便是document对象(严格讲是子类HTMLDocument对象),下面单独介绍Document类型时会指出。
每一段HTML标记都可以用相应的节点表示,比如:HTML元素可以通过元素节点表示,注释通过注释节点表示,文档类型通过文档类型节点表示等。
一共定义了12种节点类型,而这些类型又都继承自Node类型。
2. Node类型(基类,所有节点都继承了他的方法)
2.1 Node类型的属性
-
nodeType:节点类型
-
nodeName:标签名,以大写形式表示
-
nodeValue:Text节点或Comment节点的文本内容
如下图:
-
childNodes:返回节点的所有直接子元素,返回NodeList对象(类数组),动态查询的(即每次调用它都会对DOM结构查询,注意性能)
-
firstChild:父节点的第一个子元素
-
lastChild:父节点的最后一个子元素
-
parentNode:返回父节点
-
nextSibling:下一个兄弟元素
-
previousSibling:上一个兄弟元素
如下图:
- ownerDocument:返回文档节点的引用(在html里就是document对象)
2.2 Node的方法
-
hasChildNodes():如果包含子节点就返回true,比查询childNodes的length来的简单。
- appendChild():在节点的childNodes的末尾增加一个节点,值得注意的是,如果这个节点是已经存在文档中的,那么便会删除原节点,像移动节点一样。
var li4 = oUl.childNodes[oUl.childNodes.length-2]; oUl.appendChild(li4);
- insertBefore():在某节点之前插入节点。接受两个参数,第一个是要插入的节点,第二个是参照的节点。如果第二个参数为null,则insertBefore和appendChild一样。否则便会把节点插入到参照节点之前。这里要注意的是,如果第二个参数不为null,那么插入的节点不能是已经存在的节点。
//插入后成为最后一个子节点 returnedNode = someNode.insertBefore(newNode, null); alert(newNode == someNode.lastChild); //true //插入后成为第一个子节点 var returnedNode = someNode.insertBefore(newNode, someNode.firstChild); alert(returnedNode == newNode); //true alert(newNode == someNode.firstChild); //true //插入到最后一个子节点前面 returnedNode = someNode.insertBefore(newNode, someNode.lastChild); alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true
-
replaceChild():替换节点。接受两个参数,需要插入的节点和需要替换的节点。返回被替换掉的节点。
- removeChild():移除节点。这里有个常见需求,比如我有一个节点 #waste-node ,那么如何移除它呢?
var wasteNode = document.getElementById("waste-node"); wasteNode.parentNode.removeChild(wasteNode); // 先拿到父节点,再调用removeClild删除自己
- cloneNode():复制节点。接受一个参数 true或者false。如果true就是复制那个节点和它的子节点。如果是false,就是复制节点本身(复制出来的节点就会没有任何子元素)。这个方法返回复制的节点,如果需要操作它,那么需要借助前面讲的4个方法来把这个节点放入到html中去。
3、Document类型
最开始讲DOM是什么的时候提到了Document类型。其实关于这个类型最重要的是它的一个子类HTMLDocument有一个实例对象document。而这个document对象是我们最常用的一个对象了。
document对象又挂载在window对象上,所以在浏览器就可以直接访问document了。
3.1 document对象的属性
- document.childNodes:继承自上面讲的Node类型,可以返回文档的直接子节点(通常包括文档声明和html节点)
var li3 = oUl.childNodes[2]; var len = oUl.childNodes.length;
-
document.documentElement:可以直接拿到html节点的引用(等价于document.getElementsByTagName(“html”)[0])。
-
document.body:body节点的引用
-
document.head:head元素
-
document.title:页面的title,可以修改,会改变浏览器标签上的名字
-
document.URL:页面的url
-
document.referrer:取得referrer,也就是打开这个页面的那个页面的地址,做来源统计时候比较有用
- document.domain:取得域名,可以设置,但是通常只能设置为不包含子域名的情况,在一些子域名跨域情况下有效。
3.2 document对象的方法
-
getElementById():传入id,得到元素节点。里面的参数区分大小写(IE8-不区分)。注意:如果有多个id相同的元素,则返回第一个。IE7-里面表单元素的name也会被当做id来使用。
-
getElementsByTagName():根据标签名取得元素,得到的是HTMLCollection类型。如果传入的是 “*” ,则可以取得全部元素。
-
getElementsByName():只有HTMLDocument类型(也就是document对象)才有的方法。顾名思义,根据name返回元素。
-
write() writeln():它们都是向页面写入东西,区别就是后者会多加入一个换行符。注意的是:在页面加载的过程中,可以使用这两个方法向页面添加内容。如果页面已经加载完了,再调用write,会重写整个页面。
还有一点,如果要动态写入脚本 例如 这样的 ,那么要注意把</script>分开来拼装下,否则会被误以为是脚本结束的标志,导致这个结束符匹配到上面一个开始符。可以这样写”<scr” + “ipt>”;
- open()和close():open和close分别是打开和关闭网页的输出流,在页面加载过程中,就相当于open状态。这两个方法一般不会去用它。
3.3 document对象的集合
HTMLCollection就是一个包含一个或多个元素的集合,和上面讲的NodeList还挺像的。HTMLCollection这个类型有两个方法,一个是通过下标(或者.item())得到具体元素,还有就是通过[‘name’](或者.namedItem())获得具体元素。
-
document.forms 可以返回所有的form表单。类型也是HTMLCollection。
-
document.images 可以返回所有的。类型也是HTMLCollection。
-
document.links 可以返回所有的。类型也是HTMLCollection。
4.Element类型
我们日常所操作的都是Element类型(实质是HTMLElement,这里为了方便理解,就简单这么说),比如
document.getElementById("test")
返回的就是Element类型。我们日常所说的“DOM对象”,通常也就是指Element类型的对象。
4.1 Element类型的常见属性:
首先最开始说的Node类型上的那些属性方法它都有,这个就不再重复了,主要说说它自己独有的。
- tagName,这个和继承自Node类型的nodeName一样。都是返回标签名,通常是大写,结果取决于浏览器。所以在做比较的时候最好是调用下类似toLowerCase()这种方法再做比较。
HTMLElement类型
HTMLElement类型继承自Element类型,也是HTML元素的实际类型,我们在浏览器里用的元素都是这个类型。
HTMLElement类型的标准属性
-
id 元素的唯一标识
-
title 通常是鼠标移上去时候会显示的信息
-
className 类名
等等,这几个属性是可读写的,也就是说你改变他们会得到相应的效果。
- attributes属性,保存了元素的全部属性。
HTMLElement类型的方法
首先说说操作节点属性的方法
-
getAttribute 、setAttribute 、removeAttribute这3个方法:这些是操作属性最常用的方法了,怎么用就不说了,很简单,顾名思义。总得来说,这3个方法通常用来处理自定义的属性,而不是id、class等这种“公认特性”。
-
document.createElement():创建一个元素。在IE中,还可以直接穿整个HTML字符串进去,来创建元素,比如
document.createElement("<div>test</div>");
最后,元素节点也支持HTMLDocument类型的那些查找方法,比如getElementsByTagName。不过它只会找自己后代的节点。所以可以这么写代码
document.getElementById("test").getElementsByTagName("div"); // 找到id为test元素下的所有div节点
5.Text类型
这个类型很特殊,也是第三常见类型(第一第二分别就是Document和Element)。
这个节点简单来说就是一段字符串。
有个很重要的特征就是,它没有子元素(不过这个仔细想想也知道= =)
5.1 属性
-
nodeValue或者data属性:访问text节点的文本内容
-
lenght属性,返回字符长度的。
5.2 方法
- appendData(); // 在text末尾加内容
- deleteData(offset, count); // 从offset指定的位置开始删除count个字符
- insertDate、replaceData、splitText等方法,就不一一说了,用的机会很少,可以用的时候再查阅。
这里说一个常见的坑。比如下面这个html结构
<ul>
<li></li>
<li></li>
</ul>
这里,ul的第一个子节点(firstChild)是什么呢?第一眼看过去,肯定认为是li了,但是实际上,你会发现不是li,而是一个文本节点!
这是因为浏览器认为ul和第一个li之间有空白字符,所以就有文本节点了。
这里一个常见的问题就是遍历ul的childNodes的时候,遍历的元素一定要判断下nodeType是不是等于1(等于1就代表是元素节点),这样才能跳过这个坑。否则你也可以删除所有的空格和换行符。
- document.createTextNode:创建文本节点,然后接下来和操作Element类型一样,就是再插入到元素中,浏览器就可以看到了。
6、其他的一些类型 Comment、DocumentType和DocumentFragment
Comment是注释节点
DocumentType就是doctype节点,通过docment.doctype来访问
DocumentFragment这个节点是一个文档片段,偶尔会用到。
比如一种常见的用法是,在一个ul中插入3个li。
如果你循环插入3次,那么浏览器就要渲染3次,对性能有蛮大的影响。
所以大家一般这么做
var fragment = document.createDocumentFragment();
然后循环把li,用appendChild插入到fragment里面
最后在一次把fragment插入到ul里面。这样就会很快。
7、DOM扩展
浏览器为了方便开发者,扩展了一些DOM功能。
因为是浏览器自己扩展的,所以使用前兼容性问题一定要注意
-
document.compatMode和新的document.documentMode:判断“标准模式”和“混杂模式”
-
children属性:这个属性只包含元素节点。(可以解决一个文本节点作为第一子元素的坑)
- contains方法:为了方便判断A节点是不是B节点的子节点
B.contains(A); // true就代表是,false就代表不是
这个方法有兼容性问题,使用前可以谷歌解决方法。
- innerText/innerHTML/outerTEXT/outerHTML:通过这些方法,可以读和写元素。
其中,*TEXT是返回文本内容 *HTML是返回html文本。
而outer*则是代表是否包含元素本身。
实际使用来看,在读内容的时候 inner和outer没有区别。
在把内容写入元素的时候,就是是否包含元素本身的区别。
重要的是,这几个方法有性能问题,比如在IE中,通过inner*删除的节点,其绑定的事件依然在内存中,就很容易消耗大量内存。
还有一个技巧是,插入大量的html代码,用innerHTML是非常快的,建议使用。
9、MORE
DOM知识远不止这些,接下来会简单说说DOM的一些其余接口
- 首先就是一个通过document查找到窗口(也就是window)的方法
var parentWindow = document.defaultView || document.parentWindow;
defaultView是标准方法,parentWidow是兼容IE用的。
-
然后,Node类型新增了 isSameNode()和isEqualNode()。 它们的区别就是:same是指同一个节点,equal是指“看过去”一模一样的两个节点。
- 提供了一个访问iframe方法 标准的是通过ele.contentDocument 这个会返回iframe里面的document对象,但是有兼容性问题,ie不支持,但是ie支持contentWindow,相当于拿到window对象,那么document对象也很容易取到,所以一般这么写。
var iframe = document.getElementById("myIframe"); var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
处理样式
设置样式的基本的方式就是 ele.style.width = “20px”;这个大家估计很常用。 使用类似这种方法来处理要注意的点就是记得加单位。 快捷一点的方法就是 ele.style.cssText = “width: 20px;height:20px;”;这个可以批量设置。
上面说的是设置,那么获取元素所使用的样式,一般而言会用 document.defaultView.getComputedStyle(ele, null);方法来实现。这个方法会返回这个一个CSSStyleDeclaration对象(就和ele.style返回的一样),但是会包含这个元素的全部样式信息,然后直接返回值.width或者别的属性名就可以得到样式了。
不过IE不支持这种方式,IE更简单,直接ele.currentStyle就可以得到和 document.defaultView.getComputedStyle(ele, null)一样的返回值了。
元素的大小
取得元素的宽高 就使用 offsetHeight/offsetWidth,注意,不包括margin部分
得到元素的偏移值就复杂了一点,有两个方法offsetTop/offsetLeft,但是这两个方法是指元素针对与它的offsetParent对象而言的,所以,如果你想得到元素距离视口的位置,那么还需要找到offsetParent,计算他们的offsetTop/offsetLeft,在找offsetParent的offsetParent,如此循环直到offsetParent为null。注意,这个计算的位置也不包括margin部分。
上面说的是不包括margin,然后还有一组属性clientWidth/clientHeight,它们也可以得到宽高,但是它们是不包括margin和border,也不计算滚动条。
最后,滚动的位置与距离,这个很难说清,大家直接看图比较清楚明了。
其中,scrollLeft和scrollTop是可以设置的,如果你把scrollTop设置成 “10px”,那么元素就是滚动到10px的地方去。