灏天阁

DOM 操作

· Yin灏

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

  • firstChildlastChild

文本节点的 firstChildlastChild 都将返回 null

  • nextSiblingpreviousSibling

如果同属于一个父节点的相邻节点,就会返回 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("*");

元素节点

遍历元素

使用 parentNodenextSiblingpreviousSiblingfirstChildlastChild 属性可以遍历文档树中任意类型节点,包括空字符(文本节点)。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() 方法,删除空文本节点,合并相邻文本节点。
*/

访问文本节点

使用 nodeValuedata 属性可以访问文本节点包含的文本,使用 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 字符串

outerHTMTLinnerHTML

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 等于 2nodeName 等于属性的名称,nodeValue 等于属性的值,parentNode 等于 null

属性节点包含 3 个专用属性:

  1. name:属性名称,等效于 nodeName
  2. value:表示属性值,可读可写,等效于 nodeValue
  3. 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)

- Book Lists -