DOM 操作
DOM 基础
访问节点
/*
ownerDocument:返回当前节点的根元素(document 对象)
parentNode:返回当前节点的父节点,所有节点都仅有一个父节点
childNodes: 返回当前节点的所有子节点列表
firstChild:返回当前节点的第一个子节点
lastChild:返回当前节点的最后一个子节点
nextSibling:返回当前节点之后的相邻的同级节点
previousSibling:返回当前节点之前的相邻的同级节点
*/
childNodes
var tag = document.getElementsByTagName('ul')[0];
var a = tag.childNodes;
console.log(a); // 包含文本节点
console.log(a[0].nodeType); // 第一个节点类型,3(文本节点)、1 (元素节点)
console.log(a.item(1).innerHTML); // D 表示文档 显示第 2 个节点包含的文本
console.log(a.length); // 7
childNodes
是一个类数组,不能使用数组的方法。
var tag = document.getElementsByTagName('ul')[0];
var a = tag.childNodes;
a = Array.prototype.slice.call(a, 0);
console.log(a); // [text, li, text, li, text, li, text]
文本节点和属性节点都不包含任何子节点,所以它们的 childNodes
属性返回值是一个空集合。可以使用 haschildNodes()
或者 childNodes.length > 0
来判断。
parentNode
返回元素类型的父节点,因为只有元素才能包含子节点。不过 document
节点没有父节点,将会返回 null
。
firstChild
和lastChild
文本节点的 firstChild
和 lastChild
都将返回 null
。
nextSibling
和previousSibling
如果同属于一个父节点的相邻节点,就会返回 null
ownerDocument
表示根节点,node.ownerDocument
等价于 document.documentElement
。
var b = document.documentElement.lastChild;
console.log(b); // <body>
b = document.documentElement.firstChild.nextSibling.nextSibling;
console.log(b); // <body>
操作节点
方法 | 说明 |
---|---|
appendChild() |
向节点的子节点列表结尾添加新的子节点 |
cloneChild() |
复制节点 |
hasChildNodes() |
判断当前节点是否含有子节点 |
insertBofore() |
在指定节点前插入新的子节点 |
normalize() |
合并相邻的 Text 节点并删除空的 Text 节点 |
removeChild() |
删除并返回当前节点的指定子节点 |
replaceChild() |
用新节点替换一个子节点 |
var ul = document.getElementsByTagName('ul')[0];
ul.onclick = function() {
this.style.border = "solid blue 1px";
}
var ul1 = ul.cloneNode(true); // 深度克隆
document.body.appendChild(ul1);
文档节点
访问文档
//访问方法说明
/*
1. document.documentElement 可以访问 html 元素
2. document.doctype 可以访问 doctype,部分浏览器不支持
3. document.childNodes 可以遍历子节点
4. document.firstChild 可以访问第一个子节点,一般为 doctype
5. document.lastChild 可以访问最后一个子节点,如 html 元素或注释
*/
访问特殊元素
文档中很多特殊的元素,使用下面的方法获取,如果获取不到就返回 null
。
/*
1. document.body 可以访问 body 元素
2. document.head 可以访问 head 元素
3. document.defaultView 可以访问默认视图,即所属的窗口对象 window
4. document.scrollingElement 可以访问文档内滚动的元素
5. document.activeElement 可以访问文档内获取焦点的元素
6. document.fullscreenElement 可以访问文档内正在全屏显示的元素
*/
访问元素集合
document
包含一组集合对象,使用它们可以快速访问文档内元素。
/*
document.anchors 返回所有设置了 name 属性的 a 标签
document.links 返回所有设置了 href 属性的 a 标签
document.forms 返回所有 form 对象
document.images 返回所有 images 对象
document.applets 返回所有 applets 对象
document.embeds 返回所有 embeds 对象
document.plugins 返回所有 plugin 对象
document.scripts 返回所有 script 对象
document.styleSheets 返回所有样式表集合
*/
访问文档信息
- 静态信息
/*
document.URL 返回当前文档的网址
document.domain 返回当前文档的域名,不包含协议和端口
document.location 访问 location 对象
document.lastModified 返回当前文档最后修改时间
document.title 返回当前文档标题
document.characterSet 返回当前文档编码
document.referrer 返回当前文档的访问者来自哪里(上一个页面)
document.dir 返回文字方向
*/
- 状态信息
/*
document.hidden 表示当前页面是否可见,如果窗口最小化,切换页面,则为 true
document.visibilityState 返回文档的可见状态,取值包括 visible、hidden、prerender(正在渲染)、unloaded(已卸载)
document.readyState 返回当前文档的状态,取值包括 loading(正在加载)、interactive(加载外部资源)、complete(加载完成)
*/
访问文档元素
/*
getElementById()
getElementsByTagName() 返回所有指定标签名称的元素节点
getElementsByName() 返回所有指定名称(name 属性值)的元素节点
*/
HTMLCollection
对象还包含一个 namedItem()
方法,该方法通过元素的 name
特性取得集合中的项目。
var images = document.getElementsByTagName('img');
var news = images.namedItem('news');
获取页面中所有元素:
var allElements = document.getElementsByTagName("*");
元素节点
遍历元素
使用 parentNode
、nextSibling
、previousSibling
、firstChild
、lastChild
属性可以遍历文档树中任意类型节点,包括空字符(文本节点)。HTML5
新添加 5
个属性专门访问元素节点。
-
childElementCount
返回子元素个数,不包括文本节点和注释。 -
firstElementChild
返回第一个子元素 -
lastElementChild
返回最后一个子元素 -
previousElementSibling
返回前一个相邻兄弟元素 -
nextElementSibling
返回后一个相邻的兄弟元素
创建元素
var p = document.createElement("p");
var info = "nodeName:" + p.nodeName;
info += ", nodeType:" + p.nodeType;
console.log(info); // nodeName:P, nodeType:1
复制节点
var p = document.createElement('p');
var p1 = p.cloneNode(false); // true 会克隆子节点,false 不会克隆子节点
var info = "nodeName:" + p1.nodeName;
info += ", nodeType:" + p1.nodeType;
console.log(info); // nodeName:P, nodeType:1
var p = document.createElement('p');
var h1 = document.createElement('h1');
var txt = document.createTextNode('Hello World');
var hello = txt.cloneNode(false); // 复制文本节点
p.appendChild(txt);
h1.appendChild(p);
document.body.appendChild(h1);
var p = document.createElement('p');
var h1 = document.createElement('h1');
var txt = document.createTextNode('Hello World');
var hello = txt.cloneNode(false); // 复制文本节点
p.appendChild(txt);
h1.appendChild(p);
document.body.appendChild(h1);
var new_h1 = h1.cloneNode(true); // 深层复制
document.body.appendChild(new_h1);
插入节点
在文档中插入节点主要包括两种方法:
appendChild()
:向当前元素的子节点列表的末尾添加新的子节点。insertBefore()
:向已有的子节点前插入一个新的子节点。
删除元素
removeChild(node)
从子节点列表中删除某个节点。返回被删除的节点,如果删除失败,则返回 null
。
封装删除节点函数:
/*
e 表示要删除的节点
*/
function remove(e) {
if(e) {
var _e = e.parentNode.removeChild(e);
return _e;
}
return undefined;
}
封装删除所有子节点的方法:
/*
e 表示预删除所有子节点的父节点
*/
function empty(e) {
while(e.firstChild) {
e.removeChild(e.firstChild)
}
}
替换节点
replaceChild()
可以将某个子节点替换为另一个。替换成功则返回被替换的节点,如果替换失败,则返回 null
。
<div id="red">
<h1>红盒子</h1>
</div>
<div id="blue">
蓝盒子
</div>
var ok = document.getElementById('ok');
ok.onclick = function() {
var red = document.getElementById('red');
var h1 = document.getElementsByTagName('h1')[0];
var h2 = document.createElement('h2');
red.replaceChild(h2, h1); // 替换其包含的所有子节点和所有内容
}
var ok = document.getElementById('ok');
ok.onclick = function() {
var red = document.getElementById('red');
var blue = document.getElementById('blue');
var h1 = document.getElementsByTagName('h1')[0];
red.replaceChild(blue, h1); // 将红盒子中的一级标题替换为蓝盒子
}
找回被替换的节点元素:
var ok = document.getElementById('ok');
ok.onclick = function() {
var red = document.getElementById('red');
var blue = document.getElementById('blue');
var h1 = document.getElementsByTagName('h1')[0];
var del_h1 = red.replaceChild(blue, h1);
console.log(del_h1);
red.parentNode.insertBefore(del_h1, red);
}
文本节点
创建文本节点
document.createTextNode(data);
var element = document.createElement('div');
element.className = "red";
document.body.appendChild(element);
/*
由于 dom 操作的原因,可能会出现文本节点不包含文本,或是接连出现两个文本节点的情况,为了避免这种情况的发生,一般会在父元素上调用 normalize() 方法,删除空文本节点,合并相邻文本节点。
*/
访问文本节点
使用 nodeValue
或 data
属性可以访问文本节点包含的文本,使用 length
属性可以虎丘包含文本的长度,利用该属性可以遍历文本节点中的每个字符。
设计一个读取元素包含文本的通用方法:
<div id="div1">
<span class="red">div</span>
元素
</div>
// 参数 e 表示指定元素
// 返回值:返回包含的所有文本,包括子元素中包含的文本
function text(e) {
var s = "";
var e = e.childNodes || e;
for(var i = 0; i < e.length; i++) {
s += e[i].nodeType != 1 ? e[i].nodeValue : text(e[i].childNodes)
}
return s;
}
var div = document.getElementById('div1');
var s = text(div);
console.log(s); // div 元素
插入 HTML 字符串
创建一个 1000
行的表格
function tableInnerHTML() {
var i, h = ['<table border="1" width="100%">'];
h.push('<thead>');
h.push('<tr><th>id<\/th><th>yes?<\/th><th>name<\/th><th>url<\/th><th>action<\/th><\/tr>');
h.push('<\/thead>');
h.push('<tbody>');
for(var i = 0; i <= 1000; i++) {
h.push('<tr><td>');
h.push(i);
h.push('<\/td><td>');
h.push('And the answer is...' + (i % 2 ? 'yes':'no'));
h.push('<\/td><td>');
h.push('my name is #' + i);
h.push('<\/td><td>');
h.push('<a href="http://example.org/'+ i +'.html">http://example.org/'+ i +'.html<\/a>');
h.push('<\/td><td>');
h.push('<ul>');
h.push('<li><a href="edit.php?id='+ i +'">edit</a><\/li>');
h.push('<li><a href="delete.php?id='+ i +'-id001">delete</a><\/li>');
h.push('<\/ul>');
h.push('<\/td>');
h.push('<\/tr>');
}
h.push('<\/tbody>');
h.push('<\/table>');
document.getElementById('here').innerHTML = h.join('');
}
tableInnerHTML();
替换 HTML 字符串
outerHTMTL
和 innerHTML
var ul = document.getElementsByTagName('ul')[0];
var lis = ul.getElementsByTagName('li');
lis[0].onclick = function() {
this.innerHTML = '<h2>我是一名初学者</h2>';
}
// outerHTML 是直接替换,h2 标签把 li 标签给替换了
lis[1].onclick = function() {
this.outerHTML = '<h2>当然喜欢</h2>';
}
属性节点
属性节点的主要特征值:nodeType
等于 2
,nodeName
等于属性的名称,nodeValue
等于属性的值,parentNode
等于 null
。
属性节点包含 3 个专用属性:
name
:属性名称,等效于nodeName
value
:表示属性值,可读可写,等效于nodeValue
specified
:如果属性值是在代码中设置的,则返回true
,如果为默认值,则返回false
创建属性节点
var element = document.getElementById('box');
var attr = document.createAttribute('align');
attr.value = 'center';
element.setAttributeNode(attr);
// 获取属性
console.log(element.attributes['align'].value); // center
console.log(element.getAttributeNode('align').value); // center
console.log(element.getAttribute('align')); // center
console.log(element.attributes['align'].nodeType); // 2
console.log(element.attributes['align'].nodeName); // align
读取属性值
var red = document.getElementById('red');
console.log(red.getAttribute('id')); // 'red'
// 也支持点语法
console.log(red.id); // 'red'
var blue = document.getElementById('blue');
console.log(blue.getAttribute('id')); // 'blue'
console.log(blue.id); // 'blue'
var label = document.getElementById('label1');
console.log(label.className); // calss1
console.log(label.htmlFor); // textfield
/*
对于 class 属性,必须使用 className
对于 for 属性,必须使用 htmlFor
float 被改名为 cssFloat
text 被改名为 cssText
*/
var red = document.getElementById('red');
// 所有类名生成的数组
var classNameArray = document.getElementById('red').className.split(" ");
console.log(classNameArray); // ["red", "blue"]
设置属性名
var red = document.getElementById('red');
var blue = document.getElementById('blue');
red.setAttribute("title", "这是红盒子");
blue.setAttribute("title", "这是蓝盒子");
使用快捷方式设置属性值:
var label = document.getElementById('label1');
label.className = "class1";
label.className += " class2";
label.htmlFor = "textfield";
定义检测函数,判断元素是否包含指定的类,然后再决定是否添加类。
function hasClass(element, className) {
var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
return reg.test(element.className); // 使用正则检测是否有相同的样式
}
function addClass(element, className) {
if(!hasClass(element, className)) {
element.className += ' ' + className;
}
}
var red = document.getElementById('red');
addClass(red, 'red');
addClass(red, 'blue');
删除属性
removeAttribute(name)
演示如何动态设置表格边框:
window.onload = function() {
var table = document.getElementsByTagName('table');
var del = document.getElementById('del');
var reset = document.getElementById('reset');
del.onclick = function() {
table.removeAttribute('border')
}
reset.onclick = function() {
table.setAttribute('border', '2')
}
}
演示如何定义删除类函数,并调用该函数删除指定类名:
<div id="red" class="red blue bold">
红盒子
</div>
function hasClass(element, className, reg) {
return reg.test(element.className); // 使用正则检测是否有相同的样式
}
function deleteClass(element, className) {
var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
if(hasClass(element, className, reg)) {
element.className = element.className.replace(reg, ' ');
}
}
var red = document.getElementById('red');
deleteClass(red, 'blue');
使用类选择器
<div id="box">
<div class="blue red green">blue red green</div>
</div>
<div class="blue red black">blue red black</div>
/*
使用 document.getElementById('box') 方法先获取 <div id="box"></div>,然后在它下面使用 getElementsByClassName("blue red") 选择同时包含 red 和 blue 类的元素
*/
var divs = document.getElementById('box').getElementsByClassName("blue red");
console.log(divs[0].innerHTML);// blue red green
自定义属性
HTML5
允许用户为元素自定义属性,但要求添加 data-
前缀。
添加自定义属性后,可以通过元素的 dataset
属性访问自定义属性。
<div id="box" data-myid="12345" data-myname="zhangsan" data-mypass="zhang123">自定义数据属性</div>
var div = document.getElementById('box');
// 访问自定义属性值
var id = div.dataset.myid;
var name = div.dataset.myname;
var pass = div.dataset.mypass;
// 重置自定义属性
div.dataset.myid = "54321";
div.dataset.myname = "lisi";
div.dataset.mypass = "lisi543";
// 检测自定义属性
if(div.dataset.myname) {
console.log(div.dataset.myname);
}
文档片段节点
DocumentFragment
是一个虚拟的节点类型,仅存在与内存中,没有添加到文档树中,所以看不到渲染效果。使用文档片段的好处,就是避免浏览器渲染和占用资源,当文档片段设计完善后,在使用 JS
一次性添加到文档树中显示出来,这样可以提高效率。
主要特征值:
-
nodeType
值等于11
-
nodeName
等于#document-fragment
-
nodeValue
等于null
-
parentNode
等于null
每次使用 JS
操作 dom
都会改变页面呈现,并触发整个页面重新渲染(回流),从而消耗系统资源,为了解决整个问题,可以先创建一个文档片段,把所有的新节点附加到文档片段上,最后再把文档片段一次性添加到文档中,减少页面重绘的次数。
<ul id="ul"></ul>
var element = document.getElementById('ul');
var fragment = document.createDocumentFragment();
var browsers = ['Firefox', 'Chrome', 'Opera', 'Safari', 'Internet Explorer'];
console.log(fragment); // #document-fragment
console.log(fragment.nodeType); // 11
console.log(fragment.nodeName); // #document-fragment
console.log(fragment.nodeValue); // null
browsers.forEach(function(browser) {
var li = document.createElement('li');
li.textContent = browser;
fragment.appendChild(li);
});
element.appendChild(fragment)