Дважды в месяц мы возвращаемся к любимым постам наших читателей на протяжении всей истории Nettuts +. Этот учебник был впервые опубликован в октябре 2010 года.
Блестящий Шаблоны JavaScript », был достаточно любезен, чтобы предоставить отрывок из книги для наших читателей, в которой подробно описываются основы написания высококачественного JavaScript, такие как избегание глобальных переменных с использованием объявлений единой переменной, длина предварительного кэширования в циклах, соблюдение правил кодирования и многое другое.
Этот отрывок также включает в себя некоторые привычки, которые не обязательно связаны с самим кодом, но больше касаются всего процесса создания кода, включая написание документации API, проведение экспертных проверок и запуск JSLint. Эти привычки и лучшие практики могут помочь вам написать лучший, более понятный и обслуживаемый код — код, которым можно гордиться (и который можно выяснить) при повторном рассмотрении месяцев и лет спустя.
Написание обслуживаемого кода
Ошибки в программном обеспечении требуют больших затрат. И их стоимость со временем увеличивается, особенно если ошибки попадают в публикуемый продукт. Лучше всего, если вы сможете исправить ошибку сразу же, как только вы ее найдете; это когда проблема, которую решает ваш код, еще свежа в вашей голове. В противном случае вы перейдете к другим задачам и забудете все об этом конкретном коде. Повторное посещение кода через некоторое время требует:
- Время переучиваться и понимать проблему
- Время понять код, который должен решить проблему
Другая проблема, характерная для более крупных проектов или компаний, заключается в том, что человек, который в конечном итоге исправляет ошибку, — это не тот человек, который создал ошибку (а также не тот, кто обнаружил ошибку). Поэтому очень важно сократить время, необходимое для понимания кода, написанного вами некоторое время назад или написанным другим разработчиком в команде. Это имеет решающее значение как для конечного результата (доход от бизнеса), так и для счастья разработчика, потому что мы все предпочитаем разрабатывать что-то новое и захватывающее, а не тратить часы и дни на поддержку старого унаследованного кода.
Еще один жизненный факт, связанный с разработкой программного обеспечения в целом, заключается в том, что обычно на чтение кода тратится больше времени, чем на его написание . Во время, когда вы сосредоточены и глубоко погружены в проблему, вы можете сесть и за один день создать значительный объем кода.
Код, вероятно, будет работать тогда и там, но по мере развития приложения происходит много других вещей, которые требуют пересмотра, изменения и настройки вашего кода. Например:
- Ошибки обнаружены.
- Новые функции добавлены в приложение.
- Приложение должно работать в новых средах (например, на рынке появляются новые браузеры).
- Код перераспределяется.
- Код полностью переписывается с нуля или переносится на другую архитектуру или даже на другой язык.
В результате изменений, несколько человеко-часов, потраченных на написание кода, вначале заканчиваются человеко-неделями, потраченными на его чтение. Вот почему создание поддерживаемого кода имеет решающее значение для успеха приложения.
Поддерживаемый код означает код, который:
- Читаемо
- Согласуется
- Предсказуемо
- Выглядит так, как будто это написал один и тот же человек
- Документировано
Минимизация глобалов
JavaScript использует функции для управления областью действия. Переменная, объявленная внутри функции, является локальной для этой функции и недоступна вне функции. С другой стороны, глобальные переменные — это переменные, объявленные вне какой-либо функции или просто используемые без объявления.
Каждая среда JavaScript имеет глобальный объект, доступный, когда вы используете его вне какой-либо функции. Каждая создаваемая вами глобальная переменная становится свойством глобального объекта. В браузерах для удобства имеется дополнительное свойство глобального объекта, называемое окном, которое (обычно) указывает на сам глобальный объект. В следующем фрагменте кода показано, как создать и получить доступ к глобальной переменной в среде браузера:
myglobal = "привет"; // антипаттерн console.log (myglobal); // "Здравствуйте" console.log (window.myglobal); // "Здравствуйте" console.log (окно [ "myglobal"]); // "Здравствуйте" console.log (this.myglobal); // "Здравствуйте"
Проблема с глобалами
Проблема с глобальными переменными заключается в том, что они являются общими для всего кода в вашем приложении JavaScript или на веб-странице. Они живут в одном и том же глобальном пространстве имен, и всегда существует вероятность именования коллизий — когда две отдельные части приложения определяют глобальные переменные с одинаковым именем, но с разными целями.
Веб-страницы также часто включают код, написанный не разработчиками страницы, например:
- Сторонняя библиотека JavaScript
- Скрипты от рекламного партнера
- Код из стороннего скрипта отслеживания пользователей и аналитики
- Различные виды виджетов, значков и кнопок
Допустим, один из сторонних скриптов определяет глобальную переменную, которая называется, например, result. Затем в одной из ваших функций вы определяете другую глобальную переменную с именем result. В результате последняя переменная результата перезаписывает предыдущие, а сторонний скрипт может просто перестать работать.
Поэтому важно быть хорошим соседом для других сценариев, которые могут быть на той же странице, и использовать как можно меньше глобальных переменных. Позже в книге вы узнаете о стратегиях минимизации числа глобальных переменных, таких как шаблон пространства имен или самовыполняющиеся непосредственные функции, но наиболее важный шаблон для уменьшения количества глобальных глобальных переменных — всегда использовать var для объявления переменных.
Удивительно легко создавать глобальные объекты невольно благодаря двум функциям JavaScript. Во-первых, вы можете использовать переменные, даже не объявляя их. И, во-вторых, в JavaScript есть понятие подразумеваемых глобальных переменных, что означает, что любая переменная, которую вы не объявляете, становится свойством глобального объекта (и доступна так же, как правильно объявленная глобальная переменная). Рассмотрим следующий пример:
сумма функций (х, у) { // антипаттерн: подразумеваемый глобальный результат = х + у; вернуть результат; }
В этом коде result
используется без объявления. Код работает нормально, но после вызова функции вы получаете еще одну переменную, result
к глобальному пространству имен, которая может стать источником проблем.
Основное правило — всегда объявлять переменные с помощью var
, как показано в улучшенной версии функции sum()
:
сумма функций (х, у) { var result = x + y; вернуть результат; }
Другим антипаттерном, который создает подразумеваемые глобальные переменные, является цепочка назначений как часть объявления var. В следующем фрагменте a
локально, но b
становится глобальным, что, вероятно, не то, что вы хотели сделать:
// антипаттерн, не использовать function foo () { var a = b = 0; // ... }
Если вам интересно, почему это происходит, это из-за оценки справа налево. Сначала вычисляется выражение b = 0, и в этом случае b не объявляется. Возвращаемое значение этого выражения равно 0, и оно присваивается новой локальной переменной, объявленной с помощью var a. Другими словами, это как если бы вы набрали:
var a = (b = 0);
Если вы уже объявили переменные, цепочка назначений в порядке и не создает неожиданных глобальных переменных. Пример:
function foo () { вар а, б; // ... a = b = 0; // оба локальные }
Еще одна причина избегать глобальных связей — это мобильность. Если вы хотите, чтобы ваш код выполнялся в разных средах (хостах), опасно использовать глобальные переменные, потому что вы можете случайно перезаписать хост-объект, который не существует в вашей исходной среде (поэтому вы подумали, что имя безопасно использовать), но который делает в некоторых других.
Побочные эффекты при забвении вар
Есть одно небольшое различие между подразумеваемыми глобальными и явно определенными — разница в способности отменить определение этих переменных с помощью оператора delete:
- Глобалы, созданные с помощью var (созданные в программе вне какой-либо функции), не могут быть удалены.
- Подразумеваемые глобалы, созданные без var (независимо от того, созданы ли они внутри функций), могут быть удалены.
Это показывает, что подразумеваемые глобальные переменные технически не являются реальными переменными, но они являются свойствами глобального объекта. Свойства могут быть удалены с помощью оператора удаления, тогда как переменные не могут:
// определить три глобала var global_var = 1; global_novar = 2; // антипаттерн (function () { global_fromfunc = 3; // антипаттерн } ()); // попытка удалить удалить global_var; // ложный удалить global_novar; // правда удалить global_fromfunc; // правда // проверить удаление typeof global_var; // "число" typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined"
В строгом режиме ES5 присвоение необъявленным переменным (таким как два антипаттерна в предыдущем фрагменте) вызовет ошибку.
Доступ к глобальному объекту
В браузерах глобальный объект доступен из любой части кода через свойство window
(если вы не сделали что-то особенное и неожиданное, например, объявив локальную переменную с именем window
). Но в других средах это удобное свойство может называться как-то иначе (или даже недоступно для программиста). Если вам нужен доступ к глобальному объекту без жесткого кодирования window
идентификатора, вы можете сделать следующее из любого уровня области вложенных функций:
var global = (function () { верни это; } ());
Таким образом, вы всегда можете получить глобальный объект, потому что внутри функций, которые были вызваны как функции (то есть не как ограничители с new
), это всегда должно указывать на глобальный объект. На самом деле это больше не относится к ECMAScript 5 в строгом режиме, поэтому вы должны использовать другой шаблон, когда ваш код находится в строгом режиме. Например, если вы разрабатываете библиотеку, вы можете заключить код библиотеки в непосредственную функцию, а затем из глобальной области передать ссылку на нее в качестве параметра вашей непосредственной функции.
Одинарный шаблон
Использование одного оператора var в верхней части ваших функций — полезный шаблон для принятия. Он имеет следующие преимущества:
- Предоставляет единственное место для поиска всех локальных переменных, необходимых для функции
- Предотвращает логические ошибки, когда переменная используется до ее определения
- Помогает вам не забывать объявлять переменные и, следовательно, минимизировать глобальные переменные
- Меньше кода (набрать и передать по проводу)
Один шаблон var выглядит следующим образом:
function func () { var a = 1, б = 2, сумма = а + б, myobject = {}, я, J; // тело функции ... }
Вы используете один оператор var и объявляете несколько переменных, разделенных запятыми. Хорошей практикой также является инициализация переменной начальным значением во время ее объявления. Это может предотвратить логические ошибки (все неинициализированные и объявленные переменные инициализируются со значением undefined
), а также улучшить читабельность кода. Когда вы посмотрите на код позже, вы сможете получить представление о предполагаемом использовании переменной на основе ее начального значения — например, должен ли он быть объектом или целым числом?
Вы также можете выполнить некоторую фактическую работу во время объявления, как в случае с sum = a + b
в предыдущем коде. Другой пример — при работе со ссылками DOM (объектная модель документа). Вы можете назначить ссылки DOM локальным переменным вместе с одним объявлением, как показано в следующем коде:
function updateElement () { var el = document.getElementById ("result"), style = el.style; // сделать что-нибудь с el и style ... }
Подъем: проблема с разбросанными переменными
JavaScript позволяет вам иметь несколько операторов var
любом месте функции, и все они действуют так, как если бы переменные были объявлены в верхней части функции. Такое поведение известно как подъем. Это может привести к логическим ошибкам при использовании переменной и последующем объявлении ее в функции. Для JavaScript, если переменная находится в той же области видимости (той же функции), она считается объявленной, даже если она используется перед объявлением var. Посмотрите на этот пример:
// антипаттерн myname = "global"; // глобальная переменная function func () { оповещения (MyName); // "undefined" var myname = "local"; оповещения (MyName); // "местный" } FUNC ();
В этом примере вы можете ожидать, что первый alert()
будет запрашивать «global», а второй — «local». Это разумное ожидание, потому что во время первого предупреждения myname
не было объявлено, и поэтому функция должна вероятно, «увидеть» глобальное myname
. Но это не так, как это работает. В первом предупреждении будет указано «undefined», поскольку myname
считается объявленным как локальная переменная для функции. (Хотя объявление идет после.) Все объявления переменных поднимаются в начало функции. Поэтому, чтобы избежать путаницы такого типа, лучше заранее объявить все переменные, которые вы собираетесь использовать.
Предыдущий фрагмент кода будет вести себя так, как если бы он был реализован так:
myname = "global"; // глобальная переменная function func () { var myname; // так же, как -> var myname = undefined; оповещения (MyName); // "undefined" myname = "local"; оповещения (MyName); // "местный" } FUNC ();
Для полноты давайте отметим, что на самом деле на уровне реализации все немного сложнее. Существует два этапа обработки кода, где на первом этапе создаются переменные, объявления функций и формальные параметры, то есть этап синтаксического анализа и ввода контекста. На втором этапе создается этап выполнения кода времени выполнения, выражения функций и неквалифицированные идентификаторы (необъявленные переменные). Но для практических целей мы можем принять концепцию подъема, которая на самом деле не определяется стандартом ECMAScript, но обычно используется для описания поведения.
для петель
В циклах for
вы перебираете arrays
или объекты, похожие на arrays
такие как arguments
и объекты HTMLCollection
. Обычный шаблон цикла выглядит следующим образом:
// субоптимальный цикл для (var i = 0; i <myarray.length; i ++) { // сделать что-нибудь с myarray [i] }
Проблема с этим шаблоном заключается в том, что длина массива доступна на каждой итерации цикла. Это может замедлить ваш код, особенно когда myarray
— это не массив, а объект HTMLCollection
.
HTMLCollection
s — это объекты, возвращаемые такими методами DOM, как:
-
document.getElementsByName()
-
document.getElementsByClassName()
-
document.getElementsByTagName()
Существует также ряд других HTMLCollections
, которые были введены до стандарта DOM и используются до сих пор. Там включают (среди прочих):
-
document.images
: все элементы IMG на странице -
document.links
: все элементы A -
document.forms
: все формы -
document.forms[0].elements
: Все поля в первой форме на странице
Проблема с коллекциями состоит в том, что они являются живыми запросами к базовому документу (странице HTML). Это означает, что каждый раз, когда вы получаете доступ к length
любой коллекции, вы запрашиваете действующий DOM, и операции DOM в целом дороги.
Вот почему лучшим шаблоном для циклов for
является кэширование длины массива (или коллекции), по которому вы выполняете итерацию, как показано в следующем примере:
для (var i = 0, max = myarray.length; i <max; i ++) { // сделать что-нибудь с myarray [i] }
Таким образом, вы получаете значение длины только один раз и используете его в течение всего цикла.
Кэширование длины при итерации по HTMLCollections
быстрее во всех браузерах — где-то между двумя HTMLCollections
(Safari 3) и 190 раз (IE7).
Обратите внимание, что когда вы явно намереваетесь изменить коллекцию в цикле (например, добавив больше элементов DOM), вы, вероятно, захотите, чтобы длина обновлялась и не была постоянной.
Следуя единственному шаблону var, вы также можете извлечь var из цикла и сделать цикл следующим образом:
function looper () { var i = 0, Максимум, myarray = []; // ... для (i = 0, max = myarray.length; i <max; i ++) { // сделать что-нибудь с myarray [i] } }
Этот шаблон обладает преимуществом согласованности, потому что вы придерживаетесь единственного шаблона var. Недостатком является то, что при рефакторинге кода становится немного сложнее копировать и вставлять целые циклы. Например, если вы копируете цикл из одной функции в другую, вы должны убедиться, что вы также перенесли i
и max
в новую функцию (и, вероятно, удалите их из исходной функции, если они там больше не нужны).
Последним изменением цикла будет замена i++
одним из следующих выражений:
я = я + 1 я + = 1
JSLint предложит вам сделать это; причина в том, что ++
и --
поощряют «чрезмерную хитрость». Если вы не согласны с этим, вы можете установить опцию plusplus
в false
. (Это правда по умолчанию.)
Два варианта шаблона for вводят некоторые микрооптимизации, потому что они:
- Используйте на одну переменную меньше (не
max
) - Обратный отсчет до
0
, который обычно быстрее, потому что эффективнее сравнивать с 0, чем с длиной массива или с чем-либо, кроме0
Первый измененный шаблон:
var i, myarray = []; for (i = myarray.length; i--;) { // сделать что-нибудь с myarray [i] }
А второй использует цикл while:
var myarray = [], я = myarray.length; в то время как я--) { // сделать что-нибудь с myarray [i] }
Это микрооптимизации, которые будут замечены только в операциях, критичных к производительности. Кроме того, JSLint будет жаловаться на использование i--
.
петли для
Циклы for-in
должны использоваться для перебора объектов без массива. Цикл for-in
также называется enumeration
.
Технически вы также можете использовать for-in для циклического перебора массивов (потому что в JavaScript массивы являются объектами), но это не рекомендуется. Это может привести к логическим ошибкам, если объект массива уже дополнен пользовательскими функциями. Кроме того, порядок (последовательность) перечисления свойств не гарантируется в for-in
. Поэтому предпочтительно использовать normal для циклов с массивами и for-in циклов для объектов.
Важно использовать метод hasOwnProperty()
при переборе свойств объекта, чтобы отфильтровать свойства, которые попадают в цепочку прототипов.
Рассмотрим следующий пример:
// объект var man = { руки: 2, ноги: 2, головы: 1 }; // где-то еще в коде // метод был добавлен ко всем объектам if (typeof Object.prototype.clone === "undefined") { Object.prototype.clone = function () {}; }
В этом примере у нас есть простой объект с именем man, определенный с помощью литерала объекта. Где-то до или после определения человека прототип Object был дополнен полезным методом clone()
. Цепочка прототипов является действующей, что означает, что все объекты автоматически получают доступ к новому методу. Чтобы избежать появления метода clone()
при перечислении man, вам нужно вызвать hasOwnProperty()
чтобы отфильтровать свойства прототипа. Отказ от фильтрации может привести к появлению функции clone()
, что является нежелательным поведением в большинстве случаев:
// 1. // цикл for-in for (var i in man) { if (man.hasOwnProperty (i)) {// фильтр console.log (i, ":", man [i]); } } / * результат в консоли руки: 2 ноги: 2 головы: 1 * / // 2. // антипаттерн: // цикл for-in без проверки hasOwnProperty () for (var i in man) { console.log (i, ":", man [i]); } / * результат в консоли руки: 2 ноги: 2 головы: 1 клон: функция () * /
Другой шаблон для использования hasOwnProperty()
— вызвать этот метод из Object.prototype, например:
for (var i in man) { if (Object.prototype.hasOwnProperty.call (man, i)) {// фильтр console.log (i, ":", man [i]); } }
Преимущество заключается в том, что вы можете избежать коллизий именования в случае, если объект man
переопределил hasOwnProperty
. Также, чтобы избежать длинных поисков свойств вплоть до Object
, вы можете использовать локальную переменную для « кэширования » его:
var i, hasOwn = Object.prototype.hasOwnProperty; для (я в человеке) { if (hasOwn.call (man, i)) {// фильтр console.log (i, ":", man [i]); } }
Строго говоря, не использование
hasOwnProperty()
не является ошибкой. В зависимости от задачи и уверенности в коде, вы можете пропустить его и немного ускорить циклы. Но когда вы не уверены в содержимом объекта (и в цепочке его прототипов), вам безопаснее просто добавитьhasOwnProperty()
.
Вариант форматирования (который не передает JSLint) пропускает фигурную скобку и помещает if в той же строке. Преимущество в том, что оператор цикла читается больше как законченная мысль («для каждого элемента, который имеет собственное свойство X
, сделайте что-нибудь с X
»). Также есть меньше отступов, прежде чем вы доберетесь до основной цели цикла:
// Предупреждение: не проходит JSLint var i, hasOwn = Object.prototype.hasOwnProperty; for (я в человеке) if (hasOwn.call (man, i)) {// фильтр console.log (i, ":", man [i]); }
(Не) Дополнение встроенных прототипов
Увеличение свойства prototype функций конструктора является мощным способом добавления функциональности, но иногда оно может быть слишком мощным.
Заманчиво дополнить прототипы встроенных конструкторов, таких как Object()
, Array()
или Function()
, но это может серьезно повредить удобству сопровождения, потому что это сделает ваш код менее предсказуемым. Другие разработчики, использующие ваш код, вероятно, ожидают, что встроенные методы JavaScript будут работать согласованно, и не ожидают ваших дополнений.
Кроме того, свойства, которые вы добавляете в прототип, могут отображаться в циклах, которые не используют hasOwnProperty()
, поэтому они могут создавать путаницу.
Поэтому лучше не расширять встроенные прототипы. Вы можете сделать исключение из правила только при соблюдении всех этих условий:
- Ожидается, что будущие версии ECMAScript или реализации JavaScript будут последовательно реализовывать эту функциональность как встроенный метод. Например, вы можете добавить методы, описанные в ECMAScript 5, ожидая, когда браузеры подтянутся. В этом случае вы просто заранее определяете полезные методы.
- Вы проверяете, не существует ли уже ваше собственное свойство или метод — возможно, уже реализовано где-то еще в коде или уже является частью механизма JavaScript одного из поддерживаемых вами браузеров.
- Вы четко документируете и сообщаете об изменениях команде.
Если эти три условия соблюдены, вы можете перейти к пользовательскому дополнению к прототипу, следуя этой схеме:
if (typeof Object.protoype.myMethod! == "function") { Object.protoype.myMethod = function () { // реализация... }; }
Шаблон переключения
Вы можете улучшить удобочитаемость и надежность ваших операторов switch
, следуя этой схеме:
var inspect_me = 0, результат = ''; switch (inspect_me) { случай 0: результат = "ноль"; перемена; Дело 1: результат = "один"; перемена; дефолт: результат = "неизвестно"; }
Соглашения по стилю, используемые в этом простом примере:
- Выравнивание каждого
case
сswitch
(исключение из правила отступа фигурных скобок). - Отступ кода в каждом случае.
- Завершение каждого
case
с явнымbreak;
, - Избегать провалов (когда вы намеренно пропускаете разрыв). Если вы абсолютно уверены в том, что провал — это лучший подход, убедитесь, что вы документируете такие случаи, поскольку они могут показаться читателям вашего кода ошибками.
- Завершение
switch
поdefault:
чтобы убедиться, что всегда есть нормальный результат, даже если ни один из случаев не соответствует.
Избежание подразумеваемых типов
JavaScript неявно типизирует переменные при их сравнении. Вот почему сравнения, такие как false == 0
или "" == 0
возвращают true
.
Чтобы избежать путаницы, вызванной подразумеваемым приведением типов, всегда используйте операторы ===
и !==
которые проверяют как значения, так и тип выражений, которые вы сравниваете:
var zero = 0; if (zero === false) { // не выполняется, потому что ноль равен 0, а не ложь } // антипаттерн if (zero == false) { // этот блок выполняется ... }
Есть другая школа мысли, которая придерживается мнения, что использование ===
когда ==
достаточно, излишне. Например, когда вы используете typeof, вы знаете, что он возвращает строку, поэтому нет причин использовать строгое равенство. Однако JSLint требует строгого равенства; это делает код выглядящим непротиворечивым и уменьшает умственные усилия при чтении кода. («Это ==
намеренно или упущение?»)
Как избежать eval ()
Если вы заметили использование eval()
в своем коде, помните мантру «eval () — зло». Эта функция принимает произвольную строку и выполняет ее как код JavaScript. Когда рассматриваемый код известен заранее (не определяется во время выполнения), нет смысла использовать eval()
. Если код генерируется динамически во время выполнения, часто есть лучший способ достичь цели без eval()
. Например, просто использовать квадратную скобку для доступа к динамическим свойствам лучше и проще:
// антипаттерн var property = "name"; alert (eval ("obj." + свойство)); // предпочтительнее var property = "name"; оповещение (OBJ [свойство]);
Использование eval()
также имеет последствия для безопасности, поскольку вы можете выполнять код (например, поступающий из сети), который был подделан. Это распространенный антипаттерн при работе с JSON-ответом на Ajax-запрос. В этих случаях лучше использовать встроенные в браузер методы для анализа ответа JSON, чтобы убедиться в его безопасности и допустимости. Для браузеров, которые изначально не поддерживают JSON.parse()
, вы можете использовать библиотеку из JSON.org.
Также важно помнить, что передача строк в setInterval()
, setTimeout()
и конструктор Function()
по большей части похожа на использование eval()
и, следовательно, ее следует избегать. За кулисами JavaScript все еще должен оценивать и выполнять строку, которую вы передаете как программный код:
// антипаттерны setTimeout ("myFunc ()", 1000); setTimeout ("myFunc (1, 2, 3)", 1000); // предпочтительнее setTimeout (myFunc, 1000); setTimeout (function () { myFunc (1, 2, 3); }, 1000);
Использование нового конструктора Function()
аналогично eval()
и к нему следует подходить с осторожностью. Это может быть мощная конструкция, но часто используется неправильно. Если вам абсолютно необходимо использовать eval()
, вы можете использовать вместо него новую Function()
. Есть небольшое потенциальное преимущество, потому что код, оцененный в new Function()
будет работать в локальной области функций, поэтому любые переменные, определенные с помощью var
в оцениваемом коде, не станут автоматически глобальными. Другой способ предотвратить автоматические глобальные переменные — это превратить вызов eval()
в непосредственную функцию.
Рассмотрим следующий пример. Здесь только un
остается глобальной переменной, загрязняющей пространство имен:
console.log (typeof un); // "undefined" console.log (typeof deux); // "undefined" console.log (typeof trois); // "undefined" var jsstring = "var un = 1; console.log (un);"; Eval (jsstring); // логи "1" jsstring = "var deux = 2; console.log (deux);"; новая функция (jsstring) (); // Журналы "2" jsstring = "var trois = 3; console.log (trois);"; (function () { Eval (jsstring); } ()); // логи "3" console.log (typeof un); // число console.log (typeof deux); // не определено console.log (typeof trois); // не определено
Другое различие между eval()
и конструктором Function заключается в том, что eval()
может вмешиваться в цепочку областей видимости, тогда как Function
является гораздо более изолированной средой. Независимо от того, где вы выполняете Function
, она видит только глобальную область видимости. Таким образом, это может сделать меньше локального переменного загрязнения. В следующем примере eval()
может получить доступ и изменить переменную во внешней области, тогда как Function не может (также обратите внимание, что использование Function или новой Function идентично):
(function () { var local = 1; eval ("local = 3; console.log (local)"); // logs 3 console.log (местное); // logs 3 } ()); (function () { var local = 1; Function ("console.log (typeof local);") (); // журналы не определены } ());
Преобразование чисел с parseInt ()
Используя parseInt()
вы можете получить числовое значение из строки. Функция принимает второй параметр radix, который часто опускается, но не должен быть. Проблемы возникают, когда анализируемая строка начинается с 0: например, часть даты вводится в поле формы. Строки, начинающиеся с 0, обрабатываются как восьмеричные числа (основание 8) в ECMAScript 3; однако, это изменилось в ES5. Чтобы избежать противоречий и неожиданных результатов, всегда указывайте параметр radix:
var month = "06", год = "09"; month = parseInt (month, 10); год = parseInt (год, 10);
В этом примере, если вы опустите параметр radix, такой как parseInt(year)
, возвращаемое значение будет 0
, потому что « 09
» предполагает восьмеричное число (как если бы вы делали parseInt( year, 8 )
), а 09
не является действительной цифрой в база 8
.
Альтернативные способы преобразования строки в число включают в себя:
+ "08" // результат 8 Номер («08») // 8
Они часто бывают быстрее, чем parseInt()
, потому что parseInt()
, как следует из названия, анализирует, а не просто конвертирует. Но если вы ожидаете ввода, такого как «08 hello», parseInt()
вернет число, тогда как остальные потерпят неудачу с NaN
.
Соглашения о кодировании
Важно установить и следовать соглашениям о кодировании — они делают ваш код непротиворечивым, предсказуемым и намного легче читаемым и понятным. Новый разработчик, присоединившийся к команде, может прочитать соглашения и работать гораздо быстрее, понимая код, написанный любым другим членом команды.
Многие пламенные войны велись на собраниях и в списках рассылки по конкретным аспектам определенных соглашений о кодировании (например, отступы кода — табуляции или пробелы?). Так что, если вы тот, кто предлагает принятие конвенций в вашей организации, будьте готовы встретить сопротивление и услышать разные, но одинаково сильные мнения. Помните, что гораздо важнее установить и последовательно следовать соглашению, любому соглашению, чем точные детали этого соглашения.
вдавливание
Код без отступа читать невозможно. Единственное, что хуже, это код с непоследовательным отступом, потому что похоже, что он следует соглашению, но на этом пути могут возникнуть неясные сюрпризы. Важно стандартизировать использование отступов.
Некоторые разработчики предпочитают отступ с помощью вкладок, потому что любой может настроить свой редактор, чтобы отображать вкладки с индивидуально предпочтительным количеством пробелов. Некоторые предпочитают пробелы — обычно четыре. Это не имеет значения, пока все в команде придерживаются одного и того же соглашения. Например, в этой книге используется отступ в четыре пробела, который также используется по умолчанию в JSLint.
И что вы должны сделать отступ? Правило простое — все в фигурных скобках. Это означает тела функций, циклов ( do, while, for, for-in
), ifs
, switches
и свойств object
литеральной нотации object
. Следующий код показывает несколько примеров использования отступов:
функция external (a, b) { var c = 1, д = 2, внутренний; если (а> б) { inner = function () { возвращение { r: c - d }; }; } еще { inner = function () { возвращение { r: c + d }; }; } вернуться внутрь; }
Фигурные скобки
Всегда следует использовать фигурные скобки, даже если они не являются обязательными. Технически, если у вас есть только одно утверждение в if
или for
, фигурные скобки не требуются, но вы всегда должны их использовать. Это делает код более согласованным и простым в обновлении.
Представьте, что у вас есть цикл for только с одним оператором. Вы можете опустить фигурные скобки, и синтаксическая ошибка не возникнет:
// плохая практика для (var i = 0; i <10; i + = 1) предупреждение (я);
Но что, если позже вы добавите еще одну строку в тело цикла?
// плохая практика для (var i = 0; i <10; i + = 1) предупреждение (я); alert (i + "is" + (i% 2? "odd": "even"));
Второе предупреждение находится за пределами цикла, хотя отступы могут вас обмануть. В долгосрочной перспективе лучше всего использовать фигурные скобки, даже для блоков из одной строки:
// лучше для (var i = 0; i <10; i + = 1) { предупреждение (я); }
Аналогично для условий:
// Плохо если правда) оповещение (1); еще оповещение (2); // лучше если правда) { оповещение (1); } еще { оповещение (2); }
Расположение открывающей скобки
Разработчики также склонны иметь предпочтения относительно того, где должна быть открывающая фигурная скобка — в той же строке или в следующей строке?
если правда) { alert («Это правда!»); }
ИЛИ:
если правда) { alert («Это правда!»); }
В этом конкретном примере это вопрос предпочтения, но есть случаи, когда программа может вести себя по-разному в зависимости от того, где находится скобка. Это происходит из-за semicolon insertion mechanism
с semicolon insertion mechanism
JavaScript не требователен, если вы решите не заканчивать свои строки точкой с запятой должным образом и добавляет ее для вас. Такое поведение может вызвать проблемы, когда функция возвращает литерал объекта и открывающая скобка находится на следующей строке:
// предупреждение: неожиданное возвращаемое значение function func () { возвращение // недостижимый код следует { название: "Бэтмен" } }
Если вы ожидаете, что эта функция вернет объект со свойством name
, вы будете удивлены. Из-за подразумеваемых точек с запятой функция возвращает undefined
. Предыдущий код эквивалентен этому:
// предупреждение: неожиданное возвращаемое значение function func () { возврат не определен; // недостижимый код следует { название: "Бэтмен" } }
В заключение, всегда используйте фигурные скобки и всегда ставьте открывающую скобку на той же строке, что и предыдущий оператор:
function func () { возвращение { название: "Бэтмен" }; }
Примечание о точках с запятой: Как и в фигурных скобках, вы всегда должны использовать точки с запятой, даже если они подразумеваются парсерами JavaScript. Это не только способствует дисциплине и более строгому подходу к коду, но также помогает устранить неоднозначности, как показано в предыдущем примере.
Пустое пространство
Использование пустого пространства также может способствовать улучшению читабельности и согласованности кода. В письменных английских предложениях вы используете интервалы после запятых и точек. В JavaScript вы следуете той же логике и добавляете интервалы после выражений, похожих на списки (эквивалентно запятым) и конца операторов (эквивалентно завершению «мысли»).
Хорошие места для использования пробела включают в себя:
- После точки с запятой, разделяющей части цикла for: например,
for (var i
= 0; i < 10; i += 1) {...}for (var i
= 0; i < 10; i += 1) {...} - Инициализация нескольких переменных (i и max) в цикле
for
:for (var i = 0, max = 10; i < max; i += 1) {...}
- После запятых, которые разделяют элементы массива:
var a = [1, 2, 3];
- После запятых в свойствах объекта и после двоеточий, которые разделяют имена свойств и
их значения:var o = {a: 1, b: 2};
- Разграничение аргументов функции:
myFunc(a, b, c)
- Перед фигурными скобками в объявлениях
function myFunc() {}
:function myFunc() {}
- После
function
в выражениях анонимных функций:var myFunc = function () {};
Другое хорошее использование пробелов — разделять все операторы и их операнды пробелами, что в основном означает использование пробела до и после +, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=,
и так далее:
// щедрый и последовательный интервал // облегчает чтение кода // позволяя ему «дышать» var d = 0, а = б + 1; if (a && b && c) { d = a% c; а + = д; } // антипаттерн // пропущенные или несовместимые пробелы // сделать код запутанным var d = 0, а = б + 1; if (a && b && c) { d = a% c; а + = д; }
И последнее замечание о пустом месте — в фигурных скобках. Хорошо использовать пробел:
- Перед открытием фигурных скобок (
{
) в функциях, случаяхif-else
, циклах и литералах объектов - Между закрывающей фигурной скобкой (
}
) иelse
илиwhile
Случай с либеральным использованием пустого пространства может заключаться в том, что это может увеличить размер файла, но
минификация решает эту проблему.
Часто пропускаемым аспектом читабельности кода является использование вертикального пробела. Вы можете использовать пустые строки для разделения единиц кода, так же как абзацы используются в литературе для разделения идей.
Соглашения об именах
Другой способ сделать ваш код более предсказуемым и обслуживаемым — это принять соглашения об именах. Это означает, что вы должны выбирать имена для своих переменных и функций согласованным образом.
Ниже приведены некоторые предложения по соглашениям об именах, которые вы можете принять как есть или настроить по своему вкусу. Опять же, наличие соглашения и его последовательное соблюдение гораздо важнее, чем это соглашение на самом деле.
Использование заглавных букв в конструкторах
JavaScript не имеет классов, но имеет функции конструктора, вызываемые с помощью new
:
var adam = new Person ();
Поскольку конструкторы по-прежнему являются просто функциями, полезно, если вы можете сказать, просто взглянув на имя функции, должна ли она вести себя как конструктор или как обычная функция.
Обозначение конструкторов с заглавной буквой обеспечивает эту подсказку. Использование строчных букв для функций и методов означает, что они не должны вызываться с new
:
function MyConstructor () {...} function myFunction () {...}
Разделение слов
Если у вас есть несколько слов в переменной или имени функции, рекомендуется следовать соглашению о том, как будут разделяться слова. Общепринятым условием является использование так называемого случая верблюда . Следуя соглашению о верблюде, вы вводите слова в нижнем регистре, используя только заглавные буквы в каждом слове.
Для ваших конструкторов вы можете использовать верхний регистр верблюда , как в MyConstructor()
, а для имен функций и методов вы можете использовать нижний регистр верблюда , как в myFunction()
, calculateArea()
и getFirstName()
.
А как насчет переменных, которые не являются функциями? Разработчики обычно используют нижний регистр верблюда для имен переменных, но другой хорошей идеей является использование всех строчных слов, разделенных подчеркиванием: например, first_name
, old_company_name
и old_company_name
. Эта запись помогает вам визуально различать функции и все другие идентификаторы — примитивы и объекты.
ECMAScript использует верблюжий регистр как для методов, так и для свойств, хотя имена свойств из нескольких слов встречаются редко (свойства lastIndex
и ignoreCase
объектов регулярного выражения).
Другие шаблоны именования
Иногда разработчики используют соглашение об именах для создания или замены языковых функций.
Например, нет способа определить константы в JavaScript (хотя есть некоторые встроенные, такие как Number.MAX_VALUE
), поэтому разработчики приняли соглашение об использовании всех заглавных букв для именования переменных, которые не должны изменять значения в течение жизни программы, как:
// драгоценные константы, пожалуйста, не трогайте вар PI = 3,14, MAX_WIDTH = 800;
Есть другое соглашение, которое конкурирует за использование всех заглавных букв: использование заглавных букв для имен глобальных переменных. Обозначение глобалов со всеми заглавными буквами может усилить практику минимизации их числа и сделать их легко различимыми.
Другой случай использования соглашения для имитации функциональных возможностей — это соглашение о закрытых членах. Хотя вы можете реализовать настоящую конфиденциальность в JavaScript, иногда разработчикам проще использовать префикс подчеркивания для обозначения частного метода или свойства. Рассмотрим следующий пример:
var person = { getName: function () { вернуть this._getFirst () + '' + this._getLast (); }, _getFirst: function () { // ... }, _getLast: function () { // ... } };
В этом примере getName()
должен быть публичным методом, частью стабильного API, тогда как _getFirst()
и _getLast()
должны быть приватными. Они по-прежнему являются обычными публичными методами, но использование префикса подчеркивания предупреждает пользователей объекта person о том, что эти методы не гарантированно будут работать в следующем выпуске и не должны использоваться напрямую. Обратите внимание, что JSLint будет жаловаться на префиксы подчеркивания, если вы не установите опцию nomen: false
.
Ниже приведены некоторые варианты соглашения _private
:
- Использование завершающего подчеркивания для обозначения private, как в
name_
иgetElements_()
- Использование одного префикса подчеркивания для
_protected
свойств и двух для__private
свойств - В Firefox доступны некоторые внутренние свойства, не являющиеся технически частью языка, и они именуются префиксом с двумя подчеркиваниями и суффиксами с двумя подчеркиваниями, такими как
__proto__
и__parent__
Написание комментариев
Вы должны прокомментировать свой код, даже если вряд ли кто-то, кроме вас, когда-нибудь его коснется. Часто, когда вы глубоко погружены в проблему, вы понимаете, что делает код, но когда вы возвращаетесь к коду через неделю, вам трудно вспомнить, как он работал точно.
Вы не должны переусердствовать, комментируя очевидное: каждую переменную или каждую строку. Но обычно вам нужно документировать все функции, их аргументы и возвращаемые значения, а также любой интересный или необычный алгоритм или технику. Думайте о комментариях как о подсказках для будущих читателей кода; читатели должны понимать, что делает ваш код, не читая намного больше, чем просто комментарии и имена функций и свойств. Когда у вас есть, например, пять или шесть строк кода, выполняющих определенную задачу, читатель может пропустить детали кода, если вы предоставите однострочное описание, описывающее назначение кода и причины его появления. Там нет жесткого и быстрого правила или соотношения комментариев к коду; некоторые фрагменты кода (например, регулярные выражения) могут потребовать больше комментариев, чем кода.
Самая важная привычка, которой все же трудно следовать, — постоянно обновлять комментарии, потому что устаревшие комментарии могут вводить в заблуждение и быть намного хуже, чем вообще никаких комментариев.
об авторе
веб-разработчик и автор , участник и технический рецензент различных книг О’Рейли. Он регулярно говорит о темах веб-разработки на конференциях и в своем блоге на www.phpied.com . Стоян — создатель инструмента оптимизации изображений smush.it, участник YUI и разработчик инструмента оптимизации производительности Yahoo YSlow 2.0.
Купить книгу
Эта статья является отрывком из « Паттернов JavaScript » O’Reilly Media .