JavaScript基础教程之querySelectorAll( )方法遇到的问题

时间:2022-06-02 14:40:27

Write By Monkeyfly

以下内容均为原创,如需转载请注明出处。

一、前提

最近这两天一直在学习JavaScript的事件委托(或称事件代理),用了两天的时间看完了这篇《js中的事件委托或是事件代理详解》博客,并且将博主提供的案例手动敲了两边,可以说是基本掌握了事件委托的原理和用法。这篇博客对于JavaScript的事件委托的讲解还是比较通俗易懂,详细到位的,在这里向大家推荐一下,感兴趣的小伙伴们可以自行去里面学习。

下面就来说一说,我在学习过程中碰到的问题。

问题描述(简单明了)

如果刚开始你是用querySelectorAll()方法获取到的元素,然后再利用JavaScript的DOM操作方法进行动态添加元素的话,此时,你要是获取元素列表的长度就会发现长度根本没有变化,还是未添加元素之前的长度。虽然createElement()方法确实成功的在页面添加了元素,但是实际上这个元素列表并没有进行实时的更新,刚才添加的新元素没有被放入最开始的元素列表中,还是独自一人在外面徘徊,孤苦伶仃。

问题产生的原因

主要有以下3点:

  1. 我在写代码的过程中,并没有参照作者的代码同步去写,而是用了前几天刚熟悉的,还没有完全掌握的新方法去写的。即 querySelectorAll()。

  2. 对于这个方法,我只知其然,并不知其所以然。为什么呢?因为我连JavaScript基础教程的书到现在现在还没看完,底层的实现根本没机会去接触或者了解。只知道有这么个方法,需要用的时候拿来用就行了。

  3. 我自己写了好几组ul列表并给了不同的id,所以存在好多个li元素,而querySelectorAll()方法可以利用CSS选择器进行精准的定位到所要获取的ul列表中的li元素。但是getElementsByTagName()方法还要关心下标中 i 的取值,必须要知道当前要获取的ul列表中的li元素是整个document文档的第几个li,数起来比较麻烦,因为我只想给当前的一组li元素注册事件,不想关心其他的li元素,于是就放弃了这个方法。

其实不用纠结那么多,哪个好用,哪个顺手就用哪个。

1、querySelectorAll()方法介绍

先来看看语法:

elementList = document.querySelectorAll(css selectors);

说明:

在利用document.querySelectorAll(selectors)这个方法获取元素时,我们都知道它是HTML5新增的DOM操作的方法, 会返回一个元素的集合,而且这个集合可以利用数组的形式来操作,使用起来非常的方便。但实际上得到集合并不是一个真正意义上的数组,只能说它是一个“伪数组”(或称“类数组”),至于原因呢我会在下面告诉大家。这个方法其实和getElementsByTagName()方法很类似,返回的都是一个伪数组,大家可以对比着记忆。

其实呢,我还是对querySelectorAll( )这个方法的本质没有搞明白,只知道它是获取元素的方法之一,并且是通过CSS选择器的方式来获取元素的。

何为类数组或者伪数组?

querySelectorAll( )这个方法可以根据用户指定的选择器来返回文档中的所有匹配的元素,所以它返回的是一个元素的列表,相当于一个元素的集合,我们可以称之为“类数组”,或者“伪数组”。

问:为什么这么说呢?
答:就是因为它可以利用数组的某些特性,但是并不具备数组所拥有的方法。

类数组的特性:
(1)可以使用数组的length属性;
(2)可以利用数组的下标形式进行一些操作;

2、getElementsByTagName()方法介绍

语法:

document.getElementsByTagName(tagname)

说明:

它也是获取元素的一种方法,利用标签名来获取元素,返回的同样是一组元素,我们称之为“类数组”(即元素的集合),既可以使用数组的length属性,也可以使用数组的下标形式进行操作。

前面除了可以接document外,还可以接其他DOM元素。可以操作用JavaScript动态创建的DOM元素;但是getElementById()方法前面只能接document,就不能操作用JavaScript动态创建的DOM元素。

  • getElementsByTagName()方法返回元素的顺序是它们在文档中的顺序。
  • 如果把"*"传给 getElementsByTagName()方法,它将返回文档中所有元素的列表。

这个方法平时在代码中很常见,遇见的几率比较大,也比较大众化,基本人人都会使用这个方法。在学DOM操作的时候,这个方法是重点内容必须得掌握。

二、情景还原

我觉得图文并茂才是解决问题的最好办法,那么多文字谁愿意去看呀,要是我,我肯定也不乐意。下面就让我们开始吧。

静态页面

JavaScript基础教程之querySelectorAll( )方法遇到的问题

HTML代码

<div id="box5">我是div5
<button id="btnAdd">添加元素</button>
<button id="btnRemove">删除元素</button>
<ul id="list3">我是ul3
<li>我是li1</li>
<li>我是li2</li>
<li>我是li3</li>
</ul>
</div>

CSS代码(可直接跳过,纯粹是为了更直观的看到各元素所占的区域,方便观察事件委托的相关事件操作)

<style>
html,body{
margin: 0;
padding: 0;
}

div:not(:nth-of-type(2)){
margin:0;
width:500px;
height:100%;
background-color: #f40;
margin-top: 50px;
}

/*ul默认有外边距*/
ul{
width: 300px;
height: 100%;
background-color: #BDBDBD;
list-style: none;
/*清除默认边距*/
margin:0;
padding:0;
margin:20px auto;
margin-bottom:30px;
}

li{
width: 200px;
height: 30px;
line-height: 30px;
margin: 20px auto;
background-color: pink;
}

</style>

JavaScript代码(这才是核心)

//通过id获取添加按钮元素
var oBtnAdd = document.getElementById("btnAdd");
//通过id获取删除按钮元素
var oBtnRemove = document.getElementById("btnRemove");
//通过id获取ul元素
var oUl3 = document.getElementById("list3");
//通过css选择器获取ul中的所有li元素,返回一个类数组
var aLi2 = document.querySelectorAll("#list3 li");
//等添加元素后,为了给li元素增加文本内容做准备
var str = "我是li";
var num = 3;

//for循环给每一个li元素注册鼠标移入移出事件
function mHover(){
for (var i = 0; i < aLi2.length; i++) {
aLi2[i].onmouseover = function(){
this.style.backgroundColor = "yellow";
};
aLi2[i].onmouseout = function(){
this.style.backgroundColor = "skyblue";
}
}
}
mHover();

//给按钮添加点击事件,并实现添加li元素的功能
oBtnAdd.onclick = function(){
num++;
//创建li元素节点
var nLi = document.createElement("li");
//创建文本节点
var textNode = document.createTextNode("我是li" + num);
//将文本节点插入li元素节点中
nLi.appendChild(textNode);
//将组合后的li元素,再放入到ul元素内部子元素的末尾
oUl3.appendChild(nLi);
mHover();//动态添加li元素后,希望给它们也添加上鼠标移入移出事件,此处执行此函数即可实现
}

//给按钮添加点击事件,并实现删除li元素的功能
oBtnRemove.onclick = function(){
//删除新增加的那个li元素
oUl3.removeChild(oUl3.lastElementChild);
num--;
mHover();//如果没有这条语句,那么新增的li元素不会拥有鼠标移入移出事件
}

作者的代码(只放不同的部分)

var aLi2 = document.getElementsByTagName('li');     //作者的
var aLi2 = document.querySelectorAll("#list3 li"); //我的

其实只是获取 li 元素时选用的方法不同而已,那么,各自都会产生什么效果呢?

我实现的效果:使用querySelectorAll()方法
JavaScript基础教程之querySelectorAll( )方法遇到的问题

作者实现的效果:使用getElementsByTagName()方法
JavaScript基础教程之querySelectorAll( )方法遇到的问题

三、案情分析:

看到这里,大家大概应该已经知道是什么原因了吧。没错,就是使用querySelectorAll()方法的缘故。
我们来简单的分析一下:

问:第一次执行mHover()方法是什么时候?
答:未动态添加元素之前,也就是给每一个li元素注册完鼠标移入移出事件之后。

问:那第二次呢?
答:在添加或者删除按钮的点击事件触发后执行,执行的时候,新增的li元素已经动态创建完毕。此时,它还会去执行一次mHover()方法,给新增的li元素同样注册鼠标移入移出事件,没错吧。

我们再回过头来看一下mHover()这个方法,看看for循环中oLi2.length的返回值到底是什么?

JavaScript基础教程之querySelectorAll( )方法遇到的问题

我们在mHover()方法内部,for循环结束的下一行,添加如下两条语句:

console.log(aLi2);
console.log(aLi2.length);

得到如下的返回值:

JavaScript基础教程之querySelectorAll( )方法遇到的问题

显而易见,querySelectorAll()是一个静态的获取元素的方法,在初始的DOM元素被动态改变后,并不能实时的更新其元素列表中的值。这说明,文档结构的改变,并没有影响到NodeList中的值。它始终如一的坚持着最初的那份想法,不被外界所动摇。不管外界怎么变化,就是不为之动心,这逼装的我给满分!

四、寻根问底

为了搞清楚到底是怎么回事?就去问度娘了,因为我现在看的这本JavaScript基础教程并不能帮我解决这么高深的问题。不出我所料,还真的有人遇到过同样的问题,并且也做了一番小研究,得出了结论。

于是,就找到了如下这几个对我解决问题有帮助的文章:

  1. 关于querySelectorAll的一个坑
  2. DOM元素querySelectorAll可能让你意外的特性表现
  3. 说说HTMLCollection、NodeList以及NamedNodeMap
  4. JS中typeof与instanceof的区别

首先,我在第一篇文章,看到了这个操作:

1 var test=document.querySelectorAll('.x');
2
3 test instanceof NodeList //true
4
5 test instanceof HTMLCollection //false
6
7 //这说明,querySelectorAll返回的是NodeList对象

于是,就产生了疑问?

疑问一:instanceof 是谁?干嘛用的?

instance of?不记得是干嘛用的了?看样子是可以判断对象的类型。最初学JavaScript的时候还学过,一年都没接触了,早都忘得一干二净了。不过,最近学的typeof立马浮现在我的脑海,这就促使我又去找了它们两个的区别。

区别如下:

相同点:都可以用来判断一个变量是否为空,或者是什么类型的。
不同点:
(1)typeof 用于变量的类型判断,它返回一个字符串,这个字符串代表了它所属的类型。
返回值包括五种基本数据类型和一种复杂数据类型。即number、boolean、string、undefined和null;以及object。
(2)instance of 用于判断一个变量是否是某个对象的实例。

疑问二:NodeList 是谁?HTMLCollection 又是谁?

查证后得知:原来它们两个都是对象,且都是一个节点的集合。

相同点:它们两个都是集合对象,返回值都是一个类数组。

不同点:

(1)NodeList 是由 Node.childNodes 和 document.querySelectorAll 返回的。它有一个length属性,
返回 NodeList 对象中包含的节点个数。

注:Node.childNodes 返回包含指定节点的子节点的集合,该集合为即时更新的集合。

  • 大多数情况下,NodeList 对象都是一个实时变化(即时更新)的集合。
    什么意思呢?就是说,每当文档中的节点(DOM)树发生变化,则已经存在的 NodeList 对象也会发生相应的变化。

  • 它的底层实现相当于一组元素的快照,并不是对文档进行搜索的动态查询。
    这样可以避免使用NodeList对象通常会引起的性能问题。

  • 但是,在另外一些情况下,NodeList 是一个静态的集合。
    这也就意味着随后对DOM元素的任何改动都不会影响NodeList 集合内的内容。

  • 而 document.querySelectorAll()方法返回的就是一个静态的 NodeList

特别是当你选择如何遍历 NodeList 中的所有项,或缓存列表长度的时候,最好牢记这种区分。

如需详细了解NodeList,请点击这里查看Web/API/NodeList

(2)HTMLCollection接口表示一个包含了元素的通用集合。它也有一个length属性值,返回集合当中子元素的数目。

  • HTML DOM 中的 HTMLCollection 是即时更新的;当其所包含的文档结构发生改变时,它会自动更新。

如需详细了解HTMLCollection,请点击这里查看Web/API/HTMLCollection

如需详细了解HTML DOM,请点击这里查看《getElementById返回的是什么?串讲HTML DOM》

五、总结:

HTMLCollection 做到了随着DOM元素的改变进行动态的变化,而 NodeList 并不会即时更新元素集合中的值,这就是差异。一个是静态的获取,一个是动态的获取,当然不一样了!

最后我自己也在控制台做了判断,如下图所示:

使用querySelectorAll()方法:

JavaScript基础教程之querySelectorAll( )方法遇到的问题

使用getElementsByTagName()方法:

JavaScript基础教程之querySelectorAll( )方法遇到的问题

感兴趣的同学可以去 Mozilla 开发者网络 查看相关的Web文档,这个网站提供了有关开放网络技术(Open Web)的信息,包括HTML、CSS 和万维网及 HTML5 应用的 API。它还记录了 Mozilla 产品的文档。该网站的宗旨是:源于开发者,服务开发者

六、结束语

今天晚上有点事耽误了,没能早早把博客写完,写到这么晚也实属无奈。突然发现现在已经是凌晨05:36了,从开始到结束,不知不觉已经过去了3个小时。毕竟今天遗留的问题,最好当天就解决了,千万别拖到明天去执行,后果你懂得。人是越拖延越不想做,越不想做就会变得越懒,每天利用写博客的方式还能时刻提醒自己,激励自己,让自己坚持下来。虽然刚开始写博客确实很累,但是我又是处女座的,一向对自己比较严格,而且是极度的完美主义者,当然不允许自己的博客敷衍了事,草草结束,该认真还得认真写。今天就写这么多吧,该睡觉了。

等白天睡起来了,再去学点新的东西,反正不管每天能学多少知识,都得完全弄明白并且掌握了,不要只顾着数量而忽略了质量。基础没巩固好,到时候痛苦的是自己,而且还要花费额外的时间再来复习知识点。所以说,应该严格要求自己,合理分配时间,不要眼高手低,初学阶段就应该多观察多思考多实践,而非走马观花式的,左顾右盼式的学习。尽量做到快速高效的学习,形成自己的一套科学合理的学习方法,建立一套彰显自己风格的有特色的知识体系,这何尝不是一件好事呢?