Объекты

Использование и свойства объекта

Все в 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() не создает новый Fooinstance, а повторно использует тот, который назначен его прототипу; таким образом, все 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 не будет повторять любые свойства, для которых enumerableattribute установлен в 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, который может связывать thiscontext и аргументы с функцией. Он ведет себя идентично приведенному выше коду

Объект arguments

Каждая область действия в JavaScript может обращаться к специальной переменной arguments. Эта переменная содержит список всех аргументов, переданных функции.

Примечание. В случае, если arguments уже был определен внутри области действия функции посредством оператора var или в качестве имени формального параметра, объект arguments не будет создан.

Объект arguments не является Array. Хотя он имеет некоторую семантику массива, а именно свойство length, он не наследуется от Array.prototype и фактически является Object.

Из-за этого невозможно использовать стандартные методы массива, такие как push, pop или slice на arguments. Хотя итерация с простым for циклом работает нормально, необходимо преобразовать его в реальный Array, чтобы использовать на нем стандартные Array методы.

Преобразование в массив

Приведенный ниже код вернет новый Array, содержащий все элементы argumentsobject.

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

Передача аргументов

Ниже приводится рекомендуемый способ передачи аргументов от одной функции к другой.

Другой трюк - использовать вместе 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 и, безусловно, упрощает использование частных переменных, оно имеет некоторые недостатки.

  1. Он использует больше памяти, поскольку созданные объекты не используют общие методы прототипа.
  2. Для наследования фабрика должна скопировать все методы из другого объекта или поместить этот объект в прототип нового объекта.
  3. Отказ от цепочки прототипов только из-за пропущенного ключевого слова 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, так и functiondeclarations будут перемещены в верхнюю часть их охватывающей области.

Приведенный выше код преобразуется перед началом выполнения. JavaScript перемещает varstatements, а также function объявления в верхнюю часть ближайшей окружающей области.

Отсутствие области видимости блока не только выведет var операторы из циклов и их тел, но также сделает результаты некоторых if конструкций не интуитивно понятными.

В исходном коде, хотя оператор if, казалось, изменял глобальную переменную goo, на самом деле он изменяет локальную переменную - после применения подъема.

Не зная подъема, можно было предположить, что приведенный ниже код вызовет aReferenceError.

Но, конечно, это работает из-за того, что оператор var перемещается в верхнюю часть глобальной области видимости.

Порядок разрешения имен

Все области в JavaScript, включая глобальную область, имеют определенное в них специальное имя this, которое относится к текущему объекту.

Области функций также имеют определенное в них имя arguments, которое содержит аргументы, переданные функции.

Например, при попытке доступа к переменной с именем foo внутри области действия функции JavaScript будет искать имя в следующем порядке:

  1. Если в текущей области есть оператор var foo, используйте его.
  2. Если один из параметров функции называется foo, используйте его.
  3. Если сама функция называется foo, используйте это.
  4. Перейдите к следующему внешнему прицелу и снова начните с №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.