DOM属于Web API的一部分,Web API中定义了很多对象,通过这些对象可以完成对网页的各种操作(添加/删除元素、发送请求,操作浏览器等等)。
DOM全称Document Object Model(文档对象模型),文档就是HTML网页文档,对象表示将网页中的每一个部分都转化为了一个对象,这样就可以通过面向对象的方式去操作网页,想要操作哪个元素就获取哪个元素的对象,然后通过调用其方法或属性完成各种操作。模型是表示对象之间的关系,比如父子元素、祖先后代,兄弟元素等,明确关系后我们便可以通过任意一个对象去获取其它的对象。
- 通过浏览器提供的方法可以操作XML和HTML元素,但是无法操作CSS样式表。
- 我们所看见的改变样式其实是因为DOM操作了标签的
style
属性,增加了内联样式。
DOM对象是一种宿主对象(Host Object),由浏览器(宿主)提供,而不是ECMAScript。
JavaScript三种对象
本地对象 Native Object
Object
Function
Array
String
Number
Boolean
(包装类)Error
EvalError
SyntaxError
RangeError
ReferenceError
TypeError
URIError
Date
RegExp
内置对象 Built-in Object
Global对象,也就是全局对象,它具有一些内置方法和属性。
- 方法:
isNaN()
parseInt()
Number()
decodeURI
encodeURI()
- 属性:
Infinity
NaN
undefined
Math对象
宿主对象 Host Object
宿主对象是指执行JS脚本的环境提供的对象,比如浏览器对象,不同浏览器或版本会涉及到兼容性问题。
浏览器对象具体分为:
window
(BOM)对象,它是全局对象的一个属性,指向全局对象本身。document
(DOM)对象 它是BOM的一部分,它是有w3c标准的。
DOM结构
DOM结构就是HTML结构。
下面这张图是DOM结构树,暂时可以理解为Node函数是DOM树的顶点。
javascript
console.log(document.__proto__ === HTMLDocument.prototype); // true
console.log(HTMLDocument.prototype.__proto__ === Document.prototype); // true
节点
之前提到在DOM标准下,网页中的每一个部分都可以转换为对象,这些对象有一个共同的称呼叫做节点。节点本质上是从HTML中选出指定部分经过构造函数实例化(new HTMLParagraphElement()
)形成的,所以说DOM节点都是对象。
元素节点只是节点的一种,元素节点也叫DOM
元素。
节点具体有以下几种类型,并且它们有对应的代表值(nodeType
)用于区分类型:
- 元素节点 - 1 HTML文档中的HTML标签
- 属性节点 - 2 元素的属性
- 文本节点 text - 3 HTML标签中的文本内容
- 注释节点 comment - 8
- 文档节点 document - 9 整个HTML文档
- DocumentFragment - 11
获取节点
要使用DOM操作网页,我们需要浏览器至少先给我们一个对象,才能去完成各种操作。而document
对象就是我们可以直接使用的一个对象,它是一个全局变量代表整个文档,整个网页。注意document是window对象得一个属性,window对象代表整个浏览器窗口。
document
可以使用原型链中的诸多属性和方法,这些属性和方法就是DOM操作的基石。
-
getElementById()
方法 通过元素的id属性获取一个元素节点。 -
getElementsByName()
方法 通过元素的name
属性获取一组实时更新的元素节点集合。
但要注意的是有些方法在Element.prototype
中也有,意味着有些方法,元素节点对象也可以调用,比如以下四种方法。
-
getElementsByTagName()
方法 通过元素标签名获取一组实时更新的元素节点集合,可以配合*
选出所有元素。 -
getElementsByClassName()
方法 通过元素类名获取一组实时更新的元素节点集合。(IE8
及以下不支持)
-
querySelector()
方法 获取第一个元素(多个同名元素)节点。(IE6
及以下不支持) -
querySelctorAll()
方法 获取一组元素节点集合。(IE6
及以下不支持)
query系列方法
query
系列方法是通过CSS
选择器来查询元素节点对象,可用性很强。比如以下代码中直接用父子选择器选出想要的元素。
xml
<div>
<h1>
<p>123</p>
</h1>
</div>
<div>
<p>456</p>
</div>
<script>
var div1 = document.querySelector('div > p');
console.log(div1); // <p>456</p>
</script>
query
系列方法的性能不如get
系列,而且不会实时更新节点集合,无法进行一些操作比如remove
。
- 当我们使用
get
系列方法,然后对获取的元素集合进行remove
操作时,会发现节点集合更新了。
xml
<div>123</div>
<div>456</div>
<div>789</div>
<script>
var divs = document.getElementsByTagName('div');
console.log(divs); // [div, div, div]
divs[0].remove();
console.log(divs); // [div, div]
</script>
- 而当我们使用
query
系列方法,然后对获取的元素集合进行remove
操作时,节点集合没有更新。
xml
<div>123</div>
<div>456</div>
<div>789</div>
<script>
var divs = document.querySelectorAll('div');
console.log(divs); // [div, div, div]
divs[0].remove();
console.log(divs); // [div, div, div]
</script>
body和head元素节点
body
和head
也是元素节点,并且它们是HTMLDocument.prototype
中的属性,但是不能直接访问。可以通过其实例对象document
进行访问,所以就可以通过以下方法访问。
ini
var body = document.body;
var head = document.head;
documentElement
也是Document.prototype
中的属性,但是不能直接访问,会报错。不过可以通过其实例对象进行访问,获取的是html根元素节点。
ini
var html = document.documentElement;
document.title
获取的是title元素节点的文本内容。
节点实例化
当我们通过上面那些方法获取节点的时候,实际上中间还有一个构造函数实例化的过程(它是一个底层的内部原理),先是获取到元素,然后进行构造函数实例化。
比如以下代码的内部执行过程,是先获取到div
元素,然后经过HTMLDivElement()
实例化生成一个div
对象放在内存中,最后放入DOM
结构中形成一个div
节点。
xml
<div></div>
<script>
var div = document.getElementsByTagName('div')[0];
</script>
内部原理可以大概理解为如下:
javascript
function getElementsByTagName(element){
// 1. 从HTML中将元素选出来
// 2. 将元素进行 new HTMLDivElement(){} -> DOM对象
}
节点属性
nodeName
属性 返回当前节点的节点名称,是只读属性。
- 元素节点的nodeName是大写形式,文档节点和文本节点的nodeName是
#document
和#text
。 toLocaleLowerCase()
方法 将字符串变为小写。toUpperCase()
方法 将字符串变为大写。
css
var div = document.getElementsByTagName('div')[0];
console.log(div.nodeName);
// 'DIV'
var nodeName = div.nodeName.toLocaleLowerCase();
console.log(nodeName);
// 'div'
nodeValue
属性 返回或设置当前节点的值,是可写属性。
- 元素节点没有
nodeValue
属性。但是元素一般都有其它属性,比如class
、id
、value
等,这些属性是可以直接访问的,毕竟元素也是对象,对象的属性可以直接通过点.
语法就可以访问。要注意因为class
是关键字,所以需要通过className
属性访问class
。
xml
<button id="aaa" class="btn" value="ccc">点我</button>
<script>
var btn = document.getElementsByTagName("button")[0];
addEvent(btn, 'click', function(){
console.log(btn.id); // aa
console.log(btn.className); // btn
console.log(btn.value); // ccc
console.log(btn.innerHTML); // 点我
});
</script>
- 属性、文本、注释节点有
nodeValue
属性,它们返回的分别是属性值、文本内容、注释内容。 - 获取属性节点可以使用
getAttributeNode()
方法(不常用)。
ini
var demo = document.getElementsByTagName('div')[0];
var demoid = demo.getAttributeNode('id');
console.log(demoid.nodeValue); // cc
nodeType
属性 返回的是该节点的类型,是只读属性。可用于封装遍历子元素节点
的方法。
ini
var div = document.getElementsByTagName('div')[0];
function elementChildren(node){
var arr = {
'length': 0,
'push': Array.prototype.push,
'splice': Array.prototype.splice
},
len = node.childNodes.length;
for(var i = 0; i < len; i++){
var childItem = node.childNodes[i];
if(childItem.nodeType === 1){
// 可以用属性添加的方式,注意这种方式要手动增加length值。
// arr[arr['length']] = childItem;
// arr['length']++;
// 也可以用继承的push方法,这种方式会自动更新length值。
arr.push(childItem);
}
}
return arr;
}
console.log(elementChildren(div));
attributes
属性 返回该元素所有属性节点的一个实时集合*(该属性不常用)。
xml
<div id="demo" class="box ">
</div>
<script>
var div = document.getElementsByTagName('div')[0];
console.log(div.attributes); // NamedNodeMap {0: id, 1: class, id: id, class: class, length: 2}
console.log(div.attributes[0]); // id="demo"
</script>
parentNode
属性 返回指定节点的父节点。
xml
<ul>
<li>
<h2>我是h2标题</h2>
<a href="">我是超链接</a>
<p>我是段落</p>
</li>
</ul>
<script>
var a = document.getElementsByTagName('a')[0];
console.log(a.parentNode); // <li>...</li>
</script>
childNodes
属性 返回指定节点的所有类型的子节点的集合(实时更新)。IE8
及以下版本不会把空白文本当作子节点。
xml
<ul>
<li>
<!-- 我是注释 -->
<a href="">我是超链接</a>
<p>我是段落标签</p>
<h2>我是标题标签</h2>
</li>
</ul>
<script>
var li = document.getElementsByTagName('li')[0];
console.log(li.childNodes.length); // 9
console.log(li.childNodes); // [text, comment, text, a, text, p, text, h2, text]
</script>
firstChild
属性和lastChild
属性 分别返回指定节点的第一个子节点,最后一个子节点。
nextSibling
属性 previousSibling
属性 分别返回指定节点的下一个兄弟节点,上一个兄弟节点。
元素节点属性
parentElement
属性 返回指定节点的父元素节点(IE9
及以下不支持)。
children
属性 返回指定节点的所有子元素节点(IE7
及以下不支持)。
childElementCount
属性 返回指定节点的所有子元素节点的长度(IE9
及以下不支持),相当于children.length
。
firstElementChild
和lastElementChild
属性 分别返回指定节点的第一个子元素节点和最后一个子元素节点 (IE9
及以下不支持)。
nextElementSibling
和previousElementSibling
属性 分别返回指定节点的下一个兄弟元素节点,上一个兄弟元素节点(IE9
及以下不支持)。
innerHTML
属性 用于获取或修改元素内部的HTML代码。
innerText
属性 用于获取或修改元素内部的文本内容(会考虑CSS样式)。
textContent
属性 用于获取或修改元素内部的文本内容(不考虑CSS样式)。
节点方法
节点中的方法基本就用于节点的增删改查,注意之前提到的innerHTML
属性也可以实现节点的增删改查,但是它有一个弊端,会更新所有的节点,意味着之前如果绑定了处理函数,则更新后会失效。所以一般都会配合以下方法使用。
创建和添加节点
document.createElement('div')
方法 创建元素节点,注意此时的元素节点不在DOM中,是在内存中。
ini
var div = document.createElement('div');
div.innerHTML = 123;
document.body.appendChild(div);
document.createTextNode('hellworld')
方法 创建文本节点。
ini
var text = document.createTextNode('helloworld');
document.body.appendChild(text);
document.createComment('我是注释')
方法 创建注释节点。
ini
var comment = document.createComment('我是注释');
document.body.appendChild(comment);
document.createDocumentFragment()
方法 创建一个文档片段。该文档片段存在于内存中,而不是在DOM树中。可用于创建列表类元素时存储,优化系统。一般命名时首字母是o
,o表示object,是一个对象。
ini
var oUl = document.getElementById('list');
var oFrag = document.createDocumentFragment('div');
for(var i = 0; i < 10000; i++){
var oli = document.createElement('li');
oli.innerHTML = i + '这是第' + i + '个项目';
oli.className = 'list-item';
oFrag.appendChild(oli);
}
oUl.appendChild(oFrag);
Node.appendChild(child)
方法 在指定父节点中的末端插入新的子节点,底层原理是对节点进行剪切然后粘贴到指定父节点中的末端,该方法存在于Node.prototype
中。
Node.insertBefore(newchild, originchild)
在指定父节点的指定子节点之前插入新的子节点。
ini
var div = document.getElementsByTagName('div')[0];
var p = document.getElementsByTagName('p')[0];
var a = document.createElement('a');
a.href = '';
div.insertBefore(a, p);
Node.insertAdjacentElement(position, element)
方法 可以向元素的任意位置添加元素。第一个参数表示相对于该元素的位置,第二个参数是要插入的元素。第一个参数必须是以下字符串之一:
beforebegin
: 在该元素本身的前面。afterbegin
: 只在该元素当中,在该元素第一个子元素前面。beforeend
: 只在该元素当中,在该元素最后一个子元素后面。afterend
: 在该元素本身的后面。
Node.inserAdjacentHTML()
方法 可以向元素的任意位置添加DOM结构。
删除和替换节点
Node.removeChild(child)
方法 指定父节点删除子节点。删除的节点只是从DOM中移除,依旧存在于内存
中,并未销毁。
Node.remove()
方法 删除当前节点,节点完全销毁。
Node.replaceChild(new, origin)
方法 节点替换。
css
<div>
<h1>我是h1</h1>
<h4>我是h4</h4>
</div>
<script>
var div = document.getElementsByTagName('div')[0],
h1 = document.getElementsByTagName('h1')[0],
h2 = document.getElementsByTagName('h4')[0];
div.replaceChild(h2, h1);
</script>
Node.replaceWith(new)
方法 节点替换。
Node.hasChildNodes()
方法 返回一个布尔值,表明当前节点是否包含有子节点。
元素节点的方法
Element.setAttribute(name, value)
方法 设置指定元素属性。
less
var div = document.getElementsByTagName('div')[0];
div.setAttribute('id', 'box');
Element.getAttribute()
方法 获取指定元素的属性。
ini
var div = document.getElementsByTagName('div')[0];
var attr = div.getAttribute('class');
Element.removeAttribute()
方法 删除指定元素的属性。
HTML5
data-*
属性 元素设置自定义属性
xml
<p data-name="唐" data-age="18">helloworld</p>
<script>
var p = document.getElementsByTagName('p')[0];
</script>
- 获取自定义属性可以用
dataset
或者getAttribute()
arduino
// p.getAttribute('data-name');
// p.dataset.name;