一、event loop
1、什么是event loop
event loop(事件循环/事件轮询)
由于js是单线程运行的,异步要基于回调来实现,event loop就是异步回调的实现原理
2、event loop执行过程
Call Stack:调用栈 web APIs:浏览器定义的API(执行异步的API)
Event Loop:事件循环 Callback Queue:回调队列
同步代码会放在Call Stack中,一行行执行
遇到异步,放在web APIs中“记录”下来,等待时机(定时,网络请求等)
时机一到,web APIs就会把异步代码放进Callback Queue里
当同步代码执行完(call Stack为空),会先尝试DOM渲染,修改DOM结构(比如给div增加p标签),渲染完后,Event Loop就会开始工作
循环查找Callback Queue,如果有异步代码,则会移动到Call Stack内执行,再次DOM渲染。直至Callback Queue内的异步代码全部移除
3、DOM事件和Event loop的关系
DOM事件也使用回调,基于Event Loop
如上图所示,当Hi打印后会执行btn1的click事件,将click事件内的回调函数放入web APIs内。同步代码执行完(call Stack为空)后,Event Loop就会开始工作。如果用户点击btn1,回调函数会被移动Callback Queue里,Enent Loop循环查找Callback Queue,将它放入Call Stack中执行
二、aysnc-await
1、async和await语法介绍
异步回调有callback hell风险
promise then catch链式调用,但也是基于回调函数
async/await能用同步的语法来写异步代码,彻底消灭的回调函数
2、async-await和promise的关系
async-await是消灭异步回调的武器,但它并不和promise互斥,反而两者相辅相成
执行async函数,返回的是promise对象
await相当于promise的then
try...catch可以捕获异常,代替了promise的catch
3、async-await进阶
async-await是语法糖,异步的本质还是回调函数
await后面,可以都看作回调内容,是异步,也是微任务
4、for...of循环
for...in forEach for 是常规的同步遍历
而for...of常用于异步遍历
三、宏任务和微任务
1、宏任务和微任务有哪些
宏任务:setTimeOut、setInterval、Ajax、DOM事件
微任务:Promise、async/await
微任务的执行时机要比宏任务早
2、为什么微任务比宏任务执行更早
(alert会阻塞js代码执行,也会阻断DOM渲染)
微任务是在DOM渲染前触发,它是ES6语法规定的
宏任务是在DOM渲染后触发,它是由浏览器规定的
在执行promise这种微任务时,由于他是ES6语法规定的,所以不会经过web APIs。而是等待时机,把它先放入micro task queue(微任务队列)里。所以当call stack调用栈清空时,会遍历 micro task queue ,取出所有的微任务挨个执行,再尝试DOM渲染,最后触发Event Loop。
四、JS-Web-API-DOM
1、DOM的本质
DOM的本质是HTML文件解析出来的一棵树。
2、DOM节点操作
const h1 = document.getElementById('shop');//通过shop这个id获取当前元素
const h2 = document.getElementsByTagName('div');//通过div这个标签,获取所有div的集合
const h3 = document.getElementsByClassName('.shop');//通过shop这个class属性,获取所有相同元素的集合
const h4 = document.querySelector('div#shop');//通过属性选择器,获取所有相同元素的第一个
const h4 = document.querySelectorAll('div#shop');//通过属性选择器,获取所有相同元素的集合
3、DOM节点的property和attribute
attribute:修改HTML属性,会改变HTML结构(新增自定义属性等)
常用的有:setAttribute,getAttribute
常见用法:
p1.setAttribute('data-name','CLL');
console.log(p1.getAttribute('data-name'));
property:修改对象属性,或对DOM元素的JS变量进行修改,不会改变HTML结构
常见用法:
p1.a = 100, p1.b = 30;
两者都会引起DOM渲染
但property只有部分属性可以渲染。如 align、title、className、style,如float、border、top属性又渲染不出来。
attribute全部都可以渲染出来
4、新增&插入&移动节点
const div1 = document.getElementById('div1');
const div2 = document.getElementById('div2');
const p1 = document.createElement('p');//创造一个p标签节点
p1.innerHTML = '我喜欢你';
div1.appendChild(p1);//将p1节点插入到div1里的末尾元素后面
div2.appendChild(p1);//将p1节点移动到div2中
5、获取子元素列表&获取父元素&删除子元素
console.log(p1.parentNode);//打印p1的父元素
console.log(div2.childNodes);//打印div1的所有子元素
div2.removeChild(p1);//删除p1节点
6、一次插入多个节点,如何优化DOM操作的性能
DOM查询做缓存
//缓存DOM查询结果
const length = document.getElementsByTagName('p').length;
for(let i = 1; i < length; i++){
//缓存length,只进行一次DOM查询
}
将频繁操作改为一次性操作
const item = document.getElementById('item');
//创建一个文档片段
const frag = document.createDocumentFragment();
//for循环,执行插入
for(let i = 1; i < 10; i++) {
//创建li节点
const li = document.createElement('li');
//给li增加文本内容
li.innerHTML = "我喜欢你";
//把li插入到文档片段中
frag.appendChild(li);
}
//上树,把文档片段插入DOM树中
item.appendChild(frag);
四、JS-Web-API-BOM
1、如何识别浏览器的类型
使用navigator
const ua = navigator.userAgent;
2、location
location.href 获取网页完整网址
location.protocol 获取协议
location.host 获取域名
location.search 获取传入的参数
location.hash 获取#后面的内容
location.patnhame 获取网页路径
3、history
history.back 后退浏览器页面
history.forward 前进浏览器页面
五、JS-Web-API-事件
1、手写通用的事件绑定
//通用的事件绑定,elem是元素本身,type是事件类型,fn是回调函数,selector是事件代理
function bindEvent(elem, type, fn, selector){
//监听事件
elem.addEventListener(type, e => {
const target = event.target;
if (selector) {
//有事件代理的情况
//判断target本身是否为事件代理的元素
if(target.matches(selector)){
//是,则执行回调
fn(target, e);
}
} else {
//没有事件代理的情况
fn(elem, e);
}
})
}
(matches是DOM查询的API,可以查询某个DOM是否匹配某个css选择器)
2、事件冒泡
事件冒泡:IE的事件流,即事件开始时由最具体的元素(文档嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(document)
事件捕获:是不具体的节点更早接收到事件,而具体的节点应该最后接收到事件
//通用的事件绑定, elem是元素本身,type是事件类型,fn是回调函数
function bindEvent(elem, type, fn){
//使用addEventListener模仿事件绑定
elem.addEventListener(type, fn);
}
//获取元素
const p1 = document.getElementById('p1');
const body = document.body;
//监听事件,关于p1的点击事件
bindEvent(p1, 'click', e => {
e.stopPropagation();//阻止事件冒泡
console.log('p1');
})
//关于body的点击事件
bindEvent(body, 'click', e => {
console.log('body');
})
由上面代码我们可以看到,当p1点击的时候,会触发p1绑定的click事件,打印p1。但由于引起了事件冒泡,逐渐向上级传播触发到了body的点击事件,便又会打印body。想要阻止这个方法,可以使用stopPropagation阻止事件冒泡,让body点击事件无法触发
3、事件代理/委托
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数绑定在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫做事件代理
//获取父节点item
var item = document.querySelector('ul');
//给父节点绑定click事件
item.addEventListener('click', e => {
//判断当前点击的元素是不是事件代理元素
if (e.target.matches('li')) {
//是,则打印当前元素的文本内容
console.log(e.target.innerHTML);
}
});