从JS和jQuery浅谈DOM操作,当我们在获取时,究竟获取了什么

0、写在前面的话

自己对前端的东西一直不是很熟,现在开始要想办法从前端各个地方去获取想要的属性值的时候,也基本是在网上现炒现卖,几周下来,发现自己还是迷迷糊糊,可以算是一无所获。

所以就抽时间,把这一块的东西收集整理一下,免得每次为了得到一个值要上网查询鼓捣一万年,实在是浪费时间,知其然知其所以然,才能让问题迎刃而解。

这篇博文应该说结构还不够完整,有些知识点还没提到,覆盖的内容其实主要还是为了满足我现在入门知识理解的一个需求,日后再进行完善吧,这里先贴一些相关概念的参考链接,以后方便参考和添加:

1、理解节点

HTML文档中,所有内容都是 “节点”,是一个Element,这意味着:
  • 整个文档是一个文档节点
  • 每个 HTML 元素是元素节点
  • HTML 元素内的文本是文本节点
  • 每个 HTML 属性是属性节点
  • 注释是注释节点

先看下面的片段:
<html> 
  <head>
    <title>DOM 教程</title>
  </head>
  <body>
    <h1>DOM 第一课</h1>
    <p>Hello world!</p>
  </body>
</html>
从上面的 HTML 中:
  • <html> 节点没有父节点;它是根节点
  • <head> 和 <body> 的父节点是 <html> 节点
  • 文本节点 "Hello world!" 的父节点是 <p> 节点
并且:
  • <html> 节点拥有两个子节点:<head> 和 <body>
  • <head> 节点拥有一个子节点:<title> 节点
  • <title> 节点也拥有一个子节点:文本节点 "DOM 教程"
  • <h1> 和 <p> 节点是同胞节点,同时也是 <body> 的子节点
并且:
  • <head> 元素是 <html> 元素的首个子节点
  • <body> 元素是 <html> 元素的最后一个子节点
  • <h1> 元素是 <body> 元素的首个子节点
  • <p> 元素是 <body> 元素的最后一个子节点

重点:

2、JS获取节点

我们知道,HTML文档载入浏览器后,会成为Document对象,这个对象让我们可以从脚本中对HTML页面所有元素进行访问,我们经常获取元素使用的几大方法:getElementById()、 getElementsByClassName、getElementsByName()、getElementsByTagName()就归属其下,也是我们最常使用到的方法。

  • getElementById() 根据id属性
  • getElementsByName() 根据name属性
  • getElementsByClassName 根据class的名称
  • getElementsByTagName() 根据标签

2.1 获取单一节点

我们用document.getElementById(id)这个方法举例,因为是根据HTML中唯一的id来获取,所以得到的是单一节点,这个节点实际就是一个对象,也就是说,这个方法返回的是一个HTML对象。

每个标签的出现,都意味着有一个对应的对象被创建,比如 <select> 标签的存在,意味着有一个 Select对象 诞生;<option> 标签,意味着有一个 Option对象 诞生。

我们说过,每个节点,都是一个对象,是对象,就有属性,所以我们想要获取对应的值,你只要找到了这个对象,完全可以到w3school上查看下这个对象有哪些属性,直接调用就可以了。

实际上,很多属性是大部分对象都有的,也是我们所常用的:
  • id 该节点的id值
  • value 该节点的value值
  • text 节点的文本值
  • name 节点的name,名称

诸如此类,还有很多,具体的可以直接查询手册。

来看看示例,假如有下面的HTML片段,现在想获取关于 “张三” 的各类信息:
<table class="manage-table space" id="dataTable">
	<tr class="center">
        <th rowspan="2">学号</th>
		<th rowspan="2">姓名</th>
		<th rowspan="2">性别</th>
		<th colspan="3">班级</th>
        <th colspan="2">选课情况</th>
	</tr>
    <tr class="center">
        <th>类别</th>
		<th>学届</th>
		<th>班号</th>
        <th>选课数</th>
    </tr>
    <tr class="center" idx="89">
        <td id="studentId" hidden="true">89</td>
        <td>0001</td>
        <td>张三</td>
        <td><a>男</a></td>
        <td id="classId" hidden="true">38</td>
        <td>理科</td>
        <td>1999</td>
        <td>3</td>
        <td>3</td>
    </tr>
</table>

先通过之前提到的,可以通过查看网上的文档和浏览器的调试器来找元素的”位置“,最终通过遍历定位到该行 tr :
var dataTable = document.getElementById("dataTable");
var zhangsan;
for(var x in dataTable.rows) {
    for(var y in dataTable.rows[x].attributes) {
        if(dataTable.rows[x].attributes[y].name === "idx"
                && dataTable.rows[x].attributes[y].value === "89") {
            zhangsan = dataTable.rows[x];
        }
    }
}

写到这里,需要注意一点,那就是 for in 这个循环遍历,这个方法是将对象的所有属性遍历,我们知道,数组也是一个对象,索引视为其属性,所以假如数组还有额外的属性,遍历会将该属性输出。

所以,上面的写法实际上存在很大的问题,因为Array表面上看来只有索引,实际上因为原型继承的关系,它还有很多从父类继承下来的属性,在这里也会同时遍历出来,如索引遍历完后紧接着遍历了其原型对象的属性 item,这个属性就没有attributes属性的,但是这里之所以没有报错,是因为JS的灵(缺)活(陷),直接表示出undefined,所以也在我们的if条件判断中溜了过去。

正确的写法怎么写?就老老实实用for循环,后面的例子会再次提到,你只要知道这样写是存在很大风险的,不要这样做,然后继续往下看就好。

另,ES6中引入的iterable类型对象可以使用 for of 方法规避这个问题,而且直接遍历出元素,而不是索引这么麻烦。

回到正题,现在我们得到了这一排,也就是这个 tr,那么什么信息都可以得到了,比如每个单元格的文本内容:
for(var z in zhangsan.cells) {
    console.log(zhangsan.cells[z].innerText);
}

注意 innerHTML 和 innerText 的区别,比如对应zhangsan性别的那栏单元格:
innerHTML  --> "<a>男</a>"
innerText --> "男"

2.2 获取多个节点

获取多个节点的方法如 getElementsByClassName、getElementsByName()、getElementsByTagName() 等,其实也很简单,无非是获取到了HTML对象的集合,再根据情况遍历使用就可以了。

比如:
var trs = document.getElementsByTagName("tr");
console.log(trs);
 

2.3 DOM方式的获取

上面我们提到的获取元素属性等等的方式,实际上很粗暴,就是使用对象直接获取其属性,实际上,还可以使用DOM方法来获取,而且是有一定区别的。

假如我们获取到一个HTML对象,叫做htmlObj,现在我们要获取它的id值:
htmlObj.id;  //对象方式,直接获取属性

htmlObj.getAttribute("id");  //DOM方法

如果你在浏览器调试的时候,没有发现获取的HTML对象有类似getAttribute()这种方法,别着急,如果你知道原型的概念,你可以顺着它的 __proto__ 属性延伸一直摸索,看到一个 ElementPrototype 的时候,这些方法就在里面。现在不理解没关系,你只要知道这些方法就是HTML DOM对象从上面继承下来的,并不是你没看见就没有

用实际点的例子来说明,还是接刚才zhangsan的HTML作为例子,这次不磨叽,先拿到zhangsan看下属性:
var dataTable = document.getElementById("dataTable");
console.log(dataTable);

var zhangsan = dataTable.rows[2];
console.log(zhangsan);

通过控制台的输出,我们可以看到,对于一些常见的属性,比如id、class等,我们可以确实可以使用 zhangsan.id 或者 zhangsan.className 来获取得到。 可是我们的zhangsan还额外自定义了一个属性idx,那么问题就来了。

问题就出在,这个idx属性并没有直接放在zhangsan这个对象下,而是作为attributes集合中的元素,所以之前我们没有办法直接使用如 zhangsan.idx 的方法获取值,而是很麻烦地再次通过遍历和条件判断来进行定位。

而实际上,DOM对象有一个 getAttribute() 方法,可以通过属性名来获取属性的值,所以之前我们定义zhangsan的方法,实际上可以改成这样:
var dataTable = document.getElementById("dataTable");
console.log(dataTable);

var zhangsan;

for(var x in dataTable.rows) {
    var person = dataTable.rows[x];
    if(person.getAttribute("idx") === "89") {
        zhangsan = person;
    }
}

console.log(zhangsan);

很不幸,会报出 person.getAttribute() is not a function 的错误,why?之前提到过,for in 会遍历其所有属性,在遍历完索引以后,它继续遍历那些从父类继承下来的属性,某些属性并不是DOM对象,是没有getAttribute方法的,循环到这里的时候,自然就报错了。

所以我们还是老老实实使用for循环:
for(var x = 0; x < dataTable.rows.length; x++ ){
    if(dataTable.rows[x].getAttribute("idx") === "89") {
        zhangsan = dataTable.rows[x];
    }

}

或者把之前的方法改一下,找到了以后就让循环break,不再遍历后面的元素:
for(var x in dataTable.rows) {
    var person = dataTable.rows[x];
    if(person.getAttribute("idx") === "89") {
        zhangsan = person;
        break;
    }
}

或者把继承的属性筛掉不要:
for(var x in dataTable.rows) {
    if(dataTable.rows.hasOwnProperty(x)){        
        var person = dataTable.rows[x];
        if(person.getAttribute("idx") === "89") {
            zhangsan = person;
        }
    }
}

这下,就正确了,代码也简单了好多,就是利用DOM方法,所以在特别是使用一些自定义标签的时候,使用DOM的方法可以更加便捷。


3、jQuery获取

3.1 认识jQuery的基本概念

我们都知道jQuery是一个JS函数库,我们也知道jQuery可以更少的代码做更多的事,我们还知道用jQuery获取节点都是通过 $(selector) 的方式。可是,这里的$号什么意思呢?

JS世界中,变量命名的规范里,$是属于合法的标识符的,jQuery的函数有两个名字,一个叫 jQuery,另一个就叫 $。所以实际上 $.ajax(options) 和 jQuery.ajax(options) 是等同的,你可以用浏览器的调试器,到控制台中去输入 $===jQuery,结果会得到 true。

我们都知道JS中函数也是一个对象,所以这里的 $ 表示函数本身,如果是 $(),那么根据函数中return,你就会得到一个对象,我们常说是一个jQuery对象。

现在你不需要去深究太多,你只用知道常见的jQuery语法最终就是得到一个jQuery对象,这个对象看似和你通过document.getElementById之类的方法得到的东西一样,实际上并不是,内容相似但完全是两种不同的对象。所以,jQuery对象没办法使用HTML DOM对象中的方法,比如 getAttribute ,同理HTML DOM对象也无法使用jQuery对象的方法

不过不用担心,jQuery中封装了大量的方法,可以替代HTML对象中的很多操作。比如 $("#foo").html(); 等同于 document.getElementById("foo").innerHTML;

3.2 jQuery对象和DOM对象的相互转换

上面已经提到了,两者是不同的对象,方法也不能共通,但是他们两者之间是可以进行相互转换的。

jQuery对象 --> DOM对象
var $cr=$("#cr"); //jquery对象
var cr = $cr[0]; //dom对象 jQueryObj[0]也可写成 jQueryObj.get(0);
alert(cr.checked); //检测这个checkbox是否给选中

DOM对象 --> jQuery对象
//只需要用$()把dom对象包装起来
var cr=document.getElementById("cr"); //dom对象
var $cr = $(cr); //转换成jquery对象

另外,从规范命名来讲,如果获取的对象是 jQuery对象,那么在变量前面加上$,这样方便容易识别出哪些是jQuery对象

2018.03.22补充如下
在项目中遇到了这样的问题,有这样一段代码:
function loadLimit(trainingId, departmentId) {
    var trs = $("#dataTable tr");
    if (trs.length > 1) {
        for (var i = 1; i < trs.length; i++) {
            trs[i].remove();
        }
    }
    $().invoke("/admin/elite/join/do/loadJoin.q", {trainingId:trainingId, departmentId:departmentId}, function(html) {
        $("#dataTable").append($(html).find("tr"));
    });
}

这段代码在Chrome中运行正常,后来在IE中出错,这才发现了问题。当时误以为获取的多标签集合jQuery对象,其子元素也是jQuery对象,所以自然而然使用了remove(),实际上trs[i]获得的已经是DOM对象,不支持jQuery方法,故报错。我试着打印了结果,如下图中,上为DOM对象,下为jQuery对象:
可以看到,jQuery对象中index为0的子元素,包含典型的innerHTML等属性,这显然是DOM对象的东西。也印证了之前写过的笔记jQueyr对象转DOM对象只需要类似var cr = $cr[0];即可。所以我自己的错误应该这样修改:
$(trs[i]).remove();

亦或者有这样的方法eq(),表示将匹配元素集缩减值制定index上的一个:
trs.eq(i).remove();

3.3 jQuery的选择器和方法

jQuery的选择方式和CSS的选择器方式类似,完整版你可以参考这里:(1)jQuery的选择器 (2)jQuery 参考手册 - 选择器
至于jQuery的方法,还是直接丢链接比较方便:(1)jQuery API 中文文档 (2)w3school的jQuery教程

3.4 jQuery选择示例

还是zhangsan的例子,我们再贴一次代码,免得拉上去再看:
<table class="manage-table space" id="dataTable">
	<tr class="center">
        <th rowspan="2">学号</th>
		<th rowspan="2">姓名</th>
		<th rowspan="2">性别</th>
		<th colspan="3">班级</th>
        <th colspan="2">选课情况</th>
	</tr>
    <tr class="center">
        <th>类别</th>
		<th>学届</th>
		<th>班号</th>
        <th>选课数</th>
    </tr>
    <tr class="center" idx="89">
        <td id="studentId" hidden="true">89</td>
        <td>0001</td>
        <td>张三</td>
        <td><a>男</a></td>
        <td id="classId" hidden="true">38</td>
        <td>理科</td>
        <td>1999</td>
        <td>3</td>
        <td>3</td>
    </tr>
</table>

我们要获得zhangsan,用jQuery的方式就是:
var zhangsan = $("[idx='89']");  //表示通过属性查找,属性idx为89的

没错,就是这么简单... 

然后你要么使用jQuery的一些方法来获取里面的内容,或者粗暴地将它转换为DOM对象再操作。

posted @ 2017-04-01 11:05  Dulk  阅读(1796)  评论(0编辑  收藏  举报