js基础知识整理[节流+防抖+事件循环](二)

802 阅读6分钟

一、函数节流和防抖

  • 封装二者函数,入参都是一个函数,一个延迟时间。返回值都是一个函数(也就是事件回调)。

  • 节流是通过一个开关一个定时器实现。

  • 防抖是通过一个定时器,清除定时器来实现。

  • 二者都是在定时器中执行相应的事件回调。

**@**函数节流

节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。

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下面的脚本)

@执行顺序与注意事项(这块只是大概描述不精准,以下方的代码执行为准):

  1. 第一轮循环 - 首先执行同步任务

  2. 第二轮循环 -  执行异步任务中的微任务

  3. 第三轮循环 - 执行异步任务中的宏任务

重复以上三步......

@注意事项:

  1. Promise构造函数内会和同步任务一起执行。

  2. 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都属于宏任务