Когда дело доходит до программирования, мне очень нравится идея Кольца Власти во «Властелине Колец»: один язык, чтобы управлять ими всеми. Это правда, у каждого языка программирования есть свои преимущества и недостатки, и Javascript начинался как быстрое решение для взаимодействия с браузерами. Однако с годами Javascript сильно развился и повзрослел, так что сейчас он находится на этапе, когда с его помощью можно многого добиться. Как и в случае с другими языками, он не может делать все, но я искренне верю, что в веб-среде, на родине Javascript, он может стать королем.

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

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

Так же, как пример, Windows XP использовала 128 МБ ОЗУ, а полностью функционирующая ОС использовала для работы 128 МБ ОЗУ. Сегодня мы видим приложения и фреймворки, которые используют 128 МБ ОЗУ только для того, чтобы привет, мир. Иногда мы даже не знаем, зачем нужен такой объем памяти, а потом начинаем замечать, что некоторые библиотеки или пакеты не так производительны, как вы надеялись. Некоторые даже идут с шутками, например, babel-core, которое поставляется с ASCII Art Guy Fieri, просто для удовольствия: https://github.com/babel/babel/pull/3641/files

Давайте вернемся еще дальше и подумаем о том факте, что когда вся игра Super Mario Bros. была выпущена в 1985 году, ее размер составлял всего 31 КБ.

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

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

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

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

Поскольку ES5 и ES6 оказались в центре внимания и их поддерживало все больше браузеров, появилось множество методов массива. Более чистый синтаксис ES6 делает безболезненным связывание кодов для получения желаемых результатов. Проблема заключается в том, как мы связываем.

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

const users = [{ name: 'Constantine', age: 23 }, { name: 'Adam', age: 14 }, { name: 'Jennifer', age: 22 }] const valid = users.filter(({ age }) => age >= 18).map(({ name }) => name);

Код кажется полностью законным. Он использует два метода ES5, фильтр и карту, а также стрелочные функции ES6 и деструктуризацию объекта. Он хорошо написан и легко читается. Выглядит отлично!

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

Если бы в этом массиве было 100 000 элементов, и вы отфильтровали половину из них, все равно было бы 50 000 элементов, которые нужно было бы перебрать снова, чтобы получить их имена. Таким образом, ваша программа выполнит 150 000 итераций, а не 100 000 итераций, необходимых для выполнения действия.

Как правильно это сделать? На самом деле есть два способа: одиночный цикл или новый метод уменьшения.

const names = []; users.forEach(({ age, name }) => { age >= 18 && names.push(name); }); const valid = users.reduce((acc, { age, name }) => { return (age >= 18) ? [ac, name] : acc; }, []);

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

«Довольный спусковой крючок» - это термин, используемый для описания безответственного использования огнестрельного оружия, когда субъект нажимает на спусковой крючок и стреляет из пистолета, прежде чем четко идентифицировать цель. Программный эквивалент этого сейчас часто встречается в Javascript со стрелочными функциями.

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

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

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

Давайте посмотрим на пример, чтобы заметить разницу:

function myClass() { this.value = 1; this.myMethod = () => { this.anotherValue = 2; return this; } } const test = new myClass(); test.myMethod();

Поскольку стрелочная функция не имеет области видимости, она наследует область видимости родителя. В этом случае объект myClass. Теперь давайте посмотрим, что происходит с анонимной функцией, когда мы пытаемся привязать другую область видимости:

function myClass() { this.value = 1; this.myMethod = function() { this.anotherValue = 2; return this; } } const anotherTest = new myClass(); anotherTest.myMethod.call({another: 'scope'}, );

Как видите, он по-прежнему вытягивает область видимости у родителя, и теперь якобы новую область видимости, которую мы передали функции, нигде нет.

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

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

  • Если вы хотите, чтобы функция была доступна через общедоступный или частный интерфейс, вам не следует использовать стрелочные функции. Эти методы обычно должны обрабатывать изменяющиеся области видимости.
  • Стрелочные функции отлично работают с итераторами и другими действиями типа «запустил и забыл». Если вы напишете для него лямбду, стрелочная функция идеально подойдет.
  • Если вам нужно передать область видимости в качестве аргумента функции, вам не следует использовать стрелочную функцию.

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

Благодаря широкополосному подключению к Интернету разработчики меньше заботятся о размерах сборки. Они просто помещают все приложение в одностраничное приложение, которое весит несколько мегабайт. Базовая сборка Angular 1 со всеми загруженными модулями занимает около 1 МБ для одного неоптимизированного приложения Hello World!

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

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

Например, извлечение всей библиотеки jQuery только для использования функций Ajax.

Какое тогда решение? Просто, всегда предпочитаю родной. Конечно, есть утилитарные библиотеки, такие как Lodash и Underscore, которые предоставляют отличные функции, но многие из них теперь являются родными.

Например, вместо того, чтобы использовать Lodash each, map, filter и т. Д., Вы можете использовать собственный Javascript для каждого, map, filter и других функций. И если вам действительно нужна функция, которая находится в Lodash, но не в стандартном Javascript, вы можете вытащить эту функцию, выполнив require (‘lodash / fn_name). Допустим, вам нужна функция пересечения. Вы можете импортировать его с помощью require («lodash / пересечение»), и после сборки вы не будете извлекать всю библиотеку, а только код для этой функции.

Но всегда старайтесь придерживаться ванильного javascript. Сейчас в обещаниях почти нет необходимости, потому что теперь у нас есть собственные обещания, а также Async и Await, поэтому Q и Bluebird могут не понадобиться.

jQuery для обхода DOM? Не требуется! Теперь у нас есть document.querySelector.

Всплывающие окна Toastr? Для чего? У нас есть уведомления на рабочем столе, они круче!

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

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

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

Все эти асинхронные функции выполняются в одном потоке, ожидая завершения и прерывая выполнение кода.

Но не бойтесь больше, потому что, хотя Javascript является однопоточным, это не означает, что у вас не может быть больше «потоков», как в других языках программирования.

Вот! Веб-воркеры, решение Javascript для многопоточности! С ними у вас может быть несколько потоков. Как? Что ж, вы можете добавить функцию в Webworker. По сути, Javascript помещает этот код в отдельный процесс, обычно в другое ядро ​​ЦП, так что технически это другой поток. После завершения выполнения этого кода результаты передаются обратно в основной поток.

Это похоже на Node, внутренний Javascript, но у нас там нет веб-воркеров. Вместо этого у нас есть кластерный режим, который позволяет нам охватить дочерние процессы, куда мы можем поместить наш код. Мы даже можем легко распределить наше приложение на количество потоков «x», где «x» - это количество ядер в ЦП, и автоматически сбалансировать нагрузку кода в потоках.

Это тема для другой статьи, так что давайте вернемся к ней в будущем, не так ли?

Вот так! Это всего лишь пара ошибок, которые мы, разработчики, обычно допускаем в нашем коде, которые могут повлиять на производительность. Что касается Javascript, мы настолько полагаемся на ресурсы конечного пользователя, что можем стать полностью ленивыми при написании оптимизированного кода. А иногда нам все равно, и мы добавляем в сборку изображение Гая Фиери просто для удовольствия.

Мой лучший совет: когда вы программируете на Javascript или любом современном языке, программируйте, как в 1985 году. Ограничьте себя ограниченным количеством ресурсов, как разработчики Super Mario Bros. Только тогда, когда вы думаете Что касается ограниченного количества ресурсов, сможете ли вы действительно написать оптимальный код, который будет хорошо работать где угодно, без необходимости в дорогих машинах или ресурсах.

Автор: Фернандо Де Вега
LinkedIn: https://www.linkedin.com/in/fernandodvj/

Первоначально опубликовано на www.admios.com.