获取和操作 DOM 节点
DOM 节点也会被称为 DOM 元素。
想要操作 DOM 节点,就必须先到 DOM 节点。
DOM 节点的方式有很多,这里例举几个常用的,所有的 DOM 元素都具有以下:
返回对拥有指定 id 的第对象的引用。
element.getElementById
是指去 element
节点下根据 id 查找子节点。
通常在程序开始前,没有主动去过节点,这个时候会使用根节点 document
来进行查找。
<div id="html-element">
我是元素
</div>
<script>
var element = document.getElementById('html-element');
element.innerHTML = '<a href="//imooc.com">我变成了超</a>';
</script>
在使用 JavaScript 操作 DOM 节点的时候,也会把 DOM 节点称为 DOM 对象
,以契合编程中对象
的概念,更好理解。
以上例子通过 document.getElementById
id 为 html-element
的 DOM 节点,并通过 innerHTML
,将这个节点的进行了。
返回带有指定的对象集合。
element.getElementByName
是通过元素的 name
进行查找的,过去操作表单的时候会经常用到。
<form>
<div>
<label>
<input type="check@R_964_2@" name="skill" checked="checked" value="JavaScript"> JavaScript
</label>
<label>
<input type="check@R_964_2@" name="skill" value="c++"> C++
</label>
<label>
<input type="check@R_964_2@" name="skill" checked="checked" value="Java"> Java
</label>
</div>
</form>
<div id="result"></div>
<script>
var check@R_964_2@es = document.getElementsByName('skill');
var skills = [];
check@R_964_2@es.forEach(function(check@R_964_2@) {
if (check@R_964_2@.getAttribute('checked')) {
skills.push(check@R_964_2@.value);
}
});
document.getElementById('result').innerHTML = '选中的技能:' + skills.join('、');
</script>
通过 getElementsByName
到的是 DOM 节点的集合,需要注意的是,这个集合不是数组类型的,而是 NodeList
,其不具备数组的 map
、filter
等,但是具备 forEach
。
Tips:IE 和早期浏览器的 NodeList 是没有 forEach 的,具体版本可以通过 查看。
返回带有指定名的对象集合。
element.getElementsByTagName
是通过名 DOM 节点的,返回的也是集合。
<div>
<p>我是第段落。</p>
<p>我是第二个段落。</p>
<p>我是第三个段落。</p>
<p>我是第四个段落。</p>
<p>我是第五个段落。</p>
</div>
<div id="result" style="color: #4caf50;"></div>
<script>
var pList = document.getElementsByTagName('p');
var res = [];
var i, len;
for (i = , len = pList.length; i < len; i++) {
res.push(pList[i].innerText);
}
document.getElementById('result').innerHTML = '所有 p 的:<br>' + res.join('<br>');
</script>
此返回值的类型是 HTMLCollection
,不是 NodeList
,没有 forEach
。
可以使用 for 循环对返回值进行遍历。
Tips: 特别要注意,此为 getElements
ByTagName,前往不要忘记有个 s
。
返回包含了所有指定类名的子元素的类数组对象。
element.getElementsByClassName
通过元素的类名来 DOM 节点。
<div>
<div class="odd">1</div>
<div class="even">2</div>
<div class="odd">3</div>
<div class="even">4</div>
<div class="odd">5</div>
<div class="even">6</div>
</div>
<div id="result"></div>
<script>
var odd = document.getElementsByClassName('odd');
var res = [];
var i, len;
for (i = , len = odd.length; i < len; i++) {
res.push(odd[i].innerText);
}
var resultElement = document.getElementById('result');
resultElement.innerHTML = '所有奇数:<br>' + res.join('<br>');
</script>
与 getElementsByTagName
返回值类型相同,此返回类型也是 HTMLCollection
。
Tips:注意,getElements
ByTagName 中也有 s
。同时此也 IE8。
文档对象模型 Document 引用的 querySelector () 返回文档中与指定选择器或选择器组匹配的第 html 元素 Element 。 如果找不到匹配项,则返回 null 。
element.querySelector
是 DOM 节点最常用的之一,可以传入 CSS 选择器来匹配 DOM 节点。
如使用 CSS 在给 id 为 tip
的元素设置红色字体样式的时候,选择器使用的是 #tip
。
#tip {
color: red;
}
使用 element.querySelector
id 为 tip
的元素,传入的参数也是 #tip
,与 CSS 选择器一致。
<div>
<div id="tip">今日大甩卖!!一双袜子 <strong>三</strong> 块!三双袜子只要 <strong>十</strong> 块!!</div>
</div>
<script>
var tip = document.querySelector('#tip');
tip.style.color = 'red';
</script>
通过设置 style
下的 color
,可以颜色。通过 style
设置的样式都是内联样式。
即便传入的选择器能匹配到多个 DOM 对象,此也只会返回 DOM 对象。
返回与指定的选择器组匹配的文档中的元素列表 (使用深度优先的先序遍历文档的节点)。返回的对象是 NodeList 。
此传入的参数与 querySelector
一致,但会返回匹配到的所有 DOM 对象。
<ul>
<li>我是列表1</li>
<li>我是列表2</li>
<li>我是列表3</li>
<li>我是列表4</li>
</ul>
<button class="change">变!</button>
<script>
var lis = document.querySelectorAll('li');
var btn = document.querySelector('.change');
btn.addEventListener('click', function() {
lis.forEach(function(li, index) {
li.innerText = index;
});
});
</script>
element.querySelectorAll
返回的也是 NodeList
。
到目前为止已经做了许多 DOM 操作了,如使用 innerText
文本,使用 style
样式,这些其实都是在操作 DOM。
这里列举两个常用的操作。
节点的 class ,这个操作频率是非常高的。
如使用 class 来控制元素的与隐藏。
<style>
.show {
display: block;
}
.hidden {
display: none;
}
</style>
<p class="tip show">我要早睡早起,不能再熬夜了。</p>
<button class="toggle">切换</button>
<script>
var toggleBtn = document.querySelector('.toggle');
var tipEle = document.querySelector('.tip');
toggleBtn.addEventListener('click', function() {
var className = tipEle.className;
if (className.indexOf('show') > -) {
tipEle.className = 'tip hidden';
return;
}
tipEle.className = 'tip show';
});
</script>
通过 DOM 节点的 className
,来控制 class。
class 也属于这个场景,但使用 className 更为频繁,所以单独拿出来介绍。
节点的许多状态是使用表示的,如复选框
是否选中,就是由 checked
决定。
<label>
<input type="check@R_964_2@" class="check@R_964_2@">
爱我别走
</label>
<div style="margin-top: px;">
<button class="toggle">切换!</button>
</div>
<script>
var check@R_964_2@ = document.querySelector('.check@R_964_2@');
var toggleBtn = document.querySelector('.toggle');
toggleBtn.onclick = function() {
var checked = check@R_964_2@.getAttribute('checked');
if (checked) {
check@R_964_2@.removeAttribute('checked', '');
} else {
check@R_964_2@.setAttribute('checked', 'checked');
}
};
</script>
getAttribute
就可以获得某个的值。
setAttribute
用于给设置值。
removeAttribute
则是将从元素上移除。
这三个可以用于元素的任意, class
。
通过几种 DOM 节点的的返回值可以发现,当要多个 DOM 节点组成的集合的时候,返回的都不是数组。
如使用上述例子对到的所有 DOM 节点 用 filter
进行过滤是会报错的。
这个时候就需要通过一些方式,来获得由 DOM 节点组成的数组。
这里并不是让 DOM 节点的集合去 slice
,而是利用 slice
来将 DOM 节点的集合转化为数组。
<div>
<ul>
<li>i</li>
<li>love</li>
<li>imooc</li>
</ul>
</div>
<script>
var lisColl = document.querySelectorAll('li');
var lisArray1 = [].slice.call(lisColl);
var lisArray2 = Array.prototype.slice.call(lisColl);
var map = function(li) {
return li.innerText;
};
console.log(lisArray1.map(map).join(' ')); // :i love imooc
console.log(lisArray2.map(map).join(' ')); // :i love imooc
</script>
slice
可以接收两个参数,分别是起始下标和结束下标,其作用是浅复制起始下标到结束下标的所有项,然后产生新数组返回,如果不提供参数,则直接浅复制所有项,形成新数组返回。
使用 [].slice.call(类数组)
或 Array.prototype.slice.call(类数组)
即可将类数组转化为数组。
通过在控制台观察 NodeList
和 HTMLCollection
类型,可以发现他们是符合类数组特性的。
所以就可以通过这种方式来转化成数组。
数组的 slice
在执行的时候内部是使用循环来操作数组项的,所以操作类数组不会出现问题,使用 call
将操作的数组指定为传入的伪数组,就达到了将类数组转化为数组的目的。
<div>
<ul>
<li>i</li>
<li>love</li>
<li>imooc</li>
</ul>
</div>
<script>
NodeList.prototype.join = Array.prototype.join; // 提供 join
var lisColl = document.querySelectorAll('li');
lisColl.__proto__.map = Array.prototype.map; // 提供 map
var map = function(li) {
return li.innerText;
};
console.log(lisColl.map(map).join(' ')); // :i love imooc
</script>
通过在 NodeList
原型上提供数组的,就可以直接进行的。
其原理可以参考原型相关章节。
使用 for 循环遍历集合,将每一项放入新数组。
<div>
<ul>
<li>i</li>
<li>love</li>
<li>imooc</li>
</ul>
</div>
<script>
var lisColl = document.querySelectorAll('li');
var lis = [];
var i, len;
for (i = , len = lisColl.length; i < len; i++) {
lis.push(lisColl[i]);
}
var str = lis
.map(function(li) {
return li.innerText
})
.join(' ');
console.log(str); // :i love imooc
</script>
这种方式没有特殊情况通常不会去使用。
Array.from()
从类似数组或可迭代新的,浅拷贝的数组实。(MDN)
Array.from
可以将类数组转化为数组。
var arrayLike = {
: '9',
: '9',
: '6',
: ' bye!',
length: ,
};
var str = Array.from(arrayLike).join('');
console.log(str); // :996 bye!
该由 ES2015
提供,所以旧版的浏览器。
扩展运算符也是由 ES2015
提供的。
<div>
<ul>
<li>i</li>
<li>love</li>
<li>imooc</li>
</ul>
</div>
<script>
var lisColl = document.querySelectorAll('li');
var arr = [...lisColl];
console.log(Array.isArray(lisColl)); // :false
console.log(Array.isArray(arr)); // :true
</script>
...
即扩展运算符,根据使用场景,他还能被作为剩余参数操作符使用。
通过 Array.isArray
可以判断值是不是数组。
当节点的没有匹配到任何元素的时候,是可能返回 null 或者 空集合的。
var el = document.querySelector('#dfsafds');
var elList = document.querySelectorAll('.dfsafds');
el.innerHTML = '<p>我写的从来不会报错!</p>';
elList[].innerHTML = '<p>我写的从来不会报错!</p>';
// Cannot set property 'innerHTML' of null
碰到这种情况,上述就报错了,假如后面存在渲染逻辑,则不会再继续执行,最后换来一份 辞退报告
。
所以在没有把握的情况下一定要进行空判断。
var el = document.querySelector('#dfsafds');
if (el) {
el.innerHTML = '<p>我写的从来不会报错!</p>';
} else {
console.log('节点还没渲染出来');
}
或者使用 try ... catch ...
。
var el = document.querySelector('#dfsafds');
try {
el.innerHTML = '<p>我写的从来不会报错!</p>';
} catch (err) {
console.error(err);
console.log('节点还没渲染出来');
}
操作 DOM 是前端程序员的基本功,也是编写网页的重要知识之一。
DOM 节点的有很多,部分返回的是 NodeList
或 HTMLCollection
类型,而不是数组,不能像操作数组一样操作这些集合,转换成数组可以更方便的利用数组的原生对其进行操作。
操作节点的时候,特别是动态渲染的节点,需空判断,防止程序报错中断执行。