一、函数节流和防抖
-
封装二者函数,入参都是一个函数,一个延迟时间。返回值都是一个函数(也就是事件回调)。
-
节流是通过一个开关一个定时器实现。
-
防抖是通过一个定时器,清除定时器来实现。
-
二者都是在定时器中执行相应的事件回调。
**@**函数节流
节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。
function throttle(func, delay) {
let run = true
return function () {
if (!run) {
return // 如果开关关闭了,那就直接不执行下边的代码
}
run = false // 持续触发的话,run一直是false,就会停在上边的判断那里
setTimeout(() => {
func.apply(this, arguments)
run = true // 定时器到时间之后,会把开关打开,我们的函数就会被执行
}, delay)
}
}
// 调用的时候:
box.onmousemove = throttle(function (e) {
box.innerHTML = `${e.clientX}, ${e.clientY}`
}, 1000)
// 这样,就实现了节流,节流还可以用时间间隔去控制,就是记录上一次函数的执行时间,
// 与当前时间作比较,如果当前时间与上次执行时间的时间差大于一个值,就执行。
@函数防抖
function debounce(func, delay) {
let timeout
return function() {
clearTimeout(timeout) // 如果持续触发,那么就清除定时器,定时器的回调就不会执行。
timeout = setTimeout(() => {
func.apply(this, arguments)
}, delay)
}
}
@小tips
@最简单的防抖和节流
防抖通过进入函数先清除定时器。如多次点击,则一直在清除定时器,而导致无法执行定时器中的回调。
节流是通过一个开关一个定时器来实现,点击之前开关是打开的状态,条件为真,进入if分支语句,做一些事情,然后,把开关关上。在写一个定时器,在指定时间后把开关打开。
//防抖:
let time2;
document.getElementById('防抖').onclick =
function () {
clearTimeout(time2);
time2=setTimeout(function () {
//do something
},2000);
};
//节流:
let bool=true;
document.getElementById('节流').onclick = function () {
if(bool){
//do something
bool=false;
setTimeout(()=>{
bool=true
},2000)
}
}
@函数防抖和节流,都是控制事件触发频率的方法**。**应用场景有很多,输入框持续输入,将输入内容远程校验、多次触发点击事件、onScroll等等。 为了说明问题,假设一个场景:鼠标滑过一个div,触发onmousemove事件,它内部的文字会显示当前鼠标的坐标。
记住鼠标滑过当前位置的坐标位置触发事件的效果,
函数防抖是停下鼠标过指定时间才执行一次。(持续触发不执行,冷静下来过一会执行)。
函数节流是无论你怎么触发,在指定时间内只执行一次(有自己的节奏)。
@
@总结
防抖和节流巧妙地用了setTimeout,来控制函数执行的时机,优点很明显,可以节约性能,不至于多次触发复杂的业务逻辑而造成页面卡顿。
二、JS的执行顺序(事件循环EventLoop)
因为js是单线程的脚本语言,在同一时间,只能做一件事情,为了协调事件、用户交互、UI渲染、网络请求等行为、防止主线程阻塞。EventLoop应用而生~
@任务分为:
- 同步任务
- 异步任务
@异步任务分为:
-
宏任务(setTimeout, setInterval、点击等事件、请求、IO读写)
-
微任务(promise.then、await下面的脚本)
@执行顺序与注意事项(这块只是大概描述不精准,以下方的代码执行为准):
-
第一轮循环 - 首先执行同步任务
-
第二轮循环 - 执行异步任务中的微任务
-
第三轮循环 - 执行异步任务中的宏任务
重复以上三步......
@注意事项:
-
Promise构造函数内会和同步任务一起执行。
-
async 函数中与await后的脚本 和同步任务一起执行(是promise的语法糖(以同步的方式去写异步的代码))
@多看小例子:
setTimeout(function () {
console.log('6')
}, 0)
console.log('1')
async function async1() {
console.log('2')
await async2()
console.log('5')
}
async function async2() {
console.log('3')
}
async1()
console.log('4')
//6是宏任务在下一轮事件循环执行
//先同步输出1,然后调用了async1(),输出2。
//await async2() 会先运行async2(),5进入等待状态。
//输出3,这个时候先执行async函数外的同步代码输出4。
//最后await拿到等待的结果继续往下执行输出5。
//进入第二轮事件循环输出6。
console.log('1')
async function async1() {
console.log('2')
await 'await的结果'
console.log('5')
}
async1()
console.log('3')
new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('6')
})
//首先输出1,然后进入async1()函数,输出2。
//await后面虽然是一个直接量,但是还是会先执行async函数外的同步代码。
//输出3,进入Promise输出4,then回调进入微任务队列。
//现在同步代码执行完了,回到async函数继续执行输出5。
//最后运行微任务输出6。
async function async1() {
console.log('2')
await async2()
console.log('7')
}
async function async2() {
console.log('3')
}
setTimeout(function () {
console.log('8')
}, 0)
console.log('1')
async1()
new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('6')
})
console.log('5')
//首先输出同步代码1,然后进入async1方法输出2。
//因为遇到await所以先进入async2方法,后面的7处于等待状态。
//在async2中输出3,现在跳出async函数先执行外面的同步代码。
//输出4,5。then回调进入微任务栈。
//现在宏任务执行完了,执行微任务输出6。
//然后回到async1函数接着往下执行输出7
setTimeout(function () {
console.log('1')
}, 0)
new Promise(function (resolve, reject) {
resolve(2);
console.log('0');
}).then(function (e) { console.log(e) });
console.log('3');
// 输出0,promise构造 函数的先执行
// 继续向下执行同步代码 输出3
// 执行下一轮微任务输出2
// 最后一轮宏任务输出1
setTimeout(() => {
console.log('setTimeout');
}, 0);
let promise = new Promise(resolve => {
console.log(1);
resolve();
}).then(data => {
console.log(3);
}).then(data => {
console.log(4);
});
console.log(2);
//1 2 3 4
setTimeout(() => {
console.log('setTimeout');
}, 0);
let promise = new Promise(resolve => {
console.log(1);
resolve();
}).then(data => {
console.log(3);
}).then(data => {
console.log(4);
});
console.log(2);
//1 2 3 4
@小tips
JS中常见的异步任务 定时器、ajax、事件绑定、回调函数、async await、promise
@同步需要等待,会阻塞
@单线程就是同时只能做一件事情
@同步和异步的区别: 同步会阻塞代码执行,而异步不会,
alert是同步,setTImeout是异步
@使用异步的场景: 只要需要等待的地方就需要异步
1. 网络请求 ajax请求 动态img加载
2. 事件绑定。爱点不点,不点正常往下走,点了再执行回调
3. 定时任务 setTImeout. setInveral 等等
总结
- XHR回调、事件回调(鼠标键盘事件)、setImmediate、setTimeout、setInterval、indexedDB数据库操作等I/O以及UI rendering都属于宏任务