
文章目录
DOM:Document Object Model(文档对象模型),定义了用户操作文档对象的接口,可以说DOM是自HTML将网上相关文档连接起来后最伟大的创新。它使得用户对HTML有了空前的访问能力,并使开发者将HTML作为XML文档来处理。
本文知识导图如下:

DOM是网页的核心结构,无论是HTML、CSS还是JavaScript,都和DOM 密切相关。HTML的作用是构建DOM结构,CSS的作用是设定样式,JavaScript的作用是读取以及控制、修改DOM。
当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。
如下面一段HTML代码可以用 HTML DOM 树来表示:
代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>window</title>
</head>
<body>
<h2><a href="#">标题文本</a></h2>
<p>段落文本</p>
<ul class="ul_Web">
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
</body>
</html>
HTML DOM 树

在这个树状图中,html位于位于最顶端,它没有父辈,也没有兄弟,被称为DOM的根节点。更深入一层会发现,html有head和body两个分支,它们在同一层而不相互包含,它们之间是兄弟关系,有着共同的父元素html。再往深会发现head有两个子元素meta和title,它们互为兄弟。而body有三个子元素,分别是h2、p和ul。再继续深入还会发现h2和ul都有自己的子元素。
通过这样的关系划分,整个HTML文件的结构清晰可见,各元素之间的关系很容易表达出来,这正是DOM所实现的。
通过可编程的对象模型,JavaScript 获得了足够的能力来创建动态的 HTML。
节点(node)最初源于计算机网络中,代表着网络中的一个连接点,可以说网络就是由节点构成的集合。DOM节点也是类似,文档是由节点构成的集合。
在DOM中有3种节点,分别是元素节点、文本节点和属性节点。
元素节点:可以说整个DOM都是由元素节点构成的,如:html、body、meta、h2、p、li等都是元素节点。元素节点可以包含其他的元素(除了html根元素)。
文本节点:在HTML中光有元素节点搭建的框架是不够的,页面开发的最终目的是向用户展示内容。例如li元素中含有的文本信息,每个li之间的换行操作都是文本节点。并不是所有的元素节点中都含有文本节点。
如下例:p元素中直接包含元素节点,无文本节点。
<p><span></span></p>
属性节点:作为页面中的元素,或多或少会有一些属性,如下面例子中,含有了,class、title、color等属性。开发者可以利用这些属性来对包含在元素里的对象作出更准确的描述。
例子:
<a class="a_css" title="CSS" color="#0F0" href="#">进入CSS网站</a>
由于属性总是被放在元素里,因此属性节点总是被包含在元素节点中。
上例中可以看出各种节点的关系如下图:

每个DOM节点都有一系列的属性、方法可以使用。下表中总结了常用的节点属性和方法,供使用时快速查阅。
| 属性/方法 | 类型/返回类型 | 说明 |
|---|---|---|
| nodeName | String | 节点名称,根据节点的类型而定义 |
| nodeValue | String | 节点的值,同样根据节点的类型而定义 |
| nodeType | Number | 节点类型 |
| firstChild | Node | 指向childNodes列表中的第一个节点 |
| lastChild | Node | 指向childNodes列表中的最后一个节点 |
| childNodes | NodeList | 所有子节点的列表,方法item(i)可以访问第i+1个节点 |
| parentNode | Node | 指向节点的父节点,如果该节点已是根节点,则返回null |
| previousSibling | Node | 指向节点的前一个兄弟节点,如果该节点已是第一个节点,则返回null |
| nextSibling | Node | 指向节点的后一个兄弟节点,如果该节点已是最后一个节点,则返回null |
| hasChildNodes | Boolean | 当childNodes包含一个或多个节点时,返回true |
| attributes | NameNodeMap | 包含一个元素特征的Attr对象,仅用于元素节点 |
| appendChild | Node | 将node节点添加到childNodes的末尾 |
| removeChild | Node | 从childNodes中删除node节点 |
| replaceChild | Node | 将childNodes中的oldnode节点替换成newnode节点 |
| insetBefore | Node | 在childNodes中的refnode节点之前插入newnode节点 |
案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>dom操作</title>
</head>
<body>
<h2>登鹳雀楼</h2>
<p id="test">白日依山尽,</p>
<p>黄河入海流。</p>
<p class="demo">欲穷千里目,</p>
<p>更上一层楼。</p>
<div class="demo">div标签1</div>
<div>div标签2</div>
</body>
<script>
//获取html元素
//getElementById('id属性值'):通过标签的id属性来获取元素,因为id具有唯一性,所以通过该方法获取的html元素只有一个
var ele =document.getElementById('test');
console.log(ele);
// ele.style.color = '#f00';
console.log('--------------------');
//getElementsByTagName('标签名'):通过标签名称获取元素,获取的元素由一个或者多个,将获取的元素存储在一个类似数组的集合中,获取的元素通过下标来区分(下标从0开始)
var pEles=document.getElementsByTagName('p');
console.log(pEles);
console.log(pEles[2]);
console.log('获取的p元素个数:'+pEles.length);
console.log('--------------------');
//getElementsByClassName('class属性值'):通过标签的class属性值来获取元素,不同的标签可以使用同一个class属性值,所以获取的元素由一个或者多个,获取的元素存储在一个类似数组的集合中
var elements =document.getElementsByClassName('demo');
console.log(elements);
//querySelector('id名/标签名/class名'):根据id、标签、class名获取元素,不管是哪一个名称,都获取第一个元素
var result1 =document.querySelector('#test');
console.log(result1);
var result2 = document.querySelector('.demo');
console.log(result2);
var result3 =document.querySelector('p');
console.log(result3);
console.log('--------------------');
//querySelectorAll('标签名/class名'):获取所有执行标签名/带有指定class名的元素
var demoEles=document.querySelectorAll('.demo');
console.log(demoEles);
var pElemets = document.querySelectorAll('p');
console.log(pElemets);
</script>
</html>
| 语法 | 说明 |
|---|---|
| document.write() | 改变 HTML 输出流 |
| 对象.innerHTML=新的 HTML | 改变 HTML 内容(包含元素、属性、文本) |
| 对象.innerText=文本内容 | 改变HTML文本内容 |
| 对象.attribute=新属性值 | 改变 HTML 属性 |
例1:document.write()应用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>write()</title>
<style>
.ddd{
color: #00f;
background-color: #ccc;
}
</style>
</head>
<body>
大湖名城,创新高地2
<button onclick="show()">点我一下</button>
</body>
<script>
document.write('<br />大湖名城,创新高地<br />');
document.write('<h2>二级标题标签</h2>')
document.write('<p style="color:#f00;">段落标签</p>')
document.write('<p class="ddd">段落标签2</p>');
function show(){
document.write('hello js!');
}
</script>
</html>
运行效果:

例2:innerHTML和innerText应用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>innertHTML和innerText</title>
</head>
<body>
<ul>
<li>列表项第一项</li>
<li>列表项第二项</li>
</ul>
<div></div>
</body>
<script>
//获取ul元素
var ulEle =document.querySelector('ul');
ulEle.innerHTML = '<li>大湖名城,创新高地</li>';
var result1 =ulEle.innerHTML;
console.log(result1);
var result2 =ulEle.innerText;
console.log(result2);
//获取div标签
var divEle = document.querySelector('div');
divEle.innerText = '<h2>大湖名城,创新高地</h2>';
</script>
</html>
运行效果:

例3:通过改变img固有属性值实现两张图片切换
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>改变和获取标签固有属性值</title>
</head>
<body>
<img src="img/lyf.jpg" width="300px"/>
<button onclick="changeImg()">换一张图片</button>
</body>
<script>
//获取img标签
var imgEle = document.querySelector('img');
var flag = true;
function changeImg(){
if(flag){
imgEle.src='img/wzx.jpg';
imgEle.style.width='500px';
flag=false;
}else{
imgEle.src='img/lyf.jpg';
imgEle.style.width='300px';
flag=true;
}
}
</script>
</html>
运行效果:


通过JavaScript改变CSS样式的思路:先获取需要改变样式的元素,可通过document.querySelector()方法获取。然后再通过上图语法中的方法去改变元素的样式。
案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>改变元素的样式</title>
</head>
<body>
<p class="demo">大湖名城,创新高地</p>
<button onclick="addCss()">给P标签添加样式</button>
</body>
<script>
//获取p标签
var pEle = document.querySelector('p');
function addCss(){
pEle.style.color= '#f00';
pEle.style.backgroundColor = '#ccc';
pEle.style.fontSize = '30px';
pEle.style.textDecoration='underline';
}
</script>
</html>
运行效果:

通过节点的nodeType属性可以检测出节点的类型。该属性返回一个代表节点类型的整数值,总共有12个可取的值,例如:
console.log(document.nodeType);
上面代码执行后的返回值为9,表示DOCUMENT_NODE类型节点。然而实际上,对于大多数情况而言,真这行游泳的还是前面我们学过的3种节点,即元素节点、文本节点和属性节点,他们的nodeType值如下:
这就意味着可以对某种类型的节点做单独的处理,在搜索节点的时候非常实用。
【1】通过父节点找到子节点
父子兄弟关系是DOM模型中节点之间非常重要的关系,在获取某个节点之后,可以通过父子关系,利用hasChildNodes()方法和childNodes属性获取该节点所包含的所有子节点。
案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<ul>
<li>Java</li>
<li>Node.js</li>
<li>JavaScript</li>
</ul>
</body>
<script>
//获取ul标记
var ulEle=document.querySelector("ul");
var DOMString="";
if(ulEle.hasChildNodes()){
for (var s of ulEle.childNodes) {
DOMString+=s.nodeName+"\n";
}
}
console.log(DOMString);
</script>
</html>
这个案例的首先获取ul标记,然后利用hasChildNodes()判断其是否有子节点,如果有则利用childNodes遍历它的所有子节点。执行后控制台输出结果可以看到ul包括4个文本节点和3个元素节点。
运行效果:

除了获取全部子节点,DOM还提供了firstChild和lastChild两个属性,分别表示获取父元素的第一个子元素和最后一个子元素。
案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<ul>
<li>Java</li>
<li>Node.js</li>
<li>JavaScript</li>
</ul>
</body>
<script>
//获取ul标记
var ulEle = document.querySelector('ul');
//获取ul的第一个子节点
var firstEle = ulEle.firstChild;
console.log(firstEle);
//获取ul的最后一个子节点
var lastEle = ulEle.lastChild;
console.log(lastEle);
</script>
</html>
运行效果:

【2】通过子节点找到父节点
利用parentNode属性,可以找到一个节点的父节点。
案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<ul>
<li>列表项第1项</li>
<li>列表项第2项</li>
<li class="demo">列表项第3项</li>
<li>列表项第4项</li>
<li>列表项第5项</li>
</ul>
</body>
<script>
//获取class名为demo的li元素
var demoClassLiEle = document.querySelector('.demo');
console.log(demoClassLiEle);
//获取li元素的父节点ul元素
var liParentEle = demoClassLiEle.parentNode;
console.log(liParentEle);
liParentEle.style.color = '#f00';
</script>
</html>
这个案例首先通过class名为demo来获取子节点li标记,再通过parentNode属性,成功找到了子节点li指定的父节点,然后通过父节点的style样式来改变ul父节点的样式。运行代码后,原先的整体color样式,由默认颜色变为#f00。
运行效果:

var liParentEle = demoClassLiEle.parentNode;
liParentEle.style.color = ‘#f00’;
上面两句代码可以用链式编程的方式连写:
demoClassLiEle.parentNode.style.backgroundColor=‘#f00’;
我们还可以一直向上搜索父节点。比如上面案例,我们可以通过两次parentNode来获取body标记。
利用链式编程修改上述案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<ul>
<li>列表项第1项</li>
<li>列表项第2项</li>
<li class="demo">列表项第3项</li>
<li>列表项第4项</li>
<li>列表项第5项</li>
</ul>
</body>
<script>
//获取class名为demo的li元素
var demoClassLiEle = document.querySelector('.demo');
console.log(demoClassLiEle);
//利用链式编程获取body元素
demoClassLiEle.parentNode.parentNode.style.backgroundColor='#f00';
</script>
</html>
运行效果:整个body元素的背景颜色都被修改了。

【3】通过某个节点找到它的兄弟节点
在DOM中父子关系属于两个不同层次之间的关系,而在同一层中常用的便是兄弟关系。
DOM中提供了nextSibling和previousSibling属性来访问兄弟节点。nextSibling是访问选中节点的下一个节点,且可以一直向下搜索兄弟节点;previousSibling是访问选中节点的上一个节点,且可以一直向上搜索兄弟节点。
案例代码:通过class为Demo的li标记来获取他的前一个li和后一个li(思路:li之间存在文本节点,所以在这里我们可以利用链式编程两次运用属性找寻。)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<ul>
<li>Java</li>
<li class="demo">Node.js</li>
<li>JavaScript</li>
</ul>
</body>
<script>
//获取class名为demo的li元素
var demoClassLiEle = document.querySelector('.demo');
//获取li元素的上一个节点元素(兄弟元素)
var previousSiblingEle = demoClassLiEle.previousSibling;
console.log(previousSiblingEle);
//获取li元素的下一个节点元素(兄弟元素)
var nextSiblingEle = demoClassLiEle.nextSibling;
console.log(nextSiblingEle);
</script>
</html>
运行效果:


在找到需要的节点之后通常希望对其属性做相应的设置。DOM定义了三个便捷的方法来查询、设置和删除节点的属性,即getAttribute()、setAttribute()和removeAttribute()。
这三个方法不能通过document对象来调用,只能通过一个元素节点的对象来调用。下面一个例子将实现三个方法的具体操作,带您一步学会三种方法的应用,您可以先注释其它两种方法来看某种方法的实现效果。
案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>属性操作</title>
</head>
<body>
<img src="img/lyf.jpg" width="300px" />
</body>
<script>
var imgEle = document.querySelector('img');
//获取img标签的属性值
var srcValue =imgEle.getAttribute('src');
console.log(srcValue);
console.log(imgEle.src);
var widthValue = imgEle.getAttribute('width');
console.log(widthValue);
//设置img标签的属性值
//imgEle.setAttribute('src','img/wzx.jpg');
//imgEle.setAttribute('width','800px');
//删除img标签的属性值
//imgEle.removeAttribute('width');
//imgEle.removeAttribute('src');
</script>
</html>

除了查找节点并处理节点的属性外,DOM同样提供了很多便捷的方法来管理节点,主要包括创建、删除、替换、插入等操作。
【1】创建节点
创建节点的过程在DOM中比较规范,而且对于不同类型的节点方法还略有区别。
| 类型 | 方法 |
|---|---|
| 元素节点 | createElement() |
| 文本节点 | createTextNode() |
| 文档片段节点 | createDocumentFragment() |
假设有如下HTML文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>创建新节点</title>
</head>
<body>
</body>
<html>
希望在body中动态添加如下代码:
<p>段落标签</p>
可以利用刚才提到的两个方法来完成。首先利用createElement()创建p元素,如下所示:
var oP=document.createElement("p");
然后利用createTextNode()方法创建文本节点,并利用appendChild()方法将其添加到oP节点的childNodes列表的最后,如下图所示:
var oText=document.createTextNode("段落标签");
oP.appendChild(oText);
最后再将已经包含了文本节点的元素(oP节点)添加到body中,仍采用appendChild()方法,如下所示:
document.body.appendChild(oP);
这样便完成了body中p元素的创建。appendChild()方法添加对象的位置永远是在节点列表的尾部添加。
【2】插入节点
上面我们介绍了将新创建的元素插入到列表的尾部,如果我们希望这个节点插入已知节点之前,则可以采用insertBefore()方法。该方法有两个参数,第一个参数是新节点,第二个参数是目标节点。
案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>节点的增删改查</title>
</head>
<body>
<ul>
<li>列表项第1项</li>
<li class="demo">列表项第2项</li>
</ul>
</body>
<script>
//创建一个li元素节点
var liEle =document.createElement('li');
//创建文本节点
var textNode =document.createTextNode('列表项第n项');
//将textNode节点插入到liEle节点中
liEle.appendChild(textNode);
console.log(liEle);
//将组装好的liEle元素添加到ul元素中
var ulELe = document.querySelector('ul')
// 将liEle元素放在ul中的第一个位置
ulELe.insertBefore(liEle,ulELe.firstElementChild);
</script>
</html>
运行效果:

【3】替换节点
有时候我们需要替换页面中的某个节点。DOM提供了replaceChild()方法来完成这项任务,该方法是针对要替换节点的父节点来操作的。
案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>节点的增删改查</title>
</head>
<body>
<ul>
<li>列表项第1项</li>
<li class="demo">列表项第2项</li>
</ul>
</body>
<script>
//创建一个li元素节点
var liEle =document.createElement('li');
//创建文本节点
var textNode =document.createTextNode('列表项第n项');
//将textNode节点插入到liEle节点中
liEle.appendChild(textNode);
console.log(liEle);
//将组装好的liEle元素添加到ul元素中
var ulELe = document.querySelector('ul')
//将组装好的liEle元素替换到ul元素中
ulELe.replaceChild(liEle,ulELe.firstElementChild);
</script>
</html>
运行效果:

【4】删除节点
删除节点是通过父节点的removeChild()方法来实现的。通常方法是找到要删除的节点,然后利用parentNode属性找到父节点,最后利用removeChild()删除节点即可。
案例代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>节点的增删改查</title>
</head>
<body>
<ul>
<li>列表项第1项</li>
<li class="demo">列表项第2项</li>
</ul>
</body>
<script>
var ulELe = document.querySelector('ul')
// 删除第一个li元素
ulELe.removeChild(ulELe.firstElementChild)
</script>
</html>
运行效果:

总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只
我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA