概念
DOM 定义了访问 HTML 和 XML 文档的标准,同时它也是一个编程接口。DOM 早期针对于 XML 后来经过扩展后可用于 HTML,它把页面映射为一个多层节点结构,并提供方法、属性使得程序可以对该结构进行访问及修改,从而改变文档对应节点的样式和内容。
简单来说,浏览器在显示页面的时候需要解析文档,而脚本语言也需要获取这份文档,但是它们最终需要获取到的信息是不同的,所以 DOM 相当于一个桥梁,沟通了脚本语言和浏览器。所以当我们修改这个由 DOM 构建出的对象时,页面也会发生相应的变化。
如果将一个web页面看成一个文档,由于不同的浏览器有各自的 DOM 标准,所以浏览器在解析页面的时候会映射出不同的 DOM 树模型,从而导致在操作、访问页面节点上存在差异。例如IE中所有的DOM对象都是以 COM 对象的形式实现的,所以和其他浏览器实现的DOM对象行为不同。
注:SVG、MathML、SMIL都是基于XML的语言,它们都发布了针对了自己的 DOM 标准
产生原因
W3C 组织为了防止 Netscape 公司和微软按自己意愿开发导致浏览器兼容困难情况发生。规划了 DOM 标准,Web 浏览器在 DOM 标准出现一段时间后才开始实现它,现在的浏览器的首要目的就是支持 DOM,有了标准就对开发者更加的友好了。
同时需要注意的是 DOM 不仅针对 JavaScript,其他语言也实现了DOM。
W3C DOM标准
- 核心 DOM-适用于所有文档类型的标准模型
- XML DOM-XML 文档的标准模型
- HTML DOM-HTML 文档的标准模型
核心DOM和html DOM的区别
核心DOM
-
对象:Document、Node、ElementNode、TextNode、AttributeNode、CommentNode、NodeList
-
接口:createElement、appendChild、setAttribute等
-
接口分类:
- 视图接口:跟踪不同文档的接口(添加CSS前和后)
- 事件接口:定义了事件和事件处理
- 样式接口:定义了基于CSS的接口
- 遍历和范围接口:定义了遍历和操作文档树的接口
- ...
-
提供了所有文档类型增删改查操作,通过对象提供的接口修改 DOM 树及其代表的文档。
html DOM
-
对象:Image、Table、Form、 Input 等
-
接口:createElement、appendChild、setAttribute等
-
只提供了HTML增删改查操作,通过将 HTML 类型的封装,来简化和对象对应DOM的访问和操作。
var img = new Image(); img.src = 'xxxx'; img.style.width = '100px';
总结:他们都为指定的文档类型提供了对象、属性、方法和事件。只不过核心DOM提供的是文档基本类型的扩展,HTML DOM提供的只是HTML类型的扩展。
节点层次
DOM树结构由一个个的节点组成,节点又分为不同的类型,不同的类型有其特有的行为和特征。每个节点都有包含着自己的信息和接口等,由于节点和节点间有一定的关系,可以形成了层次关系,最终的体现就是以HTML元素即文档元素为根节点的树状结构。
Node类型
JavaScript 中所有的节点类型都是继承于Node 类型,所以所有的节点都共享着相同的基本属性和基本方法。
节点关系
- childNodes 是一个类数组,可以通过 slice 方法进行转换也可以使用 ES6 中的扩展运算符进行转换。
- 关系指针都只是可读的
- hasChildNodes()可以判断该节点是否包含一个或以上的子节点,如果是那么返回 true。
- 如果没有子节点 fristChild 和 lastChild 都为 null,如果只有一个节点那么这俩值指向同一个节点。
节点操作
-
由于关系指针都是可读的,所以 DOM 提供了操作节点的方法。操作节点的时候,节点的信息以及和其他节点的关系也会相应进行更新。
-
插入节点
- appendChild() 是最常用的节点插入方法,它将子节点插入到childNodes列表的最后。如果插入的节点已经在之前存在过,那么该节点将从之前的位置移除,在当前childNodes 列表最后进行添加。这是由于同一个节点不能出现在文档的多个位置上。
- insertBefore(insert,refer) 可以将节点插入到任意位置,其参数为插入的节点以及参考的节点,最终插入的节点会在参考节点前面,如果参考节点为null,那么相当于appendChild()
-
替换节点
- replaceChild(insertNode,replaceNode)可以将被替换的节点替换成指定的节点(insertNode)。
-
移除节点
- removeChild() 移除节点,并将要被移除的节点作为返回值 。
-
克隆节点
- cloneNode( boolean),接受一个boolean值,如果为true那么就是深拷贝节点。深拷贝节点会把该节点和其下面节点及关系进行拷贝,浅拷贝只会拷贝当前节点。需要注意的是,cloneNode 不会拷贝节点的节点中 JavaScript 属性,例如事件处理程序等。但是在 IE 中该方法会拷贝事件处理程序,所以在拷贝之前最好先移除事件。
-
优化文本节点
- normalize() 如果对节点使用该方法,那么当该节点下所有子节点出现,文本节点为空或者有 2 个连续的子节点时会进行 删除或和并文本节点的操作。
Element 类型
Element类型用于表现 XML 、HTML元素,提供了对元素标签名 (TagName)、子节点、特性的访问。也是继承于Node类型。同时在HTML中标签名始终以全部大写的形式显示,在XML中标签名和源代码中相同。
if(element.tagName.toLowerCase() == 'div'){
// 这样可以适用 HTML 和 XML 形式。
}
获取标签名
获取标签名可以使用 ele.tagName 或者是 ele.nodeName ,二者返回的值都是相同的。
标准特性
HTMLElement类型继承于Element,所有的HTML元素都是由该类型或者其子类型构建。下面的标准特性就是Element特有的特性。他们都可以通过元素访问特定属性进行访问或修改。
div.id = test;
div.id //test;
-
id 文档中的唯一标识符
-
title 有关元素的附加说明信息,一般会通过提示条显示出来
-
lang 元素内容的语言代码,很少使用
-
dir 语言的方向,默认左到右 ‘ltr’ ,可以修改为右到左 ‘rtl’ 。很少使用
-
className 和元素的 class 特性对应,可以为元素指定 CSS 类。
-
childNodes和children的区别
-
childNodes返回元素、文本甚至是注释
-
children只返回元素,不返回文本。
<div id="outer"> text1 <div>1</div> <div>2</div> <div>3</div> text2 </div>var dom = document.getElementById('outer'); console.log(dom.children)// HTMLCollection(3) //0: div //1: div //2: div console.log(dom.childNodes)// NodeList(7) //0: text //1: div //2: text //3: div //4: text //5: div //6: text
-
特性和属性的区别
-
特性(attribute )
特性是 HTML 元素节点自带的,浏览器在解析 HTML 文档时,会将标准的特性转换成属性,从而使得脚本程序也可以通过属性来访问到这些特性(id、title、class等)。
<div id = 'test'><div>div.id //由于标签id 为test,所以DOM 对象就会有 body.id="test"。 div.id = 'test1'; div.id //test1;修改属性会导致特性修改。-
自定义特性
除了标准的特性,还存在自定义特性,需要注意的是自定义特性需要加上
data-的前缀。这是由于如果我们直接使用了非标准的特性(不加data-),以后标准更新后可能会导致冲突!
<div id = 't' data-test="666"></div>let elem = getElementById('t'); elem.getAttribute('test') // 666,此时添加到DOM的特性中可以访问到 elem.dataset.test //test,可以在dataset中获取到自定义特性 elem.test //undefined,自定义特性不会添加到属性中-
方法
-
elem.hasAttribute(name)— 检查特性是否存在。 -
elem.getAttribute(name)— 获取这个特性值。 -
elem.setAttribute(name, value)— 设置这个特性值。 -
elem.removeAttribute(name)— 移除这个特性。
-
-
-
属性(property )
- 每个对象都可以有属性,DOM对象内置了很多属性可以任意的修改,当然暂时不考虑使用Object.defineProperties修改属性的权限。所以DOM对象也可以自由操作属性(此时只是对象)。
-
特殊特征
-
style
- 特性值访问的时候包含的是CSS文本
- 通过属性访问则会返回一个对象
-
事件处理程序(onclick)
- 通过特性访问获取到的是字符串
- 通过属性访问获取到的是函数
-
| 属性 | 特性 | |
|---|---|---|
| 类型 | 任何值,标准的属性具有规范中描述的类型 | 字符串 |
| 名字 | 名字(name)是大小写敏感的 | 名字(name)是大小写不敏感的 |
在大多数情况下,最好使用 DOM 属性。仅当 DOM 属性无法满足开发需求,并且我们真的需要特性时,才使用特性,例如:
- 我们需要一个非标准的特性。但是如果它以
data-开头,那么我们应该使用dataset。 - 我们想要读取 HTML 中“所写的”值。对应的 DOM 属性可能不同,例如
href属性一直是一个 完整的 URL,但是我们想要的是“原始的”值。
元素关系
类似于Node节点之间的关系,只不过当前关系只包含元素没有text节点、注释节点等其他节点。
Document类型
在浏览器中document是HTMLDocument对象的一个实例,同时HTMLDocument对象是继承自 Document 对象。 实际上docuemnt 表示的是 HTML页面,同时它也是 window 的一个属性,所以可以通过全局对象来访问。通过 document 可以获取到浏览器文档中所有节点的数据(包括方法)、以及可以操作文档。
文档信息
以下几个信息都是 Document 对象没有的,都和网页请求有关,这些所有的信息都存在 HTTP 头部 ,只不过通过这些属性可以在Javascript中访问到它们。
-
document.URL:URL格式为 <协议>://<域名>:<端口>/<路径>
-
document.domain:可以获取到当前页面的域名。域名的设置只能越来越松散,即限制条件越来越低。
可以使用document.domain实现跨域,但是前提是必须是同一个基础域名,且协议和端口都得一致。Javascript 出于对安全的考虑,所以不同域的页面不能互相操作。
此时如果存在基础域名和端口都相等的URL,url1 为
a.test.com,url2 为b.test.com, 此时如果url1中的 home.html页面需要访问到url2 中的about.html页面且操作about.html页面,此时是无法直接操作的。如果需要操作就需要在url1 和 url2 中分别设置document.domain 为 test.com。 此时两个页面同域,所以可以互相访问和修改。 -
document.referrer: 保存着链接到当前页面的 URL,如果没有来源页面可能为空。
-
document.tiltle:可以获取或者修改当前页面的标题,页面的标题会在修改后直接更新。
查找元素
-
getElementById 会返回第一个 id 为指定值的元素且区分大小写,但是需要注意如果是在 IE8 及更低版本时,不区分大小写。在 IE7 中调用该方法时会把第一个 name属性为该值或者第一个 id 为该值的元素进行返回.
<input name="test" type="text" value="Just Test"></input> <div id="test"></div> //document.getElementById('test') 返回input -
getElementsByTagName() 可以获取到指定节点为该标签的所有子节点。子节点的集合即 HTMLCollection 对象。如果需要查看到文档中所有的元素,可以通过getElementsByTagName(”*“); HTMLCollection 对象有一个nameItem方法,可以返回符合当前 name 为指定值的元素。
-
getElementsByClassName 方法返回文档中所有指定类名的元素集合,作为 NodeList 对象。同时可以同时查找多个类
var nodeList = document.getElementsByClassName("className1 className2"); -
getElementsByName() 方法可返回带有指定名称的对象的集合
-
querySelector();返回与该模式匹配的第一个元素,可兼容到CSS2
-
querySelectorAll();返回与该模式匹配的所有元素,底层类似于对一组元素的快照,而不是不断的动态查询(损耗性能 )
-
document.anchors:包含文档中所有带那么特性的元素。
-
document.forms:包含文档中所有的
元素,与document.getElementByTagName("form")的到的结果相同。
-
document.images:包含文档中所有的
元素,与document.getElementByTagName("img")的到的结果相同。
-
document.links:包含文档中所有带href特性的元素。
理解DOM操作的关键,就是理解DOM对性能的影响。DOM操作往往是 JavaScript 程序中开销最大的部分。由于NodeList是”动态的“,所以每次访问它的时候都会进行一次新的查询,最好避免的方法就是少访问和减少 DOM 操作。
-----高程
常用扩展
classList
在操作类名的时候可以通过元素的classList属性对类名进行添加,删除或者替换,实际上是直接操作className的语法糖,代表着当前元素的集合,其兼容性如图所示。
- 方法
- add(value) 添加类名
- contains(value) 判断是否包含类名
- remove(value) 移除类名
- toggle(value) 转换类名,如果该元素没有这个类名就添加,如果有就删除这个类名。
焦点管理
-
获得焦点的办法
- 页面加载
- 用户输入
- focus方法
var btn = document.getElementsByTagName('button')[0]; btn.focus() -
获取当前焦点元素
- activeElement() 在文档加载期间,其返回值为null,页面加载成功后为body
-
判断当前文档内节点是否有焦点
- hasFocus() 表明当前文档或者当前文档内的节点是否获得了焦点。该方法可以用来判断当前文档中的活动元素是否获得了焦点。
参考文档:
1、特性和属性(Attributes and properties)
2、《JavaScript 高级程序设计》