Объекты

75

Объект является фундаментальным типом данных в языке JavaScript. Объект - это составное значение: он объединяет в себе набор значений (простых значений или других объектов) и позволяет сохранять и извлекать эти значения по именам.

Объект является неупорядоченной коллекцией свойств, каждое из которых имеет имя и значение. Имена свойств являются строками, поэтому можно сказать, что объекты отображают строки в значения. Такое отображение строк в значения может называться по-разному: возможно, вы уже знакомы с такой фундаментальной структурой данных, как «хеш», «словарь» или «ассоциативный массив». Однако объект представляет собой нечто большее, чем простое отображение строк в значения.

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

Объекты в языке JavaScript являются динамическими - обычно они позволяют добавлять и удалять свойства - но они могут использоваться также для имитации статических объектов и «структур», которые имеются в языках программирования со статической системой типов. Кроме того, они могут использоваться (если не учитывать, что объекты отображают строки в значения) для представления множеств строк.

Любое значение в языке JavaScript, не являющееся строкой, числом, true, false, null или undefined, является объектом. И даже строки, числа и логические значения, не являющиеся объектами, могут вести себя как неизменяемые объекты (имеют объекты-обертки String, Number и т.п.).

Объекты являются изменяемыми значениями и операции с ними выполняются по ссылке, а не по значению. Если переменная x ссылается на объект, и выполняется инструкция var y = x;, в переменную y будет записана ссылка на тот же самый объект, а не его копия. Любые изменения, выполняемые в объекте с помощью переменной y, будут также отражаться на переменной x.

Свойство имеет имя и значение. Именем свойства может быть любая строка, включая и пустую строку, но объект не может иметь два свойства с одинаковыми именами. Значением свойства может быть любое значение, допустимое в языке JavaScript, или (в ECMAScript 5) функция чтения или записи (или обе).

В дополнение к именам и значениям каждое свойство имеет ряд ассоциированных с ним значений, которые называют атрибутами свойства:

До появления стандарта ECMAScript 5 все свойства в объектах, создаваемые программой, доступны для записи, перечисления и настройки. В ECMAScript 5 предусматривается возможность настройки атрибутов ваших свойств.

В дополнение к свойствам каждый объект имеет три атрибута объекта:

Наконец, ниже приводится описание некоторых терминов, которые помогут нам различать три обширные категории объектов в языке JavaScript и два типа свойств:

Объект базового языка

Это объект или класс объектов, определяемый спецификацией ECMAScript. Массивы, функции, даты и регулярные выражения (например) являются объектами базового языка.

Объект среды выполнения

Это объект, определяемый средой выполнения (такой как веб-браузер), куда встроен интерпретатор JavaScript. Объекты HTMLElement, представляющие структуру веб-страницы в клиентском JavaScript, являются объектами среды выполнения. Объекты среды выполнения могут также быть объектами базового языка, например, когда среда выполнения определяет методы, которые являются обычными объектами Function базового языка JavaScript.

Пользовательский объект

Любой объект, созданный в результате выполнения программного кода JavaScript.

Собственное свойство

Это свойство, определяемое непосредственно в данном объекте.

Унаследованное свойство

Это свойство, определяемое прототипом объекта.

Создание объектов

Объекты можно создавать с помощью литералов объектов, ключевого слова new и (в ECMAScript 5) функции Object.create().

Литералы объектов

Самый простой способ создать объект заключается во включении в программу литерала объекта. Литерал объекта - это заключенный в фигурные скобки список свойств (пар имя/значение), разделенных запятыми. Именем свойства может быть идентификатор или строковый литерал (допускается использовать пустую строку). Значением свойства может быть любое выражение, допустимое в JavaScript - значение выражения (это может быть простое значение или объект) станет значением свойства.

Ниже приводится несколько примеров создания объектов:

var empty = {};		// Объект без свойств
var point = { x:0, y:0 };						// Два свойства
var point2 = { x:point.x, y:point.y+1 };				// Более сложные значения

var site = {
	"url site": "www.professorweb.ru",			// Имена свойств с пробелами
	'desc-text': "Платформа .NET Framework",		// и дефисами, поэтому исп. кавычки

	author: {					// Значением этого свойства является
		firstname: "Alexandr",			// объект. Обратите внимание, что
		surname: "Frolov"			// имена этих свойств без кавычек.
	}
};

В ECMAScript 5 (и в некоторых реализациях ECMAScript 3) допускается использовать зарезервированные слова в качестве имен свойств без кавычек. Однако в целом имена свойств, совпадающие с зарезервированными словами, в ECMA-Script 3 должны заключаться в кавычки. В ECMAScript 5 последняя запятая, следующая за последним свойством в литерале объекта, игнорируется. В большинстве реализаций ECMAScript 3 завершающие запятые также игнорируются, но IE интерпретирует их наличие как ошибку.

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

Создание объектов с помощью оператора new

Оператор new создает и инициализирует новый объект. За этим оператором должно следовать имя функции. Функция, используемая таким способом, называется конструктором и служит для инициализации вновь созданного объекта. Базовый JavaScript включает множество встроенных конструкторов для создания объектов базового языка. Например:

var o = new Object();		// Создать новый пустой объект: то же, что и {}
var a = new Array();			// Создать пустой массив: то же, что и []
var d = new Date(); 		// Создать объект Date, представляющий текущее время 
var r = new RegExp("js"); 	// Создать объект RegExp для операций сопоставления с шаблоном

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

Object.create()

Стандарт ECMAScript 5 определяет метод Object.create(), который создает новый объект и использует свой первый аргумент в качестве прототипа этого объекта. Дополнительно Object.create() может принимать второй необязательный аргумент, описывающий свойства нового объекта.

Object.create() является статической функцией, а не методом, вызываемым относительно некоторого конкретного объекта. Чтобы вызвать эту функцию, достаточно передать ей желаемый объект-прототип:

// obj наследует свойства x и y
var obj = Object.create({x:1, y:2});	

Чтобы создать объект, не имеющий прототипа, можно передать значение null, но в этом случае вновь созданный объект не унаследует ни каких-либо свойств, ни базовых методов, таких как toString() (а это означает, что этот объект нельзя будет использовать в выражениях с оператором +):

// obj2 не наследует ни свойств, ни методов
var obj2 = Object.create(null);	

Если в программе потребуется создать обычный пустой объект (который, например, возвращается литералом {} или выражением new Object()), передайте в первом аргументе Object.prototype:

// obj3 подобен объекту, созданному
// с помощью {} или new Object()
var obj3 = Object.create(Object.prototype); 

Возможность создавать новые объекты с произвольными прототипами (скажем иначе: возможность создавать «наследников» от любых объектов) является мощным инструментом, действие которого можно имитировать в ECMAScript 3 с помощью функции, представленной в примере ниже:

// 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.
}

Реализация функции inherit() приобретет больше смысла, как только мы познакомимся с конструкторами в следующей статье. А пока просто считайте, что она возвращает новый объект, наследующий свойства объекта в аргументе. Обратите внимание, что функция inherit() не является полноценной заменой для Object.create(): она не позволяет создавать объекты без прототипа и не принимает второй необязательный аргумент, как Object.create().

Получение и изменение свойств

Получить значение свойства можно с помощью операторов точки (.) и квадратных скобок ([]). Слева от оператора должно находиться выражение, возвращающее объект. При использовании оператора точки справа должен находиться простой идентификатор, соответствующий имени свойства. При использовании квадратных скобок в квадратных скобках должно указываться выражение, возвращающее строку, содержащую имя требуемого свойства:

// Простой объект
var user = { login:'kot86', name:'Alexandr', age:26 };

var login = user.login;		// Получить свойство "login" объекта user
var name = user.name;		// Получить свойство "name" объекта user
var age = user['age'];		// Получить свойство "age" объекта user

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

user.age = 28;				// Изменить значение свойства 'age'
user['login'] = 'kot84';	// Изменить значение свойства 'login'
user['surname'] = 'Frolov'; // Создать новое свойство 'surname'

В ECMAScript 3 идентификатор, следующий за точкой, не может быть зарезервированным словом: нельзя записать обращение к свойству o.for или o.class, потому что for является ключевым словом, а class - словом, зарезервированным для использования в будущем.

Если объект имеет свойства, имена которых совпадают с зарезервированными словами, для доступа к ним необходимо использовать форму записи с квадратными скобками: o["for"] и o["class"]. Стандарт ECMAScript 5 ослабляет это требование (как это уже сделано в некоторых реализациях ECMAScript 3) и допускает возможность использования зарезервированных слов после оператора точки.

Прототипы

Каждый объект в языке 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. Такая связанная последовательность объектов-прототипов называется цепочкой прототипов.

Наследование

Объекты в языке JavaScript обладают множеством «собственных свойств» и могут также наследовать множество свойств от объекта-прототипа. Чтобы разобраться в этом, необходимо внимательно изучить механизм доступа к свойствам. В примерах этого раздела для создания объектов с определенными прототипами используется функция inherit(), показанная выше.

Предположим, что программа обращается к свойству x объекта obj. Если объект obj не имеет собственного свойства с таким именем, выполняется попытка отыскать свойство x в прототипе объекта obj. Если объект-прототип не имеет собственного свойства с этим именем, но имеет свой прототип, выполняется попытка отыскать свойство в прототипе прототипа. Так продолжается до тех пор, пока не будет найдено свойство x или пока не будет достигнут объект, не имеющий прототипа. Как видите, атрибут prototype объекта создает цепочку, или связанный список объектов, от которых наследуются свойства.

var  obj  = {};      // obj наследует методы объекта Object.prototype
obj.x  = 1;          // и обладает собственным свойством x.

var  p  = inherit(obj);      // p наследует свойства объектов obj и Object.prototype
p.y  =  2;            // и обладает собственным свойством y.

var  q  = inherit(p);      // q наследует свойства объектов p, obj и Object.prototype
q.z  =  3;            // и обладает собственным свойством z.

var s = q.toString();        // toString наследуется от Object.prototype
var d = q.x + q.y        // Результат 3: x и y наследуются от obj и p

Теперь предположим, что программа присваивает некоторое значение свойству x объекта obj. Если объект obj уже имеет собственное свойство (не унаследованное) с именем x, то операция присваивания просто изменит значение существующего свойства. В противном случае в объекте obj будет создано новое свойство с именем x. Если прежде объект obj наследовал свойство x, унаследованное свойство теперь окажется скрыто вновь созданным собственным свойством с тем же именем.

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

var unitcircle = { r:1 };		// Объект, от которого наследуется свойство 
var c = inherit(unitcircle);	// c наследует свойство r 

c.x = 1; c.y = 1;				// c определяет два собственных свойства
c.r = 2;						// c переопределяет унаследованное свойство

console.log(unitcircle.r);		// => 1: объект-прототип не изменился

Существует одно исключение из этого правила, когда операция присваивания значения свойству терпит неудачу или приводит к созданию/изменению свойства оригинального объекта. Если объект obj наследует свойство x и доступ к этому свойству осуществляется посредством методов доступа, то вместо создания нового свойства x в объекте obj производится вызов метода записи нового значения. Однако обратите внимание, что метод записи вызывается относительно объекта obj, а не относительно прототипа, в котором определено это свойство, поэтому, если метод записи определяет какие-либо свойства, они будут созданы в объекте obj, а цепочка прототипов опять останется неизменной.

Ошибки доступа к свойствам

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

Попытка обращения к несуществующему свойству не считается ошибкой. Если свойство x не будет найдено среди собственных или унаследованных свойств объекта obj, выражение обращения к свойству obj.x вернет значение undefined.

Однако попытка обратиться к свойству несуществующего объекта считается ошибкой. Значения null и undefined не имеют свойств, и попытки обратиться к свойствам этих значений считаются ошибкой:

// Простой объект
var user = { login:'kot86', name:'Alexandr', age:26 };

var a = user.password;			// undefined: свойство отсутствует

// Возбудит исключение TypeError. 
// Значение undefined не имеет свойства length
var len = user.password.length;

Если нет уверенности, что user и user.password являются объектами (или ведут себя подобно объектам), нельзя использовать выражение user.password.length, так как оно может возбудить исключение. Ниже демонстрируются два способа защиты против исключений подобного рода:

// Более наглядный и прямолинейный способ 
var len = undefined; 
if (user) {
	if (user.password) 
		len = user.password.length;
}

// Более краткая и характерная для JavaScript альтернатива 
// получения длины значения свойства password
var len = user && user.password && user.password.length;

Попытки установить значение свойства для других значений не всегда оканчиваются успехом: некоторые свойства доступны только для чтения и не позволяют изменять их значения. Кроме того, некоторые объекты не позволяют добавлять в них новые свойства. Однако самое интересное, что подобные неудачи, как правило, не приводят к возбуждению исключения:

// Свойства prototype встроенных конструкторов доступны только для чтения
Object.prototype = 0;	// Присваивание не возбудит исключения;
					// значение Object.prototype не изменится

Этот исторически сложившийся недостаток JavaScript исправлен в строгом режиме, определяемом стандартом ECMAScript 5. Все неудачные попытки изменить значение свойства в строгом режиме приводят к исключению TypeError.

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

Удаление свойств

Оператор delete удаляет свойство из объекта. Его единственный операнд должен быть выражением обращения к свойству. Может показаться удивительным, но оператор delete не оказывает влияния на значение свойства - он оперирует самим свойством:

// Простой объект
var user = { login:'kot86', name:'Alexandr', age:26 };

delete user.login;		// Теперь объект user не имеет свойства login
delete user['name'];	// Теперь объект user не имеет свойства name

Оператор delete удаляет только собственные свойства и не удаляет унаследованные. (Чтобы удалить унаследованное свойство, необходимо удалять его в объекте-прототипе, в котором оно определено. Такая операция затронет все объекты, наследующие этот прототип.)

Выражение delete возвращает значение true в случае успешного удаления свойства или когда операция удаления не привела к изменению объекта (например, при попытке удалить несуществующее свойство). Выражение delete также возвращает true, когда этому оператору передается выражение, не являющееся выражением обращения к свойству:

obj = {x:1};		// obj имеет собственное свойство x и наследует toString
delete obj.x;		// Удалит x и вернет true
delete obj.x;		// Ничего не сделает (x не существует) и вернет true
delete obj.toString;	// Ничего не сделает (toString не собственное свойство) и вернет true
delete 1;			// Бессмысленно, но вернет true

Оператор delete не удаляет ненастраиваемые свойства, атрибут configurable которых имеет значение false. (Однако он может удалять настраиваемые свойства нерасширяемых объектов.) Ненастраиваемыми являются свойства встроенных объектов, а также свойства глобального объекта, созданные с помощью инструкций объявления переменных и функций. Попытка удалить ненастраиваемое свойство в строгом режиме вызывает исключение TypeError. В нестрогом режиме (и в реализациях ECMAScript 3) в таких случаях оператор delete просто возвращает false:

delete Object.prototype;	// Удаление невозможно - ненастраиваемое свойство 

var x = 1;			// Объявление глобальной переменной
delete this.x;		// Это свойство нельзя удалить

function f() {}		// Объявление глобальной функции
delete this.f;		// Это свойство также нельзя удалить

Проверка существования свойств

Объекты в языке JavaScript можно рассматривать как множества свойств, и нередко бывает полезно иметь возможность проверить принадлежность к множеству - проверить наличие в объекте свойства с данным именем. Выполнить такую проверку можно с помощью оператора in, с помощью методов hasOwnProperty() и propertyIsEnumerable() или просто обратившись к свойству.

Оператор in требует, чтобы в левом операнде ему было передано имя свойства (в виде строки) и объект в правом операнде. Он возвращает true, если объект имеет собственное или унаследованное свойство с этим именем:

var obj = { x:1 }
"x" in obj;			// true: obj имеет собственное свойство "x"
"y" in obj;			// false: obj не имеет свойства "y"
"toString" in obj;	// true: obj наследует свойство toString

Метод hasOwnProperty() объекта проверяет, имеет ли объект собственное свойство с указанным именем. Для наследуемых свойств он возвращает false:

var obj = { x:1 }
obj.hasOwnProperty('x');		// true: obj имеет собственное свойство "x"
obj.hasOwnProperty('y');		// false: obj не имеет свойства "y"
obj.hasOwnProperty('toString');	// false: toString - наследуемое свойство

Метод propertyIsEnumerable() накладывает дополнительные ограничения по сравнению с hasOwnProperty(). Он возвращает true, только если указанное свойство является собственным свойством, атрибут enumerable которого имеет значение true. Свойства встроенных объектов не являются перечислимыми. Свойства, созданные обычной программой на языке JavaScript, являются перечислимыми, если не был использован один из методов ECMAScript 5, представленных ниже, которые делают свойства неперечислимыми.

Часто вместо оператора in достаточно использовать простое выражение обращения к свойству и использовать оператор !== для проверки на неравенство значению undefined:

var obj = { x:1 }
obj.x !== undefined;			// true: obj имеет свойство "x"
obj.y !== undefined;			// false: obj не имеет свойства "y"
obj.toString !== undefined;		// true: obj наследует свойство toString

Однако оператор in отличает ситуации, которые неотличимы при использовании представленного выше приема на основе обращения к свойству. Оператор in отличает отсутствие свойства от свойства, имеющего значение undefined.

Перечисление свойств

Вместо проверки наличия отдельных свойств иногда бывает необходимо обойти все имеющиеся свойства или получить список всех свойств объекта. Обычно для этого используется цикл for/in, однако стандарт ECMAScript 5 предоставляет две удобные альтернативы.

Инструкция цикла for/in выполняет тело цикла для каждого перечислимого свойства (собственного или унаследованного) указанного объекта, присваивая имя свойства переменной цикла. Встроенные методы, наследуемые объектами, являются неперечислимыми, а свойства, добавляемые в объекты вашей программой, являются перечислимыми (если только не использовались функции, описываемые ниже, позволяющие сделать свойства неперечислимыми). Например:

// Простой объект с тремя перечислимыми свойствами
var user = { login:'kot86', name:'Alexandr', age:26 };

user.propertyIsEnumerable('toString');		// false, toString - встроенный метод
for (n in user)
	console.log(n);

Некоторые библиотеки добавляют новые методы (или другие свойства) в объект Object.prototype, чтобы они могли быть унаследованы и быть доступны всем объектам. Однако до появления стандарта ECMAScript 5 отсутствовала возможность сделать эти дополнительные методы неперечислимыми, поэтому они оказывались доступными для перечисления в циклах for/in. Чтобы решить эту проблему, может потребоваться фильтровать свойства, возвращаемые циклом for/in. Ниже приводятся два примера реализации такой фильтрации:

for (n in user)
{
	if (!user.hasOwnProperty(n)) continue;
	console.log(n);
}

for (n in user)
{
	if (typeof user[n] === 'function') continue;
	console.log(n);
}

В дополнение к циклу for/in стандарт ECMAScript 5 определяет две функции, перечисляющие имена свойств. Первая из них, Object.keys(), возвращает массив имен собственных перечислимых свойств объекта.

Вторая функция ECMAScript 5, выполняющая перечисление свойств - Object.getOwnPropertyNames(). Она действует подобно функции Object.keys(), но возвращает имена всех собственных свойств указанного объекта, а не только перечислимые. В реализациях ECMAScript 3 отсутствует возможность реализовать подобные функции, потому что ECMAScript 3 не предусматривает возможность получения неперечислимых свойств объекта.

Методы чтения и записи свойств

Выше уже говорилось, что свойство объекта имеет имя, значение и набор атрибутов. В ECMAScript 5 значение может замещаться одним или двумя методами, известными как методы чтения (getter) и записи (setter). Свойства, для которых определяются методы чтения и записи, иногда называют свойствами с методами доступа, чтобы отличать их от свойств с данными, представляющих простое значение.

Когда программа пытается получить значение свойства с методами доступа, интерпретатор вызывает метод чтения (без аргументов). Возвращаемое этим методом значение становится значением выражения обращения к свойству. Когда программа пытается записать значение в свойство, интерпретатор вызывает метод записи, передавая ему значение, находящее справа от оператора присваивания. Этот метод отвечает за «установку» значения свойства. Значение, возвращаемое методом записи, игнорируется.

В отличие от свойств с данными, свойства с методами доступа не имеют атрибута writable. Если свойство имеет оба метода, чтения и записи, оно доступно для чтения/записи. Если свойство имеет только метод чтения, оно доступно только для чтения. А если свойство имеет только метод записи, оно доступно только для записи (такое невозможно для свойств с данными) и попытки прочитать значение такого свойства всегда будут возвращать undefined.

Самый простой способ определить свойство с методами доступа заключается в использовании расширенного синтаксиса определения литералов объектов:

var obj = {
	// Обычное свойство с данными 
	data_prop: value,

	// Свойство с методами доступа определяется как пара функций 
	get accessor_prop() { /* тело функции */ }, 
	set accessor_prop(value) { /* тело функции */ }
};

Свойства с методами доступа определяются как одна или две функции, имена которых совпадают с именем свойства и с заменой ключевого слова function на get и/или set.

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

Для примера рассмотрим следующий объект, представляющий декартовы координаты точки на плоскости. Для представления координат X и Y в нем имеются обычные свойства с данными, а также свойства с методами доступа, позволяющие получить эквивалентные полярные координаты точки:

var p = {
	// x и y обычные свойства с данными, доступные для чтения/записи
	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 будет ссылаться на объект точки. Благодаря этому метод чтения свойства r может ссылаться на свойства x и y, как this.x и this.y.

Свойства с методами доступа наследуются так же, как обычные свойства с данными, поэтому объект p, определенный выше, можно использовать как прототип для других объектов точек. В новых объектах можно определять собственные свойства x и y, и они будут наследовать свойства r и theta.

Атрибуты объекта

Все объекты имеют атрибуты prototype, class и extensible. Все эти атрибуты описываются в подразделах ниже.

Атрибут prototype

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

Атрибут prototype устанавливается в момент создания объекта. Для объектов, созданных с помощью литералов, прототипом является Object.prototype. Прототипом объекта, созданного с помощью оператора new, является значение свойства prototype конструктора. А прототипом объекта, созданного с помощью Object.create(), становится первый аргумент этой функции (который может иметь значение null).

Стандартом ECMAScript 5 предусматривается возможность определить прототип любого объекта, если передать его методу Object.getPrototypeOf(). В ECMAScript 3 отсутствует эквивалентная функция, но зачастую определить прототип объекта obj можно с помощью выражения obj.constructor.prototype.

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

Обратите внимание, что объекты, созданные с помощью литералов объектов или Object.create(), получают свойство constructor, ссылающееся на конструктор Object(). Таким образом, constructor.prototype ссылается на истинный прототип для литералов объектов, но обычно это не так для объектов, созданных вызовом Object.create().

Чтобы определить, является ли один объект прототипом (или звеном в цепочке прототипов) другого объекта, следует использовать метод isPrototypeOf(). Чтобы узнать, является ли p прототипом obj, нужно записать выражение p.isPrototypeOf(obj). Например:

var p = {x:1};		// Определить объект-прототип.
var obj = Object.create(p);			// Создать объект с этим прототипом.

p.isPrototypeOf(obj); 				// => true: obj наследует p
Object.prototype.isPrototypeOf(p); 	// => true: p наследует Object.prototype

Атрибут class

Атрибут class объекта - это строка, содержащая информацию о типе объекта. Ни в ECMAScript 3, ни в ECMAScript 5 не предусматривается возможность изменения этого атрибута и предоставляются лишь косвенные способы определения его значения. По умолчанию метод toString() (наследуемый от Object.prototype) возвращает строку вида:

[object class]

Поэтому, чтобы определить класс объекта, можно попробовать вызвать метод toString() этого объекта и извлечь из результата подстроку с восьмого по предпоследний символ. Вся хитрость состоит в том, что многие методы наследуют другие, более полезные реализации метода toString(), и чтобы вызвать нужную версию toString(), необходимо выполнить косвенный вызов с помощью метода Function.call().

В примере ниже определяется функция, возвращающая класс любого объекта, переданного ей:

// Название класса объекта
function classof(obj) {
	if (obj === null) return "Null";
	if (obj === undefined) return "Undefined";
	
	return Object.prototype.toString.call(obj).slice(8,-1);
}

Этой функции classof() можно передать любое значение, допустимое в языке JavaScript. Числа, строки и логические значения действуют подобно объектам, когда относительно них вызывается метод toString(), а значения null и undefined обрабатываются особо.

Атрибут extensible

Атрибут extensible объекта определяет, допускается ли добавлять в объект новые свойства. В ECMAScript 3 все встроенные и определяемые пользователем объекты неявно допускали возможность расширения, а расширяемость объектов среды выполнения определялась каждой конкретной реализацией. В ECMAScript 5 все встроенные и определяемые пользователем объекты являются расширяемыми, если они не были преобразованы в нерасширяемые объекты, а расширяемость объектов среды выполнения по-прежнему определяется каждой конкретной реализацией.

Стандарт ECMAScript 5 определяет функции для получения и изменения признака расширяемости объекта. Чтобы определить, допускается ли расширять объект, его следует передать методу Object.isExtensible(). Чтобы сделать объект нерасширяемым, его нужно передать методу Object.preventExtensions(). Обратите внимание, что после того как объект будет сделан нерасширяемым, его нельзя снова сделать расширяемым. Отметьте также, что вызов preventExtensions() оказывает влияние только на расширяемость самого объекта. Если новые свойства добавить в прототип нерасширяемого объекта, нерасширяемый объект унаследует эти новые свойства.

Назначение атрибута extensible заключается в том, чтобы дать возможность «фиксировать» объекты в определенном состоянии, запретив внесение изменений. Атрибут объектов extensible часто используется совместно с атрибутами свойств configurable и writable, поэтому в ECMAScript 5 определяются функции, упрощающие одновременную установку этих атрибутов.

Метод Object.seal() действует подобно методу Object.preventExtensions(), но он не только делает объект нерасширяемым, но и делает все свойства этого объекта недоступными для настройки. То есть в объект нельзя будет добавить новые свойства, а существующие свойства нельзя будет удалить или настроить. Однако существующие свойства, доступные для записи, по-прежнему могут быть изменены.

После вызова Object.seal() объект нельзя будет вернуть в прежнее состояние. Чтобы определить, вызывался ли метод Object.seal() для объекта, можно вызвать метод Object.isSealed().

Метод Object.freeze() обеспечивает еще более жесткую фиксацию объектов. Помимо того, что он делает объект нерасширяемым, а его свойства недоступными для настройки, он также делает все собственные свойства с данными доступными только для чтения. (Это не относится к свойствам объекта с методами доступа, обладающими методами записи; эти методы по-прежнему будут вызываться инструкциями присваивания.) Чтобы определить, вызывался ли метод Object.freeze() объекта, можно вызвать метод Object.isFrozen().

Важно понимать, что Object.seal() и Object.freeze() воздействуют только на объект, который им передается: они не затрагивают прототип этого объекта. Если в программе потребуется полностью зафиксировать объект, вам, вероятно, потребуется зафиксировать также объекты в цепочке прототипов.

Сериализация объектов

Сериализация объектов - это процесс преобразования объектов в строковую форму представления, которая позднее может использоваться для их восстановления. Для сериализации и восстановления объектов JavaScript стандартом ECMAScript 5 предоставляются встроенные функции JSON.stringify() и JSON.parse(). Эти функции используют формат обмена данными JSON. Название JSON происходит от «JavaScript Object Notation» (форма записи объектов JavaScript), а синтаксис этой формы записи напоминает синтаксис литералов объектов и массивов в языке JavaScript:

var obj = {x:1, y:{z:[false,null,""]}};		// Определить испытательный объект 
var s = JSON.stringify(obj);			// s == '{"x":1,"y":{"z":[false,null,""]}}'
var p = JSON.parse(s);				// p - копия объекта obj

Синтаксис формата JSON является лишь подмножеством синтаксиса языка JavaScript и не может использоваться для представления всех возможных значений, допустимых в JavaScript. Поддерживаются и могут быть сериализованы и восстановлены: объекты, массивы, строки, конечные числовые значения, true, false и null. Значения NaN, Infinity и -Infinity сериализуются в значение null. Объекты Date сериализуются в строки с датами в формате ISO, но JSON.parse() оставляет их в строковом представлении и не восстанавливает первоначальные объекты Date.

Объекты Function, RegExp и Error и значение undefined не могут быть сериализованы или восстановлены. Функция JSON.stringify() сериализует только перечислимые собственные свойства объекта. Если значение свойства не может быть сериализовано, это свойство просто исключается из строкового представления. Обе функции, JSON.stringify() и JSON.parse(), принимают необязательный второй аргумент, который можно использовать для настройки процесса сериализации и/или восстановления, например, посредством определения списка свойств, подлежащих сериализации, или функции преобразования значений во время сериализации.

Пройди тесты
Лучший чат для C# программистов