事件链、事件代理、页面的渲染过程、style的操作、防抖与节流【DOM(四)】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

 (1)事件链原理

(1)事件的三个阶段: 先捕获,后目标,再冒泡,只能有一个阶段触发程序执行,比如捕获阶段触发了到了冒泡阶段就不再触发

(2)事件经过所有元素都没有被处理,这个事件消失

(3)事件传递的过程 只跟文档树的结构有关系 跟界面显示的层叠效果没有任何关系

  

事件冒泡:结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,自子元素冒泡向父元素。(自底向上)

事件捕获:结构上(非视觉上)嵌套关系的元素,会存在事件捕获的功能,即同一事件,自父元素捕获至子元素(事件源元素)。(自顶向下)

2.默认在冒泡的时候执行事件:onclick/attach和 addEventListener传false的时候

3.addEventListener绑定事件,如果把第三个参数设置为true,则在捕捉的时候执行事件

3.整个事件处理过程,会有个event事件对象在整个事件过程传播(W3C标准,ie8及其以下没有)

4.ie8以下不支持addEventListener

5.focus,blur,change,submit,reset,select等事件不冒泡

      addEventListener第三个参数  true捕获阶段触发  false冒泡阶段触发

(2)阻止冒泡和默认事件

1、阻止事件冒泡: W3C标准 event.stopPropagation();但不支持 ie9以下版本

        stopImmediatePropagation() 支持stopPropagation的浏览器中也可以用                  stopImmediatePropagation()方法,这个不仅会阻止事件向祖元素的冒泡,也会阻止同一个节点上同一事件的其他的事件处理程序(优先级比它低的,同元素同事件多处理程序时)

        event.cancelBubble=true; ie8及ie8以下可用

2、阻止默认事件: 默认事件——表单提交,a标签跳转,右键菜单等等

        return false; 以对象属性的方式注册的事件才生效,用addEventListener/attachEvent这种是不生效的

        event.preventDefault(); W3C标准,IE9以下不兼容

        event.returnValue=false;兼容IE

案例如下:

2.事件代理(面试笔试题重点)

(1)事件代理: 网页设计中一种设计思想 利用事件对象中引用的目标对象这个技术来实现的

(2)无论事件触发时 是不是目标对象的监听器 在监听器内部的事件对象event中都可以访问这个事件的目标对象,利用这个特点去绑定事件给父级元素 来代理子级元素的业务,这种设计就是事件代理

 事件代理设计有两个缺点:

            1.静态的事件绑定:如果动态的添加元素进去  添加进去的元素没有这个事件

            2.性能消耗更高  业务却相同

        <style>
			.box1{
				background-color: darkgray;
			}
			.box2{
				width: 120px;
				height: 40px;
				background-color: firebrick;
				margin: 10px;
			
			}
		</style>
		<div class="box1">
			<div class="box2">hello1</div>
			<div class="box2">hello2</div>
			<div class="box2">hello3</div>
			<div class="box2">hello4</div>
		</div>
		<button onclick="load1()">网络请求了新的新闻</button>

        var box2s=document.querySelectorAll(".box2")
			box2s.forEach(el=>{
				el.addEventListener("click",function(e){
					console.log(this.innerHTML)
				})
			})


    //案例事件代理的缺点的举例:
    function load1(){
				// var box1=document.querySelector(".box1")
				// box1.innerHTML+='<div class="box2">hello5</div>'
				var box1=document.querySelector(".box1")
				var box2=document.createElement("div")
				box2.innerHTML="hello5"
				box2.className="box2"
				box1.appendChild(box2)			
			}
			

3.页面的渲染流程(高频面试题)

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(网址)后,到页面渲染出来,背后发生了什么?

==>请求网址对应的资源,得到资源,然后运行文档(简单的过程,不是标准答案)

      答案:前端=>网络=>后端=>网络=>前端  这4步都得分析

3.1 浏览器加载一份html文档是怎么加载的?

 浏览器加载一份html文档是怎么加载的?

 ==> (1)把标签、文本、注释、属性等等解析为节点树(DOM Tree)

        (2)解析DOM tree中的节点时遇到了不太的元素会有不同的操作:

                (2.1) style标签或者link-css,遇到css代码就会把css解析为css样式结构体

                (2.2) 遇到了src会去加载(网络请求)资源

        (3)把(2.1)css样式结构体和(1)DOM Tree结合变成呈现树/渲染树(Render tree)

        (4)按照Render tree绘制页面

呈现树有的文档树中一定有,呈现树中没有的文档树中可能也有

3.2重绘、回流/回档

自己理解:    

        重绘就是按照文档树的结构,重新绘制页面渲染

        回流/回档:页面的元素排版布局数量和节点在树中的位置等发生了改变,就是回流/回档

        回流必然引起重绘,但是重绘不一定引起回流

案例例如:

        <style>
			.box{
				width: 200px;
				height: 200px;
				background-color: red;
				/* visibility: hidden; */
				/* display: none; */
			}
		</style>
		<div class="box">hello</div>
		<h1>1231243254</h1>
		<button onclick="change1()">change</button>

    	function  change1 () {
				var box=document.querySelector(".box")
				// box.innerHTML="6666"                //回档
				// box.style.visibility= "hidden";    //重绘不回流
				// box.style.display= "none";        //回流
			}

2.回流与重绘

① 当render tree中因为元素的数量,尺寸,布局,隐藏等改变而需要重新构建。这就称为回流或者回档(其实我觉得叫重新布局更简单明了些)。每个页面至少需要一次回流,就是在页面第一次加载的时候。

②当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘

③(面试,选择题,问答题就答上面)从上面可以看出,回流必将引起重绘,而重绘不一定会引起回流。

3.常见的回流和重绘操作 任何对render tree中元素的操作都会引起回流或者重绘

①添加、删除元素(回流+重绘)

② 隐藏元素,display:none(回流+重绘),visibility:hidden(只重绘,不回流)

③ 移动元素,比如改变top,left(jquery的animate方法就是,改变top,left不一定会影响回流),或者移动元素到另外1个父元素中。(重绘+回流)

④ 对style的操作(对不同的属性操作,影响不一样)

⑤ 还有一种是用户的操作,比如改变浏览器大小,改变浏览器的字体大小等(回流+重绘)

④重绘与回流操作的注意点 //重绘与回流操作的次数越多,计算机的性能消耗越大 进行dom操作的时候,就要考虑

案例例如:

        <style>
			#box td{
				border: 1px gainsboro solid;
			}
		</style>

		<table id="box"></table>
    
        	我们的程序执行时 常常会操作页面  常常引起重绘/回流  
			频繁的重绘/回流会造成页面性能不好==>页面卡顿 迟缓 手机发烫
			因此操作页面时: 尽量避免高频重绘  使用框架(MVVM)
			尽量避免高频重绘案例:
			添加1万个格子到页面上 每个格子显示时间(ms)
			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)
				}
			}
			回档了1万零100次
			1658297335582-1658297335446  136ms
	
			创建一个"冰"元素来承载即将渲染的元素
			dom=>fragment
			wx=>block
			vue=>template
			react=></>
			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)//它自己不会添加到文档树中用于渲染 但是它的子代元素都会添加进去
			回档了1次
			1658297870113-1658297869968 145ms  

4.style的操作

1.获取script脚本节点后面加载的元素节点 是获取不了的

            因为文档的加载是按照文档树的顺序加载的

 解决方案一:当页面加载完成的事件触发 再去执行获取节点的方式

 解决方案二:script-- defer  async  修饰src如何加载外部js资源的异步属性

程序案例如下:

 

(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独有的属性

==>其实是获取的呈现树中的元素的属性值

5.防抖与节流(重点)

防抖:触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触 发,则重新计算时间。
思路:每次触发事件时都取消之前的延时调用方法。
/*
fn: 事件处理函数
delay :定时器延时
rest :传参给事件处理函数
*/
function debounce (fn, delay,...rest) {
        let timeout = null;
        return function (e) {
                // e:事件对象
        if (timeout) { clearTimeout(timeout) };//把前面那个 timeout 清除
        timeout = setTimeout(()=>{
                console.log(e, rest);
                console.log(this);
                fn.apply(this, [e, ...rest]);// 绑定 this 指向
        }, delay);
     }
}
function sayHi(e, ...rest) {
        console.log(e, rest);
        console.log(this.value);
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi, 300, 100, 200));
节流:高频事件触发,但在 n 秒内只执行一次,所以节流会稀释函数的执行频率。
思路:每次触发事件时都判断当前是否有等待执行的延时函数。
function throttle(fn) {
        let canRun = true; // 通过闭包保存一个标记
        return function () {        
        if (! canRun) return; // 在函数开头判断标记是否为 true ,不为 true return
        canRun = false; // 立即设置为 false
        setTimeout(() => { // 将外部传入的函数的执行放在 setTimeout
                fn. apply (this, arguments);
                // 最后在 setTimeout 执行完毕后再把标记设置为 true( 关键 ) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false ,在开头被 return
        canRun = true;
        }, 500);
     };
}
function sayHi(e) {
        console.log (e. target. innerWidth, e. target. innerHeight);
}
window. addEventListener ('resize', throttle(sayHi));

6.预加载与懒加载

  预加载: 提前加载资源--同源加载的优化

  懒加载: 先不加载 等条件成立时再加载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值