DOM相关
1. DOM API
-
DOM(Document Object Model)文档对象模型得树形结构
- 文档对象模型就是一个树形结构,类似于家谱树
html标签里面包裹了所有的文档内容。他是一个父亲节点(parent) 它没有父亲节点,也没有兄弟节点,我们称html标签为树根,也就是根节点,整个html表示整个文档。
在html节点内部通常有两个同级节点head和body,html是head的父亲节点,html也是body的父亲节点,他们同一层级并且相互不包含,我们称同属于同一个父亲的节点为兄弟节点,而head和body都是html的子节点。这样一层一层的关系就是节点树。各个标签在页面中都是元素节点(element node)
-
节点(node)的种类
- 元素节点(element node)
文档对象模型中的标签就是最基本的元素节点。它们层层嵌套形参整个页面。内部可能包含了文本和属性
- 文本节点(text node)
我们称DOM内的文本为文本节点。文本节点有的被包含在元素节点中的,比如p标签内部的文字。但是有的元素节点没有文本节点,
- 属性节点(attribute node)
属性节点从属于元素。比如其中type='radio’是元素节点p的属性节点。不是所有的元素都有属性节点,但是所有的属性节点都必然属于某一个元素。如:class/id/style
-
获取元素的方式
document.getElementById //通过ID获取某个元素,所有浏览器兼容 document.getElementsByClassName //通过class类名获取,获取是一组,不支持IE8及以下 document.getElementsByTagName //通过标签名获取,获取是一组,所有浏览器兼容 document.getElementsByName //通过name获取,获取是一组,很少用,所有浏览器兼容 querySelector、querySelectorAll
其中getElementById和querySelector获取的是元素单个节点。
而其余的方法都是获取若干个元素节点,那么会以类数组的形式存储
let arr1 = document.getElementsByClassName("item") let arr2 = document.querySelectorAll(".item") //arr1 HTMLCollection //arr2 NodeList
HTMLCollection和NodeList本质上共同点和不同:
都是DOM节点的集合,两者都属于Collections范畴,但是NodeList的范围要更广泛一点,它可以包含节点对象和文本对象。HTMLCollection比NodeList多了一个namedItem方法,其他方法保持一致
HTMLCollection
是以节点为元素的列表,可以凭借索引、节点名称、节点属性来对独立的节点进行访问。HTML DOM中的Collections是实时变动的,当原始文件变化,Collections也会随之发生变化。
NodeList
返回节点的有序集合,DOM中的NodeList
也是实时变动的
Node是一个基础类型,document, element, text, comment, documentFragment等都继承于Node. 在这篇文章最开始的测试中`NodeList`结果中有非常多的`text`,其实element, text, comment都是Node的子类,可以将它们视为:**elementNode**, **textNode**以及**commentNode**.平时在DOM中最常用的Element对象,其本质就是elementNode.
<ul class="box">
<li class="item"></li>
<li class="item"></li>
<li class="item"></li>
</ul>
let box = document.querySelector(".box")
let qli = document.querySelectorAll(".item")
let cli = document.getElementsByClassName("item")
let nli = document.getElementsByTagName("li")
box.innerHTML+=`<li class="item"></li>`//添加元素
qli.length//3 NodeList 不会动态改变
cli.length//4 HTMLCollection 会动态改变(相当于记录了一个获取方式)
nli.length//4 HTMLCollection 会动态改变
-
DOM中的增删改查
- 增加:
以前学习得时候我们用的是innerHTML添加的方式,这中添加方式的本质是什么?
box.innerHTML+="BLABLA"
将box内部的DOM节点转换成字符串,然后字符串添加内容,然后浏览器重新渲染。重新生成DOM结构。等价于重绘和重排
其中缺点有两点:1. 增加浏览器渲染不必要的开销 2. 原先的节点绑定的事件可能会消失。
for(let i = 0; i < 100; i++){ box.innerHTML+=`<li class="item">${i}</li>` } //此时浏览器重写了100次box的内部结构,每次重写都会重新渲染一次,等价于加载了100次页面 let str ="" for(let i = 0; i < 100; i++){ str+=`<li class="item">${i}</li>` } box.innerHTML+=str//两段代码效率完全不同
上面的效率其实不是最重要的。最重要的是你的新的节点已经不是原先的节点了
let box = document.querySelector(".box") let qli = document.querySelectorAll(".item") let cli = document.getElementsByClassName("item") let nli = document.getElementsByTagName("li") box.innerHTML+=`<li class="item"></li>`//添加元素 qli[0].onclick = function(){ alert(1) }//不会有任何改变,因为是静态获取,你绑定的元素已经消失了
let box = document.querySelector(".box") let qli = document.querySelectorAll(".item") let cli = document.getElementsByClassName("item") let nli = document.getElementsByTagName("li") cli[0].onclick = function(){ alert(1) }//虽然是动态获取,但是你绑定的元素还是会消失,注意顺序不同 box.innerHTML+=`<li class="item"></li>`//添加元素
所以综上,不要使用innerHTML添加元素。
正确的方式: DOM的生成节点的方式和添加元素的方式
let li = document.createElement("li")//生成一个li的标签节点。 let text1 = document.createTextNode("哈哈哈")//生成一个文本节点 li.appendChild(text1)//往li盒子中添加一个文本节点,默认添加到最后 let text2 = document.createTextNode("哇") li.insertBefore(text2, text1)//在li标签里面text1前面添加text2 document.body.appendChild(li)
这种添加方式只是对DOM树的某一个枝叶做修改,不会重新种树。但是枝叶的修改也是DOM操作,也会有渲染重排的问题。如何减少dom操作类似于str作为承接减少添加次数得方法有吗?有
let box = document.createDocumentFragment()//生成一个只存在于内存中的节点片段集合,它不是数组,更类似一个虚拟节点,里面存放元素 for(let i =0;i<100;i++){ let li = document.createElement("li")//如果生成元素写在循环外面? box.appendChild(li) } document.body.appendChild(box)//100个li就直接添加到body中了,直接父子关系,不存在中间层
如果生成元素写在循环外面从头到尾就只生成了一个节点。所以添加也只添加一次
- 删除
同样也不要innerHTML。
<ul class='list'> <li></li> <li></li> </ul>
let ali = document.querySelectorAll(".list li") let list = document.querySelector(".list") list.removeChild(ali[0])// 从父元素中删除节点。必须是父子关系。
- 修改=增加+删除
list.replaceChild(document.createElement("a"), ali[0])//选中儿子ali[0]替换成新建的a元素节点 list.relaceWith(document.createElement("li"))// 将自己替换成li元素节点
- 查询
查询元素。可以通过绝对位值关键字符查询。也可以通过相对位值查询父节点,相邻节点,子节点查询元素。
- 父元素
let li = document.querySelectorAll(".list li")[0]
li.parentNode//返回li的父元素的节点
li.parentElement//返回li的父元素
li.parentNode===li.parentElement //true
li.offsetParent //定位父级
//两者没有本质上的区别,都可以返回父元素
document.documentElement// html标签节点
document.documentElement.parentElement// null,因为这里已经没有父元素了
document.documentElement.parentNode// #document 文档
document.documentElement.parentNode===document// true
* 同级元素
nextSibling和previousSibling选择上一个或者下一个同胞元素,如果不存在就是返回null
```html
<ul>
<li></li>
asdasd
<li></li>
</ul>
let li = document.querySelector("li")
li.previousSibling // null 前面一个节点
li.nextSibling// "asdasd" 后面节点,是文本
li.nextSibling.nextSibling// 第二个li
如果只想要标签元素节点,可以自行封装方法
其中节点有节点类型。1是标签,3是文本,2是属性
function next(ele){
if(el.nextSibling.nodeType==1) return el.nextSibling
return next(ele.nextSibling)
}
//判断nodeType如果是标签就返回,如果不是就继续找下一个
//pre同理
相邻元素节点的获取去方式2:
li.nextElementSibing //下一个元素节点
li.previousElementSibing //前一个元素节点
- 子元素
子元素可能是多个,也可能是一个。
<ul>
<li></li>
asdasd
<li></li>
</ul>
let ul = document.querySelector("ul")
ul.childElementCount //子元素节点个数 2
ul.children // 子元素节点集合HTMLCollection 2个li
ul.childNodes// 子节点集合 NodeList 3个,其中第二个是text
ul.hasChildNodes // 有没有子节点,有就是true没有就是false也可以用length
ul.firstChild //第一个子节点
ul.firstElementChild //第一个元素子节点
ul.lastChild //第一个子节点
ul.lastElementChild //第一个元素子节点
- 属性节点和文本节点
let list = document.querySelector(".list")
let attr = document.createAttribute("asd")
attr.nodeType // 2
list.setAttribute(attr)
let text = document.createTextNode("asd")
text.nodeType // 3
- 节点对象的继承关系。
任何一个节点都是对象,原型链的终点都是Object。那继承关系是什么呢?在chrome浏览器可以测试
let temp = document.createElement("div")// div标签节点
temp.__proto__ // HTMLDivElement div构造函数的原型,上面有div节点独有的方法
temp.__proto__.__proto__ // HTMLCollection html元素节点构造函数得原型,节点的共有性质都在这
temp.__proto__.__proto__.__proto__ // Element 本质上和Document同级都是Node下面的一个元素
temp.__proto__.__proto__.__proto__.__proto__ // Node 所有节的构造函数得原型,存放节点基础方法
temp.__proto__.__proto__.__proto__.__proto__.__proto__ // EventTarget 事件对象的构造函数的原型
temp.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__ // Object的原型
2. 文档宽高、窗口宽高、元素宽高属性
-
元素宽高相关的属性
client: 读取元素的 content的宽高 + padding 的宽高 , 不包括滚动条和外边距以及边框
获取body的宽高属性:
console.log(document.body.clientWidth) //body的实际宽度,不包括滚动条
文档可视区域的宽高:
document.documentElement.clientWidth //当前文档区域可见宽度 document.documentElement.clientHeight //当前文档区域可见高度
注意: 不包括滚动条. 虽然没有兼容性问题,但是在IE不同的版本的宽度显示数值有一点差异,大概是3px的差异
注意: window下没有client属性
用在元素身上:
clientWidth: 元素的content + padding宽/高, 不包括滚动条, 和border
clientHeight
clientLeft
clientTop: 返回元素的content到边框的距离,也就是边框的厚度
注意: 只读属性,不能进行设置
<style> #box{ width: 200px; height: 200px; padding: 30px; margin: 70px; border: 20px black solid; background-color: #f60; } </style> <div id="box"></div> <script> var oBox = document.getElementById("box") console.log(oBox.clientWidth) console.log(oBox.clientHeight) </script>
获取窗口对的内部宽度
window.innerWidth; // 窗口的宽, 包括滚动条的宽度
如果想获取滚动条的宽度:
window.innerWidth - document.documentElement.clientWidth
offset 获取body页面的实际宽高
document.body.offsetWidth document.body.offsetHeight
对于文档元素,只能获取文档的可视区域宽,但是能获取实际高度
document.documentElement.offsetWidth //当前文档的可见区域宽度 document.documentElement.offsetHeight//当前文档的实际高度
在元素上:
offsetWidth: 元素的content+padding+border
offsetHeight:
offsetTop 元素顶部到定位父级的顶部距离,不包括定位父级的边框部分
offsetLeft 元素左边到定位父级元素左边距离, 不包括定位父级的边框部分
元素到body顶部的距离方法: 没有包括父级边框
function getOffset(dom){ let o ={ top: 0, left: 0 } while(dom!==document.body){ o.left += dom.offsetLeft o.top += dom.offsetTop dom = dom.offsetParent //定位父级 } return o } //这里忽略掉了所有的边框
包括父级边框的,元素到body顶部的距离
function getOffset(dom){ let o = { top: 0, left: 0 } while(dom!==document.body){ o.left += dom.offsetLeft+dom.offsetParent.clientLeft o.top += dom.offsetTop+dom.offsetParent.clientTop dom = dom.offsetParent //定位父级 } return o } //包含边框,两方法可以合并
scroll
读取元素的content宽度+padding宽度,包含滚动条
scrollWidth
scrollHeight
子元素超出的时候, 会加上 超出得子元素的宽度/高度。当超出的时候,左padding或者上padding加上内容宽度或者高度。右padding或者下padding已经失去意义所以不会结算。
但是当添加overflow属性的时候。会计算全部的padding + 内容宽/高 + 超出的子级宽/高
滚动高度:可读可写
获取元素的y轴或者x轴方向的被滚动挡住的那部分的宽度高度。等价于滚动过的部分
scrollTop
scrollLeft
document.documentElement.scrollTop document.documentElement.scrollLeft
可以在滚动事件中监听:
window.onscroll = function(){ console.log(document.documentElement.scrollTop) } //滚动的时候返回滚动高度
获取页面滚动高度存在兼容性问题,需要做兼容性写法
document.body.scrollTop || document.documentElement.scrollTop
鼠标位值相关的坐标
事件对象e
document.body.onclick = function(e){ // e 是 前面onclick事件触发之后整理的那一时刻的信息对象,叫事件对象 console.log(e.clientX, e.clientY) // 相对于窗口的左上角位值 console.log(e.pageX, e.pageY) // 相对于文档位置的左上角的位置,包含滚动条隐藏部分 }
事件对象的兼容性写法
e = e || window.event
3. classList对象
classList是元素节点对象下面的一个属性,他是一个记录类名以及相关方法的对象,类数组对象
//<li class="item1 item2 item3"></li>
let li = document.querySelector("li")
li.classList // ["item1","item2","item3"]
li.classList.value // "item1 item2 item3" 获取类名值
//在其原型上拥有很多方法
li.add("item4") // 返回undefined
li.contain("item4") // true
li.remove("item4","item3") //返回undefined
、、、
申明:本教程收集于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除。