目录
6.focus,blur,change,submit,reset,select等事件不冒泡
1)event.stopPropagation(); 阻止向父级元素冒泡
2)stopImmediatePropagation() 阻止程序传递执行冒泡
3)event.cancelBubble=true; ie8及ie8以下可用
3)event.returnValue=false; 兼容IE
一、事件响应链
1.事件的三个阶段:
先捕获,后目标,再冒泡,只能有一个阶段触发程序执行,比如捕获阶段触发了到了冒泡阶段就不再触发,除非自己在冒泡阶段又增加了事件
事件经过所有元素都没有被处理,这个事件消失
事件传递的过程 只跟文档树的结构有关系 跟界面显示的层叠效果没有任何关系
事件冒泡:结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,自子元素冒泡向父元素。(自底向上)
事件捕获 : 结构上(非视觉上)嵌套关系的元素,会存在事件捕获的功能,即同一事件,自父元素捕获至子元素(事件源元素)。(自顶向下)
2.默认冒泡——传false
冒泡执行事件:onclick/attach和 addEventListener传false的时候
3.addEventListener绑定事件,第三个参数:
如果把第三个参数设置为true,则在捕捉的时候执行事件,false为冒泡阶段触发。默认是false。
true和false都不会阻止事件传递,要阻止事件传递唯一的方法就是阻止事件冒泡:事件对象调用:event.stopPropagation(); (没有方法能阻止捕获)
4.event事件过程传播
整个事件处理过程,会有个event事件对象在整个事件过程传播(W3C标准,ie8及其以下没有)
5.ie8以下不支持addEventListener
6.focus,blur,change,submit,reset,select等事件不冒泡
以下都不支持冒泡:
UI事件(load、unload、resize、abort、error)
焦点事件(blur、focus)
鼠标事件(mouseleave、mouseenter)
特殊的(scroll 事件:element的scroll事件不冒泡, 但是document的defaultView的scroll事件冒泡)
<div class='box1'> <div class="box2"> <div class="box3"> </div> </div> </div> <script> var box1=document.querySelector(".box1") var box2=document.querySelector(".box2") var box3=document.querySelector(".box3") box1.addEventListener("click",(e)=>{ console.log("box111111111a",e) },true) box1.addEventListener("click",(e)=>{ console.log("box1111111111b") },false) box2.addEventListener("click",(e)=>{ console.log("box22222") }) box3.addEventListener("click",(e)=>{ console.log("box33333") }) </script>
点击box3的结果:
其中事件对象中有:
target:目标元素,点击的哪一个元素就是哪一个元素。如上面就是div.box3
path: (7) [div.box3, div.box2, div.box1, body, html, document, Window]
元素属性绑定:默认就是冒泡阶段触发,也不能设置时捕获阶段触发,因为设置不了第三个参数。 只有监听器的方法才能决定在捕获阶段触发。
box1.οnclick=(e)=>{console.log("box111")}
二、阻止冒泡和系统默认事件
不能阻止捕获(传递)
1、阻止事件冒泡:
1)event.stopPropagation(); 阻止向父级元素冒泡
阻止向父级元素冒泡,不影响捕获阶段,自己要触发
(给目标元素写,就不向上冒泡了) W3C标准 支持智能浏览器,但不支持 ie9以下版本
2)stopImmediatePropagation() 阻止程序传递执行冒泡
支持stopPropagation的浏览器中也可以用stopImmediatePropagation()方法,这个不仅会阻止事件向祖元素的冒泡,也会阻止同一个节点上同一事件的其他的事件处理程序 (优先级比它低的,同元素同事件多处理程序时)
3)event.cancelBubble=true; ie8及ie8以下可用
box1.addEventListener("click",(e)=>{
console.log("box1a",e)
})
box3.addEventListener("click",(e)=>{
console.log("box3a",e)
// e.stopPropagation() //智能浏览器:阻止向父级元素冒泡
// e.stopImmediatePropagation() //阻止程序传递执行冒泡
// e.cancelBubble=false //ie8以下
})
box3.addEventListener("click",(e)=>{
console.log("box3b",e)
})
注意:阻止冒泡,设计让监听器在捕获阶段运行 等等 都是指向同一个事件。重点!
// 易错点: 阻止冒泡 设计让监听器在捕获阶段运行 等等 都是指的同一个事件 //box1和box2不是同一个事件链,因为时不同的事件,所以互补干扰,就先执行box1的事件链,然后再执行box3 box1.addEventListener("mousedown", (e) => { console.log("box1a", e) //最先打印 }, false)//这里写了false,就是冒泡阶段触发。 box3.addEventListener("click", (e) => { console.log("box3a", e) //第二打印 e.stopImmediatePropagation() //这里阻止了,所以下面box3b不会打印了 }) box3.addEventListener("click", (e) => { console.log("box3b", e) //不打印 })
2、阻止默认事件:
默认事件——表单提交会跳转acrion,a标签跳转,右键菜单等等。
阻止默认事件的方法:
1) return false;
以对象属性的方式注册的事件才生效,用addEventListener/attachEvent这种是不生效的
2)event.preventDefault();
阻止系统默认的事件执行。不能阻止冒泡。W3C标准,IE9以下不兼容
3)event.returnValue=false; 兼容IE
<a href="http://www.baidu.com" id="a1">baidu</a>
<script>
//正常情况下是先打印我们设计的点击事件,在执行系统设计的点击跳转页面事件
a1.addEventListener("click", (e) => {
console.log(66666)
e.preventDefault()//阻止默认事件。就只会执行我们自己设计的事件
})
a1.onclick=()=>{
console.log(66666)
} //系统有默认的点击事件,这里自己设计的点击事件不会覆盖系统默认的
</script>
e.stopPropagation() e.stopImmediatePropagation() 都阻止不了默认的跳转页面的系统事件
三、事件代理——笔试面试题
事件代理: 网页设计中一种设计思想 利用事件对象中引用的目标对象这个技术来实现的
无论事件触发时,是不是目标对象的监听器,在监听器内部的事件对象event中都可以访问这个事件的目标对象,利用这个特点去绑定事件给父级元素 来代理子级元素的业务,这种设计就是事件代理
一般用在 相同的事件上,如果是不同的事件,一般就会写两个函数
好处:
1.静态的事件绑定:如果动态的添加元素进去 添加进去的元素也有这个事件
2.性能消耗低
(1.可以大量节省内存占用,减少事件注册。比如ul上代理所有li的click事件就很不错。
2.可以实现当新增子对象时,无需再对其进行事件绑定,对于动态内容部分尤为合适)
缺点:
事件代理的常用应用应该仅限于上述需求,如果把所有事件都用事件代理,可能会出现事件误判。即本不该被触发的事件被绑定上了事件。
<!-- 添加进去的没有事件,不会console.log(this.innerHTML) --> <div class="box1"> <div class="box2">hello1</div> <div class="box2">hello2</div> </div> <button onclick="load1()">网络请求了新的新闻</button> <script> var box2s = document.querySelectorAll(".box2") box2s.forEach(el => { el.addEventListener("click", function (e) { //每一个el,就要生成一个函数 console.log(this.innerHTML) }) }) function load1(){ var box1=document.querySelector(".box1") // box1.innerHTML+='<div class="box2">hello5</div>' //这样写不行,会把原来的去掉 var box2=document.createElement("div") box2.innerHTML="hello5" box2.className="box2" box1.appendChild(box2) } //这样设计有两个缺点: //1.静态的事件绑定:如果动态的添加元素进去 添加进去的元素没有这个事件 //2.性能消耗更高 业务却相同 //上面的问题解决方法是:代理!可以省去forEach那段代码 // 代理:可以实现点击新添加进来的也会打印 var box1 = document.querySelector(".box1") box1.addEventListener("click", function (e) { console.log(e.target.innerHTML) //target目标元素,点击谁就打印谁 }) </script>
四、页面渲染流程
1.页面呈现过程
不同的浏览器略微会有些不同。但基本上都是类似的
①.浏览器把html代码解析成1个Dom树,html中的每个tag都是Dom树中的1个节点,根节点就是我们常用的document对象。dom树就是html结构,里面包含了所有的html tag,包括用JS添加的元素。
②浏览器把所有样式(主要包括css和浏览器自带的样式设置)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式。
③dom tree和样式结构体结合后构建呈现树(render tree),render tree有点类似于dom tree,但有区别,render tree能识别样式,render tree中每个node都有自己的style,而且render tree不包含隐藏的节点(比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到render tree中。但是visibility:hidden隐藏的元素还是会包含到render tree中的,因为visibility:hidden 会影响布局,会占有位置。
④一旦render tree构建完毕后,浏览器就根据render tree来绘制页面。
呈现树有的,文档树中一定有。呈现树中没有的,文档树中可能有
非常高频的面试题:——用户在浏览器地址栏输入url(网址)后,到页面渲染出来 背后发生了什么?
过程大致分为:请求网址对应的资源 得到资源 然后运行文档 (目前我们只能回答文档的加载,如下:)
浏览器加载一份html文档是怎么加载的?
1.把标签、文本、注释、属性等等 解析为节点树(DOM Tree)
2.解析DOMtree中的节点时遇到了不同的元素会有不同的操作:
2.1.(遇到style标签或者link-css) 遇到css代码 就会把css代码解析为CSS样式结构体
(一般我们在style标签里写css:里面的css不是文本/属性节点,也不是DOM Tree节点,它根本不在文档树中。style标签是元素节点)
2.2 遇到了src 会去加载(网络请求)资源
3.把2.1CSS样式结构体和第一步的DOM Tree结合变成 呈现树/渲染树(Render Tree)
4.按照Render Tree绘制页面(可深入学习)
2.回流与重绘——笔试/面试题
① 当render tree中因为元素的数量,尺寸,布局,隐藏等改变而需要重新构建。这就称为回流或者回档 (可以理解为:重新布局)。
每个页面至少需要一次回流,就是在页面第一次加载的时候。
display:none; 回流 box.innerHTML="333" 回档
②当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,则就叫称为重绘。比如background-color。 vesibility:hideen; 重绘不回流
③从上面可以看出,回流必将引起重绘,而重绘不一定会引起回流。
程序执行的时,常常会操作页面,常会引起重绘/回流,频繁的重绘/回流会造成页面的性能不好==>页面卡顿,迟缓,手机发烫
解决/优化:
1、操作页面的时候,尽量避免高频重绘
2、使用框架:MVVM框架
尽量避免高频重绘案例 : 添加1万个格子到页面上 每个格子显示时间(ms)
//下面就回档了1万零100次 //1658297335582(最后一个格子的时间)-1658297335446(第一个格子的时间) 136ms let tb = document.querySelector(".box") for (let i = 0; i < 100; i++) { let tr = document.createElement("tr") tb.appendChild(tr) //添加一次就回档了一次 for (let j = 0; j < 100; j++) { let dt = new Date().getTime() let td = document.createElement("td") td.innerHTML = dt tr.appendChild(td) } }
解决方案:创建一个"Fragment"元素来承载即将渲染的元素
Fragment:它自己不会添加到文档树中用于渲染 但是它的子代元素都会添加进去
Fragment在DOM中称为=>fragment。在微信=>block。在vue=>template。在react=> </>
//回档了1次 let tb=document.querySelector("#box") let fra1=document.createDocumentFragment()//它在内存中还不在文档中,因为还没添加 for(let i=0;i<100;i++){ let tr=document.createElement("tr") fra1.appendChild(tr) for(let j=0;j<100;j++){ let dt=new Date().getTime() let td=document.createElement("td") td.innerHTML=dt tr.appendChild(td) } } tb.appendChild(fra1)//它自己不会添加到文档树中用于渲染 但是它的子代元素都会添加进去
3.常见的回流和重绘操作
任何对render tree中元素的操作都会引起回流或者重绘
①添加、删除元素(回流+重绘)
② 隐藏元素,display:none(回流+重绘),visibility:hidden(只重绘,不回流)
③ 移动元素,比如改变top,left(jquery的animate方法就是,改变top,left不一定会影响回流),或者移动元素到另外1个父元素中。(重绘+回流)
④ 对style的操作(对不同的属性操作,影响不一样)
⑤ 还有一种是用户的操作,比如改变浏览器大小,改变浏览器的字体大小等(回流+重绘)
④重绘与回流操作的注意点
//重绘与回流操作的次数越多,计算机的性能消耗越大 进行dom操作的时候,就要考虑
五、样式操作css
1.行内样式
el.style.xx 设置/获取 ,只能操作行内样式 没有兼容问题,通吃 只能设置为字符串 样式必须用驼峰命名法 遇到与保留字一样的样式,前面应加“css”(eg:float——>el.style.cssFloat) ==>其实是获取的文档树中的元素的属性值
2.获取最终绘制样式
let styleobj=window.getComputedStyle(ele); styleobj对象是一个最终绘制的样式结果对象 有with height等270多个样式值 只能获取样式,要设置使用el.style.xx 拓展:window.getComputedStyle(ele,"after") //第二个参数解决的是获取伪元素样式 ele.currentStyle 计算样式,只读 IE独有的属性 ==>其实是获取的呈现树中的元素的属性值
都是获取行内样式的
box.currentSytle.width ie678
box.style.width 现代浏览器
<script>
//1.获取script脚本节点后面加载的元素节点 是获取不了的
//因为文档的加载是按照文档树的顺序加载的
var box2=document.querySelector(".box2") //box2这个div是写在这个seript的后面
console.log(box2) //null
//上面为了不得到null的解决方法:
//解决方案一:当页面加载完成的事件触发 再去执行获取节点的方式
function fn(){
var box2=document.querySelector(".box2")
console.log(box2)
}
window.onload=fn
// 解决方案二: script-- defer async 修饰src如何加载外部js资源的异步属性
// 以下代码是写到js文件中:
// var box2 = document.querySelector(".box2")
// console.log(box2)
</script>
<script defer src="./src/test.js"></script>
<div class="box2">666</div>
<style>
.box {
width: 400px;
height: 300px;
background-color: brown;
cursor: pointer;
}
</style>
<div class='box' style="color: red;font-size: 18px;">666</div>
<button id="btn3">让box发生变化x2</button>
<script>
//行内写的样式可以用box.style获取,外部写的css(比如style中的css)就不能用box.style获取
//点击后 宽高和字体x2
btn3.onclick = function() {
//在这里可以获取后面的元素:这个函数运行时 是用户点击 这时候 页面已经加载完毕 它比window.onload事件更加靠后触发
var box = document.querySelector(".box") //这个是DOM操作。行内的样式就是属性节点,属性节点在文档树中,而外部的样式结构体就不在文档树中
// console.log(box.style.width,typeof box.style.width) //空 string 说明width返回的是空的字符串
var style1= window.getComputedStyle(box)//获取计算样式(呈现树中的)
console.log(box.style,style1,box.style.width,style1.width) //CSSStyleDeclaration对象 对象 '' 200px
box.style.fontSize = parseInt(box.style.fontSize) * 2 + 'px'
// box.style.width=parseInt(box.style.width)*2+'px'//不会生效
box.style.width = parseInt(window.getComputedStyle(box).width) * 2 + 'px'
}
</script>
六、防抖和节流
七、预加载和懒加载——笔试/面试题
Vue或者React中,页面加载之前的生命周期函数中,你做了什么?
1、预加载:
提前加载资源——同源加载的优化
<head>
<meta charset="utf-8">
<title></title>
<script>
// 预加载
var img1=new image()
img1.src="https://img2.baidu.com/it/u=1814268193,3619863984&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1658422800&t=d974afe62dd1225c4faa14b1538bf7f5"
</script>
</head>
<body>
<img src="https://img2.baidu.com/it/u=1814268193,3619863984&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1658422800&t=d974afe62dd1225c4faa14b1538bf7f5">
<img src="https://img2.baidu.com/it/u=1814268193,3619863984&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1658422800&t=d974afe62dd1225c4faa14b1538bf7f5">
<img src="https://img2.baidu.com/it/u=1814268193,3619863984&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1658422800&t=d974afe62dd1225c4faa14b1538bf7f5">
</body>
2、懒加载:
先不加载,等条件成立时加载。
案例中是最后有一张图片,设计为等滑动到一定高度的时候,再加载最后那张图片
<body>
<p>hello</p>
<!-- 此处省略n个p标签 -->
<p>hello</p>
<p id="p1">hello66666666666</p>
<p>hello</p>
<!-- 此处省略n个p标签 -->
<p>hello</p>
<script>
window.onload = function () {
document.onscroll = function (e) {
let top = window.pageYOffset || document.body.scrollTop || document.documentElement
.scrollTop //body标签滚了多远
let h = cHeight = window.innerHeight || document.body.clientHeight; //屏幕高度
if (top >= (img2.offsetTop - h - 100)) {
img2.src = img2.dataset.src1 //把位置放入到img中
}
}
}
</script>
<img id="img2"
data-src1="https://img1.baidu.com/it/u=1966616150,2146512490&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1658422800&t=f99225a791b634226dcd5ee47c8b5f3f">
</body>
八、一些小知识点:
1、点击事件除了能绑给看得见的元素,还可以绑给看不见的元素。比如: document/window.onclick
2、srcElement target 都代表点击事件的那个目标元素
3、document.documentElement ==> html元素 (如果文档中没有html,也会返回html)
4、document.body ==> body元素
5、document 根节点
6、自定义属性:
<div id="box5" data-hai="200" data-src1="http://xxxx">666</div> <!-- 后面的hai,src1是自己定义的 --> <script> console.log(box5.dataset) //DOMStringMap{hai:'200',src1:""http://xxxx""} 这是一个对象 </script>
7、document.write(" ")
<script> //在哪个脚本就在哪个地方写内容 document.write("hello") //现在在这个脚本在body里,hello就在body里 </script>
九、async,defer
脚本的异步加载
1.<script src="script.js"></script>
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。
2.<script async src="script.js"></script>
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
3.<script defer src="myscript.js"></script>
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
然后从实用角度来说呢,首先把所有脚本都丢到 </body> 之前是最佳实践,因为对于旧浏览器来说这是唯一的优化选择,此法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。