介绍
JavaScript 三部分当中,DOM
占据了很大的一部分,当js的宿主环境为浏览器时,DOM才可以使用。DOM,即Document Object Model,也就是文档对象模型。
DOM是操作网页的基础API。通过DOM,我们可以非常方便的操作网页当中的内容。
绑定事件
我们之间学习过给一个元素绑定事件。
var btn = document.getElementById("btn");
btn.onclick = function () {
// code ...
}
也可以直接将事件处理函数写在元素的内部。
<button onclick = "fn1()">点击</button>
在js中,除了上述的两种绑定事件的方式以外,还可以采用事件监听
的形式来进行事件绑定。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>DOM事件绑定</h1>
<hr>
<button id="btn">点击</button>
</body>
<script>
function fn1() {
alert(123);
}
var btn = document.getElementById("btn");
btn.addEventListener('click',fn1);
</script>
</html>
上面代码中,addEventListener
方法是w3c
推荐使用的绑定事件的方式。第一个参数为事件,需要注意的是事件名称不加on,第二个参数是事件处理函数,不需要再函数的后面添加括号,否则函数会自动执行。
我们可以通过事件监听来给一个元素绑定多个相同事件处理函数,并且当触发事件后,多个事件处理函数都会执行。
事件监听
的方法并不支持IE9以下,为了完成兼容,在ie9及以下版本,可以采用attachEvent()
方法。
通常,我们可以采用类似于下面的代码进行兼容处理:
if (btn.addEventListener) {
btn.addEventListener('click',fn);
}else {
btn.attachEvent('onclick',fn);
}
也可以将内容封装成函数:
function bindEvent(dom,event,fn) {
if (dom.addEventListener){
dom.addEventListener(event,fn);
}else if(dom.attachEvent){
dom.attachEvent("on" + event,fn);
}else {
console.log("对不起,您的浏览器不支持。");
}
}
给一组元素绑定事件
当我们碰到需要给一组元素绑定事件的时候,可以采用类似于下面代码的写法:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul id="list">
<li>aaaaa</li>
<li>aaaaa</li>
<li>aaaaa</li>
<li>aaaaa</li>
<li>aaaaa</li>
<li>aaaaa</li>
</ul>
</body>
<script>
var lis = document.getElementById('list').getElementsByTagName('li');
for(var i=0;i<lis.length;i++){
lis[i].onclick = function (){
this.style.background = 'red';
}
}
</script>
</html>
节点
DOM当中,最小的组成单位是节点(node)。文档的树形结构也就是我们常说的DOM树,就是由不同类型的节点组成。
通常,节点的类型可以分为下面的几种:
-
Document :整个文档树的顶层节点
-
DocumentType : doctype标签(比如
<!DOCTYPE html>
) -
Element : 网页的各种html标签
-
Attribute:网页元素的属性(比如
class='test'
) -
Text: 标签之间或者标签包含的文本
-
Comment: 注释
-
DocumentFragment : 文档的片段
浏览器提供了一个原生的节点对象Node
,上面的这七种节点都继承了Node,因此具备一些相同的属性和方法。
节点树
一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构。这种树状结构就是 DOM 树。它有一个顶层节点,下一层都是顶层节点的子节点,然后子节点又有自己的子节点,就这样层层衍生出一个金字塔结构,倒过来就像一棵树。
浏览器原生提供document
节点,代表整个文档。
document
// 整个文档树
文档的第一层有两个节点,第一个是文档类型节点(<!doctype html>
),第二个是 HTML 网页的顶层容器标签<html>
。后者构成了树结构的根节点(root node),其他 HTML 标签节点都是它的下级节点。
除了根节点,其他节点都有三种层级关系。
-
父节点关系(parentNode):直接的那个上级节点
-
子节点关系(childNodes):直接的下级节点
-
同级节点关系(sibling):拥有同一个父节点的节点
DOM 提供操作接口,用来获取这三种关系的节点。比如,子节点接口包括firstChild
(第一个子节点)和lastChild
(最后一个子节点)等属性,同级节点接口包括nextSibling
(紧邻在后的那个同级节点)和previousSibling
(紧邻在前的那个同级节点)属性。
节点类型
我们可以通过nodeType
属性查看节点的类型,节点的类型一共包括下面的几种类型:
不同节点的nodeType
属性值和对应的常量如下。
-
文档节点(document):9,对应常量
Node.DOCUMENT_NODE
-
元素节点(element):1,对应常量
Node.ELEMENT_NODE
-
属性节点(attr):2,对应常量
Node.ATTRIBUTE_NODE
-
文本节点(text):3,对应常量
Node.TEXT_NODE
-
文档片断节点(DocumentFragment):11,对应常量
Node.DOCUMENT_FRAGMENT_NODE
-
文档类型节点(DocumentType):10,对应常量
Node.DOCUMENT_TYPE_NODE
-
注释节点(Comment):8,对应常量
Node.COMMENT_NODE
选取文档内容
DOM定义了许多方式来选区或者查询一个或者多个文档元素,如下:
-
用指定的id属性
-
用指定的标签名
-
用指定的css类
-
用指定的name属性
-
匹配指定的css选择器
通过id选取元素
通过元素的id属性我们可以选取到元素,通过document.getElementById()
,能够准确的找到元素。
var btn = document.getElementById('btn');
如果需要通过id选取一组元素,可以采用类似于下面的代码:
function getElements(/*ids*/) {
var elements = {};
for(var i=0;i<arguments.length;i++){
var id = arguments[i];
var elt = document.getElementById(id);
if(elt === null) {
throw new Error("No element with id:" + id);
}
elements[id] = elt;
}
return elements;
}
Tip:在低于IE8版本的浏览器中,getElementById()对匹配元素的id不区分大小写,而且也返回匹配name属性的元素。
通过指定的标签名选取元素
getElementsByTagName()
方法可以用来选取指定类型的(标签名)所有的HTML元素或者XML元素。
如:
var test = document.getElementsByTagName(‘p’)[0];
Tip:html标签不区分大小写,在html文档中使用getElementsByTagName()时,将进行不区分大小写的标签名比较。
Tip:给getElementsByTagName()传递通配符*,将获得页面中的所有的元素。
如果我们要获取一个元素内的子元素,也可以如下
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>
<span>hello</span>
</div>
</body>
<script>
var div = document.getElementsByTagName('div');
var span = div.getElementsByTagName('span');
</script>
</html>
用指定的css类来选取元素
HTML元素的class属性值是一个以空格隔开的列表,可以为空或者包含多个标符。
在js中采用className属性来保存HTML的class属性值。
使用方法:var one = document.getElementsByClassName(‘class值’);
例如:
<script>
var div = document.getElementsByClassName('d1 no'); // 选中class类名中包含d1 和no的标签
var no = document.getElementsByClassName('no');// 匹配了class类名中带有no的标签
</script>
这个方法只能够使用一个字符串参数,但是这个字符串参数可以由多个空格隔开的标识符组成。只有当元素的class属性值包含所有指定的标识符时才能匹配,但是顺序无关紧要。
通过name属性来选取元素
html 的name属性最初是为表单元素分配名字,在表单数据提交到服务器时使用该属性得值。类似于id。但是和id得区别是,name属性的值不是必须唯一的,多个元素可以存在相同的名字,例如,单选和复选按钮通常是这种情况。
并且,和id最大的区别在于,id是全局属性,但是name只能够在少数的html元素中有效,
这其中包括表单,表单元素,iframe 和 img元素。
用法:var t = document.getElementsByName(‘color’);
需要注意的是,在IE中,getElementsByName()也返回id属性匹配的元素。我们为了兼容,应该小心,不要将name和id值设置为一致。
var input = document.getElementsByName('username');
通过CSS选择器选取元素
在css中,我们可以通过选择器来选取元素。而随着css3选择器的标准化,w3c推荐了另一个称作”选择器API”的方法,用以获取匹配一个给定选择器的元素方法----querySelectorAll()。
它接受一个字符串参数,这个参数中包含一个css选择器。返回一个表示文档中匹配选择器的所有元素的NodeList对象。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div></div>
<div></div>
<div></div>
</body>
<script>
var div = document.querySelectorAll('div');
console.log(div);
</script>
</html>
需要注意的是,这个方法返回的对象的内容并不是实时的:包含在调用的时刻选择器所匹配的元素,但是并不包含在调用之后匹配的元素。如果没有匹配的元素,将返回一个空的对象。如果选择器异常,将抛出一个异常。
与其相似的方法有querySelector()
。这个方法与上面的类似,但是只返回第一个匹配的元素(文档顺序)。如果没有匹配的元素就返回null。
var div = document.querySelector('div');
console.log(div);
Tip:
在css3中,新增了两个选择器::first-line 和 :first-letter 等伪元素。这种伪元素实际上匹配的文本节点的一部分,而不是实际的元素。那么当我们使用以上的两种方法的时候,就不会匹配成功。
而且,在很多的浏览器中拒绝返回:link 和:visited等伪类的匹配结果,认为这样会泄露用户的浏览信息。
文档结构和遍历
一旦选取了文档中的一个元素,有时我们需要查找文档中与之在结构上相关的部分(父亲,兄弟,子女)。针对此种问题,js给了我们两个API使用,节点对象树API 和 元素对象树API。
节点对象树API -- 作为节点树的文档
Node定义的重要属性:
-
parentNode 找到节点的父节点
-
childNodes 该节点的子节点
-
firstChild lastChild 该节点中的子节点的第一个或者最后一个
-
nextSibling previousSibling 该节点的兄弟节点的前一个和下一个
-
nodeValue text节点或者Comment节点的文本内容
-
nodeName 元素的标签名,以大写的形式显示。
Tip:这个API对文档文本的变化非常敏感,需要小心。
元素对象树API -- 作为元素树的文档
这个API所提供的方法会将文档看做Element文档树,会忽略掉Text和Comment节点。
这个API有两部分组成:
-
第一部分:children 属性
-
第二部分:Element 属性
element属性内容:
-
firstElementChild lastElementChild
-
nextElementSibling perviousElementSibling
-
childElementCount 子元素的数量,返回值和childred.length相等
属性
HTML元素由一个标签和一组属性组成。例如a元素定义了超链接,href属性值则定义链接地址。DOM定义了另一个API来获取或者设置html属性。
获取和设置HTML属性
Element类型定义了getAttribute()和 setAttribute()方法来查询和设置非标准的HTML属性,也可以用来查询和设置XML文档中元素上的属性。
getAttribute() 只有一个字符串参数,你打算查询的属性的名字
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div class="test" title="hello,world">hello,world</div>
</body>
<script>
var div = document.getElementsByTagName('div')[0];
console.log(div.getAttribute('class')); // test
console.log(div.getAttribute('title')); // hello,world
</script>
</html>
setAttribute()
修改属性节点的值 有两个参数,第一个参数是要设置的属性名
第二个参数是要设置的属性值。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div class="test" title="hello,world">hello,world</div>
</body>
<script>
var div = document.getElementsByTagName('div')[0];
var info = div.setAttribute('title','this is title....');
console.log(info); // undefined
</script>
</html>
虽然以上的代码在大部分的浏览器中很好用,但是放在IE中不是很好用,如以下代码,在IE7中会失效。
hasAttribute()检测命名属性是否存在
var d1 = document.getElementById('div'); d1.hasAttribute('title'); // true | false
removeAttribute()完全删除属性 兼容性良好,无返回值。
var d1 = document.getElementById('div'); d1.removeAttribute('title');
数据集属性
有时候在HTML**元素上绑定一些额外的信息也是很有帮助的,当javascript选取这些元素并且以某种方式操纵这些信息的时候就是很典型的情况 。有时候可以通过给class属性添加特殊的标识符来完成。其他的时候针对更复杂的数据,客户端程序员会借助使用非标准的属性。对于非标准的属性,可以通过使用getAttribute()和setAttribute()来读和写非标准属性的值。而代价是html文档不在是合法有效的文档。
而HTML5提供了一个解决方案,在HTML5中,任意以”data-”为前缀的小写的属性名字都是合法的。这些”数据集属性”将不会对其元素的表现产生影响,他们定义了一种标准的、附加额外数据的方法,并不是在文档合法性上作出让步。
针对这种情况,HTML5提供了一种解决方案,在Element对象上定义了dataset属性。
这个属性指代一个对象,它的各个属性对应于去掉前缀的data-属性。因此dataset.x应该保存data-x属性的值。带连字符的属性对应于驼峰命名法属性名:data-jquery-test 属性就变成了dataset.jqueryTest属性。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Index</title>
</head>
<body>
<div id="d1" data-name="test" data-hobby-nav="small test...">这是一个测试.....</div>
</body>
<script>
var a = document.getElementById('d1');
var name = a.dataset.name;
console.log(name); // test
var test = a.dataset.hobbyNav;
console.log(test); // small test ...
</script>
</html>
Tip:如果你赋值的data-* 不存在,将会自动创建.
在html5中还有一个与之类似的对象--classList我们可以通过这个对象里面的方法实现新增、删除、修改节点上的css类,还可以判断某个节点是否被赋予了某个css类.
Element.classList里面存在着很多有用的方法:
- length
- add
- contains
- item
- remove
- toggle
mDiv.classList.add(‘myCssClass’);
var info = document.getElementById('div');
info.classList.add('test');
myDiv.classList.remove(‘myCssClass’);
var info = document.getElementById('div');
info.classList.add('test');
var main = document.getElementsByTagName('div')[1];
main.classList.remove('ceshi');
myDiv.classList.toggle(‘myCssClass’);//增加css类myDiv.classList.toggle(‘myCssClass’);//删除css类
Tip:作用是当元素没有这个css类时,就新增这个css类。如果已经有了这个css类,就删除它。这就是反转操作。
myDiv.classList.contains(‘myCssClass’);检查是否含有某个css类
作为Attr节点的属性
还有一种使用Element的属性的方法。Node类型定义了attributes属性。针对非Element对象的任何节点,该属性为null。对于Element对象,attributes属性是只读的类数组对象,它代表元素的所有的属性。attributes对象是实时的。可以用数字索引进行访问,这就意味着可以枚举元素的所有属性。并且也可以用属性名索引。
例如:
<div class="test" id="ceshi" title="hello,world">测试....</div> <script> var div =document.getElementById('ceshi'); var info = div.attributes['title']; console.log(info.name);//显示属性名 console.log(info.value); //显示属性值 </script>
使用DocumentFragment
DocumentFragment是一个特殊的节点,作为其他节点的一个临时容器。
可以如下写法:
var frag = document.createDocumentFragment();
就如同Document节点一样,DocumentFragment也是独立的,而不是任何其他的文档的一部分。它的parentNode总是为Null。
但是类似Element,它可以有任意多的子节点,可以使用appendChild() 、insertBefore()等方法来操作他们。
DocumentFragment的特殊之处在于,它使得一组节点被当作一个节点看待:如果给appendChild() insertBefore()或者replaceChild()传递一个DocumentFragment,其实是将该文档片段的所有子节点插入到文档中,而非片段本身。
例如:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul id="list"></ul>
<script>
var frag = document.createDocumentFragment();
console.log(frag);
for(var x=0;x<10;x++){
var li = document.createElement("li");
li.innerHTML = "list item" + x;
frag.appendChild(li);
}
var lists = document.getElementById('list');
lists.appendChild(frag);
</script>
</body>
</html>
小练习
内容发布:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
ul{
list-style-type: none;}
*{margin:0;padding: 0;}
.box {
margin: 100px auto;
600px;
height: auto;
border:1px solid #333;
padding: 30px 0;
}
textarea {
450px;
resize: none; /*防止用户拖动 右下角*/
}
.box li {
450px;
line-height: 30px;
border-bottom:1px dashed #ccc;
margin-left: 80px;
}
.box li a {
float: right;
}
</style>
<script>
window.onload = function(){
//获取对象 再次操作对象
var btn = document.getElementsByTagName("button")[0];
var txt = document.getElementsByTagName("textarea")[0];
var ul = document.createElement("ul"); // 创建ul
btn.parentNode.appendChild(ul); // 追加到 他的父亲里面
btn.onclick = function(){
if(txt.value == "")
{
alert("输入不能为空");
return false; // 终止函数执行
}
var newli = document.createElement("li");
newli.innerHTML = txt.value + "<a href ='javascript:;'>删除</a>"; // 吧表单的值给 新li
txt.value = ""; // 清空 表单
var lis = ul.children; // 获得有多少个li
// if else 这个片段 让我们新发布的内容 最上显示
if(lis.length == 0) // 第一次创建
{
ul.appendChild(newli); // ul 的后面追加
}
else
{
ul.insertBefore(newli,lis[0]); // 每次生成的新的li 放到第一个li 的前面
}
var as = document.getElementsByTagName("a"); // 获得所 a
for(var i=0; i<as.length;i++)
{
as[i].onclick = function(){
//removeChild
ul.removeChild(this.parentNode); // UL 他的爸爸
// a 的父级是 li
}
}
}
}
</script>
</head>
<body>
<div class="box">
内容发布: <textarea name="" id="" cols="30" rows="10"></textarea> <button>发布</button>
</div>
</body>
</html>