Фронтенд и пять подонков для изучения JavaScript — антишейк, троттлинг и rAF

JavaScript

После прочтения "Advanced JavaScript Programming" и некоторых блогов в Интернете, я чувствую, что концепции регулирования функции и функции анти-дрожания противоположны. Понятия, которые я написал о анти-дрожании и троттлинге, зависят от концепции большинства людей, и По мотивам лондонского фронтенд-инженера Дэвида Корбачо.гостевой пост. Статья хорошо написана, и есть соответствующий код для работы, который легче понять. На самом деле, я не думаю, что это важно.Этот метод называется троттлинг или этот метод называется анти-тряска, если вы можете ясно объяснить и использовать его в производстве, вам не нужно слишком беспокоиться о имя.

«Мстители 4: Финал» представляет собой конец эпохи. С тех пор, как я посмотрел «Железного человека» с более чем 300 МБ на втором году обучения в старшей школе в 2008 году, Кинематографическая вселенная Marvel сопровождала меня на протяжении всего пути, как Волшебный мир Гарри Поттер. Конец эры обязательно станет началом новой эры. Конец игры? ? Нет!

I LOVE YOU THREE THOUSANDS TIMES

I AM IRON MAN

banner献给复仇者联盟的超级英雄们
баннер, посвященный супергероям Мстителей 🙏🙏🙏

Зачем антишейк и дроссель? ?

Стабилизатора такжедросселированиеЭто два похожих метода, оба предназначены для уменьшения количества бесполезных триггеров функции, чтобы повысить производительность или избежать пустой траты ресурсов. Мы все знаем, что js очень дорог при работе с DOM, по сравнению с операциями без DOM требует больше памяти и процессорного времени, и если наша функция часто срабатывает при прокрутке полосы прокрутки или изменении размера окна, она все равно будет быть Если страница застряла, если это сложный набор операционной логики DOM, это также может привести к сбою браузера. Поэтому нам нужно контролировать количество триггеров, чтобы оптимизировать выполнение кода.

Сарафанное радио бесполезно, и все могут не понять, как это работает, так что давайте на примере: ⬇️

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>我要节流</title>
  <style>
    body{ height: 3000px; }
    #centerNum { width: 100px; height: 100px; line-height: 100px; text-align: center; position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); }
  </style>
</head>
<body>
  <h1 id="centerNum">0</h1>
  <script>
    var num = 0;
    window.onscroll = function () {
      var root = document.getElementsByTagName('body'),
      h = document.getElementById('centerNum');
      h.innerHTML = num;
      num ++;
    }
  </script>
</body>
</html>

давайте одинwindow.onscrollфункция, которая меняется один раз, пока вы прокручиваете<h1>Число в метке, на картинке выше, мы видим, что этот триггер очень частый, если мы не будем мешать, пусть эта функция срабатывает произвольно, разве это не рай 😡

Отказаться

Что такое анти-шейк

Что такое анти-шейк? Насколько я понимаю, когда метод запускается непрерывно, метод не выполняется, но метод выполняется, когда непрерывный запуск заканчивается.

Например: прямой лифт, люди поднимаются один за другим (непрерывный триггер), когда больше не поднимаются люди (стоп, непрерывный триггер), лифт закроет дверь и двинется (метод выполнения).

Как этого добиться

Выше приведен пример, который я сделал, симулируя человека в лифте. Может быть, так будет более интуитивно смотреть на это. Ниже приведен код, который я реализовал. Это, вероятно, означает, что когда я сажусь на человека, лифт запускается. Я был в лифте, лифт запускается. Если вы не двигаетесь, пока не окажетесь на дороге, вы закроете дверь и начнете

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>电梯上人</title>
  <style>

  </style>
</head>
<body>
  <button id="addBtn">电梯上人,人数+1</button><button id="resetBtn">重置</button>
  <p id="personNum">电梯人数:0(假设电梯可以无限装人)</p>
  <p id="elevatorStatus">电梯停靠</p>
  <script>
    var personNum = 0; // 电梯人数
    var closeDoor = null; // 电梯启动延时程序
    var addBtn = document.getElementById('addBtn'); // 获取添加人数按钮
    var personNumP = document.getElementById('personNum'); // 获取显示人数的标签
    var resetBtn = document.getElementById('resetBtn'); // 获取重置按钮
    var elevatorStatus = document.getElementById('elevatorStatus'); // 获取电梯状态标签
    /**
     * @method 电梯内添加人数
     * @description 点击一次电梯内增加一人,增加完人数电梯启动初始化
     */
    function addPerson() {
      personNum ++;
      personNumP.innerHTML = `电梯人数:${personNum}(假设电梯可以无限装人)`
      initElevatorStart();
    }
    /**
     * @method 电梯启动
     * @description 电梯启动,置灰添加人数按钮,禁止上人
     */
    function elevatorStart() {
      elevatorStatus.innerHTML = '电梯启动';
      addBtn.disabled = true;
    }
    /**
     * @method 电梯启动初始化
     * @description 清除之前的关门延时,并重新计算关门延时500ms,意思是当不在触发电梯启动初始化函数时,500ms后启动电梯
     */
    function initElevatorStart() {
      clearTimeout(closeDoor);
      closeDoor = setTimeout(function () {
        elevatorStart();
      }, 500);
    }
    /**
     * @method 重置电梯
     */
    function reset() {
      personNum = 0;
      personNumP.innerHTML = `电梯人数:${personNum}(假设电梯可以无限装人)`
      elevatorStatus.innerHTML = '电梯停靠';
      addBtn.disabled = false;
    }

    addBtn.addEventListener('click', addPerson);
    resetBtn.addEventListener('click', reset);
  </script>
</body>
</html>

Приведенный выше код означает, что если я один в лифте, мне нужно закрыть дверь лифта (срабатываяinitElevatorStart()способ), после чего лифт запускается. Но я нажимал кнопку мастера, лифт не закрывал дверь и не запускал лифт.elevatorStart()метод.

Ядром кода являетсяinitElevatorStart()метод, этот метод запускает метод лифта, когда дверь закрывает дверь, которая действительно должна быть выполненаelevatorStart()добавил слойsetTimeoutметод, то есть при вызове этого метода мы выполним тот метод, который действительно нужно выполнить через 500 миллисекунд. Если в течение 500 миллисекунд, он перезапускаетсяinitElevatorStart()метод, вам нужно повторно время, иначе люди будут пойманы, и вы потеряете деньги. . . .

Это самая грубая реализация антишейка 😳😳😳

Базовая форма

Ниже приведена самая базовая форма этой реализации защиты от сотрясений, которую мы видели в разделе «Расширенное программирование на JavaScript»⬇️.

var processor = {
  timeoutId: null, // 相当于延时setTimeout的一个标记,方便清除的时候使用

  // 实际进行处理的方法
  // 连续触发停止以后需要触发的代码
  performProcessiong: function () {
    // 实际执行的代码
    // 这里实际就是需要在停止触发的时候执行的代码
  },

  // 初始处理调用的方法
  // 在实际需要触发的代码外面包一层延时clearTimeout方法,以便控制连续触发带来的无用调用
  process: function () {
    clearTimeout(this.timeoutId); // 先清除之前的延时,并在下面重新开始计算时间

    var that = this; // 我们需要保存作用域,因为下面的setTimeout的作用域是在window,调用不要我们需要执行的this.performProcessiong方法
    this.timeoutId = setTimeout(function () { // 100毫秒以后执行performProcessiong方法
      that.performProcessiong();
    }, 100) // 如果还没有执行就又被触发,会根据上面的clearTimeout来清除并重新开始计算
  }
};

// 尝试开始执行
processor.process(); // 需要重新绑定在一个触发条件里

Вышеприведенный код является самой простой реализацией.Он обернут в объект, а затем вызывают друг друга в объекте.Комментарии внутри должны быть в состоянии четко объяснить, что делает каждый шаг.Нижнийprocessor.process()Когда мы на самом деле используем его, нам обязательно нужно привязать его к условию триггера.Например, в предыдущей задаче о лифте нам нужно поставитьprocessor.process()Метод обязательно увеличит количество людей, так что вызовов будет несколько.

Это очень простая реализация, в реальной производственной среде логика будет относительно сложной, но изменения неотделимы от основ, и сделать вывод о самых основных вещах не проблема.

Это должно называться "качаться вперед"? ?

Конкретно я не знаю, как это должно называться. Это называется "Leading edge" по-английски. Неважно, как это называется по-китайски, просто знайте, что это значит. Из кода, который мы написали ранее, очевидно, что когда мы запускаем метод непрерывно, мы находимся вsetTimeoutПосле окончания реального выполнения, но бывает и другая ситуация, то есть, когда мы непрерывно запускаем метод, выполняется первый триггер, а затем последующие непрерывные триггеры уже не выполняются, и после остановки непрерывного триггера, после delay , триггер сработает снова.

Я все еще ворую фотографии. . . Распространенная форма следующая

Выполняется в конце непрерывного срабатывания, а "встряхивания вперед" о котором мы сейчас говорим - это следующая ситуация

Он выполняется в начале непрерывного триггера, а затем последующий непрерывный триггер не выполняется.После остановки непрерывного триггера триггер будет выполняться снова по истечении времени задержки.

Далее то, что я написал сам, что, вероятно, означает это, и реализация кода также выложена

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>电梯上人</title>
  <style>

  </style>
</head>
<body>
  <button id="addBtn">电梯上人,人数+1</button><button id="resetBtn">重置</button>
  <p id="personNum">电梯人数:0(假设电梯可以无限装人)</p>
  <script>
    var personNum = 0; // 电梯人数
    var okNext = true; // 是否可进行下次执行
    var timeoutFn = null;
    var addBtn = document.getElementById('addBtn'); // 获取添加人数按钮
    var personNumP = document.getElementById('personNum'); // 获取显示人数的标签
    var resetBtn = document.getElementById('resetBtn'); // 获取重置按钮
    /**
     * @method 电梯添加人数
     * @description 电梯可以上人,但是上人以后就不能再上了,不管怎么触发都不行,除非停止触发500毫秒以后,再触发的时候才可以继续执行
     */
    function addPerson() {
      if (okNext) {
        okNext = false;
        personNum ++
        personNumP.innerHTML = `电梯人数:${personNum}(假设电梯可以无限装人)`
      }
      clearTimeout(timeoutFn);
      timeoutFn = setTimeout(function () {
        okNext = true;
      }, 500)
    }
    /**
     * @method 重置
     */
    function reset() {
      personNum = 0;
      personNumP.innerHTML = '电梯人数:0(假设电梯可以无限装人)';
    }

    addBtn.addEventListener('click', addPerson);
    resetBtn.addEventListener('click', reset);
  </script>
</body>
</html>

Если вы не понимаете приведенный выше код, вы можете вставить его напрямую и выполнить следующее, чтобы увидеть, как он выглядит, и тогда вы узнаете, что он означает.

Код полностью написан мной, если что-то не так, поправьте меня.

Дроссель

что дросселирует

Дросселирование также является моим собственным пониманием.В определенный период времени, когда метод непрерывно запускается, контролируется количество выполнений метода.

Также приведем пример: если метро входит в ворота станции, и через 10 секунд входит один человек (метод выполняется в течение 10 секунд), неважно, 5 человек, 10 человек или 20 человек приходят за эти 10 секунд, входит только один человек (с 10 секунд после первого триггера, сколько бы раз он ни срабатывал, он не будет выполняться до следующих 10 секунд).

Как этого добиться? ?

отметка времени

Сначала мы используем метку времени, чтобы оценить интервал времени до и после, а затем мы можем узнать, сколько времени прошло с тех пор, как я в последний раз выполнял этот метод.После такого длительного времени, превысило ли оно время, указанное мной.Если время превышает, меня снова могут казнить

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>地铁进站</title>
</head>
<body>
  <button id="addBtn">进站人数+1</button><button id="resetBtn">重置</button>
  <p id="personTotal">旅客总人数:0</p>
  <p id="personNum">进站人数:0</p>
  <script>
    var personNum = 0; // 进站人数
    var personTotal = 0; // 一共来了多少人
    var addBtn = document.getElementById('addBtn'); // 获取添加人数按钮
    var personNumP = document.getElementById('personNum'); // 获取显示人数的标签
    var personTotalP = document.getElementById('personTotal'); // 获取显示总人数的标签
    var resetBtn = document.getElementById('resetBtn'); // 获取重置按钮
    /**
     * @method 增加进站人数
     * @description 每个时间间隔执行的方法
     */
    function addPerson() {
      personNum ++;
      personNumP.innerHTML = `进站人数:${personNum}`;
    }
    /**
     * @method 节流方法(时间戳)
     * @param {Function} fn 需要节流的实际方法
     * @param {Number} wait 需要控制的时间长度
     * @description 根据上一次执行的时间,和这一次执行的时间做比较,如果大于控制的时间,就可以执行
     */
    function throttle(fn, wait) {
      var prev = 0; // 第一次执行的时候是0,所以第一次点击的时候肯定大于这个数,所以会立马执行
      return function () {
        var context = this;
        var args = arguments;
        var now = Date.now(); // 实际执行的时间
        personTotal ++;
        personTotalP.innerHTML = `旅客总人数:${personTotal}`;
        if (now - prev >= wait) { // 执行的时间是不是比上次执行的时间大于需要延迟的时间,大于,我们就执行
          fn.apply(context, args);
          prev = now; // 执行了以后,重置上一次执行的时间为刚刚执行这次函数的时间,下次执行就用这个时间为基准
        }
      }
    }
    /**
     * @method 重置
     */
    function reset() {
      personNum = 0;
      personTotal = 0;
      personNumP.innerHTML = '进站人数:0';
      personTotalP.innerHTML = `旅客总人数:0`;
    }

    addBtn.addEventListener('click', throttle(addPerson, 1000));
    resetBtn.addEventListener('click', reset);
  </script>
</body>
</html>

Функция дроссельной заслонкиthrottleИспользуются знания о области действия, вызове, применении и закрытии.Если вы не понимаете, вы можете прочитать мою предыдущую статью

  1. «Front-end сразится с пятью подонками, чтобы выучить JavaScript — закрытие»
  2. «Сразитесь с пятью подонками, чтобы изучить JavaScript — позвоните, подайте заявку и привяжите»

В приведенном выше коде я чувствую, что очень интуитивно понятно, можно ли выполнить следующую функцию в зависимости от времени до и после решения. Ссылаясь на комментарии в коде, я думаю, что смогу его понять 😳😳😳

setTimeout

если мы используемsetTimeout, нам просто нужно изменитьthrottleметод

/**
 * @method 节流方法(setTimeout)
 * @param {Function} fn 需要节流的实际方法
 * @param {Number} wait 需要控制的时间长度
 * @description 这个方法就很类似防抖了,就是判断当前函数有没有延迟setTimeout函数,有的话就不执行了
 */
function throttle(fn, wait) {
  var timeout = null; 
  return function () {
    var context = this;
    var args = arguments;
    personTotal ++;
    personTotalP.innerHTML = `旅客总人数:${personTotal}`;
    if (!timeout) { 
      var that = this;
      timeout = setTimeout(() => {
        timeout = null;
        fn.apply(context, args)
      }, wait)
    }
  }
}

Хотя нам нужно всего лишь изменить несколько строк кода, чтобы добитьсяsetTimeoutЭто метод для достижения дросселирования, но если мы внимательно посмотрим на картинку выше, мы обнаружим, что когда я нажимаю в первый раз, количество прибывающих пассажиров не увеличивается, что отличается от нашей реальной ситуации. Не ждите, я смогу сделать яму прямо сейчас, не так ли? И когда я закончу увеличивать количество людей, прибывающие пассажиры добавят еще одного человека по истечении времени ожидания, что, конечно, не то, что мы хотим видеть.

Использовать временную метку или setTimeout зависит от бизнес-сценария.

rAF(requestAnimationFrame)

А? ? Что такое РАФ? Что такое requestAnimationFrame? Когда я не писал этот блог, я не знал, что под окном есть метод Это удивительно, так что же делает этот метод? ?

Сообщите браузеру, что вы хотите выполнить анимацию, и попросите браузер вызвать указанную функцию обратного вызова, чтобы обновить анимацию перед следующей перерисовкой. Этот метод должен передать функцию обратного вызова в качестве параметра, функция обратного вызова будет выполнена до следующей перерисовки браузера. ———《Веб-документы MDN》

Это использует это, чтобы перерисовать анимацию все время, а затем заставить его выглядеть как анимацию. Процесс перерезывания является очень частой операцией, поэтому, если мы пишем это и не мешать, это приведет к тому, что серьезные отходы Ресурсы. Итак, мы можем использовать запросанимационные фрам, чтобы использовать нашу анимацию, чтобы выглядеть гладко и не вызываться часто

преимущество

  1. Цель — 60 кадров в секунду (кадр 16 мс), и браузер сам решит, как запланировать наилучшее время для рендеринга.
  2. Относительно простой и стандартный API, который не изменится в будущем, что снижает затраты на обслуживание.

недостаток

  1. rAF это внутренний апи, поэтому модифицировать его нам не удобно
  2. Не работает, если вкладка браузера не активирована
  3. Плохая совместимость, по-прежнему не поддерживается в IE9, Opera Mini и старом Android.
  4. Не может использовать в узле

Давайте использовать РАФ

прямо над

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>rAF使用</title>
  <style>
    #SomeElementYouWantToAnimate {
      width: 100px;
      height: 100px;
      background-color: #000;
    }
  </style>
</head>
<body>
  <div id="SomeElementYouWantToAnimate"></div>
  <script>
    var start = null;
    var element = document.getElementById('SomeElementYouWantToAnimate');
    element.style.position = 'absolute';
    /**
     * @method 移动我们的小黑方块
     */
    function step(timestamp) {
      if (!start) start = timestamp;
      var progress = timestamp - start;
      element.style.left = Math.min(progress / 10, 200) + 'px';
      if (progress < 2000) {
        window.requestAnimationFrame(step);
      }
    }

    window.requestAnimationFrame(step);
  </script>
</body>
</html>

Суммировать

rAF — это внутренний API, который выполняется в фиксированные 16 миллисекунд, потому что человеческий глаз будет чувствовать себя очень плавно, когда принимается анимация 60 кадров в секунду.Если нам нужно изменить время выполнения rAF, мы можем только написать метод анимации сами, дросселирование по-прежнему Anti-shake, см. личные предпочтения

близко

Anti-shake: запускайте функцию непрерывно, независимо от того, запущена ли она для запуска или завершения выполнения, пока она запускается непрерывно, она будет выполняться только один раз.

Регулирование: выполняется только один раз в течение указанного времени, независимо от того, сколько раз оно запускается в течение указанного времени.

rAF: его также можно рассматривать как средство дросселирования, собственный API, предназначенный для плавной анимации при использовании как можно меньшего количества ресурсов.

Соответствующие _.throttle и _.debounce в lodash, на мой взгляд, являются лучшими практиками, и их рекомендуется использовать.

End Game

«Мстители 4» — это конец вселенной Marvel на данном этапе, «Гарри Поттер» и «Наруто» закончили фильмы один за другим, хотя это напоминает нам, что молодость медленно уходит, как и герои в League of Legends. это предложение, у нас есть новый враг по имени "жизнь". Когда эти концы на самом деле не конец, у «Гарри Поттера» есть «Фантастические твари», у «Наруто» есть «Биография Борена», у «Железного человека» есть «Человек-паук», младшие — от старших. продолжаем бежать вспять.Мы тоже вступили в следующий этап из наших зеленых лет.Давайте работать усердно! !


Я передовая пятая сволочь, ученица начальной школы в переднем мире.