- "Объекты: {}"
- Функции: f (x)
- Массивы: []
- Типы: =
- Ядро: js
- "Другой: ?"
Объекты
Использование и свойства объекта
Все в JavaScript действует как объект, за двумя исключениями: null
и undefined
.
Распространенное заблуждение состоит в том, что числовые литералы нельзя использовать как объекты. Это связано с тем, что ошибка в синтаксическом анализаторе JavaScript пытается проанализировать точечную нотацию числа как литерал с плавающей запятой.
Есть несколько обходных путей, которые можно использовать, чтобы числовые литералы тоже действовали как объекты.
Объекты как тип данных
Объекты в JavaScript также могут использоваться как хэш-карты; в основном они состоят из сопоставления именованных свойств значениям.
Используя литерал объекта - {}
нотацию - можно создать простой объект. Этот новый объект наследуется от Object.prototype
и не имеет собственных свойств.
Доступ к свойствам
Доступ к свойствам объекта можно получить двумя способами: через точечную запись или через квадратную скобку.
Обозначения работают почти идентично, с той лишь разницей, что обозначение квадратных скобок позволяет динамически устанавливать свойства и использовать имена свойств, которые в противном случае привели бы к синтаксической ошибке.
Удаление свойств
Единственный способ удалить свойство из объекта - использовать оператор delete
; установка для свойства значения undefined
или null
удаляет только значение, связанное со свойством, но не ключ.
Вышеупомянутые выходы bar undefined
и foo null
- только baz
был удален и поэтому отсутствует в выходных данных.
Обозначение ключей
Свойства объекта могут быть обозначены как простые символы, так и как строки. Из-за другого неправильного дизайна в парсере JavaScript вышеупомянутое будет выдавать SyntaxError
до ECMAScript 5.
Эта ошибка возникает из-за того, что delete
является ключевым словом; поэтому он должен быть обозначен как строковый литерал, чтобы гарантировать, что он будет правильно интерпретирован старыми механизмами JavaScript.
Прототип
В JavaScript нет классической модели наследования; вместо этого он использует прототип.
Хотя это часто считается одной из слабых сторон JavaScript, прототипная модель наследования на самом деле более мощная, чем классическая. Например, довольно тривиально построить классическую модель поверх прототипной модели, тогда как наоборот - гораздо более сложная задача.
JavaScript - единственный широко используемый язык с прототипным наследованием, поэтому может потребоваться время, чтобы приспособиться к различиям между двумя моделями.
Первое существенное отличие состоит в том, что наследование в JavaScript использует цепочки прототипов.
Примечание. Простое использование Bar.prototype = Foo.prototype
приведет к тому, что оба объекта будут использовать один и тот же прототип. Следовательно, изменения прототипа любого объекта повлияют и на прототип другого объекта, что в большинстве случаев не является желаемым эффектом.
// The resulting prototype chain
test [instance of Bar]
Bar.prototype [instance of Foo]
{ foo: 'Hello World' }
Foo.prototype
{ method: ... }
Object.prototype
{ toString: ... /* etc. */ }
В приведенном выше коде объект test
будет наследовать как от Bar.prototype
, так и отFoo.prototype
; следовательно, у него будет доступ к функции method
, которая была определена Foo
. Он также будет иметь доступ к свойству value
того экземпляра Foo
, который является его прототипом. Важно отметить, что new Bar()
не создает новый Foo
instance, а повторно использует тот, который назначен его прототипу; таким образом, все Bar
экземпляры будут иметь одно и то же свойство value
.
Примечание. Не используйте Bar.prototype = Foo
, поскольку он будет указывать не на прототип Foo
, а на объект функции Foo
. Таким образом, цепочка прототипов будет превышать Function.prototype
, а не Foo.prototype
; следовательно, method
не будет в цепочке прототипов.
Поиск недвижимости
При доступе к свойствам объекта JavaScript будет перемещаться по цепочке прототипов вверх, пока не найдет свойство с запрошенным именем.
Если он достигнет вершины цепочки, а именно Object.prototype
, и все еще не найдет указанное свойство, вместо этого он вернет значение undefined.
Свойство прототипа
Хотя свойство prototype используется языком для построения цепочек прототипов, ему все еще можно присвоить любое заданное значение. Однако примитивы будут просто игнорироваться при назначении в качестве прототипа.
Назначение объектов, как показано в приведенном выше примере, будет работать и позволяет динамически создавать цепочки прототипов.
Представление
Время поиска свойств, которые находятся наверху в цепочке прототипов, может отрицательно сказаться на производительности, и это может быть значительным в коде, где производительность критична. Кроме того, попытка доступа к несуществующим свойствам всегда будет проходить через всю цепочку прототипов.
Кроме того, при переборе свойств объекта каждое свойство в цепочке прототипов будет пронумеровано.
Расширение собственных прототипов
Одна неправильная функция, которая часто используется, - это расширение Object.prototype
или одного из других встроенных прототипов.
Этот метод называется обезьяньим исправлением и нарушает инкапсуляцию. Несмотря на то, что они используются популярными фреймворками, такими как Prototype, все же нет веских причин загромождать встроенные типы дополнительными нестандартными функциональными возможностями.
Единственная веская причина для расширения встроенного прототипа - это резервное копирование функций новых движков JavaScript; например, Array.forEach
.
В заключение
Важно понять прототипную модель наследования, прежде чем писать сложный код, который ее использует. Кроме того, помните о длине цепочек прототипов в своем коде и при необходимости разбейте их, чтобы избежать возможных проблем с производительностью. Кроме того, нативные прототипы никогда не следует расширять, за исключением случаев, когда это делается ради совместимости с новыми функциями JavaScript.
hasOwnProperty
Чтобы проверить, имеет ли объект свойство, определенное в самом, а не где-то в его цепочке прототипов, необходимо использовать метод hasOwnProperty
, который все объекты наследуют от Object.prototype
.
Примечание. Недостаточно проверить, соответствует ли свойство undefined
. Свойство вполне может существовать, но его значение просто установлено наundefined
.
hasOwnProperty
- единственная вещь в JavaScript, которая имеет дело со свойствами и не пересекает цепочку прототипов.
Только hasOwnProperty
даст правильный и ожидаемый результат. См. Раздел о for in
циклах для получения более подробной информации о том, когда использовать hasOwnProperty
при итерации по свойствам объекта.
hasOwnProperty
как собственность
JavaScript не защищает имя свойства hasOwnProperty
; таким образом, если существует вероятность того, что объект может иметь свойство с таким именем, необходимо использовать external hasOwnProperty
для получения правильных результатов.
В заключение
Использование hasOwnProperty
- единственный надежный метод проверки наличия свойства у объекта. Рекомендуется использовать hasOwnProperty
во многих случаях при переборе свойств объекта, как описано в разделе, посвященном for in
циклам.
for in
Петля
Так же, как оператор in
, цикл for in
проходит по цепочке прототипов при повторении свойств объекта.
Примечание. Цикл for in
не будет повторять любые свойства, для которых enumerable
attribute установлен в false
; например, свойство length
массива.
Поскольку невозможно изменить поведение самого цикла for in
, необходимо отфильтровать нежелательные свойства внутри тела цикла. В ECMAScript 3 и старше это делается с использованием метода hasOwnProperty
из Object.prototype
.
Начиная с ECMAScript 5, Object.defineProperty
можно использовать с enumerable
, установленным на false
, для добавления свойств к объектам (включая Object
) без перечисления этих свойств. В этом случае разумно предположить в коде приложения, что какие-либо перечислимые свойства были добавлены по какой-либо причине, и опустить hasOwnProperty
, поскольку это делает код более подробным и менее читаемым. В библиотеке код hasOwnProperty
по-прежнему должен использоваться, поскольку невозможно сделать предположения о том, какие перечислимые свойства могут находиться в цепочке прототипов.
Примечание. Поскольку for in
всегда проходит через всю цепочку прототипов, он будет медленнее с каждым дополнительным уровнем наследования, добавляемым к объекту.
Использование hasOwnProperty
для фильтрации
Эта версия является единственно правильной для использования со старыми версиями ECMAScript. Из-за использования hasOwnProperty
будет распечатан только moo
. Если hasOwnProperty
не указан, код подвержен ошибкам в тех случаях, когда собственные прототипы - например, Object.prototype
- были продлены.
В более новых версиях ECMAScript неперечислимые свойства могут быть определены с помощью Object.defineProperty
, что снижает риск итерации по свойствам без использования hasOwnProperty
. Тем не менее, следует соблюдать осторожность при использовании старых библиотек, таких как Prototype, которые еще не используют преимущества новых функций ECMAScript. Когда эта структура включена, for in
петли, которые не используют hasOwnProperty
, гарантированно разорвутся.
В заключение
Рекомендуется всегда использовать hasOwnProperty
в ECMAScript 3 или ниже, а также в коде библиотеки. В этих средах никогда не следует делать предположений о том, были ли расширены собственные прототипы или нет. Начиная с ECMAScript 5, Object.defineProperty
позволяет определять неперечислимые свойства и опускать hasOwnProperty
в коде приложения.
Функции
Объявления и выражения функций
Функции в JavaScript - это объекты первого класса. Это означает, что их можно передавать как любое другое значение. Одним из распространенных способов использования этой функции является передача анонимной функции в качестве обратного вызова другой, возможно, асинхронной функции.
Декларация function
Вышеупомянутая функция поднимается до начала выполнения программы; таким образом, он доступен везде в той области, в которой он был определен, даже если он был вызван до фактического определения в источнике.
function
Выражение
В этом примере переменной foo
назначается безымянная и анонимная функция.
Поскольку var
- это объявление, которое поднимает имя переменной foo
до начала фактического выполнения кода, foo
уже объявляется при выполнении сценария.
Но поскольку присвоения происходят только во время выполнения, значение foo
по умолчанию будет неопределенным до того, как будет выполнен соответствующий код.
Выражение именованной функции
Другой частный случай - назначение именованных функций.
Здесь bar
недоступен во внешней области видимости, поскольку функция назначается только foo
; однако внутри bar
он доступен. Это связано с тем, как работает разрешение имен в JavaScript, имя функции всегда доступно в локальной области видимости самой функции.
How this
Работает
В JavaScript используется иное представление о том, что относится к специальному имени this
, чем в большинстве других языков программирования. Существует ровно пять различных способов привязки значения this
в языке.
Глобальный масштаб
При использовании this
в глобальной области видимости он просто ссылается на глобальный объект.
Вызов функции
Здесь this
снова будет ссылаться на глобальный объект.
Примечание ES5: в строгом режиме глобального регистра больше не существует. В этом случае this
будет иметь значение undefined
.
Вызов метода
В этом примере this
будет относиться к test
.
Вызов конструктора
Вызов функции, которому предшествует ключевое слово new
, действует как конструктор. Внутри функции this
будет ссылаться на вновь созданный Object
.
Явная установка this
При использовании call
или apply
методов Function.prototype
значение this
внутри вызываемой функции явно устанавливается на первый аргумент соответствующего вызова функции.
В результате в приведенном выше примере case method не применяется, и this
внутри foo
будет установлено значение bar
.
Примечание. this
нельзя использовать для ссылки на объект внутри литерала Object
. Таким образом, var obj = {me: this}
не приведет к me
ссылке на obj
, поскольку this
связан только с одним из пяти перечисленных случаев.
Общие ловушки
Хотя большинство из этих случаев имеют смысл, первый можно считать еще одним неправильным дизайном языка, потому что он никогда не имеет практического применения.
Распространенное заблуждение состоит в том, что this
внутри test
относится к Foo
; хотя на самом деле это не так.
Чтобы получить доступ к Foo
из test
, вы можете создать локальную переменную внутри method
, которая ссылается на Foo
.
self
- это просто обычное имя переменной, но обычно оно используется для ссылки на внешний this
. В сочетании с замыканиями его также можно использовать для передачи this
значений.
Начиная с ECMAScript 5, вы можете использовать метод bind
в сочетании с анонимной функцией для достижения того же результата.
Назначение методов
Еще одна вещь, которая не работает в JavaScript, - это псевдоним функции, который присваивает метод переменной.
Из-за первого случая test
теперь действует как простой вызов функции; следовательно, this
внутри он больше не будет относиться к someObject
.
Хотя позднее связывание this
может сначала показаться плохой идеей, на самом деле именно это заставляет работать прототипное наследование.
Когда method
вызывается для экземпляра Bar
, this
теперь будет ссылаться на этот самый экземпляр.
Замыкания и ссылки
Одна из самых мощных функций JavaScript - наличие закрытий. При замыканиях области всегда сохраняют доступ к внешней области, в которой они были определены. Поскольку единственная область видимости JavaScript - это область действия, все функции по умолчанию действуют как замыкания.
Эмуляция частных переменных
Здесь Counter
возвращает два закрытия: функцию increment
, а также функцию get
. Обе эти функции хранят ссылку на область видимости Counter
и, следовательно, всегда сохраняют доступ к переменной count
, которая была определена в этой области.
Почему работают частные переменные
Поскольку в JavaScript невозможно ссылаться или назначать области видимости, невозможно получить доступ к переменной count
извне. Единственный способ взаимодействовать с ним - через два закрытия.
Приведенный выше код не изменит переменную count
в области Counter
, поскольку foo.hack
не был определен в этой области. Вместо этого он создаст - или переопределит - глобальную переменную count
.
Замки внутри петель
Одна из часто совершаемых ошибок - использовать замыкания внутри циклов, как если бы они копировали значение индексной переменной цикла.
Вышеупомянутое число не выводит числа с 0
по 9
, а просто выводит число 10
десять раз.
Функция анонимная сохраняет ссылку на i
. На момент вызова console.log
for loop
уже закончил, и значение i
было установлено на 10
.
Чтобы добиться желаемого поведения, необходимо создать копию значения i
.
Как избежать проблемы с ссылками
Чтобы скопировать значение индексной переменной цикла, лучше всего использовать анонимную оболочку.
Анонимная внешняя функция вызывается немедленно с i
в качестве первого аргумента и получает копию значения i
в качестве параметра e
.
Анонимная функция, передаваемая в setTimeout
, теперь имеет ссылку на e
, значение которой не изменяется в цикле.
Есть еще один возможный способ достижения этого, который состоит в том, чтобы вернуть функцию из анонимной оболочки, которая затем будет иметь то же поведение, что и приведенный выше код.
Другой популярный способ добиться этого - добавить дополнительный аргумент к функции setTimeout
, которая передает эти аргументы функции обратного вызова.
Некоторые устаревшие среды JS (Internet Explorer 9 и ниже) не поддерживают это.
Есть еще один способ добиться этого, используя .bind
, который может связывать this
context и аргументы с функцией. Он ведет себя идентично приведенному выше коду
Объект arguments
Каждая область действия в JavaScript может обращаться к специальной переменной arguments
. Эта переменная содержит список всех аргументов, переданных функции.
Примечание. В случае, если arguments
уже был определен внутри области действия функции посредством оператора var
или в качестве имени формального параметра, объект arguments
не будет создан.
Объект arguments
не является Array
. Хотя он имеет некоторую семантику массива, а именно свойство length
, он не наследуется от Array.prototype
и фактически является Object
.
Из-за этого невозможно использовать стандартные методы массива, такие как push
, pop
или slice
на arguments
. Хотя итерация с простым for
циклом работает нормально, необходимо преобразовать его в реальный Array
, чтобы использовать на нем стандартные Array
методы.
Преобразование в массив
Приведенный ниже код вернет новый Array
, содержащий все элементы arguments
object.
Поскольку это преобразование выполняется медленно, не рекомендуется использовать его в критических для производительности разделах кода.
Передача аргументов
Ниже приводится рекомендуемый способ передачи аргументов от одной функции к другой.
Другой трюк - использовать вместе call
и apply
, чтобы превратить методы - функции, которые используют значение this
, а также их аргументы, в обычные функции, которые используют только свои аргументы.
Формальные параметры и индексы аргументов
Объект arguments
создает функции getter и setter как для своих свойств, так и для формальных параметров функции.
В результате изменение значения формального параметра также изменит значение соответствующего свойства объекта arguments
, и наоборот.
Мифы и правда о производительности
Единственный раз, когда объект arguments
не создается, это когда он объявляется как имя внутри функции или одного из ее формальных параметров. Неважно, используется он или нет.
И геттеры, и сеттеры создаются всегда; таким образом, его использование практически не влияет на производительность, особенно в реальном коде, где есть нечто большее, чем простой доступ к свойствам объекта arguments
.
Примечание ES5. Эти геттеры и сеттеры не создаются в строгом режиме.
Однако есть один случай, который резко снизит производительность современных движков JavaScript. Это случай использования arguments.callee
.
В приведенном выше коде foo
больше не может быть объектом встраивания, поскольку ему нужно знать как о себе, так и о вызывающем. Это не только сводит на нет возможное повышение производительности, которое могло бы возникнуть в результате встраивания, но также нарушает инкапсуляцию, поскольку функция теперь может зависеть от конкретного контекста вызова.
Настоятельно не рекомендуется использовать arguments.callee
или любое из его свойств.
Примечание ES5: В строгом режиме arguments.callee
выдаст TypeError
, поскольку его использование устарело.
Конструкторы
Конструкторы в JavaScript снова отличаются от многих других языков. Любой вызов функции, которому предшествует ключевое слово new
, действует как конструктор.
Внутри конструктора - вызываемой функции - значение this
относится к вновь созданному объекту. Прототипом этого нового объекта устанавливается prototype
объекта функции, который был вызван как конструктор.
Если вызываемая функция не имеет явного return
оператора, то она неявно возвращает значение this
- нового объекта.
Вышеупомянутый вызов Person
как конструктор и устанавливает prototype
вновь созданного объекта на Person.prototype
.
В случае явного оператора return
функция возвращает значение, указанное этим оператором, но только если возвращаемое значение - Object
.
Если ключевое слово new
опущено, функция не вернет новый объект.
Хотя приведенный выше пример может показаться работающим в некоторых случаях, из-за работы this
в JavaScript, он будет использовать глобальный объект в качестве значения this
.
Заводы
Чтобы можно было опустить ключевое слово new
, функция-конструктор должна явно возвращать значение.
Оба вызова Robot
возвращают одно и то же - вновь созданный объект со свойством getColor
, которое является закрытием.
Также следует отметить, что вызов new Robot()
не влияет на прототип возвращаемого объекта. Хотя прототип будет установлен на вновь созданном объекте, Robot
никогда не вернет этот новый объект.
В приведенном выше примере нет функциональной разницы между использованием и неиспользованием ключевого слова new
.
Создание новых объектов с помощью фабрик
Часто рекомендуется не использовать new
, потому что забвение о его использовании может привести к ошибкам.
Чтобы создать новый объект, лучше использовать фабрику и построить новый объект внутри этой фабрики.
Хотя приведенное выше является устойчивым к отсутствию ключевого слова new
и, безусловно, упрощает использование частных переменных, оно имеет некоторые недостатки.
- Он использует больше памяти, поскольку созданные объекты не используют общие методы прототипа.
- Для наследования фабрика должна скопировать все методы из другого объекта или поместить этот объект в прототип нового объекта.
- Отказ от цепочки прототипов только из-за пропущенного ключевого слова
new
противоречит духу языка.
В заключение
Хотя пропуск ключевого слова new
может привести к ошибкам, это, конечно, не повод полностью отказываться от использования прототипов. В конце концов, все сводится к тому, какое решение лучше подходит для нужд приложения. Особенно важно выбрать определенный стиль создания объекта и последовательно использовать его.
Области действия и пространства имен
Хотя JavaScript отлично справляется с синтаксисом двух совпадающих фигурных скобок для блоков, он не поддерживает область видимости блока; следовательно, все, что осталось в языке, - это область действия.
Примечание. Когда не используется в операторе присваивания, возврата или в качестве аргумента функции, нотация {...}
будет интерпретироваться как оператор блока, а не как литерал объекта. В сочетании с автоматической вставкой точек с запятой это может привести к незначительным ошибкам.
В JavaScript также нет отдельных пространств имен, а это означает, что все определяется в одном глобальном общем пространстве имен.
Каждый раз, когда на переменную ссылаются, JavaScript будет перемещаться вверх через все области, пока не найдет ее. В случае, если он достигнет глобальной области и все еще не найдет запрошенное имя, он вызовет ReferenceError
.
Бич глобальных переменных
Два приведенных выше сценария не имеют одинакового эффекта. Сценарий A определяет переменную с именем foo
в глобальной области, а сценарий B определяет foo
в текущей области.
Опять же, это совсем не тот же эффект: отказ от использования var
может иметь серьезные последствия.
Отсутствие оператора var
внутри функции test
переопределит значение foo
. Хотя сначала это может показаться неважным, наличие тысяч строк JavaScript и неиспользование var
приведет к появлению ужасных ошибок, которые трудно отследить.
Внешний цикл завершится после первого вызова subLoop
, поскольку subLoop
перезаписывает глобальное значение i
. Использование var
для второго цикла for
позволило бы легко избежать этой ошибки. Оператор var
никогда не следует пропускать, если желаемый эффект не должен повлиять на внешнюю область видимости.
Локальные переменные
Единственным источником локальных переменных в JavaScript являются параметры функции и переменные, объявленные с помощью оператора var
.
В то время как foo
и i
являются локальными переменными внутри области действия функции test
, присвоение bar
переопределит глобальную переменную с тем же именем.
Подъем
JavaScript поднимает объявления. Это означает, что как var
, так и function
declarations будут перемещены в верхнюю часть их охватывающей области.
Приведенный выше код преобразуется перед началом выполнения. JavaScript перемещает var
statements, а также function
объявления в верхнюю часть ближайшей окружающей области.
Отсутствие области видимости блока не только выведет var
операторы из циклов и их тел, но также сделает результаты некоторых if
конструкций не интуитивно понятными.
В исходном коде, хотя оператор if
, казалось, изменял глобальную переменную goo
, на самом деле он изменяет локальную переменную - после применения подъема.
Не зная подъема, можно было предположить, что приведенный ниже код вызовет aReferenceError
.
Но, конечно, это работает из-за того, что оператор var
перемещается в верхнюю часть глобальной области видимости.
Порядок разрешения имен
Все области в JavaScript, включая глобальную область, имеют определенное в них специальное имя this
, которое относится к текущему объекту.
Области функций также имеют определенное в них имя arguments
, которое содержит аргументы, переданные функции.
Например, при попытке доступа к переменной с именем foo
внутри области действия функции JavaScript будет искать имя в следующем порядке:
- Если в текущей области есть оператор
var foo
, используйте его. - Если один из параметров функции называется
foo
, используйте его. - Если сама функция называется
foo
, используйте это. - Перейдите к следующему внешнему прицелу и снова начните с №1.
Примечание. Наличие параметра с именем arguments
предотвратит создание объекта по умолчанию arguments
.
Пространства имён
Распространенная проблема, связанная с наличием только одного глобального пространства имен, - это вероятность столкнуться с проблемами, когда имена переменных конфликтуют. В JavaScript этой проблемы легко избежать с помощью анонимных оболочек.
Безымянные функции считаются выражениями; поэтому для того, чтобы их можно было вызвать, их сначала нужно оценить.
Существуют и другие способы оценки и прямого вызова выражения функции, которые, хотя и различаются по синтаксису, ведут себя одинаково.
В заключение
Рекомендуется всегда использовать анонимную оболочку для инкапсуляции кода в собственном пространстве имен. Это не только защищает код от конфликтов имен, но также позволяет лучше модулировать программы.
Кроме того, использование глобальных переменных считается плохой практикой. Любое их использование указывает на плохо написанный код, который подвержен ошибкам и сложен в обслуживании.
Массивы
Итерация и свойства массива
Хотя массивы в JavaScript являются объектами, нет веских причин для использования цикла for in
. Фактически, есть ряд веских причин против использования for in
в массивах.
Примечание. Массивы JavaScript не являются ассоциативными массивами. В JavaScript есть только объекты для сопоставления ключей и значений. И хотя ассоциативные массивы сохраняют порядок, объекты - нет.
Поскольку цикл for in
перечисляет все свойства, входящие в цепочку прототипов, и поскольку единственный способ исключить эти свойства - использовать hasOwnProperty
, он уже в двадцать раз медленнее, чем обычный цикл for
.
Итерация
Для достижения максимальной производительности при переборе массивов лучше всего использовать классический цикл for
.
В приведенном выше примере есть еще одна уловка, а именно кеширование длины массива через l = list.length
.
Хотя свойство length
определено в самом массиве, по-прежнему существуют накладные расходы на выполнение поиска на каждой итерации цикла. И хотя последние движки JavaScript могут применять оптимизацию в этом случае, невозможно сказать, будет ли код работать на одном из этих новых движков или нет.
Фактически, отказ от кеширования может привести к тому, что цикл будет вдвое медленнее, чем при длине кеширования.
Свойство length
В то время как getter свойства length
просто возвращает количество элементов, содержащихся в массиве, setter можно использовать для усечения массива.
Назначение меньшей длины обрезает массив. При его увеличении создается разреженный массив.
В заключение
Для достижения наилучшей производительности рекомендуется всегда использовать простой цикл for
и кэшировать свойство length
. Использование for in
в массиве - признак плохо написанного кода, подверженного ошибкам и плохой производительности.
Конструктор Array
Поскольку конструктор Array
неоднозначно относится к своим параметрам, настоятельно рекомендуется использовать литерал массива - нотацию []
- при создании новых массивов.
В случаях, когда конструктору Array
передается только один аргумент и когда этот аргумент является Number
, конструктор возвращает новый разреженный массив со свойством length
, установленным на значение аргумента. Следует отметить, что таким образом будет установлено только свойство length
нового массива; фактические индексы массива не будут инициализированы.
Возможность заранее установить длину массива полезна только в некоторых случаях, например, при повторении строки, когда избегается использование цикла.
В заключение
Литералы предпочтительнее конструктора массива. Они короче, имеют более четкий синтаксис и повышают удобочитаемость кода.
Типы
Равенство и сравнения
В JavaScript есть два разных способа сравнения значений объектов на предмет равенства.
Оператор равенства
Оператор равенства состоит из двух знаков равенства: ==
Возможности JavaScript слабая типизация. Это означает, что оператор равенства приводит типы для их сравнения.
В приведенной выше таблице показаны результаты приведения типов, и это основная причина, по которой использование ==
широко считается плохой практикой. Из-за сложных правил преобразования он вносит ошибки, которые трудно отследить.
Кроме того, принуждение типов также влияет на производительность; например, строка должна быть преобразована в число, прежде чем ее можно будет сравнить с другим числом.
Оператор строгого равенства
Оператор строгого равенства состоит из трех знаков равенства: ===
.
Он работает как обычный оператор равенства, за исключением того, что оператор строгого равенства не выполняет приведение типов между своими операндами.
Вышеупомянутые результаты намного яснее и допускают преждевременную поломку кода. Это до некоторой степени делает код более жестким, а также дает улучшение производительности в случае, если операнды относятся к разным типам.
Сравнение объектов
Хотя оба ==
и ===
называются операторами равенства, они ведут себя по-разному, когда хотя бы один из их операндов - Object
.
Здесь оба оператора сравнивают идентичность, а не равенство; то есть они будут сравнивать один и тот же экземпляр объекта, как is
в Python и сравнение указателей в C.
В заключение
Настоятельно рекомендуется использовать только оператор строгого равенства. В тех случаях, когда необходимо приводить типы, это следует делать явно, а не оставлять на усмотрение сложных правил принуждения языка.
Оператор typeof
Оператор typeof
(вместе с instanceof
), вероятно, является самым большим недостатком дизайна JavaScript, поскольку он почти полностью сломан.
Хотя instanceof
по-прежнему имеет ограниченное применение, typeof
на самом деле имеет только один практический вариант использования, который не связан с проверкой типа объекта.
Примечание. Хотя typeof
также можно вызвать с помощью функции, подобной синтаксису, например, typeof(obj)
, это не вызов функции. Скобки действуют как обычно, а возвращаемое значение будет использоваться как операнд оператора typeof
. Нетtypeof
функции.
Таблица типов JavaScript
В приведенной выше таблице Тип относится к значению, которое возвращает оператор typeof
. Как ясно видно, это значение далеко не постоянное.
Класс относится к значению внутреннего свойства [[Class]]
объекта.
Из спецификации: значение [[Class]]
может быть одной из следующих строк. Arguments
, Array
, Boolean
, Date
, Error
, Function
, JSON
, Math
, Number
, Object
, RegExp
, String
.
Класс объекта
Единственный способ определить [[Class]]
значение объекта - использовать Object.prototype.toString
. Он возвращает строку в следующем формате: '[object ' + valueOfClass + ']'
, например, [object String]
или [object Array]
:
В приведенном выше примере вызывается Object.prototype.toString
со значением this, установленным для объекта, значение [[Class]]
которого должно быть получено.
Примечание ES5: для удобства возвращаемое значение Object.prototype.toString
для null
и undefined
было изменено с Object
на Null
и Undefined
в ECMAScript 5.
Тестирование неопределенных переменных
Вышеупомянутое проверяет, действительно ли был объявлен foo
; просто ссылка на него приведет к ReferenceError
. Это единственное, для чего действительно полезен typeof
.
В заключение
Для проверки типа объекта настоятельно рекомендуется использоватьObject.prototype.toString
, потому что это единственный надежный способ сделать это. Как показано в приведенной выше таблице типов, некоторые возвращаемые значения typeof
не определены в спецификации; таким образом, они могут различаться в разных реализациях.
Если не проверять, определена ли переменная, typeof
следует избегать.
Оператор instanceof
Оператор instanceof
сравнивает конструкторы двух своих операндов. Это полезно только при сравнении объектов, изготовленных на заказ. Используемый для встроенных типов, он почти так же бесполезен, как оператор typeof.
Сравнение пользовательских объектов
Использование instanceof
с собственными типами
Здесь важно отметить, что instanceof
не работает с объектами, которые происходят из разных контекстов JavaScript (например, из разных документов в веб-браузере), поскольку их конструкторы не будут одним и тем же объектом.
В заключение
Оператор instanceof
следует использовать только при работе с объектами, созданными на заказ, которые происходят из одного и того же контекста JavaScript. Как и в случае с оператором typeof
, следует избегать его любого другого использования.
Тип литья
JavaScript - это слабо типизированный язык, поэтому он будет применять приведение типов везде, где это возможно.
Примечание ES5: числовые литералы, начинающиеся с 0
, интерпретируются как восьмеричные (Base 8). Поддержка восьмеричного числа для них была удалена в строгом режиме ECMAScript 5.
Чтобы избежать проблем, описанных выше, настоятельно рекомендуется использовать оператор строгого равенства. Хотя это позволяет избежать многих распространенных ошибок, есть еще много дополнительных проблем, которые возникают из-за слабой системы типизации JavaScript.
Конструкторы встроенных типов
Конструкторы встроенных типов, таких как Number
и String
, ведут себя по-разному при использовании с ключевым словом new
и без него.
Использование встроенного типа, такого как Number
в качестве конструктора, создаст новый объект Number
, но при отсутствии ключевого слова new
функция Number
будет вести себя как преобразователь.
Кроме того, передача литералов или значений, не являющихся объектами, приведет к еще большему приведению типов.
Лучшим вариантом является явное приведение к одному из трех возможных типов.
Приведение к строке
Добавив к строке пустую строку, можно легко преобразовать значение в строку.
Приведение к числу
Используя унарный оператор плюса, можно привести к числу.
Приведение к логическому
Если дважды использовать оператор not, значение можно преобразовать в логическое.
Основной
Почему бы не использовать eval
Функция eval
выполнит строку кода JavaScript в локальной области.
Однако eval
выполняется только в локальной области видимости, когда он вызывается напрямую и, когда имя вызываемой функции на самом деле eval
.
Следует избегать использования eval
. 99,9% его «применений» могут быть достигнуты без него.
eval
под маской
Обе функции тайм-аута setTimeout
и setInterval
могут принимать строку в качестве своего первого аргумента. Эта строка всегда будет выполняться в глобальной области видимости, поскольку eval
в этом случае не вызывается напрямую.
Проблемы с безопасностью
eval
также представляет собой проблему безопасности, потому что он выполняет любой предоставленный ему код. Его никогда не следует использовать со строками неизвестного или ненадежного происхождения.
В заключение
eval
никогда не следует использовать. Любой код, который его использует, следует подвергать сомнению в отношении его работы, производительности и безопасности. Если что-то требует eval
для работы, его не следует использовать в первую очередь. Следует использовать лучший дизайн, который не требует использования eval
.
undefined
и null
В JavaScript есть два разных значения: null
и undefined
, причем последнее более полезно.
Значение undefined
undefined
- это тип с одним значением: undefined
.
В языке также определяется глобальная переменная со значением undefined
; эта переменная также называется undefined
. Однако эта переменная не является ни константой, ни ключевым словом языка. Это означает, что его значение можно легко перезаписать.
Примечание ES5: undefined
в ECMAScript 5 больше не доступен для записи в строгом режиме, но его имя все еще может быть затенено, например, функцией с именем undefined
.
Вот несколько примеров, когда возвращается значение undefined
:
- Доступ к (неизмененной) глобальной переменной
undefined
. - Доступ к объявленной , но еще не инициализированной переменной.
- Неявный возврат функций из-за отсутствия операторов
return
. return
операторы, которые ничего явно не возвращают.- Поиск несуществующих свойств.
- Параметры функции, для которых не передано явное значение.
- Все, что было установлено на значение
undefined
. - Любое выражение в виде
void(expression)
Обработка изменений значения undefined
Поскольку глобальная переменная undefined
содержит только копию фактического значения ofundefined
, присвоение ей нового значения не изменяет значение типа undefined
.
Тем не менее, чтобы что-то сравнить со значением undefined
, необходимо сначала получить значение undefined
.
Для защиты кода от возможной перезаписи переменной undefined
обычно используется добавление дополнительного параметра к анонимной оболочке, которая не получает аргументов.
Другой способ добиться того же эффекта - использовать объявление внутри оболочки.
Единственная разница в том, что эта версия приводит к использованию дополнительных 4 байтов, если она минифицирована, и нет другого оператора var
внутри анонимной оболочки.
Использование null
В то время как undefined
в контексте языка JavaScript в основном используется в смысле традиционного null, фактический null
(как литерал, так и тип) является более или менее просто другим типом данных.
Он используется в некоторых внутренних компонентах JavaScript (например, при объявлении конца цепочки прототипов путем установки Foo.prototype = null
), но почти во всех случаях его можно заменить на undefined
.
Автоматическая вставка точки с запятой
Хотя JavaScript имеет синтаксис в стиле C, он не требует использования точек с запятой в исходном коде, поэтому их можно опустить.
JavaScript - это не язык без точек с запятой. Фактически, точки с запятой нужны для понимания исходного кода. Следовательно, синтаксический анализатор JavaScript автоматически вставляет их, когда обнаруживает ошибку синтаксического анализа из-за отсутствия точки с запятой.
Вставка происходит, и анализатор пытается снова.
Автоматическая вставка точки с запятой считается одним из самых больших недостатков дизайна языка, потому что она может изменить поведение кода.
Как это работает
В приведенном ниже коде нет точек с запятой, поэтому синтаксический анализатор должен решить, куда их вставить.
Ниже представлен результат игры парсера в «угадайку».
Примечание: синтаксический анализатор JavaScript «неправильно» обрабатывает операторы возврата, за которыми следует новая строка. Хотя это не обязательно ошибка автоматической вставки точки с запятой, это все же может быть нежелательным побочным эффектом.
Парсер кардинально изменил поведение приведенного выше кода. В некоторых случаях он делает неправильные вещи.
Ведущая скобка
В случае первой круглой скобки синтаксический анализатор не будет вставлять точку с запятой.
Этот код превращается в одну строку.
Очень высоки шансы, что log
не вернет функцию; следовательно, приведенное выше приведет к TypeError
с указанием undefined is not a function
.
В заключение
Настоятельно рекомендуется никогда не пропускать точки с запятой. Также рекомендуется сохранять фигурные скобки в той же строке, что и соответствующие им операторы, и никогда не опускать их для однострочных операторов if
/ else
. Эти меры не только улучшат согласованность кода, но также предотвратят изменение поведения кода парсером JavaScript.
Оператор delete
Короче говоря, невозможно удалить глобальные переменные, функции и некоторые другие вещи в JavaScript, для которых установлен атрибут DontDelete
.
Глобальный код и код функции
Когда переменная или функция определены в глобальной области или области функции, это свойство объекта активации или глобального объекта. Такие свойства имеют набор атрибутов, одним из которых является DontDelete
. Объявления переменных и функций в глобальном коде и коде функции всегда создают свойства с DontDelete
, и поэтому не могут быть удалены.
Явные свойства
Явно установленные свойства можно удалить обычным образом.
В приведенном выше примере obj.x
и obj.y
можно удалить, поскольку у них нет атрибута DontDelete
. Вот почему пример ниже тоже работает.
Здесь мы используем уловку для удаления a
. this
здесь относится к глобальному объекту, и мы явно объявляем переменную a
как его свойство, которое позволяет нам удалить его.
В IE (как минимум 6–8) есть ошибки, поэтому приведенный выше код не работает.
Аргументы и встроенные функции функций
Обычные аргументы функций, arguments
объекты и встроенные свойства также имеют DontDelete
набор.
Хост-объекты
Поведение оператора delete
может быть непредсказуемым для размещенных объектов. В соответствии со спецификацией хост-объектам разрешено реализовывать любое поведение.
В заключение
Оператор delete
часто имеет неожиданное поведение и может безопасно использоваться только для удаления явно установленных свойств для обычных объектов.
Другой
setTimeout
и setInterval
Поскольку JavaScript является асинхронным, можно запланировать выполнение функции с помощью функций setTimeout
и setInterval
.
Примечание. Тайм-ауты не являются частью стандарта ECMAScript. Они были реализованы в BOM, или DOM Level 0, которые никогда не определялись и не документировались официально. Пока не опубликовано никаких рекомендуемых спецификаций, однако в настоящее время они стандартизируются HTML5. Из-за этого реализация может отличаться в зависимости от браузеров и движков.
Когда вызывается setTimeout
, он возвращает идентификатор тайм-аута и расписание foo
для выполнения примерно через тысячу миллисекунд в будущем. foo
будет выполнен один раз.
В зависимости от разрешения таймера движка JavaScript, на котором запущен код, а также от того факта, что JavaScript является однопоточным, а другой выполняемый код может блокировать поток, ни в коем случае нельзя с уверенностью сказать, что будет получена точная указанная задержка. в звонке setTimeout
.
Функция, переданная в качестве первого параметра, будет вызвана глобальным объектом, что означает, что this
внутри вызываемой функции относится к глобальному объекту.
Примечание. Поскольку setTimeout
принимает объект функции в качестве первого параметра, распространенной ошибкой является использование setTimeout(foo(), 1000)
, который будет использовать возвращаемое значение вызова foo
, а не foo
. В большинстве случаев это скрытая ошибка, поскольку, когда функция возвращает undefinedsetTimeout
, ошибки не возникает.
Объединение вызовов с setInterval
В то время как setTimeout
запускает функцию только один раз, setInterval
- как следует из названия - будет выполнять функцию каждые X
миллисекунды, но его использование не рекомендуется.
Когда выполняемый код блокирует вызов тайм-аута, setInterval
по-прежнему будет вызывать дополнительные вызовы указанной функции. Это может, особенно с небольшими интервалами, привести к накоплению вызовов функций.
В приведенном выше коде foo
вызывается один раз и затем блокируется на одну секунду.
Пока foo
блокирует код, setInterval
будет планировать дальнейшие вызовы к нему. Теперь, когда foo
завершит свою работу, к нему уже будет еще десять вызовов, ожидающих выполнения.
Работа с возможным кодом блокировки
Самое простое, а также наиболее управляемое решение - использовать setTimeout
в самой функции.
Это не только инкапсулирует вызов setTimeout
, но также предотвращает наложение вызовов и дает дополнительный контроль. foo
теперь может сам решать, запускать он снова или нет.
Очистка тайм-аутов вручную
Очистка тайм-аутов и интервалов работает путем передачи соответствующего идентификатора в clearTimeout
или clearInterval
, в зависимости от того, какая функция set
использовалась в первую очередь.
Очистка всех тайм-аутов
Поскольку нет встроенного метода для очистки всех таймаутов и / или интервалов, необходимо использовать грубую силу для достижения этой функции.
Но все же могут быть таймауты, на которые это произвольное число не влияет. Другой способ сделать это - учесть, что идентификатор, присвоенный таймауту, увеличивается на единицу каждый раз, когда вы вызываете setTimeout
.
Несмотря на то, что сегодня это работает во всех основных браузерах, не указано, что идентификаторы должны быть упорядочены таким образом, и это может измениться. Поэтому вместо этого рекомендуется отслеживать все идентификаторы тайм-аута, чтобы их можно было очистить отдельно.
Скрытое использование eval
setTimeout
и setInterval
также могут принимать строку в качестве первого параметра. Эту функцию никогда не следует использовать, потому что она использует eval
внутри.
Примечание. Точная работа при передаче им строки может отличаться в различных реализациях JavaScript. Например, Microsoft JScript использует конструктор Function
вместо eval
.
Поскольку в этом случае eval
не вызывается напрямую, строка, переданная в setTimeout
, будет выполняться в глобальной области; таким образом, он не будет использовать локальную переменную foo
из области bar
.
Также рекомендуется не использовать строку для передачи аргументов функции, которая будет вызываться любой из функций тайм-аута.
Примечание. Хотя также можно использовать синтаксис setTimeout(foo, 1000, 1, 2, 3)
, это не рекомендуется, так как его использование может привести к незначительным ошибкам при использовании с методами. Кроме того, синтаксис может не работать в некоторых реализациях JavaScript. Например, Internet Explorer от Microsoft не передает аргументы непосредственно в функцию обратного вызова.
В заключение
Строку никогда не следует использовать в качестве параметра setTimeout
или setInterval
. Когда вызываемой функции необходимо предоставить аргументы, это явный признак действительно плохого кода. Следует передать анонимную функцию, которая затем позаботится о фактическом вызове.
Кроме того, следует избегать использования setInterval
, потому что его планировщик не блокируется выполнением JavaScript.