09-BOM and DOM
BOM和DOM
js的组成部分 |
---|
![]() |
前面学习的部分是ECMAScript部分,都是基础语法部分。基础语法只是规定的代码如何写,并不能实现很多主流的操作,比如跳转页面,获取浏览器的尺寸等操作。再比如让HTML中的元素动起来。
所以需要学习BOM来操作浏览器。学习DOM来操作HTML标签。
一、BOM
1、BOM介绍
BOM是三个单词的首拼–Browser Object Model,即浏览器对象模型。
所谓对象模型,指的是用对象来描述的一个结构。
BOM意思是通过对象组成的结构来操作浏览器的。
window对象中包含的对象 |
---|
![]() |
2、浏览器的历史记录
历史记录的操作是window的子对象history。可以操作网页的前进和后退。
1 | history.back(); # 返回到上一个页面,相当于浏览器的后退按钮 |
3、浏览器的地址栏信息
浏览器的地址栏操作,window对象交给了自己的子对象location对象去处理。
1 | console.log(location.hash); // 设置或返回从#开始的url --- 锚点 |
方法:
1 | 语法: |
4、浏览器的弹出层
1 | window.alert("恭喜你!"); |
5、浏览器窗口尺寸
- window.innerHeight - 浏览器窗口的高度
- window.innerWidth - 浏览器窗口的宽度
1 | var w = window.innerWidth; |
说明:
- 这两个属性返回的单位是像素
- 这两个属性计算的范围包含滚动条的区域
6、浏览器的事件
onload事件:当网页中的所有资源都加在完成之后执行这个事件
1 | window.onload = function(){ |
使用说明:通常是将script标签放到head标签中的时候使用。因为放在head中默认是获取不到body中的内容的,但是有了这个事件后,就可以了。
onscroll 滚动事件:当网页滚动条的位置发生改变的时候触发这个事件
1 | window.onscroll = function () { |
使用说明:浏览器要有滚动条才行。
浏览器窗口改变事件:resize
1 | window.onresize = function(){ |
7、定时器
延迟执行:
1 | var timerId = setTimeout(function () { |
使用说明:第一个参数是要执行的函数,第二个参数延迟的时间(单位毫秒)。返回一个数字,表示当前页面中第几个定时器。
每间隔一段时间执行:
1 | var timerId = setInterval(function () { |
使用说明:第一个参数是要执行的函数,第二个参数是间隔的时间(单位毫秒)。返回一个数字,表示当前页面中第几个定时器。
关闭定时器:
定时器返回的数字,就是用来关闭定时器的
1 | clearTimeout(timerId); // 关闭延迟执行的定时器 |
使用说明:其实这两个方法可以混用。
1 | var timerId = setTimeout(function () { |
8、异步操作
异步和同步的概念
张三叫李四一起去吃饭,张三说他还有作业没做完,让李四先去,李四去吃饭,张三在写作业,这两个人做的事情不一样,但是在同一时间段内,两个人的事情在同时做,这就是异步。
张三叫李四一起去吃饭,张三说他还有作业没做完,让李四等一下,李四就等张三做完作业后两个人才一起去吃饭。这就是同步。
同步意思是同一时间只能做一件事情,多件事情需要排队按照顺序执行。异步就是同一时间可以做多件事情。
我们平常写的代码都是同步代码,定时器我们接触到的第一个异步代码。
js的特点是单线程的,也就是同一时间只能做一件事情,所以js会将异步代码交给浏览器去处理,因为浏览器是多线程的。
js的线程,会先将所有同步代码执行完,与此同时,浏览器也在处理异步代码(例如正在等待时间),当浏览器中的异步代码到了应该执行的时候了,会将需要执行的异步代码放在一个队列中,进行排队等候;当js线程将所有同步代码执行结束后,会从队列中拿出第一个需要执行的代码去执行,执行过一个以后,再次到队列中拿出第二个需要执行的代码去执行。。。。
js的异步操作,是在所有同步代码执行完成以后才执行异步代码
一般情况代码是按顺序执行的,上一行代码执行结束之前,下一行代码一直在等待,这是同步机制
同步执行示意图 |
---|
![]() |
异步代码是等待同步代码都执行完成以后,才运行异步代码,如下图:
异步执行示意图 |
---|
![]() |
9、窗口控制
9.1、打开新的窗口
语法:
1 | window.open(url, 窗口名称, 窗口风格) |
参数说明:
1 | 如果url为空,会打开一个空的标签页;url不为空,就打开目标url页面 |
返回新窗口的window
9.2、关闭窗口
语法:
1 | window.close() |
9.3、设置窗口滚动
语法:
1 | window.scrollTo(x, y) |
设置卷去的距离。
二、DOM
1、DOM介绍
DOM的全拼是:Document Object Model,叫做文档对象模型。
就是使用对象结构操作html文档。例如,改变标签的背景颜色,让标签移动产生动画。。。。
DOM中的顶级对象是document,言外之意,DOM其实是属于BOM的。
例如:
1 | window.document.write(); |
2、html基本结构的操作:
- document.body :body比较常用, 并且在页面中是唯一的, 因此可以使用document.body直接获取
- document.documentElement : 可以获取html元素及其所有内容
- document.head : 可以直接获取head元素
- document.title : 可以直接获取title的文本
3、获取元素
在js中,标签的id名,可以当做是变量,直接就能代表这个标签元素,但是,id名可以随便定义,但是变量名不能随便定义,所以使用id名代表标签是有风险的。所以需要通过方法来获取到标签元素,自己定义给变量,可以避免这种风险。
通过document获取节点
1 | document.getElementById("标签id名"); // 通过标签的id名获取标签 |
使用css选择器获取元素:
1 | documen.querySelector(css选择器); // 获取到匹配css的第一个元素 |
4、内容操作
1 | 语法: |
例:
1 | <body> |
5、样式操作
5.1、设置样式
语法:
1 | 元素.style.css属性名 = css属性值; # 给标签设置样式 |
例:
1 | <body> |
这样设置的样式都是行内样式。
5.2、获取样式
语法:
1 | window.getComputedStyle(标签) |
返回所有样式键值对组成的对象。
6、元素类名
使用元素的className属性可以设置元素的类名,也可以获取类名
1 | <body> |
classList类名操作:
添加类名,语法:类名列表.add(类名)
例:
1 | <style> |
效果:
添加类名 |
---|
![]() |
删除类名,语法:类名列表.remove(类名)
1 | btn.onclick = function(){ |
效果:
删除类名 |
---|
![]() |
让类名在删除和添加之间切换,语法:类名列表.toggle(类名)
例:
1 | btn.onclick = function(){ |
效果:
切换类名 |
---|
![]() |
判断类名列表中是否包含指定类名:类名列表.contains(类名)
例:
1 | console.log(list.contains('green')) |
7、属性操作
7.1、标签属性
1 | 元素.setAttribute(属性名,属性值) # 设置元素的属性 |
7.2、对象属性
1 | 元素.属性名 = 值; |
例:
1 | btn.onclick=function(){ |
7.3、H5的自定义属性操作
当给标签添加属性以data-XXX
开头时,H5提供了一个快速操作属性的api:
1 | 标签.dataset.XXX // 获取属性的值 |
8、页面卷去的距离
语法:
1 | document.documentElement.scrollTop |
上面这个语法当html没有文档声明的时候,是获取不到的,彼时,需要用到另外一个语法来获取:
1 | document.body.scollTop |
兼容写法 - 不管什么情况下都能获取到:
1 | var t = document.documentElement.scrollTop || document.body.scrollTop |
9、短路运算
利用逻辑运算中的&&
和||
让赋值操作变得更灵活,并带有选择性。
1 | var 变量 = 数据1 && 数据2 |
当数据1为true,不能决定整个条件的结果,还需要进行到数据2,所以此时会将数据2赋值给变量。
当数据1为false,就已经知道整个条件的结果了,就没有必要进行到数据2了,所以此时会将数据1赋值给变量
1 | var 变量 = 数据1 || 数据2 |
当数据1为true,就已经能决定整个条件的结果了,就没有必要进行数据2了,此时就将数据1赋值给变量
当数1为false,还不能决定整个条件的结果,需要进行到数据2,此时就会将数据2赋值给变量
案例:
- 随机点名
- 实时显示当前时间
- 全选全不选案例
10、节点操作
DOM 就是我们 html 结构中一个一个的节点构成的。不光我们的标签是一个节点,我们写的文本内容也是一个节点,注释,包括空格都是节点。
DOM节点分三种:元素节点、文本节点、属性节点。元素节点就是我们获取到的标签元素;标签里面的文本就是文本节点、标签上的属性就是属性节点。
10.1、获取节点
获取所有子标签节点:
1 | 父.children |
获取到的是一个所有子标签组成的伪数组
获取第一个子标签节点:
1 | 父.firstElementChild |
获取最后一个子标签节点:
1 | 父.lastElementChild |
获取父标签节点:
1 | 子.parentElement |
获取上一个兄弟标签节点:
1 | 标签.previousElementSibling |
获取下一个兄弟标签节点:
1 | 标签.nextElemenetSibling |
10.2、创建标签
1 | document.createElement('标签名字符串') |
返回创建好的标签
10.3、插入节点
给父标签追加子标签:
1 | 父.appendChild(子标签对象) |
将新的子标签插入到某个旧的子标签前面:
1 | 父.insertBefore(新的子标签, 旧的子标签) |
10.4、替换节点
使用新的子标签替换掉旧的子标签:
1 | 父.replaceChild(新的子标签, 旧的子标签) |
10.5、删除节点
父标签将指定的子标签删除:
1 | 父.removeChild(子标签) |
10.6、克隆节点
将一个标签复制一份出来:
1 | 标签.cloneNode() |
返回一个标签对象,这样只能复制一个空的标签,没有内容。
1 | 标签.cloneNode(true) |
返回一个标签对象,这样可以将标签中的内容也复制出来。
11、获取标签尺寸
包含标签边框的尺寸:
1 | 标签.offsetWidth |
不包含边框的尺寸:
1 | 标签.clientWidth |
返回纯数字。
12、获取元素位置
1 | 标签.offsetLeft |
获取的是相对于设置过定位的父标签的左边距和上边距离,返回纯数字。
13、获取边框大小
1 | 标签.clientTop |
获取到的是上边框和左边框的厚度,纯数字。
三、回流和重绘
我们在做案例的时候,通常一个标签要设置很多样式。为了方便我们批量设置样式,可以封装一个批量设置样式的函数:
1 | function setStyle(ele, styleObj) { |
这个函数在批量设置样式的时候,每遍历一次,设置一次样式,每次设置样式都设置在了行内,这样会造成多次回流,影响页面性能。
1、浏览器渲染过程
浏览器在渲染页面的时候,大致是以下几个步骤:
- 解析html生成DOM树,解析css,生成CSSOM树,将DOM树和CSSOM树结合,生成渲染树;
- 根据渲染树,浏览器可以计算出网页中有哪些节点,各节点的CSS以及从属关系 - 回流
- 根据渲染树以及回流得到的节点信息,计算出每个节点在屏幕中的位置 - 重绘
- 最后将得到的节点位置信息交给浏览器的图形处理程序,让浏览器中显示页面
如下图:
2、回流
回流:英文叫reflow,指的是当渲染树中的节点信息发生了大小、边距等问题,需要重新计算各节点和css具体的大小和位置。
例:在css中对一个div修饰的样式中,修饰了宽度、高度等样式,浏览器需要重新计算标签大小,这个计算的过程,就是回流的过程。
容易造成回流的操作:
布局流相关操作
- 盒模型的相关操作会触发重新布局
- 定位相关操作会触发重新布局
- 浮动相关操作会触发重新布局
节点操作
改变节点的结构或其中的文本结构会触发重新布局。
对标签进行下面这些属性或方法操作的时候,会强行回流:
- offsetTop
- offsetLeft
- offsetWidth
- offsetHeight
- scrollTop
- scrollLeft
- scrollWidth
- scrollHeight
- clientTop
- clientLeft
- clientWidth
- clientHeight
- getComputedStyle
css
- width
- height
- padding
- border
- margin
- position
- top
- left
- bottom
- right
- float
- clear
- text-align
- vertical-align
- line-height
- font-weight
- font-size
- font-family
- overflow
- white-space
3、重绘
重绘:英文叫repaint,当节点的部分属性发生变化,但不影响布局,只需要重新计算节点在屏幕中的绝对位置并渲染的过程,就叫重绘。比如:改变元素的背景颜色、字体颜色等操作会造成重绘。
回流的过程在重绘的过程前面,所以回流一定会重绘,但重绘不一定会引起回流。
容易造成重绘操作的css:
- color
- border-style
- border-radius
- text-decoration
- box-shadow
- outline
- background
4、优化
不管是回流还是重绘,都会对浏览器的渲染造成影响,所以我们在项目中,尽量避免回流。
4.1、合并样式修改
减少造成回流的次数,如果要给一个节点操作多个css属性,而每一个都会造成回流的话,尽量将多次操作合并成一个,例:
1 | var oDiv = document.querySelector('.box'); |
操作div的3个css属性,分别是padding、border、margin,此时就可以考虑将多次操作合并为一次。
方法1:使用style的cssText
1 | oDiv.style.cssText = 'padding:5px; border:1px solid #000; margin:5px;'; |
方法二:将这几个样式定义给一个类名,然后给标签添加类名:
1 | <style> |
4.2、批量操作DOM
当对DOM有多次操作的时候,需要使用一些特殊处理减少触发回流,其实就是对DOM的多次操作,在脱离标准流后,对元素进行的多次操作,不会触发回流,等操作完成后,再将元素放回标准流。
例:
1 | var data = [ |
这样每次给ul中新增一个li的操作,每次都会触发回流。
方法1:方法一:隐藏ul后,给ul添加节点,添加完成后再将ul显示
1 | oUl.style.display = 'none'; |
此时,在隐藏ul和显示ul的时候,触发了两次回流,给ul添加每个li的时候没有触发回流。
方法二:创建文档碎片,将所有li先放在文档碎片中,等都放进去以后,再将文档碎片放在ul中
1 | var fragment = document.createDocumentFragment(); |
文档碎片就是一个虚拟的DOM节点。对文档碎片操作不会造成回流。
方法三:将ul拷贝一份,将所有li放在拷贝中,等都放进去以后,使用拷贝替换掉ul
1 | var newUL = oUl.cloneNode(true); |
4.3、避免多次触发布局
如下回到顶部的操作:
1 | goBack.onclick = function(){ |
每隔20毫秒都会重新获取滚动过的距离,每次都会触发回流,代码优化如下:
1 | goBack.onclick = function(){ |
只获取一次,每次都让数字递增,避免每次都获取滚动过的距离。
对于页面中比较复杂的动画,尽量将元素设置为绝对定位,操作元素的定位属性,这样只有这一个元素会回流,如果不是定位的话,容易引起其父元素以及子元素的回流。
4.4、修改批量设置样式函数
1 | function setStyle(ele, styleObj) { |