----------------------------------------------------------------------------------------------第一部分 DOM基本知识
一、定义:
1、DOM: Document Object Model,是操作html和css的,是一系列对象的集合, 是html和xml的标准编程接口。
2、document 代表着整个文档,但不是指整个html,确切说是document包含html。
二、增删改查:
(1)、查询(选择多个元素的返回的是一个类数组)
i、getElementById('idName');返回仅一个对象。
P.S. 元素id 在Ie8以下的浏览器,不区分id大小写,而且也返回匹配name属性的元素
ii、getElementsByTagName('标签名');返回该标签的所有的集合,可看做一个类数组,即使仅有一个,访问时也要用数组访问的方式
iii、 getElementsByClassName('类名(也可以是用空格分隔的多个类名)');返回满足该类名的所有的集合,可看一个类数组,即使仅有一个,访问时也要用数组访问的方式
<div class = 'demo demo1'></div> var divs = document.gerElementsByClassName('demo demo1');//true
兼容问题:IE8 及 IE8以下 没有getElementsByClassName('')方法,或者说不兼容
iv、getElementsByName('标签中属性name的值');返回满足该条件的所有的集合,可看做一个类数组,即使仅有一个,访问时也要用数组访问。
局限:只有部分元素name生效(表单,表单元素,img,iframe);
v、此方法返回的是一个副本,即非实时,1. (a)、即通过其不可影响html和css的内容样式,不实用,但jquery中选择元素用的就是这种方法;(2)、选取的选取这一刻的dom,之后动态添加的也符合选取条件的dom选取不到; 2.在IE7 及IE7一下浏览器没有,不兼容
通过css选择器的写法来选择dom节点
a、 document.querySelector('Css选择器写法');返回的是满足条件的一组中的第一个。
b、document.querySelectorAll('Css选择器写法');返回的满足要求的一组元素。
vi、document
代表整个文档
(2)、创建:
i、document.createElement() 创建元素节点
ii、document.createTextNode() 创建一个文本节点
iii、document.createComment() 创建一个注释节点
iv、document.createDocumentFragment() 创建一个dom文档碎片
(3)、插入:
i、parentNode.appendChild() 在parentNode中插入一个子元素节点,且插入的位置为最后一个子元素(较为常用的方法)
限制:必须要先能获取到父节点
初始dom结构为:
<ul> <li>1</li> <li>2</li> </ul>
创建及插入操作:
var oUl = document.getElementsByTagName('ul')[0]; // 创建dom var oLi = document.createElement('li'); oLi.setAttribute('class', 'insert'); var oText = document.createTextNode('3'); // 插入 文本节点插入oLi下,oLi元素节点插入oLi下 oLi.appendChild(oText); oUl.appendChild(oLi);
最终的dom结构:
<ul> <li>1</li> <li>2</li> <!--增加的--> <li class="insert">3</li> </ul>
ii、parentNode.insertBefore(a, b) 在b节点前插入a节点
这就上demo代码了,上边的理解了,触类旁通,这个也就能理解,大同小异罢了
(4)、删:
parent.removeChild() 删除parent节点下的某个子节点,正好同appendChild方法相反
(5)、替换:
parent.replaceChild(new, origin) 用new节点来替换parent下的origin字节点
我们利用上面添加的例子,我们改 oUl.appendChild(oLi); 为 oUl.replaceChild(oLi, oUl.children[0]); 则将oUl下的第一个子元素节点替换为我们创建的dom节点,如下dom结构
<ul> <!--替换的--> <li class="insert">3</li> <li>2</li> </ul>
三、dom节点:
(1)、 节点类型:
元素节点 - 1, 属性节点 - 2, 文本节点 - 3, 注释节点 - 8, document节点 - 9, DocumentFragment节点 - 11
在控制台,通过 节点名.nodeType 可返回节点类型,通过返回的节点类型判断,我们可以做很多事。
(2)、 遍历节点的一些属性(任何类型dom节点):
a.、parentNode 返回父节点
b.、childNodes 返回子节点们(类数组)
c、firstChild 第一个子节点
d、 lastChild 最后一个子节点
e、nextSibling 后一个兄弟节点
f、previousSibling 前一个兄弟节点
一个坑: 请问如下结果?
<ul class="parent"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> </ul> <script> var oPar = document.getElementsByTagName('ul')[0]; var oChild = oPar.childNodes; console.log(oChild.length); // 6 or 13 </script>
答案其实是13,为什么呢?因为dom节点有好多种,有元素节点,属性节点,文本节点等等,现在你应该有点明白了,我们来打印oChild来看看
console.log(oChilds); // [text, li, text, li, text, li, text, li, text, li, text, li, text]
所以这就很容易得到解释了,第一个li前的空格和换行作为一个文本节点,第一个li是一个元素节点,后面5个li情况一样, 然后是最后一个li的后面形成一个文本节点,所以一共是6*2 + 1 = 13个dom节点
下面这道题如果你能得到正确答案,说明你理解了,哈
<ul class="parent"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li> <li>6</li> </ul> //同样问ul下有几个dom节点?
你想的很对,骚年,就是8个dom节点,6个li元素节点 + 第5个li后空格形成一个文本节点 + 最后一个li后换行形成一个文本节点 = 8个dom节点,嘿嘿
(3)、 遍历元素节点的一些属性(除children慎用)(只针对元素节点)
a、parentElement 返回父级 元素节点(IE不兼容)
b、children 返回子级的 元素节点
c、childElementCount == node.children.length(IE不兼容)
d、firstElementChild 返回第一个子级 元素节点(IE不兼容)
e、lastElementChild 返回最后一个子级 元素节点(IE不兼容)
f、nextElementSibling 返回后一个兄弟 元素节点(IE不兼容)
g、previousElementSibling 返回前一个兄弟 元素节点(IE不兼容)
看到这你可能会想,好多方法啊,这要怎么记住啊,好痛苦,但细心的你会发现这些方法和上面的方法是如此的相似,所以只要上面的方法都记住了,下面的自然也就能掌握。
首先这些方法是用来遍历元素节点(过滤了其他类型的节点),你可能想我可以用这些方法来解决上面那该死的问题,但是这些方法有很多IE都不兼容,仅仅children好用一些,
那怎么办呢?通过JS我们自己封装这些方法让它兼容IE就可以了,而过滤通过nodeType来判断选择就可以。
(4)、 dom节点的一些属性:
a、nodeName(只读,不可写)(元素的标签以大写形式表示)
b、nodeValue(可读可写)(仅文本节点 和 注释节点有)
c、attributes 属性节点类数组集合
d、nodeType 该节点的类型,只读
(5)、节点方法:
hasChildNodes(), 判断是否该节点存在子节点(所有类型的)。
(6)、DOM结构树:dom元素的继承关系,阐述了dom上一些方法的来源
类似对象原型的关系,上级是下级的原型,而每一个都为一个构造函数,而上级为其原型。可这样理解
a、Node代表所有节点类型;
b、 Document理解为继承Node的一个构造函数,而document是Document构造函数的事例对象,继承了Document.prototype上的所有方法。但浏览器是禁止我们使用Doucment来通过new操作符来创建, 但如果我们在Document.prototype上增加属性,我们可以通过document来查看。
Document.prototype.abc = 'abc'; console.log(document.abc); // abc
HTMLDocument,有上边的图,我们不难猜出HTMLDocument是继承自Document的。这样说,Document下有两个,分别为XMLDocument和HTMLDocument,其文件后缀名分别为.xml和.html,则如果是xml文件,则其中的document为XMLDocument的实例对象,若是html文件,则其中的document为HTMLDocument的实例对象。
c、CharacterData同Document一样是继承自Node的一个对象,而Text和Comment则继承自CharacterData,然后所有的文本节点则继承自Text,而所有的注释节点则继承自Comment。
d、Element是继承自Node的对象,而HTMLElement则继承自Element,然后所有的元素节点都继承自HTMLElement
e、Attr是继承自Node的对象,而所有的属性节点则都继承自Attr
如果理解对象原型链继承机制的话,这些应该很好理解的
(7)、DOM基本操作的一些注意事项
i、getElementById方法定义在Document.prototype上,即Element节点上不能使用。
ii、getElementsByName方法定义在HTMLDocument.prototype上,即除html中的document以外不能使用(xml document,Element)
iii、getElementsByTagName方法定义在Document.prototype 和 Element.prototype上,则docuemnt和element都可使用
iv、HTMLDocument.prototype定义了一些常用的属性,body,head,分别指代HTML文档中的<body><head>标签,所以直接用document.body和docuemtn.head即可访问body和head标签
v、Document.prototype上定义了documentElement属性,指代文档的根元素,在HTML文档中,他总是指代<html>元素
vi、getElementsByClassName、querySelectorAll、querySelector在Document,Element类中均有定义
(8)、element节点的一些属性:
i、innerHTML 代表的是该element节点下html内容
ii、innerText(火狐不兼容) / textContent(老版本IE不好使) 代表的是该element节点下的文本内容
(9)、Element节点的一些方法
i、ele.setAttribute() 写element的一些属性
ii、ele.getAttribute() 读element下的一些属性
小总结:
1、获取dom节点常用的方法:
getElementById('idName'); 只能id用
getElementsByTagName(''); 都能用,无兼容
getElementsByClassName(''); 都能用, IE8及以下不兼容
2、document.body => body
document.head => head
document.documentElement => html
四、以上内容的简单实践:
1、模仿实现chldren方法
//重写children Element.prototype.retChild = function(){ var child = this.childNodes, len = child.length, obj = { 'length' : 0, 'push' : Array.prototype.push } for(var i = 0; i < len; i++ ) { if(child[i].nodeType == 1){ obj.push(child[i]); } } return obj; }
Element.prototype.retChildren = function(){ var arrObj = { length: 0, push: Array.prototype.push } for(var node = this.firstChild; node; node = node.nextSibling){ if(node.nodeType == 1){ arrObj.push(node); } } return arrObj; }
2、封装函数,返回元素e的第n层祖先元素节点
Element.prototype.findIndexParent = function (n){ var ret = this.parentNode? this.parentNode : this; while(--n) { if(!ret.parentNode) { return ret; } ret = ret.parentNode; } return ret; }
3、封装函数,返回元素e的第n个兄弟节点,n为正,返回后面的兄弟节点,n为负,返回前面的,n为0,返回自己
// 封装函数,返回元素e的第n个兄弟节点,n为正,返回后面的兄弟节点,n为负,返回前面的,n为0,返回自己 Element.prototype.retIndexSibling = function (n) { var ret = this; // n === 0 if(n === 0) { return this; } // n !=== 0 while(n) { ret = n > 0? ret.nextSibling : ret.previousSibling; // 没有n个兄弟元素节点,则返回本身 if(!ret) { return this; } ret.nodeType === 1 && n > 0 && n--; ret.nodeType === 1 && n < 0 && n++; } return ret; }
4、请编写一段JavaScript脚本生成下面这段DOM结构。要求:使用标准的DOM方法或属性。提示 dom.className 可以读写class
<div class="example">
<p class="slogan">text</p>
</div>
// 创建div标签 且添加属性 var oDiv = document.createElement('div'); oDiv.className = 'example'; // 创建p标签 且添加属性 填充内容 var oP = document.createElement('p'); oP.className = 'slogan'; oP.innerHTML = 'text'; // 按顺序插入 oDiv.appendChild(oP); document.body.appendChild(oDiv);
5、封装函数insertAfter();功能类似insertBefore();提示:可忽略老版本浏览器,直接在Element.prototype上编程
//封装insertAfter() Element.prototype.insertAfter = function(targetNode, afterNode){ var nextSib = afterNode.nextElementSibling;
//如果父节点没有子元素或afterNode不存在的容错处理 if(this.children.length <= 1 || !nextSib){ this.appendChild(afterNode); }else { this.insertBefore(targetNode, nextSib); } }
6、封装remove(); 使得child.remove()直接可以销毁自身
Element.prototype.remove = function (){ // 像那些属性节点,注释节点,文本节点等等根本不可能做父节点,所以可以说parentNode返回的一般都是父元素节点 var definedParent = this.parentNode; definedParent.removeChild(this); }
7、将目标节点内部的节点顺序逆序。
eg:<div> <a></a> <em></em></div>
// 将所有子元素逆序 Element.prototype.reverseChildren = function () { var children = this.children; var len = children.length; for(var i = 0; i < len; i++) { this.insertBefore(children[i], children[len - i - 1]); this.insertBefore(children[len - i - 1], children[i]); } }
8、parentElement 返回父级 元素节点(IE不兼容), 个人直接用node.parentNode就可以了,感觉除了元素节点外,其他类型节点都不适合做父节点
9、childElementCount (IE不兼容), 我们直接用node.children.length代替他即可解决不兼容问题
10、firstElementChild 返回第一个子级 元素节点(IE不兼容),我们用node.children[0]即可
11、lastElementChild 返回最后一个子级 元素节点(IE不兼容), 我们用直接返回node.dhildren的最后一个元素就可
12、nextElementSibling 返回后一个兄弟 元素节点(IE不兼容),js封装
// 返回后一个兄弟 元素节点 Element.prototype.nextEleSibling = function () { // 利用for循环的赋初值, 判断, 调整, 循环 这四种特性 for(var ret = this.nextSibling; ret && ret.nodeType !== 1; ret = ret.nextSibling) {} return ret; }
13、previousElementSibling 返回前一个兄弟 元素节点(IE不兼容), js封装, 原理同上
// 返回前一个兄弟 元素节点 Element.prototype.nextEleSibling = function () { // 利用for循环的赋初值, 判断, 调整, 循环 这四种特性 for(var ret = this.previousSibling; ret && ret.nodeType !== 1; ret = ret.previousSibling) {} return ret; }
--------------------------------------------------------------------第一部分 end
------------------------------------------------------------------------------------------------------------------------------------第二部分 DOM操作
一、DOM操作:
1、基本操作
3、css脚本化
4、事件
--------------------------------------------------------------------第二部分 end
-------------------------------------------------------------------第三部分 JS加载
一、异步加载
时间线
-------------------------------------------------------------------第三部分 end
总结: 在我看来DOM的大概可以分为DOM基础知识、DOM操作、JS记载三部分,然后再细分即可,这是在写博客时想出来的,不管对不对,能够让自己记住并理解知识点就可以,等到理解更深再说,学习的快乐莫过于遇到困难后的坚持,直到解决问题,甚至有些自己的独特理解,不能仅仅为了以后找工作而学,而是因为自己的兴趣才学,因为自己想学才培养这方面的兴趣,在我们不能真正理解一个东西时,我们没资格说我们对其没兴趣,因为我们根本就不了解,还谈何是否有兴趣呢。
--------------------------------------------------------------------end