Статьи

Polyfilling не должен быть сложным

Автор Аурелио Де Роса для Telerik Mobile Blog.

polyfill_header

Web

Polyfilling не должен быть сложным

В 2010 году Реми Шарп опубликовал в своем блоге статью, в которой представил новое слово: polyfill . Polyfill, как он написал, — это кусок кода (или плагин), который предоставляет технологию, которую вы, разработчик, ожидаете от браузера, чтобы обеспечить изначально. В том же посте он объяснил, почему он придумал это слово:

Ранее в этом году я стал соавтором «Представления HTML5» с Брюсом Лоусоном. Во время исследований и разработок я рассматривал прокладки и методы для подключения отсутствующих API, но они не были достаточно прогрессивным улучшением. Я хотел одно слово, которое представляло бы эту идею, что есть «вещь», которая могла бы подключить браузеры, но не была прогрессивным улучшением, но и не была постепенной деградацией.

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

В этой статье я расскажу о двух примерах функций, один из которых представлен в ECMAScript 6, а другой — в HTML5, для которого я создам соответствующий polyfill, чтобы эти функции можно было использовать также в браузерах, которые не поддерживают их изначально.

Number.isInteger (): простой пример

Один из самых простых примеров, которые я могу вспомнить, — это Number.isInteger()метод polyfill . Этот метод является новым для JavaScript, поскольку он относится к 6-й версии стандарта ECMAScript. Этот метод проверяет, является ли данное значение, переданное в качестве параметра, целым числом. Пример использования показан ниже:

var num = 10;
var floatNum = 13.4
var notNum = 'test';
// prints true
console.log(Number.isInteger(num));
// prints false
console.log(Number.isInteger(floatNum));
// prints false
console.log(Number.isInteger(notNum));

Проблема этого метода заключается в том, что он поддерживается в последних версиях Chrome, Opera и Firefox, но не поддерживается в Internet Explorer или Safari. Это означает, что если вы используете этот метод на своем веб-сайте, как только пользователь, просматривающий с помощью Internet Explorer или Safari, посещает веб-сайт, будет сгенерирована ошибка, поскольку метод не определен. Как следствие ошибки, весь код после этого оператора не будет выполнен. Используя try...catchблок, вы можете обойти эту проблему, но у вас все еще нет собственного метода, чтобы проверить, является ли данное значение целым числом или нет. Это как раз то, где полифилл пригодится.

Многие разработчики считают, что разработка полифилла — это их задача. Но это не правда. Большинство полифилов не очень сложны (некоторые содержат всего 5 строк кода, и вам не нужно пересоздавать Windows в JavaScript), но они требуют немного воображения, терпения и навыков. Воображение необходимо, потому что для некоторых функций вам нужно подумать о творческом способе воспроизведения или имитации функции в вашем полифилле.

Чтобы понять, что я имею в виду, давайте попробуем разработать polyfill для isInteger()метода.

Создание полифилла

Чтобы создать полифилл, вам нужно собрать всю информацию о функции, которую вы хотите воспроизвести. isInteger()Метод принимает только один параметр и возвращает , trueесли переданное значение является целым числом; falseв противном случае. Прежде чем мы углубимся в разработку полизаполнения, важно помнить, что JavaScript является свободно типизированным языком, поэтому все эквивалентности, подобные приведенным ниже, верны:

1 == 'test'
true == 1
'1' == true

Это важно, так как мы исследуем, как построить полифилл.

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

  • 10
  • +23,5
  • 6,342
  • 6

Плавающее число — это целое число с дробной частью, или, с другой точки зрения, целое число — это плавающее число с дробной частью, равной нулю. Итак, чтобы проверить, является ли число целым числом, мы должны быть в состоянии проверить, что дробная часть равна нулю. Для этого мы можем вычесть целую часть из любого числа и проверить, равен ли результат нулю. Это может быть достигнуто путем вычитания числа из себя с использованием Math.floor()метода и проверки, равен ли результат вычитания нулю, как в следующей функции:

function isInteger(number) {
   return number - Math.floor(number) === 0;
}

Это хорошее начало, но что, если вызывающая сторона этой функции передает значение, которое не является числом nullили массивом? Чтобы решить эту проблему, мы должны проверить, является ли данное значение на самом деле Number. Добавление этого изменения в нашу функцию приводит к следующему коду:

function isInteger(number) {
   return typeof number === 'number' && number - Math.floor(number) === 0;
}

До сих пор мы создали функцию, которая делает то, что нам нужно, но это не нативный метод. Мы не можем вызвать нашу isInteger()функцию как Number.isInteger(). Следующим шагом является присоединение нашей функции к Numberтипу данных:

Number.isInteger = function(number) {
   return typeof number === 'number' && number - Math.floor(number) === 0;
};

Осталась одна последняя проблема. Если мы добавим предыдущий код, то каждый браузер, обращающийся к веб-сайту, выполнит его. В результате, если браузер изначально поддерживает метод, последний будет переопределен по нашему определению, чего мы хотим избежать. Чтобы решить эту проблему, мы можем проверить наличие метода и, если метод не существует, мы присоединяем нашу функцию.

Есть два способа, которыми мы можем выполнить эту задачу. Во-первых, обернуть утверждение внутри if:

if (!Number.isInteger) {
   Number.isInteger = function(number) {
      return typeof number === 'number' && number - Math.floor(number) === 0;
   };
}

Второй, который я предпочитаю, состоит в использовании оператора ИЛИ:

Number.isInteger = Number.isInteger || function (number) {
   return typeof number === 'number' && number - Math.floor(number) === 0;
};

Вот и мы! Полностью рабочий полифилл, который мы можем использовать в нашем коде… почти. Этот polyfill работает в большинстве случаев, но он не был создан в соответствии с этими раздражающими длинными описаниями, известными как спецификации. Полное заполнение для Number.isInteger()метода, взятого на соответствующей странице в Mozilla Developer Network, выглядит следующим образом:

if (!Number.isInteger) {
  Number.isInteger = function isInteger(nVal) {
    return typeof nVal === 'number'
      && isFinite(nVal)
      && nVal > -9007199254740992
      && nVal < 9007199254740992
      && Math.floor(nVal) === nVal;
  };
}

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

Атрибут placeholder: более сложный случай

Некоторые из вас могут подумать, что я выбрал очень простой пример, который не отражает реальность сложности построения полифилла. Вы можете подумать, что создание полифилла для чего-то более сложного, например, встроенной визуальной функции, было бы слишком сложно сделать самостоятельно. В этом разделе я попытаюсь доказать, что это неправильно, объяснив, как разработать polyfill для placeholderатрибута. Еще раз хочу подчеркнуть, что помимо некоторых исключений, разработка полифилла — это просто комбинация воображения, терпения и навыков.

В этом разделе я не буду охватывать все детали, как я делал в предыдущем примере, но я предоставлю вам достаточно подробностей, чтобы понять суть.

Разработка полифилла

placeholderАтрибут используется на inputполях и задает текст , чтобы показать в поле , пока выигрыш поля не фокус, когда текст скрыт. Еще одна важная деталь, placeholderкасающаяся атрибута, состоит в том, что цвет текста, отображаемого внутри полей, является серым и соответствует значению HEX A9A9A9.

Хотя есть много методов, которые вы можете использовать для создания полизаполнения для placeholderатрибута, я расскажу о том, который использует valueатрибут для имитации функции. Когда страница загрузится, мы скопируем значение placeholderатрибута в valueатрибут. Это делается с помощью следующего кода:

var inputsWithPlacehoder = document.querySelectorAll('input[placeholder]');
for (var i = 0; i < inputsWithPlacehoder.length; i++) {
   inputsWithPlacehoder[i].setAttribute('value', inputsWithPlacehoder[i].getAttribute('placeholder'));
}

Чтобы воспроизвести цвет заполнителя, мы можем создать класс, подобный следующему:

input.fake-placeholder
{
   color: #A9A9A9;
}

Вы можете спросить: «Зачем использовать класс?» Причина в том, что ваш сайт мог определить цвет для текста поля формы, например зеленый. В этом случае текст поля должен быть серым, когда заполнитель находится на месте, и зеленым, когда поле содержит текст, написанный пользователем. Для этого мы переключаем fake-placeholderкласс.

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

  • focus: Если в поле установлен заполнитель, а пользователь ничего не набрал, мы переместим курсор в начало текста заполнителя.
  • keydown: Удалите заполнитель и восстановите естественный цвет текста.
  • keyup: Если значение пустое, восстановите заполнитель и серый цвет.
  • blur: Если значение пусто, восстановите заполнитель и серый цвет.

Например, чтобы переместить курсор в начало текста заполнителя, мы должны добавить следующую функцию в качестве прослушивателя focusсобытия:

function moveCaret() {
   if (this.value === this.getAttribute('placeholder')) {
      if (this.createTextRange) {
         var part = this.createTextRange();
         part.move('character', 0);
         part.select();
      } else if (this.setSelectionRange) {
         this.setSelectionRange(0, 0);
      }
   }
}

Примечание. Фактический код немного сложнее из-за проблемы Chrome # 32865 . Если вы хотите прочитать весь необходимый код, взгляните на источник моего плагина jQuery Audero Unified Placeholder . Я уже говорил, что при разработке полифилса необходимо терпение, не так ли?

Используя эту информацию, вы теперь сможете создавать собственный polyfill placeholderатрибута, но, если вы ищете полное руководство, я написал статью под названием Как создать расширенный HTML5-заполнитель-заполнитель, который вы можете прочитать.

Другие примеры полифилов

Полифиллы могут быть настолько простыми или сложными, насколько вы хотите, и обычно объем необходимого кода зависит от функции, которую вы заполняете. В настоящее время существуют тысячи полифиллов, и вы можете найти хороший список в репозитории Modernizr в разделе HTML5 Cross Browser Polyfills . На этой странице перечислены все прокладки, запасные варианты и полифиллы для внедрения функций HTML5 в браузеры, которые их не поддерживают. Некоторые примеры взяты из этого списка:

Выводы

В этой статье я познакомил вас с концепцией полизаполнения и с тем, как со временем, воображением и навыками вы можете создавать свои собственные, чтобы поддерживать функции, которые вам нравятся и которые вам нужны в браузерах, которые изначально не предоставляют эти функции. Мы рассмотрели простой пример, показывающий, как разработать полифил для Number.isInteger()метода, введенного в ECMAScript 6. Затем мы обсудили, как создать полифилл для placeholderатрибута, введенного в HTML5.

Я надеюсь, вам понравилась эта статья, и вы обнаружите, что полифилы не страшны, и что вы могли бы подумать о том, чтобы создать их для себя.

Изображение заголовка любезно предоставлено Сарой Рид