Статьи

Я знаю JQuery. Что теперь?

Я дал этот доклад: я знаю, JQuery. Что теперь? на jQuery UK 2013, но вместо моего обычного подхода post-it взрыв на моем столе, я сначала написал пост и создал слайды из поста. Итак, вот мой (довольно неотредактированный) краткий обзор того, как я использовал jQuery, и как я смотрю на то, где я использую нативную технологию браузера.

7 лет назад…

17 июня 2006 года я публикую свою первую в истории реальную статью : использование обычного JavaScript и упрощение до синтаксиса jQuery. Он превратил 14 строк JavaScript в 3 строки jQuery (до jQuery 1.0).

Наиболее важно, что он прошел путь от некоторой сложной DOM-навигации к простому выражению CSS и добавил класс, как только это будет сделано. Оригинальный JavaScript также был довольно хрупким, поэтому у вас была одинаковая разметка.

Я представил jQuery команде разработчиков, с которыми работал, еще в середине 2000-х годов, и даже дизайнеры могли видеть привлекательность (поскольку они обычно понимали CSS-селекторы (и, следовательно, как родился jQuery для дизайнеров)).

Навигация по DOM внезапно стала легкой

В то время в DOM было сложно ориентироваться. Вы можете быть уверены, что если бы он работал в Firefox 1.5, он не работал бы в IE6.

Простота, с которой jQuery можно было изучить, была привлекательной для меня. Вся навигация по DOM осуществлялась с использованием CSS-выражения с использованием какой-то безумной магии черного ящика, придуманной Джоном Резигом — сохраняя мои ограниченные возможности мозга, и, получив эти DOM-узлы, я мог делать с ними то, что хотел (обычно некоторые комбинации показа скрывается, исчезает и т. д.)

Грокинговый Аякс

JQuery также абстрагировал AJAX для меня. Термин был только что придуман ранее в 2005 году, но документация была не широкой и не тривиальной для понимания (вспомните, как вычислительная мощность моего мозга была ниже).

Это был первый раз, когда мне действительно пришлось иметь дело с объектом XMLHttpRequest, и, увидев его впервые, я понял onreadystatechangeсобытие и комбинацию и не был явно ясен! JQuery (и другие библиотеки) также убрал беспорядок, который был XHR в IE через ActiveX…this.statusthis.readyState

function getXmlHttpRequest() {
  var xhr;
  if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
  } else {
    try {
      xhr = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) {
        xhr = false;
      }
    }
  }
  return xhr;
}

// disclaimer: John's jQuery version is a lot more elegant!

Как только я увидел, что с помощью утилиты ajax внутри jQuery для всасывания HTML-адреса URL (который, как правило, мы хотели сделать ajax), внезапно щелкнул ajax.

JQuery быстро стал моей стандартной утилитой на многие годы. Это был мой швейцарский армейский нож, как бы украдавший титул Адама !

Назад в будущее: сегодня

Давайте перенесемся назад сегодня. Что случилось за эти годы?

Начнем с того, что моя начальная позиция по умолчанию больше не «включает jQuery по умолчанию». Я понимаю больше JavaScript и как все работает.

У меня есть свои собственные критерии, когда включать jQuery, а когда не включать jQuery. Но если я не включу jQuery, что тогда?

За эти 7 лет произошло немало. Вероятно, одним из наиболее важных шагов вперед было введение querySelectorAll.

Возможность дать нативной функции браузера выражение CSS, и он выполняет работу по навигации по DOM, является огромной (буквально) частью jQuery. Основная поддержка была в Chrome с самого начала, а также в IE8 и Firefox 3.5 примерно в середине 2009 года.

Эндрю Ланни (из PhoneGap & Adobe) обладает блестящей простотой в функции bling:

var $ = document.querySelectorAll.bind(document);
Element.prototype.on = Element.prototype.addEventListener;

$('#somelink')[0].on('touchstart', handleTouch);

Красиво просто.

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

Когда я всегда использую jQuery

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

Прежде чем я это сделаю, я должен указать, когда я абсолютно не использую jQuery: если я пытаюсь воспроизвести ошибку браузера, я никогда не использую библиотеку. Если вы пытаетесь скрыть ошибку, чтобы подать проблему, вы хотите, чтобы ваш случай был как можно меньше (если, конечно, вы не сообщаете об ошибке в jQuery!).

1. Когда проект должен работать в не-горчичных браузерах

Би-би-си довольно красноречиво относится к тому, что они определяют как « подрезание горчицы », и теперь я думаю об этом, это мой критерий того, когда включать jQuery по умолчанию.

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

Что режет горчицу?

Это почти так же просто, как: есть ли у этого браузера querySelectorAll?

BBC использует следующий тест для резки горчицы:

if (querySelector in document && 
    localStorage in window && 
    addEventListener in window) {
    // bootstrap the JavaScript application
}

Я знаю, что IE8 не поддерживает addEventListener( но есть поли-заполнение ), поэтому, если это важный браузер для проекта, я знаю, что не хочу запускать скачки проекта для IE8.

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

Я также классифицирую это как «когда сложность превышает легкость».

2. Когда это быстро и грязно

Если я создаю проверку концепции, проверяю идею и, как правило, хакую и создаю JS Bin, я обычно добавляю jQuery по умолчанию. Это спасает меня от размышлений.

JQuery-бесплатно

Вы можете подумать: «Так, использует ли Реми jQuery, и если он этого не делает, то он просто все заново реализует?».

Я, конечно, не хочу изобретать велосипед. Если я нахожу, что разработка без jQuery только побуждает меня заново создавать большую часть функциональности jQuery с нуля, то я явно трачу свое время.

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

Так как же мне жить без jQuery и насколько хороша поддержка?

document.ready

Даже когда я использую jQuery, если у меня (или у моей компании) есть контроль над проектом, я очень, очень редко использую (или укороченную версию ).document.ready$(function)

Это потому, что я поместил весь свой JavaScript ниже DOM, перед </body>тегом. Таким образом, я всегда знаю, что DOM будет скомпилирован к этому моменту.

Надеюсь, это общеизвестно, но JavaScript блокирует рендеринг. Поместите свой JavaScript над контентом, и ваш сервер остановится, и вы получите пустую страницу. Я использовал этот пример много раз раньше, но «значок» твиттера ( давным- давно) раньше вставлялся в ваш HTML. Их сайт часто падал, а мой блог (также несущий значок твиттера) зависал с пустой страницей, создавая впечатление, что мой сайт не работает.

.attr (‘значение’) и .attr (‘href’)

Мне грустно, когда я вижу jQuery для получения значения из элемента ввода:

$('input').on('change', function () {
  var value = $(this).attr('value');

  alert('The new value is' + value);
});

Зачем? Потому что вы можете получить значение с помощью . Что еще более важно — вы должны подумать о том, как вы используете библиотеку JavaScript. Не вызывайте без необходимости jQuery, если вам это не нужно.this.value

На самом деле, это не JQuery, а лучшая практика. Код должен просто читать:

$('input').on('change', function () {


  alert('The new value is' + this.value);
});

Это также часто использовать JQuery , чтобы получить HREF якоря: , но вы также можете легко получить это от знания DOM: . Однако обратите внимание, что это другое, это абсолютный URL, поскольку мы говорим об API DOM, а не об элементе. Если вы хотите значение атрибута (как это предлагается в версии jQuery), вы хотите .$(this).attr('href')this.hrefthis.hrefthis.getAttribute('href')

Затем есть установка класса для элемента, для этого вам также не нужен jQuery, если вы просто добавляете класс.

Я видел это раньше:

  <script src="http://code.jquery.com/jquery.min.js"></script>
</head>
<body>
  <script>
    $('body').addClass('hasJS');
  </script>

Но почему нет:

</head>
<body>
  <script>
    document.body.className = 'hasJS';
  </script>

Если тело может иметь класс уже, просто добавьте (JQuery должен прочитать свойство Classname тоже): .document.body.className +=' hasJS'

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

classList для добавления, удаления и переключения

classListПоддержка HTML5 есть во всех последних производственных браузерах (обратите внимание, что не в IE9 — но это тот случай, когда я могу выбрать polyfill).

Вместо:

$('body').addClass('hasJS');

// or

document.body.className += ' hasJS';

Мы сможем:

document.body.classList.add('hasJS');

Разве это не красиво?

Как насчет удаления:

$('body').removeClass('hasJS');

// or some crazy ass regular express

Или мы можем сделать:

document.body.classList.remove('hasJS');

But more impressive is the native toggle support:

document.body.classList.toggle('hasJS');

// and

document.body.classList.contains('hasJS');

What does suck though, is the weird white space issues. Sadly you can’t add more than one class in one go:

document.body.classList.add('hasJS ready');
// InvalidCharacterError: DOM Exception 5

document.body.classList.contains('');
// SyntaxError: DOM Exception 12

// work around...but still... 🙁
DOMTokenList.prototype.add.apply(body.classList, 'hasJS ready'.split(' '));

That’s pretty rubbish. But! On the upside, I know the problem areas and I avoid them. Pretty much what we’ve grown up on working with browsers anyway.

Storing data

jQuery implemented arbitrary data storage against elements in 1.2.3 and then object storing in 1.4 – so a little while ago.

HTML5 has native data storage against elements, but there’s a fundamental difference between jQuery and native support: object storage won’t work in HTML5 dataset.

But if you’re storing strings or JSON, then native support is perfect:

element.dataset.user = JSON.stringify(user);
element.dataset.score = score;

Support is good, but sadly no native support in IE10 (though you can add a polyfill and it’ll work perfectly again – but that’s a consideration when using dataset).

Ajax

Like I said before, jQuery helped me grok ajax fully. But now ajax is pretty easy. Sure, I don’t have all the extra options, but more often than not, I’m doing an XHR GET or POST using JSON.

function request(type, url, opts, callback) {
  var xhr = new XMLHttpRequest(),
      fd;

  if (typeof opts === 'function') {
    callback = opts;
    opts = null;
  }

  xhr.open(type, url);

  if (type === 'POST' && opts) {
    fd = new FormData();

    for (var key in opts) {
      fd.append(key, JSON.stringify(opts[key]));
    }
  }

  xhr.onload = function () {
    callback(JSON.parse(xhr.response));
  };

  xhr.send(opts ? fd : null);
}

var get = request.bind(this, 'GET');
var post = request.bind(this, 'POST');

It’s short and simple. XHR is not hard and it’s well documented nowadays. But more importantly, having an understanding of how it really works and what XHR can do, gives us more.

What about progress events? What about upload progress events? What about posting upstream as an ArrayBuffer? What about CORS and the xml-requested-with header?

You’ll need direct access to the XHR object for this (I know you can get this from jQuery), but you should get familiar with the XHR object and it’s capabilities, because things like file uploads via drag and drop is insanely easy with native functionality today.

Finally forms!

The jQuery form validation plugin was a stable plugin from the early days of jQuery, and frankly made working with forms so much easier.

But regardless of your client side validation – you must always run server side validation – this remains true regardless of how you validate.

But what if you could throw away lines and lines of JavaScript and plugins to validate an email address like this:

<input type="email">

Want to make it a required field?

<input type="email" required>

Want to allow only specific characters for a user?

<input pattern="a-z0-9">

Bosh. It even comes with assistive technology support – i.e. the keyboard will adapt to suit email address characters.

Since these types fall back to text, and you have to have server side validation, I highly encourage you to rip out all your JavaScript validation and swap in native HTML5 form validation.

jQuery animations VS. CSS animations VS. JavaScript animations

It’s not really a contest. CSS wins. Animating using CSS is being processed on the GPU. Animating in JavaScript has the additional layer of computation in the simple fact there’s JavaScript.

Even then, if I’m writing the code myself, I’m going to choose to use requestAnimationFrame over setInterval based animations.

Jake Archibald has some excellent slides showing the issue here, whereby setInterval won’t be smooth and will quickly start to drop frames:

setInterval

RAF

What’s more, CSS animations go through the same scheduler as requestAnimationFrame, which is what we want to use.

So if your browser support allows for it, use CSS animations. Sure, it’s not as easy as $foo.animate('slow',{ x:'+=10px'}) but it’s going to be cleaner and smoother. It should be a well known fact that touching the DOM is expensive. So if you’re animating the x position of an element by updating the el.style.left attribute, you’re going back and forth for the animation.

However, if you can simply do foo.classList.add('animate'), the CSS class animate will transition the left position of the element. And you have control if that’s just the left property, or if you take advantage of hardware acceleration by using translateX with translateZ(0).

But what about my animation end callback I hear you all cry?! That’s available too. Though it’s a little icky:

el.addEventListener("webkitTransitionEnd", transitionEnded);
el.addEventListener("transitionend", transitionEnded);

  • Note the the lowercase ‘e’ on ‘end’…

A few kind people on twitter did also point me to a sort-of-polyfill for jQuery, that enhances the .animate function if CSS animations are available.

There’s also a separate plugin called Transit which gives you JavaScript based control over creating CSS animations. A really nice aspect (for me) is the chaining support. Since it relies exclusively on CSS animations, it requires IE10 and above.

Which begs me to ask: why does this plugin require jQuery specifically?

Aside: jQuery plugins – just because.

Me:

I don’t know why, but I really want to hurt the people who write jQuery plugins that don’t really need jQuery. /seeks-anger-management

Reply:

@rem Me too, I suspect there’s a support group for that. Probably rather large.

I was working on a project recently, and knew of the fitText.js project. I went to drop it in, but then spotted that it needed jQuery.

Hmm. Why?

It uses the following jQuery methods:

  1. .extend
  2. .each
  3. .width
  4. .css
  5. .on (and not with much thought to performance)

The plugin is this:

  $.fn.fitText = function( kompressor, options ) {

    // Setup options
    var compressor = kompressor || 1,
        settings = $.extend({
          'minFontSize' : Number.NEGATIVE_INFINITY,
          'maxFontSize' : Number.POSITIVE_INFINITY
        }, options);

    return this.each(function(){

      // Store the object
      var $this = $(this);

      // Resizer() resizes items based on the object width divided by the compressor * 10
      var resizer = function () {
        $this.css('font-size', Math.max(Math.min($this.width() / (compressor*10), parseFloat(settings.maxFontSize)), parseFloat(settings.minFontSize)));
      };

      // Call once to set.
      resizer();

      // Call on resize. Opera debounces their resize by default.
      $(window).on('resize orientationchange', resizer);

    });

  };

.extend is being used against an object that only has two options, so I would rewrite to read:

  if (options === undefined) options = {}; 
  if (options.minFontSize === undefined) options.minFontSize = Number.NEGATIVE_INFINITY;
  if (options.maxFontSize === undefined) options.maxFontSize = Number.POSITIVE_INFINITY;

returnthis.each used to loop over the nodes. Let’s assume we want this code to work stand alone, then our fitText function would receive the list of nodes (since we wouldn’t be chaining):

  var length = nodes.length,
      i = 0;

  // would like to use [].forEach.call, but no IE8 support
  for (; i < length; i++) {
    (function (node) {
      // where we used `this`, we now use `node`
      // ...
    })(nodes[i]);
  }

$this.width() gets the width of the container for the resizing of the text. So we need to get the computed styles and grab the width:

  // Resizer() resizes items based on the object width divided by the compressor * 10
  var resizer = function () {
    var width = node.clientWidth;

    // ...
  };

$this.css is used as a setter, so that’s just a case of setting the style:

  node.style.fontSize = Math.max(...);

$(window).on('resize', resizer) is a case of attaching the event handler (note that you’d want addEvent too for IE8 support):

  window.addEventListener('resize', resizer, false);

In fact, I’d go further and store the resizers in an array, and on resize, loop through the array executing the resizer functions.

Sure, it’s a little more work, but it would also easy to upgrade this change to patch in jQuery plugin support as an upgrade, rather than making it a prerequisite.

Rant will be soon over: it also irks me when I see a polyfill requires jQuery – but I know the counter argument to that is that jQuery has extremely high penetration so it’s possibly able to justify the dependency.

Closing

My aim was to show you that whilst jQuery has giving me such as huge helping hand over the years (particularly those years of poor interoperability), that native browser support is a long way to doing a lot of the common workflows I have when writing JavaScript to “do stuff” to the DOM.

Forget about X feature doesn’t work in Y browser – approach it from the point of view of: what am I trying to solve? What’s the best tool for the job? What’s my audience?

I still believe in progressive enhancement, but I don’t believe in bending over backwards to support an imaginary user base (because we don’t have data to say what browsers our users are using).

Google (last I read) support latest minus 1. I try to start from the same baseline support too.

I’ll continue to use jQuery the way that suits me, and I’ll continue to evangelise that front end devs learn what the browsers they work with are capable of.

With that then, I close, and I hope this has been helpful to you.

Maybe some of you knew all this already (though I would question why you’re attending/reading this!), hopefully I’ve shown some of you that there’s a world beyond jQuery and that you can start using it today in some of your projects.

Maybe some of you are new to jQuery – I hope that you go on to look further in to what JavaScript and the DOM are capable of.

But for the majority of you: I’m singing to the choir. You’re already invested. You already believe in standards, doing it right, learning and bettering yourself. It’s those people who aren’t getting this information that you need to help.

You need to share your experiences with the others around you. You’re the expert now, and it’s up to you to help those around you, to help them come up to your standards and beyond.

Conferences will be looking for new speakers, new experts: you are that person.