=
Объект является фундаментальным типом данных в языке JavaScript. Объект – это составное значение: он объединяет в себе набор значений (простых значений или других объектов) и позволяет сохранять и извлекать эти значения по именам. Объект является неупорядоченной коллекцией свойств, каждое из которых имеет имя и значение. Имена свойств являются строками, поэтому можно сказать, что объекты отображают строки в значения. Такое отображение строк в значения может называться по-разному: возможно, вы уже знакомы с такой фундаментальной структурой данных, как «хеш», «словарь» или «ассоциативный массив». Однако объект представляет собой нечто большее, чем простое отображение строк в значения. Помимо собственных свойств объекты в языке JavaScript могут также наследовать свойства от других объектов, известных под названием «прототипы». Методы объекта – это типичные представители унаследованных свойств, а «наследование через прототипы» является ключевой особенностью языка JavaScript.
Объекты в языке JavaScript являются динамическими – обычно они позволяют добавлять и удалять свойства – но они могут использоваться также для имитации статических объектов и «структур», которые имеются в языках программирования со статической системой типов. Кроме того, они могут использоваться (если не учитывать, что объекты отображают строки в значения) для представления множеств строк.
Любое значение в языке JavaScript, не являющееся строкой, числом, true
, false
,
null
или undefined
, является объектом. И даже строки, числа и логические
значения, не являющиеся объектами, могут вести себя как неизменяемые объекты
(раздел 3.6).
Как вы помните, в разделе 3.7 говорилось, что объекты являются изменяемыми
значениями и операции с ними выполняются по ссылке, а не по значению. Если
переменная x
ссылается на объект, и выполняется инструкция var y = x
, в
переменную y
будет записана ссылка на тот же самый объект, а не его копия. Любые
изменения, выполняемые в объекте с помощью переменной y
, будут также
отражаться на переменной x
.
Наиболее типичными операциями с объектами являются создание объектов,
назначение, получение, удаление, проверка и перечисление их свойств. Эти базовые
операции описываются в начальных разделах этой главы. В следующих за ними
разделах будут рассматриваться более сложные темы, многие из которых имеют
прямое отношение к стандарту ECMAScript 5.
Свойство имеет имя и значение. Именем свойства может быть любая строка, включая и пустую строку, но объект не может иметь два свойства с одинаковыми именами. Значением свойства может быть любое значение, допустимое в языке JavaScript, или (в ECMAScript 5) функция чтения или записи (или обе). Поближе с функциями чтения и записи свойств мы познакомимся в разделе 6.6. В дополнение к именам и значениям каждое свойство имеет ряд ассоциированных с ним значений, которые называют атрибутами свойства:
writable
определяет доступность значения свойства для записи.
enumerable
определяет доступность имени свойства для перечисления
в цикле for/in.
configurable
определяет возможность настройки, т. е. удаления
свойства и изменения его атрибутов.
До появления стандарта ECMAScript 5 все свойства в объектах, создаваемые программой, доступны для записи, перечисления и настройки. В ECMAScript 5 предусматривается возможность настройки атрибутов ваших свойств. Как это делается, описывается в разделе 6.7.
В дополнение к свойствам каждый объект имеет три атрибута объекта:
prototype
содержит ссылку на другой объект, от которого
наследуются свойства.
class
содержит строку с именем класса объекта и определяет тип
объекта.
extensible
(в ECMAScript 5) указывает на возможность добавления
новых свойств в объект. Поближе с прототипами и механизмом наследования свойств мы познакомимся в разделах 6.1.3 и 6.2.2, а более детальное обсуждение всех трех атрибутов объектов вы найдете в разделе 6.8.
Наконец, ниже приводится описание некоторых терминов, которые помогут нам различать три обширные категории объектов в языке JavaScript и два типа свойств:
HTMLElement
, представляющие структуру веб-страницы в клиентском JavaScript,
являются объектами среды выполнения. Объекты среды выполнения могут
также быть нативными объектами, например, когда среда выполнения
определяет методы, которые являются обычными объектами Function
JavaScript.
Объекты можно создавать с помощью литералов объектов, ключевого слова new
и (в ECMAScript 5) функции Object.create(). Все эти приемы описываются в
следующих разделах.
Самый простой способ создать объект заключается во включении в программу литерала объекта. Литерал объекта представляет собой список разделенных запятыми пар имя:значение, заключенных в фигурные скобки. Именем свойства может быть идентификатор или строковый литерал (допускается использовать пустую строку). Значением свойства может быть любое выражение, допустимое в JavaScript, – значение выражения (это может быть простое значение или объект) станет значением свойства. Ниже приводится несколько примеров создания объектов:
var o = {}; // Объект без свойств: var empty = {}; // Объект без свойств: var point = { x:0, y:0 }; // Два свойства var point2 = { x:point.x, y:point.y+1 }; // Более сложные значения // // var book = { "main title": "JavaScript", // Имена свойств с пробелами, 'sub-title': "The Definitive Guide", // и дефисами, поэтому // используются строковые литералы "for": "all audiences", // for - зарезервированное слово, // поэтому в кавычках author: { // Значением этого свойства firstname: "David", // является объект. Обратите surname: "Flanagan" // внимание, что имена этих свойств } // без кавычек };
В ECMAScript 5 (и в некоторых реализациях ECMAScript 3) допускается использовать зарезервированные слова в качестве имен свойств без кавычек. Однако в целом имена свойств, совпадающие с зарезервированными словами, в ECMAScript 3 должны заключаться в кавычки. В ECMAScript 5 последняя запятая, следующая за последним свойством в литерале объекта, игнорируется. В большинстве реализаций ECMAScript 3 завершающие запятые также игнорируются, но IE интерпретирует их наличие как ошибку.
Литерал объекта – это выражение, которое создает и инициализирует новый объект всякий раз, когда производится вычисление этого выражения. Значение каждого свойства вычисляется заново, когда вычисляется значение литерала. Это означает, что с помощью единственного литерала объекта можно создать множество новых объектов, если этот литерал поместить в тело цикла или функции, которая будет вызываться многократно, и что значения свойств этих объектов могут отличаться друг от друга.
Оператор new
создает и инициализирует новый объект. За ключевым
словом new
должен следовать вызов функции. Функция,
используемая таким образом, называется конструктором и служит для инициализации
вновь созданного объекта. Базовый JavaScript включает встроенные конструкторы
для нативных¹ типов. Например:
var o = new Object(); // Создать новый пустой объект: то же, что и {}. o; // это объект без свойств: o.x !== undefined; // : o не имеет свойство x, o.x === undefined: // var a = new Array(); // Создать пустой массив: то же, что и []. a; // это пустой массив a: var d = new Date(); // Создать объект Date, представляющий текущее время d; // это объект Date: // d.getDate(); // число текущего месяца: d.getMonth(); // № месяца (нумерация месяцев с нуля): var r = new RegExp("js"); // Создать объект RegExp² для операций.
Помимо этих встроенных конструкторов имеется возможность определять свои собственные функции-конструкторы для инициализации вновь создаваемых объектов. О том, как это делается, рассказывается в главе 9.
Прежде чем перейти к третьему способу создания объектов, необходимо сделать
паузу, чтобы познакомиться с прототипами. Каждый объект в языке JavaScript
имеет второй объект (или, значительно реже, null
), ассоциированный с ним.
Этот второй объект называется прототипом; первый объект наследует от
прототипа его свойства.
Все объекты, созданные с помощью литералов объектов, фактически имеют один и тот же
объект-прототип, на который в программе JavaScript можно сослаться так:
Object.prototype. Объекты, созданные с помощью ключевого слова new
и вызова
конструктора, в качестве прототипа получают значение свойства prototype
функции–конструктора. Поэтому объект, созданный выражением new Object()
, наследует
свойства объекта Object.prototype
, как если бы он был создан с помощью литерала
в фигурных скобках {}. Аналогично прототипом объекта, созданного
выражением new Array()
, является Array.prototype
, а прототипом объекта, созданного
выражением new Date()
, является Date.prototype
.
Object.prototype
– один из немногих объектов, которые не имеют прототипа: у
него нет унаследованных свойств. Другие объекты-прототипы являются самыми
обычными объектами, имеющими собственные прототипы. Все встроенные
конструкторы (и большинство пользовательских конструкторов) наследуют
прототип Object.prototype
. Например, Date.prototype
наследует свойства от
Object.prototype
, поэтому объект Date
, созданный выражением new Date()
, наследует свойства
от обоих прототипов, Date.prototype
и Object.prototype
. Такая связанная
последовательность объектов–прототипов называется цепочкой прототипов.
Описание механизма наследования свойств приводится в разделе 6.2.2. Как
получить ссылку на прототип объекта, рассказывается в разделе 6.8.1.
А в главе 9
более детально будет обсуждаться связь между прототипами и конструкторами:
там будет показано, как определять новые «классы» объектов посредством
объявления функций–конструкторов и как записывать ссылку на объект–прототип
в их свойство prototype
для последующего использования «экземплярами»,
созданными с помощью этого конструктора.
Стандарт ECMAScript 5 определяет метод Object.create()
, который создает новый
объект и использует свой первый аргумент в качестве прототипа этого объекта.
Дополнительно Object.create()
может принимать второй необязательный
аргумент, описывающий свойства нового объекта. Описание этого второго аргумента
приводится в разделе 6.7.
Object.create()
является статической функцией, а не методом, вызываемым
относительно некоторого конкретного объекта. Чтобы вызвать эту функцию,
достаточно передать ей желаемый объект-прототип:
var o1 = Object.create({x:1, y:2}); // // o1 наследует свойства x и y от {x:1, y:2}.Чтобы создать объект, не имеющий прототипа, можно передать значение
null
, но
в этом случае вновь созданный объект не унаследует ни каких-либо свойств, ни
базовых методов, таких как toString()
(а это означает, что этот объект нельзя
будет использовать в выражениях с оператором +):
var o2 = Object.create(null); // o2 не наследует ни свойств, ни методов: JSON.stringify(o2) // => - o2 не наследует свойств. ("toString" in o2) // => - o2 не унаследовал методов. o2.x = 100; // - объекту o2 можно задавать свойства o2.x // =>Если в программе потребуется создать обычный пустой объект (который, например, возвращается литералом {} или выражением
new Object()
), передайте в
первом аргументе Object.prototype
:
var o3 = Object.create(Object.prototype); // o3 подобен объекту, созданному с помощью {} или new Object(). o3; // это объект без свойств: , но ("toString" in o3) // => ; - имеет унаследованные методы. // Добавляем ему свойства и значения, например, такие: o3.x = 4; o3.y = 3; o3.z = 7; JSON.stringify(o3); //
Возможность создавать новые объекты с произвольными прототипами (скажем иначе: возможность создавать «наследников» от любых объектов) является мощным инструментом, действие которого можно имитировать в ECMAScript 3 с помощью функции, представленной в примере 6.1¹.
Пример 6.1. Создание нового объекта, наследующего прототип
// inherit() возвращает вновь созданный объект, наследующий свойства // объекта-прототипа p. Использует функцию Object.create() // из ECMAScript 5, если она определена, иначе используется // более старый прием. function inherit(p) { if (p == null) throw TypeError(); // p не может иметь значение null if (Object.create) // Если Object.create() определена... return Object.create(p); // использовать ее. var t = typeof p; // Иначе выяснить тип и проверить его if (t !== "object" && t !== "function") throw TypeError(); function f() {}; // Определить фиктивный конструктор. f.prototype = p; // Записать в его свойство prototype. // ссылку на объект p. return new f(); // Использовать f() для создания // "наследника" объекта p. }
var p = {first:"q"}; // прототип объекта p; JSON.stringify(p); // // С помощью function inherit(p) создаем новый объект q, // прототипом которого является объект p: q = inherit(p); // Добавляем ему свойства и значения, например, такие: q.second = "w"; q.third = "e"; JSON.stringify(q); // - добавленные свойства q: // - значения всех трех свойств "toString" in q // => - q унаследовал метод toString "q.toString" // =>
Реализация функции inherit()
приобретет больше смысла, как только мы
познакомимся с конструкторами в главе 9.
А пока просто считайте, что она возвращает
новый объект, наследующий свойства объекта в аргументе. Обратите внимание,
что функция inherit()
не является полноценной заменой для Object.create()
: она
не позволяет создавать объекты без прототипа и не принимает второй
необязательный аргумент, как Object.create()
. Тем не менее мы будем использовать
функцию inherit()
во многих примерах в этой главе и в главе 9.
Функцию inherit()
можно использовать, чтобы защитить объекты от
непреднамеренного (не с целью нанести вред) изменения их библиотечными функциями,
неподконтрольными вам. Вместо того чтобы передавать функции сам объект
непосредственно, можно передать его наследника. Если функция прочитает
свойства наследника, она получит унаследованные значения. Однако если она изменяет
значения свойств, эти изменения коснутся только наследника и никак не
отразятся на оригинальном объекте:
var o = { x: "не изменяйте это значение" }; library_function(inherit(o)); // Защита объекта o от непреднамеренного // изменения
Чтобы понять принцип действия этого приема, необходимо знать, как производится чтение и запись значений свойств объектов в языке JavaScript. Об этом рассказывается в следующем разделе.
Получить значение свойства можно с помощью операторов точки (.) или квадратных скобок ([ ]), описанных в разделе 4.4. Слева от оператора должно находиться выражение, возвращающее объект.
При использовании оператора точки справа должен находиться простой идентификатор, соответствующий имени свойства. При использовании квадратных скобок в квадратных скобках должно указываться выражение, возвращающее строку с именем требуемого свойства (см. 6.1.1):
var author = book.author; // Получить свойство "author" объекта book: // var name = author.surname // Получить свойство ""surname" объекта // author: . var title = book["main title"] // Получить свойство "main title" объекта // book: .
// book:
Чтобы создать новое свойство или изменить значение существующего свойства, также используются операторы точки и квадратные скобки, как в операциях чтения значений свойств, но само выражение помещается уже слева от оператора присваивания:
// Создать свойство "edition" объекта book: book.edition = 6; // Изменить значение свойства "main title": book["main title"] = "ECMAScript";
// book:
В ECMAScript 3 идентификатор, следующий за точкой, не может быть
зарезервированным словом: нельзя записать обращение к свойству o.for
или o.class
,
потому что for
является ключевым словом, a class
– словом, зарезервированным для
использования в будущем. Если объект имеет свойства, имена которых
совпадают с зарезервированными словами, для доступа к ним необходимо использовать
форму записи с квадратными скобками: o["for"]
и o["class"]
. Стандарт
ECMAScript 5 ослабляет это требование (как это уже сделано в некоторых реализациях
ECMAScript 3) и допускает возможность использования зарезервированных слов
после оператора точки.
Выше уже говорилось, что при использовании формы записи с квадратными скобками выражение в скобках должно возвращать строку. Если быть более точными, это выражение должно возвращать строку или значение, которое может быть преобразовано в строку. В главе 7, например, мы увидим распространенный прием использования чисел в квадратных скобках.
Как отмечалось выше, следующие два выражения возвращают одно и то же значение:
object.property object["property"]Первая форма записи, с использованием точки и идентификатора, напоминает синтаксис доступа к статическому полю структуры или объекта в языке С или Java. Вторая форма записи, с использованием квадратных скобок и строки, выглядит как обращение к элементу массива, но массива, который индексируется строками, а не числами. Такого рода массивы называются ассоциативными массивами (а также хешами и словарями). Объекты в языке JavaScript являются ассоциативными массивами, и в этом разделе объясняется, почему это так важно.
В С, C++, Java и других языках программирования со строгим контролем типов объект может иметь только фиксированное число свойств, а имена этих свойств должны определяться заранее. Поскольку JavaScript относится к языкам программирования со слабым контролем типов, данное правило в нем не действует: программы могут создавать любое количество свойств в любых объектах. Однако при использовании для обращения к свойству оператора точка (.) имя свойства определяется идентификатором. Идентификаторы должны вводиться в тексте программы буквально – это не тип данных, поэтому в программе невозможно реализовать вычисление идентификаторов.
Напротив, когда для обращения к свойствам объекта используется форма записи
с квадратными скобками ([]
), имя свойства определяется строкой. Строки в
языке JavaScript являются типом данных, поэтому они могут создаваться и
изменяться в ходе выполнения программы. Благодаря этому, например, в языке
JavaScript для объекта customer
}имеется возможность писать такой программный код:
var addr = ""; for(i = 0; i < 4; i++) addr += customer["address" + i] + '\n';
Этот фрагмент читает и объединяет в одну (содержащую символы переноса) строку
addr значения свойств address0, address1, address2
и address3
объекта customer
:
portfolio
. Объект имеет по
одному свойству для каждой компании. Имя свойства одновременно является
названием компании, а значение свойства определяет количество акций этой компании.
То есть если, к примеру, пользователь владеет 50 акциями компании IBM,
свойство portfolio.ibm
будет иметь значение 50. Следующая функция, добавляющая информацию об очередном пакете акций, могла бы быть частью такой программы:
function addstock(portfolio, stockname, shares) portfolio[stockname] = shares; }
Поскольку пользователь вводит имена компаний во время выполнения, нет
никакого способа заранее определить эти имена. А так как на момент создания
программы имена свойств нам неизвестны, мы не можем использовать оператор
точки (.) для доступа к свойствам объекта portfolio
. Однако мы можем задействовать
оператор []
, потому что для обращения к свойствам он позволяет использовать
строковые значения (которые являются динамическими и могут изменяться во
время выполнения) вместо идентификаторов (которые являются статическими
и должны жестко определяться в тексте программы).
В главе 5 был представлен цикл for/in
(и еще раз мы встретимся с ним чуть ниже,
в разделе 6.5). Мощь этой инструкции языка JavaScript становится особенно
очевидной, когда она применяется для работы с ассоциативными массивами. Ниже
показано, как можно использовать ее для вычисления суммарного объема
инвестиций в portfolio
:
function getvalue(portfolio) { var total = 0.0; for(stock in portfolio) { // Для каждой компании в portfolio: var shares = portfolio[stock]; // получить количество акций var price = getquote(stock); // отыскать стоимость одной акции total += shares * price; // прибавить к суммарному значению } return total; // Вернуть сумму. }
Объекты в языке JavaScript обладают множеством «собственных свойств» и
могут также наследовать множество свойств от объекта-прототипа. Чтобы
разобраться в этом, необходимо внимательно изучить механизм доступа к свойствам.
В примерах этого раздела для создания объектов с определенными прототипами
используется функция inherit()
из примера 6.1.
Предположим, что программа обращается к свойству x
объекта o
. Если объект o
не имеет собственного свойства с таким именем, выполняется попытка отыскать
свойство x
в прототипе объекта o
. Если объект-прототип не имеет собственного
свойства с этим именем, но имеет свой прототип, выполняется попытка отыскать
свойство в прототипе прототипа. Так продолжается до тех пор, пока не будет
найдено свойство x
или пока не будет достигнут объект, не имеющий прототипа. Как
видите, атрибут prototype
объекта создает цепочку, или связанный список
объектов, от которых наследуются свойства.
var o = {} // o наследует методы объекта Object.prototype // и обладает собственным свойством x: o.x = 1; // => o.x = ;. var p = inherit(o); // p наследует свойства объектов o // и Object.prototype и обладает // собственным свойством y: p.y = 2; // => p.y = ;. var q = inherit(p); // q наследует свойства объектов p, o // и Object.prototype и обладает // собственным свойством z. q.z = 2; // => q.z = ;. ("toString" in q) // => ; - toString наследуется // от Object.prototype. var s = q.toString(); // toString наследуется от Object.prototype q.x + q.y // => : x и y наследуются от o и p
Теперь предположим, что программа присваивает некоторое значение свойству x
объекта o
. Если объект o
уже имеет собственное свойство (не унаследованное)
с именем x
, то операция присваивания просто изменит значение существующего
свойства. В противном случае в объекте o
будет создано новое свойство с именем x
.
Если прежде объект o
наследовал свойство x
, унаследованное свойство теперь
окажется скрыто вновь созданным собственным свойством с тем же именем.
Операция присваивания значения свойству проверит наличие этого свойства в
цепочке прототипов, чтобы убедиться в допустимости присваивания. Например,
если объект o
наследует свойство x
, доступное только для чтения, то присваивание
выполняться не будет. (Подробнее о том, когда свойство может устанавливаться,
рассказывается в разделе 6.2.3) Однако если присваивание допустимо, всегда
создается или изменяется свойство в оригинальном объекте и никогда в цепочке
прототипов. Тот факт, что механизм наследования действует при чтении свойств, но
не действует при записи новых значений, является ключевой особенностью языка
JavaScript, потому что она позволяет выборочно переопределять унаследованные
свойства:
var unitcircle = { r:1 }; }; // Объект, от которого наследуется свойство var c = inherit(unitcircle); // c наследует от него свойство r: c.x = ; c.y = ; // с определяет два собственных // свойства: c.r = // c переопределяет унаследованное // свойство r объекта unitcircle JSON.stringify(unitcircle) // => - объект-прототип не изменился
Существует одно исключение из этого правила, когда операция присваивания
значения свойству терпит неудачу или приводит к созданию/изменению свойства
оригинального объекта. Если объект o
наследует свойство x и доступ к этому
свойству осуществляется посредством методов доступа (раздел 6.6), то вместо
создания нового свойства x
в объекте o
производится вызов метода записи нового
значения. Однако обратите внимание, что метод записи вызывается относительно
объекта o
, а не относительно прототипа, в котором определено это свойство, поэтому,
если метод записи определяет какие-либо свойства, они будут созданы в объекте
o
, а цепочка прототипов опять останется неизменной.
Выражения обращения к свойствам не всегда возвращают или изменяют
значение свойства. В этом разделе описываются ситуации, когда операции чтения или
записи свойства терпят неудачу.
Попытка обращения к несуществующему свойству не считается ошибкой. Если
свойство х не будет найдено среди собственных или унаследованных свойств
объекта o
, выражение обращения к свойству o.x
вернет значение undefined
. Напомню,
что наш объект book
имеет свойство с именем «sub-title», но не имеет свойства
«subtitle»:
book.subtitle; // => : property doesn't exist\ // (свойство отсутствует)
Однако попытка обратиться к свойству несуществующего объекта считается
ошибкой. Значения null
и undefined
не имеют свойств, и попытки обратиться к
свойствам этих значений считаются ошибкой. Продолжим пример, приведенный выше:
// Возбудит исключение. undefined не имеет свойства length: var len = book.subtitle.length;
Если нет уверенности, что book
и book.subtitle
являются объектами (или ведут
себя подобно объектам), нельзя использовать выражение book.subtitle.length
, так
как оно может возбудить исключение. Ниже демонстрируются два способа
защиты против исключений подобного рода:
// Более наглядный и прямолинейный способ var len = undefined; if (book) { if (book["sub-title"]) len = book["sub-title"].length; // len: } len = undefined; if (book) { if (book.subtitle) len = book.subtitle.length; // len: } // Более краткая и характерная для JavaScript альтернатива получения // длины значения свойства subtitle var len = book && book.subtitle && book.subtitle.length; // len =
Чтобы понять, почему второе выражение позволяет предотвратить появление исключений TypeError
,
можете вернуться к описанию короткой схемы вычислений, используемой оператором &&
,
в разделе 4.10.1.
Разумеется, попытка задать свойства для null
или undefined
также вызывает исключение TypeError
.
Попытки задать свойства для других величин также не всегда оканчиваются успехом: некоторые свойства доступны только для чтения и не позволяют изменять их значения. Кроме того, некоторые объекты не позволяют добавлять в них новые свойства. Однако самое интересное, что подобные неудачи, как правило, не приводят к возбуждению исключения:
// Свойства prototype встроенных конструкторов // доступны только для чтения. Object.prototype = 0; // Присваивание не возбудит исключения; // значение Object.prototype не изменится: Object.prototype; // =>
Этот исторически сложившийся недостаток JavaScript исправлен в строгом
режиме, определяемом стандартом ECMAScript 5. Все неудачные попытки
изменить значение свойства в строгом режиме приводят к исключению TypeError
(см. консоль).
Правила, позволяющие определить, когда попытка выполнить операцию
присваивания завершится успехом, а когда неудачей, просты и понятны, но их
сложно выразить в достаточно краткой форме. Попытка присвоить значение свойству p
объекта o
потерпит неудачу в следующих случаях:
o
имеет собственное свойство p
, доступное только для чтения: нельзя
изменить значение свойства, доступного только для чтения. (Обратите, однако,
внимание на метод defineProperty()
, который представляет собой исключение,
позволяющее изменять значения настраиваемых свойств, доступных только
для чтения.) o
имеет унаследованное свойство p
, доступное только для чтения:
унаследованные свойства, доступные только для чтения, невозможно
переопределить собственными свойствами с теми же именами.o
не имеет собственного свойства p
; объект o
не наследует свойство p
с методами доступа и атрибут extensible
(раздел 6.8.3) объекта o
имеет
значение false
. Если свойство p
отсутствует в объекте o
и для него не определен
метод записи, то операция присваивания попытается добавить свойство p
в
объект o
. Но поскольку объект o
не допускает возможность расширения, то
попытка добавить в него новое свойство потерпит неудачу. Оператор delete
(раздел 4.13.3) удаляет свойство из объекта. Его единственный
операнд должен быть выражением обращения к свойству. Может показаться
удивительным, но оператор delete
не оказывает влияния на значение свойства – он
оперирует самим свойством:
book["main title"]; // delete book["main title"]; // Теперь он не имеет свойства 'main title'. book["main title"]; // book.author.firstname; // book.author.surname; // delete book.author; // Теперь объект book не имеет свойства author. book.author //
Оператор delete
удаляет только собственные свойства и не удаляет
унаследованные. (Чтобы удалить унаследованное свойство, необходимо удалять его в
объекте-прототипе, в котором оно определено. Такая операция затронет все объекты,
наследующие этот прототип.)
var oo = { y: } // - создан объект-прототип. var o = inherit(oo); // o: o.x = ; // delete(o.y); // o: - нет собственного свойства "y", // но есть унаследованное свойство "y": "y" in o; // => // delete(oo.y); // , удаляем свойство y прототипа. y in o; // // - наследованное свойство y удалено. o; oo; // o:.Выражение
delete
возвращает значение true
в случае успешного удаления
свойства или когда операция удаления не привела к изменению объекта (например, при
попытке удалить несуществующее свойство). Выражение delete
также
возвращает true
, когда этому оператору передается выражение, не являющееся
выражением обращения к свойству:
o = {x:}; // o имеет собственное свойство x и наследует toString delete o.x; // Удалит x и вернет delete o.x; // Ничего не сделает (x не существует) и вернет o.toString; // toString: delete o.toString; // Ничего не сделает (toString не является // собственным свойством) и вернет : o.toString; // toString: delete 1; // Бессмысленно, но вернет ;Оператор
delete
не удаляет ненастраиваемые свойства, атрибут configurable
которых имеет значение false
. (Однако он может удалять настраиваемые свойства
нерасширяемых объектов.) Ненастраиваемыми являются свойства встроенных
объектов, а также свойства глобального объекта, созданные с помощью инструкций
объявления переменных и функций. Попытка удалить ненастраиваемое свойство
в строгом режиме вызывает исключение Type Error. В нестрогом режиме (и в
реализациях ECMAScript 3) в таких случаях оператор delete
просто возвращает
false
:
delete Object.prototype; // => : // Удаление невозможно - ненастраиваемое var x = 1;; // Объявление глобальной переменной delete this.x; // => : Это свойство нельзя удалить this.x; // => function f() {} // Объявление глобальной функции delete this.f; // => : Это свойство также нельзя удалить.При удалении настраиваемых свойств глобального объекта в нестрогом режиме допускается опускать ссылку на глобальный объект и передавать оператору
delete
только имя свойства:
this.y = // Создать настраиваемое глобальное свойство (без var) delete y; // И удалить его =>Однако в строгом режиме оператор
delete
возбуждает исключение SyntaxError
,
если его операндом является неквалифицированный идентификатор, такой как имя переменной, функции или параметра
функции, в нашем примере это y
, поэтому необходимо указывать явное выражение обращения к свойству:
"use strict"
"use strict"
Объекты в языке JavaScript можно рассматривать как множества свойств, и
нередко бывает полезно иметь возможность проверить принадлежность к
множеству – проверить наличие в объекте свойства с данным именем. Выполнить такую
проверку можно с помощью оператора in
, с помощью методов hasOwnProperty()
и propertylsEnumerable()
или просто обратившись к свойству.
Оператор in
требует, чтобы в левом операнде ему было передано имя свойства
(в виде строки) и объект в правом операнде. Он возвращает true
, если объект
имеет собственное или унаследованное свойство с этим именем:
var o = { x: 1 } "x" in this; // => , this не имеет собственного свойства "x", // "x": "y" in this; // => , this не имеет собственного свойства "y" "toString" in this; // => "x" in o; // => "y" in o; // => "toString" in o; // =>Метод
hasOwnProperty()
объекта проверяет, имеет ли объект собственное свойство
с указанным именем. Для наследуемых свойств он возвращает false
:
this.hasOwnProperty("x"); // => , this не имеет собственного // свойства "x": this.x ' this.hasOwnProperty("y"); // => , не имеет собственного свойства "y" this.hasOwnProperty("z"); // => , не имеет собственного свойства "z" var o = { x: } o.hasOwnProperty("x"); // : o имеет собственное свойство "x": o.x= o.hasOwnProperty("y"); // : o не имеет собственного свойства "y" o.hasOwnProperty("toString"); // : "toString" – наследуемое свойствоМетод
propertylsEnumerable()
накладывает дополнительные ограничения по
сравнению с hasOwnProperty()
. Он возвращает true
, только если указанное свойство
является собственным свойством, атрибут enumerable
которого имеет значение true
.
Свойства встроенных объектов не являются перечислимыми. Свойства,
созданные обычной программой на языке JavaScript, являются перечислимыми, если
не был использован один из методов ECMAScript 5, представленных ниже,
которые делают свойства неперечислимыми.
var o = inherit({ y: 2 }); o.x = 1; o.propertyIsEnumerable("x"); // : o имеет собственное // перечислимое свойство x. o.propertyIsEnumerable("y"); // : y - унаследованное свойство, // не собственное Object.prototype.propertyIsEnumerable("toString"); // : неперечислимоеЧасто вместо оператора
in
достаточно использовать простое выражение
обращения к свойству и использовать оператор !== для проверки на неравенство
значению undefined
:
var o = <script>"x" in o;</script> => <script>"y" in o;</script> => <script>o.x !== undefined;</script> => : o не имеет свойство x <script>o.x === undefined;</script> => <script>o.y !== undefined;</script> => : o не имеет свойства y <script>o.toString !== undefined;</script> => : o наследует свойство toString
Однако оператор in
отличает ситуации, которые неотличимы при использовании
представленного выше приема на основе обращения к свойству. Оператор in
отличает отсутствие свойства от свойства, имеющего значение undefined
. Взгляните на
следующий пример:
var o = { x: undefined } // Свойству явно присвоено значение undefined o.x !== undefined // : свойство имеется, но // со значением undefined. var o = { x: undefined } // Свойству явно присвоено значение undefined !(o.x !== undefined) // : свойство имеется, но со значением // undefined. !(o.y !== undefined) // : свойство не существует "x" in o // : свойство существует "y" in o // : свойство не существует delete o.x; // Удалить свойство x "x" in o // : оно больше не существуетОбратите внимание, что в этом примере использован оператор
!==
, а не !=
.
Операторы !==
и ===
различают значения undefined
и null
,
хотя иногда в этом нет необходимости:
// Если объект o имеет свойство x, значение которого отлично // от null и undefined, то удвоить это значение: var o = {x:1}; o.x = if (o.x != null) o.x *= 2; // => o.x= if (o.x != null) o.x *= 2; // => // Если объект o имеет свойство x, значение которого не может быть // преобразовано в false, то удвоить это значение. // Если x имеет значение undefined, null, false, "", 0 или NaN, // оставить его в исходном состоянии. o.x= if (o.x) o.x *= 2; // => o.x= if (o.x) o.x *= 2; // => o.x= if (o.x) o.x *= 2; // =>
Вместо проверки наличия отдельных свойств иногда бывает необходимо обойти
все имеющиеся свойства или получить список всех свойств объекта. Обычно для
этого используется цикл for/in
, однако стандарт ECMAScript 5 предоставляет две
удобные альтернативы.
Инструкция цикла for/in
рассматривалась в разделе 5.5.4. Она выполняет тело
цикла для каждого перечислимого свойства (собственного или унаследованного)
указанного объекта, присваивая переменной цикла имя свойства. Встроенные
методы, наследуемые объектами, являются неперечислимыми, а свойства,
добавляемые в объекты вашей программой, являются перечислимыми (если
только не использовались функции, описываемые ниже, позволяющие сделать
свойства неперечислимыми). Например:
var o = {x:1, y:2, z:3}; // Три собственных перечислимых свойства o.propertyIsEnumerable("toString") // => false: неперечислимое for(p in o) // Цикл по свойствам console.log(p); // Выведет в консоли , // но не "toString"
Некоторые библиотеки добавляют новые методы (или другие свойства) в объект
Object.prototype
, чтобы они могли быть унаследованы и быть доступны всем
объектам. Однако до появления стандарта ECMAScript 5 отсутствовала возможность
сделать эти дополнительные методы неперечислимыми, поэтому они
оказывались доступными для перечисления в циклах for/in
. Чтобы решить эту проблему,
может потребоваться фильтровать свойства, возвращаемые циклом for/in
. Ниже
приводятся два примера реализации такой фильтрации:
Пример 1 for(p in o) { if (!o.hasOwnProperty(p)) continue; // Пропустить унаследованные свойства } Пример 2 for(p in o) { if (typeof o[p] === "function") continue;// Пропустить методы }
Пример с добавлением свойства, имеющего заданные атрибуты:
o.str = ""; Object.defineProperty(o, "none_num", { value : "", writable: true, enumerable: false, configurable: true}); var q = inherit(o); К унаследованным добавляем "u" и "v": q.u = ; q.v = ; // => // Значения унаследованных свойств: q.str; // => "" q.none_num; // => "" q.toString; // => for(p in q) { // Ищем перечислимые собственные свойства: if (!q.hasOwnProperty(p)) continue; // пропустить унаследованные свойства if (typeof q[p] === "function") continue; // пропустить методы else console.log(p); // Выведет в консоли . // но не х, у, z, str, toString и none_num: } // Own keys q: . //All Keys of Enumerable properties q:В примере 6.2 определяются вспомогательные функции, использующие цикл
for/in
для управления свойствами объектов. Функция extend()
, в частности, часто
используется в библиотеках JavaScript.
(Функция extend()
, представленная здесь, реализована правильно, но она не
компенсирует хорошо известную проблему в Internet Explorer. Более надежная версия функции
extend()
будет представлена в примере 8.3.)
Пример 6.2. Вспомогательные функции, используемые для перечисления свойств объектов
/* * Копирует перечислимые свойства из объекта p в объект o и возвращает o. * Если o и p имеют свойства с одинаковыми именами, значение свойства * в объекте o затирается значением свойства из объекта p. Эта функция * не учитывает наличие методов доступа и не копирует атрибуты. */ function extend(o, p) { for(prop in p) { // Для всех свойств в p. o[prop] = p[prop]; // Добавить свойство в o. } return o; }
Создаем новые объекты p: var p = var o = {txt: "}; // Объект p имеет свойство с таким же // именем, p.v =
// Исходные свойства объектов // o: // p: // Вызываем функцию extend(o, p): extend(o, p); // => o.hasOwnProperty: /* Перечислимые собственные свойства объекта o: */
" | |
// o.v = |
/* * Удаляет из объекта o свойства, отсутствующие в объекте p. * Возвращает o. */ function restrict(o, p) { for(prop in o) { // Для всех свойств в o. if (!(prop in p)) delete o[prop]; // Удалить, если отсутствует в p } return o; } // Даны объекты o и p:1 // Объект o, // keys(o): // Объект p, keys(p): // Вызываем функцию restrict(o, p) o = restrict(o, p); // => keys(o): o: // - значения оставшихся свойств o не изменились. "toString" in o; // => , o наследует свойство "toString" .........................................................................
/* * Удаляет из объекта o свойства, присутствующие в объекте p. * Return o. */ function subtract(o, p) { for(prop in p) { // Для всех свойств в p delete o[prop]; // Удалить из o (удаление несуществующих // свойств можно выполнять без опаски) } return o; } // keys(o): // o: // keys(p): // , // p: subtract(o, p); //Вызываем функцию subtract(o, p): subtract(o, p); keys(o): o: // - значения оставшихся свойств o не изменились. "toString" in o;// => , // o наследует свойство "toString"
/* * Возвращает новый объект, содержащий свойства, присутствующие хотя бы * в одном из объектов, o или р. Если оба объекта, o и p, имеют свойства * с одним и тем же именем, используется значение свойства из объекта p. */ function union(o,p) { return extend(extend({},o), p); } Первый аргумент o, keys(o): o: // Второй аргумент p, keys(p): // Три свойства с одинаковыми именами в исходных o и p o: p: // Вызываем функцию union(o, p): keys(o): o: // Свойств с общими именами, охранивших прежние значения, в o нет: // поскольку в o значения свойств с общими именами взяты из p: ......................................................................... /* * Возвращает новый объект, содержащий свойства, присутствующие сразу * в обоих объектах, o или p. Результат чем-то напоминает пересечение * o и p, но значения свойств объекта p отбрасываются */ function intersection(o,p) { return restrict(extend({}, o), p); } var o = {"s":20,"str":"hi","t":11,"txt":"vi","u":70,"v":50,"z":80}; var p = {"str":"vo","txt":"ls","u":9,"ux":11,"x":4,"y":2,"z":3} function intersection(o,p) { return restrict(extend({}, o), p); } // Первый аргумент o, keys(o): // o: // Второй аргумент p, keys(p): // p: // Свойства с одинаковыми именами в исходном o: // Свойства с одинаковыми именами в исходном p: // Вызываем функцию intersection(o,p): var o = intersection(o,p); // В o сохранились значения общих свойств: // Имена свойств результата, keys(o): ......................................................................... /* * Возвращает массив имен собственных перечислимых свойств объекта o. */ function keys(o) { // Aprумент должен быть объектом: if (typeof o !== "object") throw TypeError(); var result = []; // Возвращаемый массив for(var prop in o) { // Для всех перечислимых свойств if (o.hasOwnProperty(prop)) // Если это собственное свойство, result.push(prop); // добавить его в массив array. } return result; // Вернуть массив. } var o = {"r":100,"s":200,"t":110,"u":70,"v":50,"z":80}; var p = inherit(o); o.str = "hi"; o.txt = "vi"; p.u = 9; p.y = 2;
for/in
стандарт ECMAScript 5 определяет две функции,
перечисляющие имена свойств. Первая из них, Object.keys()
, возвращает массив
имен собственных перечислимых свойств объекта. Она действует аналогично
функции keys()
из примера 6.2.
Object.keys(o) // Object.keys(p) //Вторая функция ECMAScript 5, выполняющая перечисление свойств, –
Object.getOwnPropertyNames()
. Она действует подобно функции Object.keys()
, но
возвращает имена всех собственных свойств указанного объекта, а не только
перечислимые. В реализациях ECMAScript 3 отсутствует возможность реализовать
подобные функции, потому что ECMAScript 3 не предусматривает возможность
получения неперечислимых свойств объекта (o.none
и p.hi
в следующем примере).
keys(o) = Object.defineProperty(o, "none", { value : "no", writable: true, enumerable: false, configurable: true} ); Object.defineProperty(p, "hi", { value : "oh", writable: true, enumerable: false, configurable: true} ); Object.keys(o); // Object.getOwnPropertyNames(o); // Object.getOwnPropertyNames(p); //
Пример с добавлением свойства, имеющего заданные атрибуты:
AllKeys(o): o.str = "" Object.defineProperty(o, "none_num",{ value : "", writable: true, enumerable: false, configurable: true}); var q = inherit(o); // К унаследованным от o свойствам добавляем "u" и "v": // Значения унаследованных свойств: q.str; // => "" q.none_num; // => "" q.toString; // => for(p in q) { // Ищем перечислимые собственные свойства: if (!q.hasOwnProperty(p)) continue; // пропустить унаследованные свойства if (typeof q[p] === "function") continue; // пропустить методы. else console.log(p); // Выведет в консоли // но не х, у, z, str, toString и none_num: } // Own keys q: // .
Выше уже говорилось, что свойство объекта имеет имя, значение и набор атрибутов. В ECMAScript 5 (и в последних версиях реализации стандарта ECMAScript 3 в основных браузерах, кроме IE) значение может замещаться одним или двумя методами, известными как методы чтения (getter) и записи (setter). Свойства, для которых определяются методы чтения и записи, иногда называют свойствами с методами доступа, чтобы отличать их от свойств с данными, представляющих простое значение.
Когда программа пытается получить значение свойства с методами доступа, интерпретатор вызывает метод чтения (без аргументов). Возвращаемое этим методом значение становится значением выражения обращения к свойству. Когда программа пытается записать значение в свойство, интерпретатор вызывает метод записи, передавая ему значение, находящее справа от оператора присваивания. Этот метод отвечает за «установку» значения свойства. Значение, возвращаемое методом записи, игнорируется.
В отличие от свойств с данными, свойства с методами доступа не имеют атрибута
writable
. Если свойство имеет оба метода, чтения и записи, оно доступно для
чтения/записи. Если свойство имеет только метод чтения, оно доступно только для
чтения. А если свойство имеет только метод записи, оно доступно только для
записи (такое невозможно для свойств с данными), и попытки прочитать значение
такого свойства всегда будут возвращать undefined
.
Самый простой способ определить свойство с методами доступа заключается в использовании расширенного синтаксиса определения литералов объектов:
var o = { // Обычное свойство с данными data_prop: value, //Свойство с методами доступа определяется как пара функций get accessor_prop() { /* тело функцииe */ }, set accessor_prop(value) { /* тело функции */ } };
Свойства с методами доступа определяются как одна или две функции, имена
которых совпадают с именем свойства и с заменой ключевого слова function
на get
и/или set
. Обратите внимание, что не требуется использовать двоеточие для
отделения имени свойства от функции, управляющей доступом к свойству, но
по-прежнему необходимо использовать запятую после тела функции, чтобы
отделить метод от других методов или свойств с данными. Для примера рассмотрим
следующий объект, представляющий декартовы координаты точки на
плоскости. Для представления координат X
и Y
в нем имеются обычные свойства с
данными, а также свойства с методами доступа, позволяющие получить
эквивалентные полярные координаты точки; после задания декартовых координат
имеется возможность изменить растояние до начала координат при неизменном
полярном угле :
var p = { // х и у – обычные свойства с данными, доступные для чтения/записи. x: 1.0, y: 1.0, // r – доступное для чтения/записи свойство с двумя методами доступа. // Не забывайте добавлять запятые после методов доступа. get r() { return Math.sqrt(this.x*this.x + this.y*this.y); }, set r(newvalue) { var oldvalue = Math.sqrt(this.x*this.x + this.y*this.y); var ratio = newvalue/oldvalue; this.x *= ratio; this.y *= ratio; }, // theta – доступное только для чтения свойство // с единственным методом чтения. get theta() { return Math.atan2(this.y, this.x); } };
Обратите внимание на использование ключевого слова this
в методах
чтения и записи выше. Интерпретатор будет вызывать эти функции, как методы объекта,
в котором они определены, т. е. в теле функции this
будет ссылаться
на объект точки. Благодаря этому метод чтения свойства к может ссылаться на свойства
x
и y
, как this.x
и this.y
.
Подробнее о методах и ключевом слове this
рассказывается в разделе
8.2.2.
Свойства с методами доступа наследуются так же, как обычные свойства с данными,
поэтому объект p
, определенный выше, можно использовать как прототип
для других объектов точек. В новых объектах будут присутствовать
унаследованные свойства r
и theta
, и можно определять
собственные свойства x
и y
:
var q = inherit(p); // Создать новый объект, наследующий методы доступа q.x = 0, q.y = 0; // Создать собственные свойства с данными в объекте q console.log(q.r); // И использовать унаследованные свойства console.log(q.theta); // с методами доступа
Приведенный фрагмент использует свойства с методами доступа для определения API, обеспечивающего представление единого набора данных в двух системах координат (декартовой и полярной). Еще одной причиной использования свойств с методами доступа может быть необходимость проверки значения перед записью и возврат разных значений при каждом чтении свойства:
// Этот объект генерирует последовательность увеличивающихся чисел var serialnum = { // Это свойство с данными хранит следующее число в последовательности. // Знак $ в имени свойства говорит о том, что оно является частным. $n: 0, // Возвращает текущее значение и увеличивает его get next() { return this.$n++; }, // Устанавливает новое значение n, но только если оно больше текущего set next(n) { if (n >= this.$n) this.$n = n; else throw "число может быть только увеличено"; } };
Установить свое начальное значение serialnum
можно в окне, вызываемом двойным щелчком на этом абзаце.
// Этот объект имеет свойства с методами доступа, при обращении к которым // возвращаются случайные числа. Например, каждый раз при вычислении // выражения "random.octet" будет возвращаться случайное число в диапазоне // от 0 до 255. var random = { get octet() { return Math.floor(Math.random()*256); }, get uint16() { return Math.floor(Math.random()*65536); }, get int16() { return Math.floor(Math.random()*65536)-32768; } };
В этом разделе было показано, как определять свойства с методами доступа при создании нового объекта с помощью литерала. В следующем разделе будет показано, как добавлять свойства с методами доступа в существующие объекты.
Свойства, помимо имени и значения, обладают атрибутами, определяющими их доступность для записи, перечисления и настройки. В ECMAScript 3 не предусматривается возможность изменения атрибутов: все свойства, создаваемые программами, выполняющимися под управлением реализации ECMAScript 3, доступны для записи, перечисления и настройки, и нет никакой возможности изменить эти атрибуты. Данный раздел описывает прикладной интерфейс (API), определяемый стандартом ECMAScript 5 для получения и изменения атрибутов свойств. Данный API имеет особое значение для разработчиков библиотек, потому что он позволяет:
Для целей данного раздела мы будем рассматривать методы чтения и записи
свойств с методами как атрибуты свойств. Следуя этой логике, можно даже
сказать, что значение свойства с данными также является атрибутом. Таким
образом, свойства имеют имя и четыре атрибута. Четырьмя атрибутами свойств с
данными являются: значение (value), признак доступности для записи (writable
),
признак доступности для перечисления (enumerable
) и признак доступности для
настройки (configurable
). В свойствах с методами доступа отсутствуют атрибуты
value и writable
: их доступность для записи определяется наличием или
отсутствием метода записи. Поэтому четырьмя атрибутами свойств с методами доступа
являются: метод чтения (get
), метод записи (set
), признак доступности для
перечисления (enumerable
) и признак доступности для настройки (configurable
).
Методы получения и записи значений атрибутов свойств, предусмотренные
стандартом ECMAScript 5, используют объект, называемый дескриптором свойства
(property descriptor), представляющий множество из четырех атрибутов. Объект
дескриптора свойства обладает свойствами, имена которых совпадают с именами
атрибутов свойства, которое он описывает. То есть объекты-дескрипторы свойств
с данными имеют свойства с именами value, writable
,
enumerable
и configurable
.
А дескрипторы свойств с методами доступа вместо свойств value и
writable
имеют свойства get
и set
.
Свойства writable
, enumerable
и configurable
являются
логическими значениями, а свойства get
и set
– функциями.
Получить дескриптор свойства требуемого объекта можно вызовом
// Вернет {value: 1, writable:true, enumerable:true, configurable:true} Object.getOwnPropertyDescriptor({x:1}, "x"); // Теперь получим свойство octet объекта random, объявленного выше. // Вернет { get: /*func*/, set:undefined, enumerable:true, configurable:true} Object.getOwnPropertyDescriptor(random, "octet"); // Вернет undefined для унаследованных и несуществующих свойств. Object.getOwnPropertyDescriptor({}, "x"); // undefined, нет такого свойства Object.getOwnPropertyDescriptor({}, "toString"); // undefined, унаследованное
Object.getOwnPropertyDescriptor()
//Object.getOwnPropertyDescriptor(object, properties):
Как можно заключить из названия метода, Object.getOwnPropertyDescriptor()
работает только с собственными свойствами. Чтобы получить атрибуты
унаследованного свойства, необходимо явно выполнить обход цепочки прототипов (смотрите
описание Object.getPrototypeOf()
в разделе 6.8.1).
Чтобы изменить значение атрибута свойства или создать новое свойство с
заданными значениями атрибутов, следует вызвать метод Object.defineProperty()
,
передав ему
var o = {}; // Создать пустой объект без свойств // Создать неперечислимое простое свойство x со значением 1 Object.defineProperty(o, "x", { value : 1, writable: true, enumerable: false, configurable: true}); // Убедиться, что свойство создано и является неперечислимым o.x; // => 1 Object.keys(o) // => [] // Теперь сделать свойство х доступным только для чтения Object.defineProperty(o, "x", { writable: false }); // Попытаться изменить значение свойства o.x = 2; // Неудача, в строгом режиме возбудит ТуреЕггог o.x // => 1 // Свойство все еще доступно для настройки, // его значение можно изменить так: Object.defineProperty(o, "x", { value: 2 }); o.x // => 2 // Теперь превратить простое свойство в свойство с методами доступа Object.defineProperty(o, "x", { get: function() { return 0; } }); o.x // => 0
Дескриптор свойства, передаваемый методу Object.defineProperty()
,
необязательно должен иметь все четыре атрибута. При создании нового свойства
отсутствующие атрибуты получат значение false
или undefined
. При изменении
существующего свойства для отсутствующих атрибутов будут сохранены текущие
значения. Обратите внимание, что этот метод изменяет существующее собственное
свойство или создает новое собственное свойство – он не изменяет
унаследованные свойства.
Если возникнет необходимость создать или изменить сразу несколько свойств,
можно воспользоваться методом Object.defineProperties()
. Первым аргументом
ему передается объект, который требуется изменить. Вторым аргументом –
объект, отображающий в дескрипторы свойств имена создаваемых или модифицируемых
свойств . Например:
var p = Object.defineProperties({}, { x: { value: 1, writable: true, enumerable:true, configurable:true }, y: { value: 1, writable: true, enumerable:true, configurable:true }, r: { get: function() { return Math.sqrt(this.x*this.x + this.y*this.y) }, enumerable:true, configurable:true } });
В этом примере все начинается с пустого объекта, в который затем добавляются
два свойства с данными и одно свойство с методами доступа, доступное только
для чтения. Он опирается на то, что Object.defineProperties()
возвращает
модифицированный объект.
Object.create()
, определяемым стандартом ECMAScript 5, мы
познакомились в разделе 6.1, где узнали, что первым аргументом этому методу
передается объект, который будет служить прототипом для вновь созданного объекта.
Этот метод также принимает второй необязательный аргумент, такой же, как
и второй аргумент метода Object.defineProperties()
. Если методу Object.create()
,
передать множество дескрипторов свойств, они будут использованы для создания
свойств нового объекта.
Методы Object.defineProperty()
и Object.defineProperties()
возбуждают
исключение TypeError
, когда создание или изменение свойств запрещено. Например, при
попытке добавить новое свойство в нерасширяемый объект (раздел 6.8.3). Другие
причины, по которым эти методы могут возбудить исключение TypeError
, имеют
непосредственное отношение к атрибутам. Атрибут writable
контролирует
попытки изменить атрибут value. А атрибут configurable
контролирует попытки
изменить другие атрибуты (а также определяет возможность удаления свойства).
Однако все не так просто. Например, значение свойства, доступного только для
чтения, можно изменить, если это свойство доступно для настройки. Кроме того,
свойство, доступное только для чтения, можно сделать доступным для записи,
даже если это свойство недоступно для настройки. Ниже приводится полный
перечень правил. Вызовы Object
, defineProperty()
или Object.defineProperties()
,
нарушающие их, возбуждают исключение TypeError
:
configurable
и enumerable
. writable
с false
на true
,
но его можно изменить с true
на false
. extend()
, которая копирует свойства из одного
объекта в другой. Эта функция просто копирует имена и значения свойств и
игнорирует их атрибуты. Кроме того, она не копирует методы чтения и записи из свойств
с методами доступа, а просто преобразует их в свойства со статическими
данными. В примере 6.3 показана новая версия extend()
, которая копирует все атрибуты
свойств с помощью Object.getOwnPropertyDescriptor()
и Object.defineProperty()
.
Данная версия оформлена не как функция, а как новый метод объекта
и добавляется в Object.prototype
как свойство, недоступное для перечисления.
Пример 6.3. Копирование атрибутов свойств
/* * Добавляет неперечислимый метод extend() в Object.prototype. * Этот метод расширяет объекты возможностью копирования свойств. * из объекта переданного в аргументе. Этот метод копирует не только * значение свойств, но и все их атрибуты. Из объекта в аргументе * копируются все собственные свойства (даже недоступные для * перечисления), за исключением одноименных свойств, имеющихся * в текущем объекте. */ Object.defineProperty(Object.prototype, "extend", // Определяется Object.prototype.extend { writable: true, enumerable: false, // Сделать неперечислимым configurable: true, value: function(o) { // Значением свойства является данная функция // Получить все собственные свойства, даже неперечислимые var names = Object.getOwnPropertyNames(o); // Обойти их в цикле for(var i = 0; i < names.length; i++) { // Пропустить свойства, уже имеющиеся в данном объекте if (names[i] in this) continue; //Получить дескриптор свойства из o var desc = Object.getOwnPropertyDescriptor(o,names[i]); // Создать с его помощью свойство в данном объекте Object.defineProperty(this, names[i], desc); } } });
Синтаксис определения свойств с методами доступа в литералах объектов,
описанный разделе 6.6, позволяет определять свойства с методами в новых объектах, но
он не дает возможности получать методы чтения и записи и добавлять новые
свойства с методами доступа к существующим объектам. В ECMAScript 5 для этих
целей можно использовать Object.getOwnPropertyDescriptor()
и Object.defineProperty()
.
Большинство реализаций JavaScript (за исключением веб-браузера IE)
поддерживали синтаксис get и set в литералах объектов еще до принятия стандарта
ECMAScript 5. Эти реализации поддерживают нестандартный, устаревший API
для получения и назначения методов чтения и записи. Этот API состоит из
четырех методов, доступных во всех объектах. __lookupGetter__()
и
__lookupSetter__()
возвращают методы чтения и записи для
указанного свойства. А методы __defineGetter__()
и
__defineSetter__()
позволяют определить метод чтения или записи:
в первом аргументе они принимают имя свойства, а во втором – метод чтения или
записи. Имена всех этих методов начинаются и оканчиваются двумя символами
подчеркивания, чтобы показать, что они являются нестандартными методами.
Эти нестандартные методы не описываются в справочном разделе.
Все объекты имеют атрибуты prototype
, class
и extensible
. Все эти атрибуты
описываются ниже в соответствующих подразделах; в них также рассказывается, как получать и
изменять значения атрибутов (если это возможно).
Атрибут prototype
объекта определяет объект, от которого наследуются свойства.
(Дополнительные сведения о прототипах и наследовании прототипов приводятся
в разделах 6.1.3 и
6.2.2) Этот атрибут играет настолько важную роль, что обычно
мы будем говорить о нем как о «прототипе объекта o
», а не как об «атрибуте
prototype
объекта о». Кроме того, важно понимать, что когда в программном коде
встречается ссылка prototype
, она обозначает обычное свойство объекта, а не
атрибут prototype
.
Атрибут prototype
устанавливается в момент создания объекта. В разделе 6.1.3
уже говорилось, что для объектов, созданных с помощью литералов, прототипом
является Object.prototype
. Прототипом объекта, созданного с помощью оператора
new
, является значение свойства prototype
конструктора. А прототипом объекта,
созданного с помощью Object.create()
, становится первый аргумент этой функции
(который может иметь значение null
).
Стандартом ECMAScript 5 предусматривается возможность определить
прототип любого объекта, если передать его методу Object.getPrototypeOf()
. В
ECMAScript 3 отсутствует эквивалентная функция, но зачастую определить прототип
объекта o можно с помощью выражения o.constructor.prototype
. Объекты,
созданные с помощью оператора new
, обычно наследуют свойство constructor,
ссылающееся на функцию-конструктор, использованную для создания объекта. И как уже
говорилось выше, функции-конструкторы имеют свойство prototype
, которое
определяет прототип объектов, созданных с помощью этого конструктора.
Подробнее об этом рассказывается в разделе 9.2, где также объясняется, почему этот
метод определения прототипа объекта не является достаточно надежным. Обратите
внимание, что объекты, созданные с помощью литералов объектов или Object.create()
,
получают свойство constructor
, ссылающееся на конструктор Object()
. Таким
образом, constructor.prototype
ссылается на истинный прототип для литералов
объектов, но обычно это не так для объектов, созданных вызовом Object.create()
.
Чтобы определить, является ли один объект прототипом (или звеном в цепочке
прототипов) другого объекта, следует использовать метод isPrototypeOf()
. Чтобы
узнать, является ли p
прототипом o
, нужно записать выражение p.isPrototypeOf(o)
.
Например:
var p = {x:1}; // Определить объект-прототип. var o = Object.create(p); // Создать объект с этим прототипом. p.isPrototypeOf(o) // => : o наследует от p Object.prototype.isPrototypeOf(o) // => : // p наследует от Object.prototypeОбратите внимание, что
isPrototypeOf()
по своему действию напоминает оператор
instanceof
(раздел 4.9.4).
В реализации JavaScript компании Mozilla (первоначально созданной в Netscape)
значение атрибута prototype
доступно через специальное свойство __proto__
,
которое можно использовать напрямую для определения и установки прототипа
любого объекта. Использование свойства __proto__
ухудшает переносимость: оно
отсутствует (и, вероятно, никогда не появится) в реализациях браузеров IE или Opera,
хотя в настоящее время оно поддерживается браузерами Safari и Chrome. Версии
Firefox, реализующие стандарт ECMAScript 5, все еще поддерживают свойство
__proto__
, но не позволяют изменять прототип нерасширяемых объектов.
Атрибут class
объекта – это строка, содержащая информацию о типе объекта. Ни
в ECMAScript 3, ни в ECMAScript 5 не предусматривается возможность
изменения этого атрибута и предоставляются лишь косвенные способы определения его
значения. По умолчанию метод toString() (наследуемый от Object.prototype
)
возвращает строку вида:
[object class]
Поэтому, чтобы определить класс объекта, можно попробовать вызвать метод
toString()
этого объекта и извлечь из результата подстроку с восьмого по
предпоследний символ. Вся хитрость состоит в том, что многие методы наследуют
другие, более полезные реализации метода toString()
, и чтобы вызвать нужную
версию toString()
, необходимо выполнить косвенный вызов с помощью метода
Function.call()
(раздел 8.7.3). В примере 6.4 определяется функция, возвращающая
класс любого объекта, переданного ей.
Пример 6.4. Функция classoff()
function classof(o) { if (o === null) return "Null"; if (o === undefined) return "Undefined"; return Object.prototype.toString.call(o).slice(8,-1) }Этой функции
classoff
можно передать любое значение, допустимое в языке
JavaScript. Числа, строки и логические значения действуют подобно объектам,
когда относительно них вызывается метод toString()
, а значения null
и undefined
обрабатываются особо. (В ECMAScript 5 особая
обработка не требуется.) Объекты, созданные с помощью встроенных конструкторов,
таких как Array
и Date
, имеют
атрибут class
, значение которого совпадает с именами их конструкторов.
Объекты среды выполнения обычно также получают осмысленное значение атрибута
class
, однако это зависит от реализации. Объекты, созданные с помощью
литералов или вызовом Object.create, получают атрибут class
со значением «Object».
Если вы определите свой конструктор, все объекты, созданные с его помощью,
получат атрибут class
со значением «Object
»: нет никакого способа установить иное
значение в атрибуте class
для собственных классов объектов:
classof(null) // => "Null" classof(1) // => "Number" classof("") // => "String" classof(false) // => "Boolean" classof({}) // => "Object" classof([]) // => "Array" classof(/./) // => "Regexp" classof(new Date()) // => "Date" classof(window) // => "Window" (a client-side host object) function f() {}; // Define a custom constructor classof(new f()); // => "Object"
Атрибут extensible
объекта определяет, допускается ли добавлять в объект новые
свойства. В ECMAScript 3 все встроенные и определяемые пользователем
объекты неявно допускали возможность расширения, а расширяемость объектов
среды выполнения определялась каждой конкретной реализацией. В ECMAScript 5
все встроенные и определяемые пользователем объекты являются
расширяемыми, если они не были преобразованы в нерасширяемые объекты, а
расширяемость объектов среды выполнения по-прежнему определяется каждой
конкретной реализацией.
Стандарт ECMAScript 5 определяет функции для получения и изменения
признака расширяемости объекта. Чтобы определить, допускается ли расширять
объект, его следует передать методу Object.preventExtensions()
. Чтобы сделать объект
нерасширяемым, его нужно передать методу Object.preventExtensions()
. Обратите
внимание, что после того как объект будет сделан нерасширяемым, его нельзя
снова сделать расширяемым. Отметьте также, что вызов preventExtensions()
оказывает влияние только на расширяемость самого объекта. Если новые свойства
добавить в прототип нерасширяемого объекта, нерасширяемый объект
унаследует эти новые свойства.
Назначение атрибута extensible
заключается в том, чтобы дать возможность
«фиксировать» объекты в определенном состоянии, запретив внесение изменений.
Атрибут объектов extensible
часто используется совместно с атрибутами свойств
configurable
и writable
, поэтому в ECMAScript 5 определяются функции,
упрощающие одновременную установку этих атрибутов.
Метод Object.seal()
действует подобно методу Object.preventExtensions()
, но он не
только делает объект нерасширяемым, но и делает все свойства этого объекта
недоступными для настройки. То есть в объект нельзя будет добавить новые
свойства, а существующие свойства нельзя будет удалить или настроить. Однако
существующие свойства, доступные для записи, по-прежнему могут быть изменены.
Чтобы определить, вызывался ли метод Object.seal()
для объекта, можно вызвать
метод Object.isSealed()
.
Метод Object.freeze()
обеспечивает еще более жесткую фиксацию объектов.
Помимо того, что он делает объект нерасширяемым, а его свойства недоступными для
настройки, он также делает все собственные свойства с данными доступными
только для чтения. (Это не относится к свойствам объекта с методами доступа,
обладающими методами записи; эти методы по-прежнему будут вызываться
инструкциями присваивания.) Чтобы определить, вызывался ли метод Object.freeze()
объекта, можно вызвать метод Object.isFrozen()
.
Важно понимать, что Object.seal()
и Object.freeze()
воздействуют только на
объект, который им передается: они не затрагивают прототип этого объекта. Если
в программе потребуется полностью зафиксировать объект, вам, вероятно,
потребуется зафиксировать также объекты в цепочке прототипов.
Все методы, Object.preventExtensions(), Object.seal()
и Object.freeze()
, возвращают
переданный им объект, а это означает, что их можно использовать во вложенных
вызовах:
// Создать нерасширяемый объект с ненастраиваемыми свойствами, с жестко // зафиксированным прототипом и свойством, недоступным для перечисления var o = Object.seal(Object.create(Object.freeze({x:1}), {y: {value: 2, writable: true}}));
Сериализация объектов – это процесс преобразования состояния объектов в строковую
форму представления, которая позднее может использоваться для их
восстановления. Для сериализации и восстановления объектов JavaScript стандартом ЕСМА-Script 5
предоставляются встроенные функции JSON.stringify()
и JSON.parse()
. Эти
функции используют формат обмена данными JSON. Название JSON происходит
от «JavaScript Object Notation» (форма записи объектов JavaScript), а синтаксис
этой формы записи напоминает синтаксис литералов объектов и массивов в
языке JavaScript:
o = {x:1, y:{z:[false,null,""]}}; // Определить испытательный объект s = JSON.stringify(o); // s == p = JSON.parse(s); // p - глубокая копия объекта oБазовые реализации этих функций в ECMAScript 5 очень точно повторяют общедоступные реализации в ECMAScript 3, доступные в http://json.org/json2.js. С практической точки зрения это совершенно одинаковые реализации, и эти функции стандарта ECMAScript 5 можно использовать в ECMAScript 3, подключив указанный выше модуль json2.js.
Синтаксис формата JSON является лишь подмножеством синтаксиса языка
JavaScript и не может использоваться для представления всех возможных
значений, допустимых в JavaScript. Поддерживаются и могут быть сериализованы
и восстановлены: объекты, массивы, строки, конечные числовые значения, true
,
false
и null
. Значения NaN
, Infinity
и -Infinity
сериализуются в значение null
.
Объекты Date
сериализуются в строки с датами в формате ISO (смотрите описание
функции Date.toJSON()
), но JSON.parse()
оставляет их в строковом представлении
и не восстанавливает первоначальные объекты Date
. Объекты Function
, RegExp
и Error и значение undefined
не могут быть сериализованы или восстановлены.
Функция JSON.stringifу()
сериализует только перечислимые собственные
свойства объекта. Если значение свойства не может быть сериализовано, это свойство
просто исключается из строкового представления. Обе функции, JSON.stringifу()
и JSON.parse()
, принимают необязательный второй аргумент, который можно
использовать для настройки процесса сериализации и/или восстановления,
например, посредством определения списка свойств, подлежащих сериализации, или
функции преобразования значений во время сериализации. В справочном
разделе приводится полное описание этих функций.
Как описывалось выше, все объекты в языке JavaScript (за исключением тех, что
явно созданы без прототипа) наследуют свойства от Object.prototype
. Эти
наследуемые свойства являются первичными методами и представляют особый
интерес для программистов на JavaScript, потому что доступны повсеместно. Мы уже
познакомились с методами hasOwnProperty()
, propertyIsEnumerable()
и isPrototypeOf()
. (И мы уже охватили достаточно много статических функций,
определяемых конструктором Object
, таких как Object.create()
и Object.getPrototypeOf()
.)
В этом разделе описывается несколько универсальных методов объектов,
которые определены в Object.prototype
и предназначены для переопределения в
других, более специализированных классах.
Метод toString()
не требует аргументов; он возвращает строку, каким-либо
образом представляющую значение объекта, для которого он вызывается.
Интерпретатор JavaScript вызывает этот метод объекта во всех тех случаях, когда ему
требуется преобразовать объект в строку. Например, это происходит, когда
используется оператор + для конкатенации строки с объектом, или при передаче объекта
методу, требующему строку.
Метод toString()
по умолчанию не очень информативен (однако его удобно
использовать для определения класса объекта, как было показано в разделе 6.8.2).
Например, следующий фрагмент просто записывает в переменную s строку
"[object Object]":
Этот метод по умолчанию не отображает особенно полезной информации, поэтому
многие классы определяют собственные версии метода toString()
. Например,
когда массив преобразуется в строку, мы получаем список элементов массива,
каждый из которых преобразуется в строку, а когда в строку преобразуется функция,
мы получаем исходный программный код этой функции. Эти
специализированные версии метода toString()
описываются в справочном руководстве. Смотрите,
например, описание методов Array.toString()
, Date.toString()
и Function.toString()
.
В разделе 9.6.3 описывается, как можно переопределить метод toString()
для
своих собственных классов.
В дополнение к методу toString()
все объекты имеют метод toLocaleString()
.
Назначение последнего состоит в получении локализованного строкового
представления объекта. По умолчанию метод toLocaleString()
, определяемый классом
Object
, никакой локализации не выполняет; он просто вызывает метод toString()
и возвращает полученное от него значение. Классы Date
и Number
определяют
собственные версии метода toLocaleString()
, возвращающие строковые
представления чисел и дат в соответствии с региональными настройками. Класс Array
определяет версию метода toLocaleString()
, действующую подобно методу toString()
за
исключением того, что он форматирует элементы массива вызовом их метода
toLocaleString()
, а не toString()
.
В действительности Object.prototype
не определяет метод toJSON()
, но метод
JS0N.stringify()
(раздел 6.9)
пытается отыскать и использовать метод toJSON()
любого
объекта, который требуется сериализовать. Если объект обладает этим методом,
он вызывается и сериализации подвергается возвращаемое значение, а не
исходный объект. Примером может служить метод Date.toJSON()
.
Метод valueOf()
во многом похож на метод toString()
, но вызывается, когда
интерпретатору JavaScript требуется преобразовать объект в значение какого-либо
простого типа, отличного от строки, – обычно в число. Интерпретатор JavaScript
вызывает этот метод автоматически, если объект используется в контексте
значения простого типа. Метод valueOf()
по умолчанию не выполняет ничего, что
представляло бы интерес, но некоторые встроенные классы объектов переопределяют
метод valueOf()
(например, Date.valueOf()
). В разделе 9.6.3 описывается, как
можно переопределить метод valueOf()
в собственных типах объектов.