事件链
事件执行分为三步:捕获 目标 冒泡
事件链原理(同一事件)
事件监听addEventListener第三个参数 ,true捕获阶段触发 ,false冒泡阶段触发
鼠标事件与案件事件有捕捉冒泡阶段,输入框事件没有冒泡
mousemove这种一直改变的不适合事件代理
var box1=document.querySelector(".box1")
var box2=document.querySelector(".box2")
var box3=document.querySelector(".box3")
box1.addEventListener("click",(e)=>{
console.log("box111111111a",e)
},true)
box2.addEventListener("click",(e)=>{
console.log("box22222")
})
box3.addEventListener("click",(e)=>{
console.log("box33333")
})
阻止冒泡和默认事件
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
<a href="http://www.baidu.com" id="a1">baidu</a>
<script>
a1.addEventListener("click",(e)=>{
console.log(66666)
//阻止系统默认事件
e.preventDefault()//可以阻止默认事件
e.stopPropagation()//阻止不了默认的跳转页面的系统事件
e.stopImmediatePropagation()//阻止不了默认的跳转页面的系统事件
})
事件代理
事件代理:
利用事件对象中引用的目标对象这个技术来实现的
无论事件触发时 是不是目标对象的监听器 在监听器内部的事件对象event中都可以访问这个事件的目标对象,利用这个特点去绑定事件给父级元素 来代理子级元素的业务,这种设计就是事件代理
不代理:
1.静态的事件绑定:如果动态的添加元素进去 添加进去的元素没有这个事件
2.性能消耗更高 业务却相同
var box2s = document.querySelectorAll(".box2")
box2s.forEach(el => {
el.addEventListener("click", function (e) {
console.log(this.innerHTML)
})
})
function load1() {
var box1 = document.querySelector(".box1")
var box2 = document.createElement("div")
box2.innerHTML = "hello5"
box2.className = "box2"
box1.appendChild(box2)
}
代理:
var box1 = document.querySelector(".box1")
box1.addEventListener("click", function (e) {
console.log(e.target.innerHTML)
})
自定义属性以及各类节点
自定义属性:date-属性名
<div id="box5" data-hqyj1="200" data-src1="http://xxxx">
666
</div>
console.log(box5.dataset)
各类节点:
var box1 = document.querySelector(".box1")
box1.addEventListener("click", function (e) {
console.log(e.target) //事件的目标对象
console.log(e.srcElement) //事件的目标对象
console.log(document.documentElement) //html元素
console.log(document.body) //body元素
console.log(document) //根节点
})
页面渲染流程
浏览器加载一份html文档:
1.把标签 文本 注释 属性等等 解析为节点树(DOM Tree)
2.解析DOMtree中的节点时遇到了不同的元素会有不同的操作:
2.1.style标签或者link-css 遇到css代码 就会把css代码解析为CSS样式结构体
2.2 遇到了src 会去加载(网络请求)资源
3.把2.1CSS样式结构体和1DOM Tree结合变成呈现树/渲染树(Render Tree)
4.按照Render Tree绘制页面(腾讯 京东 美团 百度等等互联网大厂意向的同学)
重绘和回流/回档:
重绘:就是按照文档树的结构,重新绘制页面渲染
回流/回档:就是页面的元素排版布局数量和节点在树中位置等发生了改变 就是回流
注:回流必然引起重绘,但是重绘不一定引起回流
function change1() {
var box = document.querySelector(".box")
// box.innerHTML = "6666" //回档
// box.style.visibility= "hidden";//重绘不回流
// box.style.display= "none";//回流
}
避免高频重绘的几种方法
dom=>fragment
wx=>block
vue=>template
react=></>
创建1w个格子;
普通for循环(回档1万零1次)
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次):
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)//它自己不会添加到文档树中用于渲染 但是它的子代元素都会添加进去
style操作
获取script脚本节点后面加载的元素节点
因为文档的加载是按照文档树的顺序加载的,获取不了
解决方案1:当页面加载完成的事件触发 再去执行获取节点的方式
function fn(){
var box2=document.querySelector(".box2")
console.log(box2)
}
window.onload=fn
方案2 :script-- defer async 修饰src如何加载外部js资源的异步属性
外部js文件
var box2 = document.querySelector(".box2")
console.log(box2)
内部引入
<script async src="./src/test.js"></script>
<div class="box2">
6662
</div>
async,defer
脚本的异步加载
1.
<script src="script.js">
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。
2.
<script async src="script.js">
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
3.
<script defer src="myscript.js">
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
然后从实用角度来说呢,首先把所有脚本都丢到 之前是最佳实践,因为对于旧浏览器来说这是唯一的优化选择,此法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。
样式操作
行内样式:box.style.width
css结构体(写在style标签中,link引入的外部css文件中):window.getComputedStyle(box).width获取的是字符串,需要用parseInt转化为数字. window.getComputedStyle(box).width)只能用来获取,不能修改,要修改需要用box.style.width
<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>
//点击后 宽高和字体x2
btn3.onclick = function () {
//在这里可以获取后面的元素:这个函数运行时 是用户点击 这时候 页面已经加载完毕 它比window.onload事件更加靠后触发
var box = document.querySelector(".box")
// var style1= window.getComputedStyle(box)//获取计算样式(呈现树中的)
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'
}
防抖与节流
防抖:触发高频函数事件后,n秒内函数只能执行一次,如果在n秒内这个事件再次被触发的话,那么会重新计算时间
思路:每次触发事件时都取消之前的延时调用方法
<style>
div {
width: 100px;
height: 100px;
background-color: skyblue;
}
</style>
<body>
<div id="box"></div>
<script>
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(
() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
let box = document.getElementById('box')
box.addEventListener('click', debounce(sayHi))
</script>
</body>
节流: 高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
思路:每次触发事件时都取消之前的延时调用方法
resize事件是窗口大小改变时发生的事件,可以在窗口开启、最大化、最小化、窗口大小改变(如拖拉改变窗口大小、move语句改变窗口大小、改变width或height属性以改变窗口大小)时发生
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('节流');
}
window.addEventListener('resize', throttle(sayHi));
预加载与懒加载
预加载:预加载简单来说就是将所有所需要的资源全部都提前进行请求,把资源都先加载到本地上,这样之后用到的时候,就直接从缓存中取资源就好
懒加载:懒加载其实也叫做延迟加载、按需加载,比如在长网页中延迟加载图片数据,是一种比较好的网页性能优化的方法。在比较长的网页或应用中,如果图片有很多,一下子之间把所有的图片都加载出来的话,耗费很多性能,而且用户说不定会把图片一一全部看完。
如果使用图片的懒加载的话,就可以解决上述问题咯,解决方法就是 只加载在可视窗口内的部分图片,其余图片都先不加载,之后随着鼠标的滚轮或者滚动条的滚动,到哪了,哪里才会加载图片,通过这种方式,好处也很明显:
(1)网页的加载速度更快
(2)服务器的负载一定程度的减小了
懒加载的使用,一般适用于图片较多,页面列表较长的场景之中。
懒加载的实现原理
首先我们要明白,图片的加载是怎么产生的? 图片的加载是由src属性引起的,当对src赋值时,浏览器就会请求图片资源,根据此特点,我们先用data-load属性来存储图片的路径,在需要加载图片的时候,将data-load 中图片的路径赋值给src,这样就实现了懒加载啦。
实现一个懒加载
通过JS 实现一个懒加载:
(1)window.innerHeight 获取浏览器可视区域的高度
(2)document.body.scrollTop || document.documentElement.scrollTop 获取浏览器滚动过的距离
(3)xx.offsetTop 是元素的顶部,距离文档顶部的高度
(4)图片的加载条件: xx.offsetTop < window.innerHeight + document.body.scrollTop (即也就是当图片所在位置,进入到了当前浏览器的可视窗口中去)
<div>
<img src ='loading.git' data-load="xx.png">//自定义一个属性存放图片路径
<img src ='loading.git' data-load="xx.png">
<img src ='loading.git' data-load="xx.png">
<img src ='loading.git' data-load="xx.png">
<img src ='loading.git' data-load="xx.png">
<img src ='loading.git' data-load="xx.png">
<img src ='loading.git' data-load="xx.png">
</div>
<script>
var img =document.querySelectorAll('img')
function lazyLoad(){
// 获取 浏览器滚动过的距离
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop
// 获取 可视区域的高度
var winHeight= window.innerHeight
for( var i=0; i<img.length ; i++){
//图片离定位父元素的上偏移量(没定位就是body)小于可视区域加上浏览器向下滑动的距离
if(img[i].offsetTop < scrollTop + winHeight){
img[i].src =img[i].getAttribute('data-load')
}
}
}
//onscroll事件在元素滚动条滚动时触发
window.onscroll = lazyLoad