事件循环机制
事件循环,也就是EventLoop,也就是完成一个任务会需要完成一系列的任务,因此会有一个任务队列,用循环的方式一个个执行,同时执行期间的机制依据着:
同步 > 异步
很好理解,就是同步的任务一定是排着队的一个个从前往后执行,但是异步是会比同步慢一点,这是无可厚非的,但是在异步里面也是要分先后的。
JS单线程
这里我们需要了解,JS是单线程机制程序,所谓单线程,便是同一时间,只能有一个线程任务执行,因为如果存在多个线程,那么页面的加载就完全不受控制了,不知道究竟谁会先出现,为保证页面的顺利进行,必须实行单线程机制。
因为JavaScript是单线程运行的,所有的任务只能在主线程上排队执行,这就是事件循环机制的基础——事件队列。
异步
但是我们不能保证事件队列里面的事件一定可以在合适的时间完成,举个🌰:
如果一个网络请求会去请求网络数据,需要花费20分钟,那么我们的页面要等待20分钟吗,很明显,不可以,这样就会卡住页面,我们需要实行异步,便是这个网络请求需要让出cpu,让事件队列的其他事件先执行,等我请求数据返回再占用cpu,所以这就是JS前端异步的重要,也是必须要实现的。
任务类型
任务类型主要分为两种:宏任务 marco-task 和 微任务 micro-task
。
在ES6中,宏任务又称为ScriptJobs,而微任务又称PromiseJobs
宏任务
宏任务在真实性况下有以下几种:
脚本(主程序代码),setTimeout,setInterval,setImmediate
微任务
微型任务的真实包含任务:
async/await,Promises
所以我们可以罗列一下任务执行的顺序:
脚本(主程序)—>Promise...--> setTimeout
举个🌰:
setTimeout(function () {
console.log(3);
}, 0);
Promise.resolve().then(function () {
console.log(2);
});
console.log(1);
结果是: 1、2、3
执行顺序:
主程序代码——>Promise——>setTimeOut
再举个🌰:
setTimeout(function(){console.log(1)},0); // 宏任务(排后面)
new Promise(function(resolve,reject){
console.log(2); // 它实际上是主程序,根本没有异步
resolve();
}).then(function(){console.log(3) // Promise的resolve 异步
}).then(function(){console.log(4)}); // Promise的reject 异步
process.nextTick(function(){console.log(5)}); // process.nextTick 异步
console.log(6); // 主程序
// 输出2,6,5,3,4,1
主程序代码——>Promise——>setTimeOut
经典案例:
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start')
setTimeout(function(){
console.log('setTimeOut')
}, 0)
async1()
new Promise(function(resolve){
console.log('promise1')
resolve()
}).then(function(){
console.log('promise2')
})
console.log('script end')
输出:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeOut
JS的防抖与节流
防抖
简单来讲,防抖就是当页面的一个标签被反复触发,只会让最后一次
的事件一段时间后生效。
既然涉及到了时间,就需要设置一个计时器,然后计时器延时执行任务:
setTimeOut(function(){
// 这里是要执行的操作
func(); // 执行触发函数
}, wait); // wait 是延迟的时间
因为多次点击后,只有最后一次的可以执行,很明显,多次点击后,前面的定时器被清除了,我们来了解一下定时器的清除:
clearTimeout();
所以就是一个定时器如果存在,那么就清除它,然后从头开始一个计时器:
var timer;
if(timer != null){
clearTimeOut(timer);
}else {
timer = setTimeOut(function(){
func();
}, wait);
}
然后把它们放进一个函数,因为涉及到一个存在问题,可以认为timer是一个全局变量,但是可以利用闭包放进局部变量里面:
function fangdou(func, wait){
var timer;
return function TimeMaker(){
if(timer != null){
clearTimeOut(timer);
}else {
timer = setTimeOut(function(){
func();
}, wait);
}
}
节流
节流和防抖不太一样,如果我们持续点击一个button,那么我们会让button绑定的函数在一个时间段内保证只执行一次,然后此次执行完成后就会开启另外一个时间段来执行下一次的任务,不会执行太多次,但并不是一次。简单来讲一句话,同一时间段,只让第一个操作
执行。
因此可以设计一个标签,判断当前时间段有没有定时器在执行,如果有,那么就可以直接返回了,当时没有定时器,那么就可以在这个时间段申请我的事件。
function jieliu(func, wait){
var timerTag = false; // 没有占用
return function TimeMaker(){
if(timerTag){
return; // 当前有定时器,这个时间段就不用再执行了
}else {
timerTag = true; // 占用这个时间段
setTimeOut(function(){
func();
// 执行完成,可以更改标签允许下一个时间段进行任务了
timerTag = false; // 释放允许占用
}, wait);
}
}