DOM之通俗易懂讲解

2016-01-13 admin

DOM 是所有前端开发每天打交道的东西,但是随着 jQuery 等库的出现,大大简化了 DOM 操作,导致大家慢慢的 “遗忘” 了它的本来面貌。不过,要想深入学习前端知识,对 DOM 的了解是不可或缺的,所以本文力图系统的讲解下 DOM 的相关知识,如有遗漏或错误,还请大家指出一起讨论 ^ ^。

一、DOM 是什么?

DOM(文档对象模型)是针对 HTML 和 XML 文档的一个 API,通过 DOM 可以去改变文档。

这个说法很官方,大家肯定还是不明白。

举个例子:我们有一段 HTML,那么如何访问第二层第一个节点呢,如何把最后一个节点移动到第一个节点上面去呢?

DOM 就是定义了如果做类似操作,那么应该怎么做的标准。比如用 getElementById 来访问节点,用 insertBefore 来插入节点。

当浏览器载入 HTML 时,会生成相应的 DOM 树。

简而言之,DOM 可以理解为一个访问或操作 HTML 各种标签的实现标准。

对于一个 HTML 来说,文档节点 Document(看不到的)是它的根节点,对应的对象便是 document 对象(严格讲是子类 HTMLDocument 对象,下面单独介绍 Document 类型时会指出)。

换句话说存在一个文档节点 Document,然后它有子节点,比如通过 document.getElementsByTagName(“html”),得到类型为元素节点的 Element html。

每一段 HTML 标记都可以用相应的节点表示,例如:

HTML 元素通过元素节点表示,注释通过注释节点表示,文档类型通过文档类型节点表示等。

一共定义了 12 种节点类型,而这些类型又都继承自 Node 类型。

所以我们首先讲 Node 类型,因为这个类型的方法是所有节点都会继承的。

二、Node 类型(基类,所有节点都继承了它的方法)

Node 是所有节点的基类型,所有节点都继承自它,所以所有节点都有一些共同的方法和属性。

先讲 Node 类型的属性

首先是 nodeType 属性,用来表明节点类型的,例如:

document.nodeType;    // 返回 9 ,其中document对象为文档节点Document的实例

这里面,9 代表的就是 DOCUMENT_NODE 节点的意思,可以通过 Node.DOCUMENT_NODE 查看节点对应的数字

document.nodeType === Node.DOCUMENT_NODE;    // true

至于一共有哪些节点,每个节点对应的数字又是多少,这个可以问谷歌就知道了。反正常用的就是元素节点 Element(对应数字为 1) 和文本节点 Text(对应数字为 3)

然后常用的还有 nodeName 和 nodeValue

对于元素节点 nodeName 就是标签名,nodeValue 就是 null

对于文本节点 nodeName 为 “#text”(chrome 里面测试的),nodeValue 就是实际的值

每个节点还有 childNodes 属性,这是个十分重要的属性,它保存了这个节点所有直接子元素

调用 childNodes 返回的是一个 NodeList 对象,它极其像数组,但是有一个最关键的地方,它是动态查询的,也就是说每次调用它都会对 DOM 结构查询,所以对它的使用需要慎重,注意性能。

访问 childNodes 可以使用数组下表或者 item 方法

然后各个节点还存在各种属性让它们可以相互访问,下图很好的总结了

image.png

比较有用的方法和属性:

1、hasChildNodes()

如果包含子节点就返回 true,比查询 childNodes 的 length 来的简单。

2、ownerDocument

返回文档节点的引用(在 html 里面也就是 document 对象)

再介绍下 Node 类型常用的方法

appendChild() 方法可以在节点的 childNodes 的末尾增加一个节点,值得注意的是如果这个节点是已经存在在文档中的,那么便会删除原节点,感觉上就像是移动节点一样。

insertBefore() 方法接受两个参数,一个是插入的节点,另外一个是参照的节点。如果第二个参数为 null,则 insertBefore 和 appendChild 效果一样。否则便会把节点插入到参照节点之前。这里要注意的是,如果第二个参数不为 null,那么插入的节点不能是已经存在的节点。

replaceChild() 方法可以替换节点,接受两个参数,需要插入的节点和需要替换的节点。返回被替换掉的节点。

removeChild() 移除节点。这里有个常见需求,比如我有一个节点 #waste-node ,那么如何移除它呢?

var wasteNode =  document.getElementById("waste-node");
wasteNode.parentNode.removeClhid(wasteNode);    // 先拿到父节点,再调用removeClild删除自己

这里先暂停一下,不知道大家注意到没有,以上的几个方法都是操作某个节点的子节点,也就是说,操作前必须找到父节点(通过 parentNode 来找)

接下来说下复制节点的方法:

cloneNode(); 复制节点,接受一个参数 true 或者 false。如果 true 就是复制那个节点和它的子节点。**如果是 false,就是复制节点本身(复制出来的节点就会没有任何子元素)。**这个方法返回复制的节点,如果需要操作它,那么需要借助前面讲的 4 个方法来把这个节点放入到 html 中去。

至此,Node 类型的常见属性和方法都介绍完了。结合开头讲的,所有节点类型都继承自 Node 类型,所以这些方法是所有节点都有的。

三、Document 类型

最开始讲 DOM 是什么的时候提到了 Document 类型。其实关于这个类型最重要的是它的一个子类 HTMLDocument 有一个实例对象 document。而这个 document 对象是我们最常用的一个对象了。

document 对象又挂载在 window 对象上,所以在浏览器就可以直接访问 document 了。

老规矩,先讲讲 document 对象的属性,等会讲讲它的方法。

document 对象上的一些属性

document.childNodes 继承自上面讲的 Node 类型,可以返回文档的直接子节点(通常包括文档声明和 html 节点)

document.documentElement 可以直接拿到 html 节点的引用(等价于 document.getElementsByTagName(“html”)[0])。

document.body body 节点的引用

document.title  页面的 title,可以修改,会改变浏览器标签上的名字

document.URL 页面的 url

document.referrer 取得 referrer,也就是打开这个页面的那个页面的地址,做来源统计时候比较有用

document.domain 取得域名,可以设置,但是通常只能设置为不包含子域名的情况,在一些子域名跨域情况下有效。

接下来介绍两个熟悉的方法

getElementById 和 getElementsByTagName

getElementById,传入 id,得到元素节点。里面的参数区分大小写(IE8 - 不区分)。注意:如果有多个 id 相同的元素,则返回第一个。IE7 - 里面表单元素的 name 也会被当做 id 来使用。

getElementsByTagName 根据标签取得元素,得到的是 HTMLCollection 类型。如果传入的是 "" ,则可以取得全部元素。*

还有一个是只有 HTMLDocument 类型(也就是 document 对象)才有的方法 getElementsByName 顾名思义,根据 name 返回元素。

document 对象还有一些集合,例如 document.forms 可以返回所有的 form 表单。类型也是 HTMLCollection。

说到 HTMLCollection,就再说说它

HTMLCollection 就是一个包含一个或多个元素的集合,和上面讲的 NodeList 还挺像的。HTMLCollection 这个类型有两个方法,一个是通过下标(或者. item())得到具体元素,还有就是通过 [‘name’](或者. namedItem())获得具体元素。

最后,关于 document 对象还有一套重要的方法,那便是

write() writeln() open() close()

open 和 close 分别是打开和关闭网页的输出流,在页面加载过程中,就相当于 open 状态。这两个方法一般不会去用它。

然后重要的方法就是 write 和 writeln,它们都是向页面写入东西,区别就是后者会多加入一个换行符。

注意的是:在页面加载的过程中,可以使用这两个方法向页面添加内容。如果页面已经加载完了,再调用 write,会重写整个页面。

还有一点,如果要动态写入脚本 例如 <script>xxx</script > 这样的 ,那么要注意把 </script > 分开来拼装下,否则会被误以为是脚本结束的标志,导致这个结束符匹配到上面一个开始符。可以这样写 “<scr” + “ipt>”;

四、Element 类型

接下来讲讲最重要也是最常见的一个类型,Element 类型。

我们日常所操作的都是 Element 类型(实质是 HTMLElement,这里为了方便理解,就简单这么说),比如

document.getElementById("test") 

返回的就是 Element 类型。我们日常所说的 “DOM 对象”,通常也就是指 Element 类型的对象。

然后说说这个类型的常见属性:

首先最开始说的 Node 类型上的那些属性方法它都有,这个就不再重复了,主要说说它自己独有的。

首先是 tagName,这个和继承自 Node 类型的 nodeName 一样。都是返回标签名,通常是大写,结果取决于浏览器。所以在做比较

的时候最好是调用下类似 toLowerCase() 这种方法再做比较。

说说上面提到过的 HTMLElement 类型

HTMLElement 类型继承自 Element 类型,也是 HTML 元素的实际类型,我们在浏览器里用的元素都是这个类型。

这个类型都具有一些标准属性,比如:

id 元素的唯一标识

title 通常是鼠标移上去时候会显示的信息

className 类名

等等,这几个属性是可读写的,也就是说你改变他们会得到相应的效果。

除了属性外,还有几个重要的方法

首先说说操作节点属性的方法

getAttribute 、setAttribute 、removeAttribute 这 3 个方法。

这些是操作属性最常用的方法了,怎么用就不说了,很简单,顾名思义。

还有一个 attributes 属性,保存了元素的全部属性。

这里停下来,出个问题,ele.className 和 ele.getAttribute(“class”) 返回的结果是不是同一个东西?

解答这个问题,我要说一个重要知识点,一个元素的属性结构是这么来的,比如一个 inpnt 元素

<input id="test" checked="checked">

那么这个元素的属性被包含在 input.attributes 里面,比如你在 html 元素上看到的 class、id 或者你自己定义的 data-test 这种属性。

然后 getAttribute 、setAttribute 、removeAttribute 这 3 个方法可以认为是快捷的取 attributes 集合的方法。**而直接 input.id 或者 input.className 都是直接挂在 input 下的属性,和 attributes 是同级的。**所以返回的东西也许看过去一样,实际是不一样的,不信你可以试试 input.checked 这 input.getAttribute(“checked”) 试试。

关于这个知识点,详细的说可以再写一篇文章,在我的博客 从 is(":checked") 说起 中有谈到过,大家可以看看这篇文章和文章后的讨论,便可以知道是怎么一回事。

总得来说,这 3 个方法通常用了处理自定义的属性,而不是 id、class 等这种 “公认特性”。

接下来说说创建元素

document.createElement() 可以创建一个元素,比如:

document.createElement("div");

一般之后可以为元素设置属性,两种方法,一种是直接 node.property 还可以 node.setAttribute(“propertyName”,“value”)。等

但是做完这些之后,这个元素还是没有在页面中,所以你还得通过最上面讲的类似 appendChild 这些方法把元素添加到页面里面。

在 IE 中,还可以直接穿整个 HTML 字符串进去,来创建元素,比如

document.createElement("<div>test</div>");

最后,元素节点也支持 HTMLDocument 类型的那些查找方法,比如 getElementsByTagName。不过它只会找自己后代的节点。所以可以这么写代码

document.getElementById("test").getElementsByTagName("div");    // 找到id为test元素下的所有div节点

五、Text 类型

这个类型很特殊,也是第三常见类型(第一第二分别就是 Document 和 Element)。

这个节点简单来说就是一段字符串。

有个很重要的特征就是,它没有子元素(不过这个仔细想想也知道 = =)

访问 text 节点的文本内容,可以通过 nodeValue 或者 data 属性。

下面简单说说它提供的一些方法

appendData();    // 在text末尾加内容
deleteData(offset, count);    // 从offset指定的位置开始删除count个字符

还有 insertDate、replaceData、splitText 等方法,就不一一说了,用的机会很少,可以用的时候再查阅。

然后它还有一个 lenght 属性,返回字符长度的。

这里说一个常见的坑。比如下面这个 html 结构

<ul>
     <li></li>
     <li></li>
</ul>

这里,ul 的第一个子节点(firstChild)是什么呢?第一眼看过去,肯定认为是 li 了,但是实际上,你会发现不是 li,而是一个文本节点!

这是因为浏览器认为 ul 和第一个 li 之间有空白字符,所以就有文本节点了。

这里一个常见的问题就是遍历 ul 的 childNodes 的时候,遍历的元素一定要**判断下 nodeType 是不是等于 1(等于 1 就代表是元素节点),**这样才能跳过这个坑。否则你也可以删除所有的空格和换行符。

创建文本节点的方法是 document.createTextNode

然后接下来和操作 Element 类型一样,就是再插入到元素中,浏览器就可以看到了。

六、其他的一些类型 Comment、DocumentType 和 DocumentFragment

这些不常用的一句话带过把

Comment 是注释节点

DocumentType 就是 doctype 节点,通过 docment.doctype 来访问

DocumentFragment 这个节点是一个文档片段,偶尔会用到。

比如一种常见的用法是,在一个 ul 中插入 3 个 li。

如果你循环插入 3 次,那么浏览器就要渲染 3 次,对性能有蛮大的影响。

所以大家一般这么做

var fragment = document.createDocumentFragment();

然后循环把 li,用 appendChild 插入到 fragment 里面

最后在一次把 fragment 插入到 ul 里面。这样就会很快。

七、DOM 扩展

进过上面讲的这么多节点类型,想必大家对 DOM 节点已经有了很深的了解,下面讲一讲 DOM 扩展的一些东西。

浏览器为了方便开发者,扩展了一些 DOM 功能。

因为是浏览器自己扩展的,所以使用前兼容性问题一定要注意

判断 “标准模式” 和“混杂模式”通过 document.compatMode 和新的 document.documentMode

上面不是说了一个文本节点作为第一子元素的坑吗,所以浏览器又实现了一个 children 属性,这个属性只包含元素节点。

为了方便判断 A 节点是不是 B 节点的子节点,引入了 contains 方法,比如

B.contains(A);    // true就代表是,false就代表不是

这个方法有兼容性问题,使用前可以谷歌解决方法。

针对访问元素,又提供了 4 个方法 innerText/innerHTML/outerTEXT/outerHTML。

通过这些方法,可以读和写元素。

其中,*TEXT 是返回文本内容 *HTML 是返回 html 文本。

而 outer * 则是代表是否包含元素本身。

实际使用来看,在读内容的时候 inner * 和 outer * 没有区别。

在把内容写入元素的时候,就是是否包含元素本身的区别。

重要的是,这几个方法有性能问题,比如在 IE 中,通过 inner * 删除的节点,其绑定的事件依然在内存中,就很容易消耗大量内存。

还有一个技巧是,插入大量的 html 代码,用 innerHTML 是非常快的,建议使用。

八、总结

首先感谢所有看到这里的朋友,哈哈,关于 DOM 的东西实在是太多了,不过这也算是最重要的一个前端知识点之一吧。文章比较长,也许有点乏味,不过希望你们耐着性子看完后可以有所收货 ^ ^。

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-449.html

文章标题:DOM之通俗易懂讲解

相关文章
bootstrap table之通用方法( 时间控件,导出,动态下拉框, 表单验证 ,选中与获取信息)代码分享
1.bootstrap-table 单击单行选中 $(&#x27;#gzrwTable&#x27;).on(&#x27;click-row.bs.table&#x27;, function(e, row, $element) { $(&#x...
2017-02-17
数据格式之战:JSON vs XML
在比较JSON和XML之前,我们先来上一堂关于数据格式的简要历史(更准确的说,是关于XML的始祖): 早在1970年,IBM开发了一种叫Generalized Markup Language的标记语言,简称GML,它主要是为脚本语言定义的一...
2016-01-13
JavaScript深入之类数组对象与
类数组对象 所谓的类数组对象: 拥有一个 length 属性和若干索引属性的对象 举个例子: var array = [&#x27;name&#x27;, &#x27;age&#x27;, &#x27;sex&#x27;]; var ...
2017-05-27
javascript数据结构与算法之检索算法
查找数据有2种方式,顺序查找和二分查找。顺序查找适用于元素随机排列的列表。二分查找适用于元素已排序的列表。二分查找效率更高,但是必须是已经排好序的列表元素集合。 一:顺序查找 顺序查找是从列表的第一个元素开始对列表元素逐个进行判断,直到找到...
2017-03-22
Vue.js原理分析之observer模块详解
介绍 observer是Vue核心中最重要的一个模块(个人认为),能够实现视图与数据的响应式更新,底层全凭observer的支持。 **注意:**本文是针对Vue@2.1.8进行分析 observer模块在Vue项目中的代码位置是src/c...
2017-03-16
jsdom 中文文档(纯翻译)
jsdom是一个纯粹由 javascript 实现的一系列 web标准,特别是 WHATWG 组织制定的DOM和 HTML 标准,用于在 nodejs 中使用。大体上来说,该项目的目标是模拟足够的Web浏览器子集,以便用于测试和挖掘真实世界...
2018-05-14
HTML5之WebSocket入门3 -通信模型socket.io
socket.io为什么会诞生呢?请看下面文字说明。 为什么需要socket.io? node.js提供了高效的服务端运行环境,但是由于浏览器端对HTML5的支持不一,为了兼容所有浏览器,提供卓越的实时的用户体验,并且为程序员提供客户端与服...
2017-03-29
文字垂直滚动之javascript代码
在大型的网站新闻公告和友情链接等领域经常有这种文字或图片垂直滚动的效果,下面就介绍一下文字垂直滚动的javascript代码。 javascript代码如下: &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;h...
2017-03-27
JS中处理时间之setUTCMinutes()方法的使用
javascript Date.setUTCMinutes()方法按照通用时间设置分钟为一个指定日期。 语法 Date.setUTCMinutes(minutesValue[, secondsValue[, msValue]]) 注:括号...
2017-03-24
js面向对象之公有、私有、静态属性和方法详解
现下,javascript大行其道,对于网站开发人员来说,javascript是必需掌据的一门语言,但随着jquery等框架的流行和使用,许多人对于原生javascript缺乏深入的理解,习惯了函数式的编辑风格,对于闭包、原型总是说不清道不...
2017-03-22
回到顶部