В 5-й редакции ECMAScript для работы с объектами появилось много новых методов, однако их подробное описание и внутреннюю реализацию на русском языке (зачастую и на английском) найти не так просто. Именно по этой причине в этой статье будут подробно рассмотрены и описаны все методы объекта Object согласно 3-й и 5-й редакции ECMAScript спецификации.
Содержание
1. Object.create(proto [, properties ])
2. Object.defineProperty(object, property, descriptor)
3. Object.defineProperties(object, properties)
4. Object.getOwnPropertyDescriptor(object, properties)
5. Object.keys(object)
6. Object.getOwnPropertyNames(object)
7. Data descriptor
8. Accessor descriptor
9. Object.getPrototypeOf(object)
10. Object.preventExtensions(object)
11. Object.isExtensible(object)
12. Object.seal(object)
13. Object.isSealed(object)
14. Object.freeze(object)
15. Object.deepFreeze(object) (non-standard)
16. Object.prototype.hasOwnProperty(property)
17. Object.prototype.isPrototypeOf(object)
18. Object.prototype.propertyIsEnumerable(object)
19. Заключение
Object.create(proto [, properties ])
Object.create() создает новый объект с заданным объектом-прототипом и свойствами:
object = Object.create property: 1
object.property # 1
Метод может принимать два инициализирующих параметра.
Первый параметр обязательный, он задает прототип объекта и должен быть null или объект.
Второй параметр опциональный, он инициализирует свойства объекта.
Рассмотрим пример:
object = Object.create {},
property:
value: 1
object.property # 1
В этом примере первым параметром мы задали пустой объект, который будет являться объектом-прототипом для свойств определенных во втором параметре.
Пусть вас не смущает загадочное свойство value, чуть позже мы рассмотрим эту тему более подробно.
Так в чем же отличия между первым и вторым вариантом?
Думаю что ответ на этот вопрос сможет прояснить следующий пример:
object = Object.create property: 1
object.property = 2
object.property # 2
object = Object.create {},
property:
value: 1
object.property = 2
object.property # 1
Какого черта спросите вы?
Просто, вторая форма записи по-умолчанию подразумевает, что свойства доступны только для чтения, их нельзя удалить и они не могут быть перечислены!
Давайте рассмотрим пример:
object = Object.create {}
property:
value: 1
object.property = 2
object.property # 1
object.method = -> object.property
do object.method # 1
for property of object
property # method
delete object.property # false
Как видите, у нас не получилось ни задать новое значение свойству, ни перечислить, ни удалить.
А сейчас я бы хотел обратить внимание на то, как с помощью Object.create() осуществляется прототипное наследование:
A =
a: 1
b: 2
B = Object.create A, c:
value: 3
enumerable: on
for own key, value of B
console.log " # {key}: # {value}" # c:3, a:1, b:2
В этом примере мы реализовали наследование свойств от объекта A, и добавили атрибут enumerable, для того чтобы свойство определенное в объекте B можно было перечислить в цикле for-of.
Хочу заметить, что наличие оператора own в инструкции for-of позволяет избежать использования метода hasOwnProperty в теле цикла и не допустить проверки свойств в цепочке прототипов объекта.
Более подробное описание этого оператора можно найти в моей предыдущей статье.
Если вас интересует как устроен метод create() изнутри, то в качестве задания на закрепление прочитанного привожу его реализацию согласно спецификации:
Object.create = (object, properties) ->
# 1. If Type(O) is not Object or Null throw a TypeError exception.
if typeof object is not 'object'
throw new TypeError "Object.create: # {object.toString()} is not an Object or Null"
# 2. Let obj be the result of creating a new object as if by the expression new Object()
# where Object is the standard built-in constructor with that name
object = new Object
# 3. Set the [[Prototype]] internal property of obj to O.
object.constructor:: = properties
# 4. If the argument Properties is present and not undefined, add own properties
# to obj as if by calling the standard built-in function Object.defineProperties
# with arguments obj and Properties.
if typeof props is not 'undefined'
Object.defineProperties object, props
# 5. Return obj.
object
Object.defineProperty(object, property, descriptor)
Метод Object.defineProperty() позволяет определить новое свойство объекта и/или модифицировать атрибуты существующего свойства. В качестве результата возвращается новый объект.
object — объект для которого нужно определить свойства
property — имя свойства, которое должно быть определено или модифицировано
descriptor — дескриптор
Рассмотрим пример:
object = {}
Object.defineProperty object, 'property'
value: 1
При этом, наверняка у вас уже возникли вопросы относительно использования свойства value. Сейчас мы постараемся осветить этот вопрос более подробно.
С новыми методами в ECMAScript 5 появились и новые термины, в частности пользовательский дескриптор (descriptor).
Дескриптор — это простой объект (plain object), позволяющий устанавливать значения и уровень доступа для собственных свойств, а также хранить их описание.
Иными словами дескрипторы позволяют контролировать атрибуты свойств.
До появления ECMAScript 5, нам были доступны куда более скромные инструменты для работы с атрибутами объектов: Object.prototype.propertyIsEnumerable() и Object.prototype.hasOwnProperty().
Формально дескрипторы делятся на три вида: дескрипторы данных (Data descriptors), дескрипторы доступа (Accessor descriptors) и общие дескрипторы (Generic descriptors).
Однако последний тип дескрипторов мы рассматривать не будем, т.к. их описание носит более теоретический характер на уровне реализации нежели практический.
Data descriptor
Это свойство, создается если дескриптор пустой или дескриптор имеет один из двух атрибутов: value или writable.
За проверку наличия атрибутов отвечает внутренний метод [[IsDataDescriptor]]:
IsDataDescriptor (Descriptor):
if Descriptor is undefined
return off
if !Descriptor.[[Value]] and !Descriptor.[[Writable]]
return off
return on
Помимо обязательных атрибутов, в дескрипторе данных есть опциональные атрибуты: configurable и enumerable.
{
configurable: false,
enumerable: false
}
Теперь давайте разберем все атрибуты по отдельности:
value — устанавливает значение свойства объекта.
writable — определяет возможность изменения свойства.
configurable — определяет возможность удаления свойства.
enumerable — определяет доступность перечисления свойства
Внутри реализации эти атрибуты имеют следующие имена: [[Value]], [[Writable]], [[Enumerable]], [[Configurable]].
По умолчанию, все атрибуты дескриптора данных имеют значение false, за исключением атрибута value, его значение установлено undefined.
Примечание: формально, в ECMAScript 3 пользовательские дескрипторы отсутствуют, но есть внутренние атрибуты DontEnum, ReadOnly и DontDelete.
Таким образом, мы сами можем устанавливать подобные атрибуты для свойств объекта:
Object.defineProperty {}, 'property'
value: 1
writable: on
enumerable: on
configurable: on
А теперь давайте представим что нам потребовалось расширить прототип Object:
Object::method = -> @
Так мы добавили новый метод в прототип Object и дальше можем его использовать.
В действительности же, такое решение имеет массу недостатков.
Во-первых, есть вероятность того, что метод с таким же именем может появится в будущем стандарте и наш метод его переопределит. Как вариант можно дать методу какое-то замысловатое название и спать спокойно.
Например:
Object::my_super_method = -> @
Маловероятно что метод с таким названием когда-нибудь попадет в спецификацию.
Во-вторых, все что мы добавляем в прототип Object попадает и в другие объекты:
Object::method = -> 1
list = []
do list.method # 1
В-третьих, такой метод будет иметь значение true атрибута enumerable:
Object::method = -> 1
object = {};
i for i of object # method
Предотвратить перечисление метода method в object можно можно так:
i for own i of object
Несмотря на то что это решение вполне работает, его можно усовершенствовать еще на этапе добавления метода в прототип Object:
Object.defineProperty Object::, 'method'
value: -> 1
enumerable: false
i for i of object #
Как видите, method не был перечислен в инструкции for-of.
Несмотря на то что расширение прототипов базовых типов не всегда является хорошей практикой, такая потребность иногда возникает. Поэтому со своей стороны, постарайтесь принять все разумные меры чтобы потом не наступить на собственные грабли, тем более если с вашим кодом будут работать другие люди.
Accessor descriptors
Дескриптор доступа — это такой дескриптор, в котором присутствует атрибут get или set. При этом допустимо присутствие этих атрибутов вместе. Однако недопустимо присутствие атрибутов value и writable:
IsDataDescriptor (Descriptor):
if Descriptor is undefined
return off
if !Descriptor.[[Get]] and !Descriptor.[[Set]]
return off
return on
После чего вызывается внутренний метод [[DefineOwnProperty]]
Внутри реализации атрибуты дескриптора имеют следующие имена: [[Get]] и [[Set]].
Атрибуты configurable и enumerable, также доступны и в дескрипторе данных:
property = 0
object = Object.defineProperty {}, 'property'
get: ->
property
set: (value) ->
property = value
configurable: on
enumerable: on
object.property = 1 # set
object.property # 1, get
Также хочу заметить, что не допустимо одновременное использование дескрипторов данных и доступа:
Object.defineProperty {}, 'property'
get: -> 1
value: 1
# TypeError: property descriptors must not specify a value or be writable when a getter or setter has been specified 'value: 1"
Как и в случае с Object.create(), наследование осуществляется по тому же принципу:
A =
a: 1
b: 2
B = Object.defineProperty A, 'c'
value: 3
enumerable: on
for own key, value of B
console.log " # {key}: # {value}" # a:1, b:2, c:3
Object.defineProperties(object, properties)
Позволяет определить новые свойства объекта и/или модифицировать атрибуты существующих свойств. В качестве результата возвращается новый объект. Иными словами Object.defineProperties() делает тоже самое что и Object.defineProperty() только со множеством свойств.
object = Object.defineProperties {},
a:
value: 1
enumerable: on
b:
value: 2
enumerable: on
for own key, value of object
console.log " # {key}: # {value}" # a:1, b:2
Если имена определяемых свойств совпадают с наследуемыми, то наследуемые переопределяются:
A =
a: 1
b: 2
object = Object.defineProperties {}
a:
value: 3
enumerable: on
b:
value: 4
enumerable: on
for own key, value of object
console.log " # {key}: # {value}" # a:3, b:4
При этом стоит помнить, что переопределить аксессор просто так не получится:
object = Object.defineProperty {}, 'property'
get: -> 0
Object.defineProperty object, 'property'
get: -> 1
object.property # 0
Очевидно чтобы переопределить свойство нужно поменять значение атрибута configurable:
object = Object.defineProperty {}, 'property'
get: -> 0
configurable: on
Object.defineProperty object, 'property'
get: -> 1
object.property # 1
К небольшому сожалению, IE не поддерживает этот метод ниже 9-й версии. Однако, не все так плохо, т.к. в 8-й версии есть метод Object.defineProperty(), на основе которого можно реализовать и Object.defineProperties():
Object.defineProperties = (object, properties) ->
type = (object) ->
Object::toString.call object is '[object Object]'
if (!type object and !type properties)
throw new TypeError 'Object.defineProperties(Object object, properties Object)'
if !Object.
return object;
for own key, value of properties
Object.defineProperty object, key, value
object;
Object.getOwnPropertyDescriptor(object, properties)
Этот метод позволяет получить доступ к свойствам дескриптора.
object = {}
Object.defineProperty object, 'property'
value: 1
writable: off
enumerable: off
configurable: on
Object.getOwnPropertyDescriptor object, 'property'
###
{
value: 1,
writable: true,
enumerable: true,
configurable: true
}
###
Т.к. свойства дескриптора являются внутренними свойствами ECMAScript, Object.getOwnPropertyDescriptor() единственный метод с помощью которого можно получить такую информацию. При этом стоить заметить, что Object.getOwnPropertyDescriptor() работает только с собственными свойствами:
Object.getOwnPropertyDescriptor {}, 'valueOf' # undefined
Потому что свойство toString унаследованное и находится в цепочке прототипов:
{}.hasOwnProperty 'valueOf' # false
'valueOf' of {} # true
Как уже я отмечал метод hasOwnProperty() в отличии от оператора of проверяет только собственные свойства объекта.
Обратиться к свойству valueOf напрямую, можно так:
{}.constructor::toString # function
Для большей ясности мне бы хотелось рассмотреть следующий пример:
object = {}
object.constructor::valueOf = 1
object.valueOf # 1
На первый взгляд, между явным добавлением свойства в объект и через объект-прототип нет никакого отличия и эти формы записи могут друг-друга заменять. Однако это не совсем так:
object = {}
object.valueOf = 1
object.constructor::valueOf = 2
object.toString # 1
Как видите, если свойство объекту задается явно (own property), то его значение «перекрывает» свойство заданное через объект-прототип. Точнее, значение свойства в объекте-прототипе никуда не пропадает, оно просто не резоливится и всегда доступно напрямую:
object = {}
object.toString = 1
object.constructor::valueOf = 2
object.toString # 1
object.constructor::valueOf # 2
'valueOf' of object # true
object.hasOwnProperty 'valueOf' # false
Иными словами, если свойство объекта на задано явно, поиск продолжается в цепочке прототипов:
object = {}
object.constructor::constructor::constructor::property = 1
object.property # 1
В качестве «домашнего» задания, предлагаю вам рассмотреть следующий пример:
fn = (x) -> x * x
fn 2 # 4
fn::constructor 4 # 8
Object.keys(object)
Возвращает массив, содержащий имена перечислимых собственных свойств объекта.
Рассмотрим типичный пример:
Object.keys
a: 1
b: 2
.length # 2
Довольно часто Object.keys применяется в связке с другими методами объекта Array:
object =
a: 1
b: 2
Object.keys(object).filter (i) -> i if object[i] > 1 # 2
Реализация метода Object.keys() довольна простая:
Object.keys = (object) -> i for own i of object
Однако я настоятельно рекомендую всегда использовать проверку входных аргументов.
После небольших поправок типичная реализация Object.keys() должна иметь примерно такой вид:
Object.keys = (object) ->
if Object::toString.call(object) is not '[object Object]'
throw new TypeError "Object.keys: # {object.toString()} is not an Object"
i for own i of object
Имплементация Object.keys() согласно спецификации:
Object.keys = (object) ->
# 1. If the Type(O) is not Object, throw a TypeError exception
if Object::toString.call(object) is not '[object Object]'
throw new TypeError "Object.keys: # {object.toString()} is not an Object"
# 2. Let count be the number of own enumerable properties of O
count = Object.getOwnPropertyNames(object).length
# 3. Let array be the result of creating a new Object as if by the expression new Array(n)
# where Array is the standard built-in constructor with that name
array = new Array count
# 4. Let index be 0
index = 0;
# 5. For each own enumerable property of O whose name String is P
for own property of object
if !object.propertyIsEnumerable property
continue
# a. Call the [[DefineOwnProperty]] internal method of array with arguments
# ToString(index), the PropertyDescriptor
# {[[Value]]: P, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false
# b. Increment index by 1
Object.defineProperty array, index++,
value: property
writable: on
enumerable: on
configurable: on
# 6. Return array
array
Object.getOwnPropertyNames(object)
Возвращает массив, содержащий имена собственных свойств объекта.
В отличии от Object.keys(object) этот метод не учитывает значение атрибута enumerable:
object =
a: 1
b: 2
Object.defineProperty object, 'с'
value: 3,
enumerable: off,
Object.keys(object).length # 2
Object.getOwnPropertyNames(object).length # 3
Имплементация Object.getOwnPropertyNames() согласно спецификации:
Object.getOwnPropertyNames = (object) ->
# 1. If the Type(O) is not Object, throw a TypeError exception
if Object::toString.call(object) is not '[object Object]'
throw new TypeError "Object.getOwnPropertyNames: # {object.toString()} is not an Object"
# 2. Let array be the result of creating a new Object as if by the expression new Array(n)
# where Array is the standard built-in constructor with that name
array = new Array
# 3. Let index be 0
index = 0;
# 4. For each named own property P of O
for own name of object
# a. Let name be the String value that is the name of P.
# b. Call the [[DefineOwnProperty]] internal method of array with arguments
# ToString(n), the PropertyDescriptor {[[Value]]: name, [[Writable]]: true,
# [[Enumerable]]: true, [[Configurable]]: true}, and false.
# c. Increment n by 1.
Object.defineProperty array, index++,
value: name
writable: on
enumerable: on
configurable: on
# console.log array
# 5. Return array
array
Object.getPrototypeOf(object)
Возвращает ссылку на свойство [[Prototype]] заданного объекта.
object = {}
Object.getPrototypeOf(object).property = 1
object.property # 1
Реализация метода:
Object.getPrototypeOf = (object) ->
if Object::toString.call(object) is not '[object Object]'
throw new TypeError "Object.getPrototypeOf: # {object.toString()} is not an Object"
object.__proto__ or object.constructor::
Object.preventExtensions(object)
Блокирует расширение объекта.
object = a: 1
Object.preventExtensions object
object.b = 1
'b' of object # false
Object.getOwnPropertyDescriptor object, 'a'
# { configurable: true, enumerable: true, value: 1, writable: true }
Особое внимание хочу обратить на то, что попытка расширить заблокированный объект с помощью методов: Object.defineProperty и Object.defineProperties приведет к выбросу исключения типа TypeError!
object = {}
Object.preventExtensions object
Object.defineProperty object, 'property'
value: 1
# TypeError: Object.defineProperties(object, 'property', ...) is not extensible
Также, TypeError всегда выбрасывается в строгом режиме:
do ->
'use strict'
object = {}
Object.preventExtensions object
object.property = 1
# "TypeError: object.property is not extensible
В этом случае, будьте особо внимательны, потому что если не перехватить TypeError, приложение прекратит свою работу!
Object.isExtensible(object)
Определяет доступность расширения объекта. Возвращает булево значение.
object = {}
Object.preventExtensions object
Object.isExtensible object # false
Object.seal(object)
Опечатывает свойства объекта.
Object.seal = Object.preventExtensions + {[[Configurable]]: off}
Рассмотрим пример:
object = property: 1
Object.seal object
delete object.property # false
Object.getOwnPropertyDescriptor object, 'property'
# { configurable: false, enumerable: true, value: 1, writable: true }
Object.seal() устанавливает значение false атрибуту configurable для всех свойств объекта:
Object.getOwnPropertyDescriptor(Object.seal property: 1, 'property').configurable # false
Стоит заметить, что опечатывается не сам объект, а только его свойства:
object = {}
Object.defineProperty, 'property'
value: 1
Object.seal object
delete object.property # false
delete object # false
object.property # 1
object # Object
Как видите, удалить object не получится, потому что оператор delete [[Delete]] удаляет только свойства объекта.
Это происходит потому что на этапе трансляции в JavaScript код все переменные предваряют ключевым словом var.
Чтобы удалить сам объект нужно сделать его свойством глобального объекта global:
global.object = {} # или @object, если текущий контекст глобальный
Object.seal object
delete object
object # ReferenceError: object is not defined 'object'
Однако подобное определение объектов не является хорошей практикой, потому что object попадает в глобальную область видимости:
do ->
global.object = {}
object # Object
Если вы хотите удалить объект, просто присвойте ему неопределенное значение:
object = {}
object = undefined
object # undefined
Если в какой-то момент вам вновь потребуется обратиться к этой переменной, то следует использовать оператор null, который будет сигнализировать что переменная когда-то содержала ссылку на объект.
1. B CoffeeScript отсутствует оператор void, вместо него следует использовать undefined, который транслируется в void 0.
2. На счет удаления предопределенных свойств нет четкого правила.
Hапример, вполне допустимо удалить свойство now объекта Date, но в тоже время, нельзя удалить метод call объекта Function:delete Date.now # true Date.now # undefined delete Function.call Function.call # [Function: call]
При этом, допускается удалить сам конструктор, в т.ч. и Object ([Configurable]]: true):
delete Object # true typeof Object # undefined Object # ReferenceError: Object is not defined
2. Я не стал рассматривать оператор delete более подробно, т.к. это довольно большая тема, чтобы ее включать в эту статью. Если вас все-таки интересует этот вопрос, то рекомендую прочитать статью kangax'a Understanding delete
Помимо того что Object.seal() запрещает удаление свойств объекта, но еще и блокирует добавление новых:
object = {}
Object.seal object
object.property = 1
object.property # undefined
Примечание: серверная реализация выбросит TypeError!
Однако опечатывание не распространяется на модификацию значений свойств объекта:
object = property: 1
Object.seal object
object.property = 2
object.property # 1
Как и в случае с Object.preventExtensions(), попытка модифицировать свойства объекта в строгом режиме или с помощью Object.defineProperty/Object.defineProperties приведет к выбросу исключения типа TypeError!
Имплементация Object.seal() согласно спецификации:
Object.seal = (object) ->
# 1. If Type(O) is not Object throw a TypeError exception
if Object::toString.call(object) is not '[object Object]'
throw new TypeError "Object.seal: # {object} is not callable!"
# 2. For each named own property name P of O,
Object.getOwnPropertyNames(object).forEach (property) ->
# a. Let desc be the result of calling the [[GetOwnProperty]] internal method of O with P
__desc__ = Object.getOwnPropertyDescriptor object, property
# b. If desc.[[Configurable]] is true, set desc.[[Configurable]] to false.
if __desc__.configurable is on
__desc__.configurable = off
# c. Call the [[DefineOwnProperty]] internal method of O with P, desc, and true as arguments
Object.defineProperty object, property, __desc__
# 3. Set the [[Extensible]] internal property of O to false
# 4. Return O.
Object.preventExtensions object
Object.isSealed(object)
Определяет запечатан ли объект. Возвращает булево значение.
object = {}
Object.seal object
Object.isSealed object # true
Если пустой объект сделать не расширяемым, то он станет запечатанным:
object = {}
Object.preventExtensions object
Object.isSealed object # true
Однако, если теперь добавить свойство в объект, то он перестанет быть запечатанным:
object = property: 1
<source lang="coffeescript">
Object.preventExtensions object
Object.isSealed object # false
Имплементация Object.isSealed() согласно спецификации:
Object.isSealed = (object) ->
# 1. If Type(O) is not Object throw a TypeError exception.
if Object::toString.call(object) is not '[object Object]'
throw new TypeError "Object.isSealed: # {object} is not callable!"
# 2. For each named own property name P of O then
Object.getOwnPropertyNames(object).forEach (property) ->
# a. Let desc be the result of calling the [[GetOwnProperty]] internal method of O with P.
__desc__ = Object.getOwnPropertyDescriptor object, property
# b. If desc.[[Configurable]] is true, then return false.
if __desc__.configurable is on
return off
# 3. If the [[Extensible]] internal property of O is false, then return true.
# 4. Otherwise, return false.
if !Object.isExtensible(object) then on else off
Object.freeze(object)
Замораживает объект.
Object.freeze = Object.preventExtensions + Object.seal + {[[Writable]]: off}
Иными словами Object.freeze() предотвращает добавление новых свойств в объект, модификацию и удаление существующих.
object = a: 1
object.a = 0 # false нельзя модифицировать свойства
object.b = 0 # false нельзя добавлять новые свойства
delete object.a # false нельзя удалять свойства
Object.getOwnPropertyDescriptor object, 'a'
# { configurable: false, enumerable: true, value: 1, writable: false}
Т.к. мы уже подробно рассмотрели Object.preventExtensions() и Object.seal() нет смысла повторяться.
Единственное на что бы мне бы хотелось обратить ваше внимание, так это на глубину «замораживания»:
object =
property:
internal: 1
Object.freeze object
object.property = 0 # false
object.property.internal = 0 # true
Object.getOwnPropertyDescriptor(object.property, 'internal').writable # true
Как видите, блокируется только дочерние свойства первого уровня!
На самом деле, в том, что в ECMASctipt 5 отсутствует метод глубокого замораживания ничего страшного нет. Попробуем реализовать Object.deepFreeze() сами:
Object.deepFreeze = (object) ->
isObject = (value) ->
Object::toString.call(value) is '[object Object]'
if !isObject object
throw new TypeError "Object.deepFreeze: # {object} is not callable!"
for own key, value of object
if isObject(value) and !Object.isFrozen value
Object.deepFreeze(value)
Object.freeze object
object =
property:
internal: 1
Object.deepFreeze object
Object.getOwnPropertyDescriptor(object.property, 'internal').writable # false
Имплементация Object.freeze() согласно спецификации:
Object.freeze = (object) ->
# 1. If Type(O) is not Object throw a TypeError exception
if Object::toString.call(object) is not '[object Object]'
throw new TypeError "Object.freeze: # {object} is not callable!"
# 2. For each named own property name P of O,
Object.getOwnPropertyNames(object).forEach (property) ->
# a. Let desc be the result of calling the [[GetOwnProperty]] internal method of O with P
__desc__ = Object.getOwnPropertyDescriptor object, property
# b. If IsDataDescriptor(desc) is true, then
# If desc.[[Writable]] is true, set desc.[[Writable]] to false
if __desc__.value and __desc__.writable is on
__desc__.writable = off
# c. If desc.[[Configurable]] is true, set desc.[[Configurable]] to false
if __desc__.configurable is on
__desc__.configurable = off
# d. Call the [[DefineOwnProperty]] internal method of O with P, desc, and true as arguments
Object.defineProperty object, property, __desc__
# 3. Set the [[Extensible]] internal property of O to false
# 4. Return O.
Object.preventExtensions object
Object.isFrozen(object)
Определяет заморожен ли объект.
object =
property: 1
Object.isFrozen object # false
Object.freeze object
Object.isFrozen object # true
Имплементация Object.isFrozen() согласно спецификации:
Object.isFrozen = (object) ->
# 1. If Type(O) is not Object throw a TypeError exception.
if Object::toString.call(object) is not '[object Object]'
throw new TypeError "Object.isFrozen: # {object} is not callable!"
# 2. For each named own property name P of O then
Object.getOwnPropertyNames(object).forEach (property) ->
# a. Let desc be the result of calling the [[GetOwnProperty]] internal method of O with P.
__desc__ = Object.getOwnPropertyDescriptor object, property
# b. If IsDataDescriptor(desc) is true then
# i. If desc.[[Writable]] is true, return false.
if __desc__.value and __desc__.writable is on
return off
# c. If desc.[[Configurable]] is true, then return false.
if __desc__.configurable is on
return off
# 3. If the [[Extensible]] internal property of O is false, then return true.
# 4. Otherwise, return false.
if !Object.isExtensible(object) then on else off
Object.prototype.hasOwnProperty(property)
Определяет является ли свойство объекта собственным. Возвращает логическое значение.
object =
property: 1
object.hasOwnProperty 'property' # true
object.hasOwnProperty 'toString' # false
Если нужно проверить не только собственные свойства, но и унаследованные, в этом случае, следует использовать оператор of, который анализирует цепочку прототипов:
object =
property: 1
'property' of object # true
'toString' of object # true
Метод .hasOwnProperty() особенно полезен в связке с циклом for-of:
Object::inherited = 0
object =
property: 1
(i for i of object) # [inherited, property]
for i of object
i if object.hasOwnProperty i # property
Как видите, метод hasOwnProperty() может гарантировать что в перечисление не попадут унаследованные свойства.
Тем не менее, есть вероятность того, что в перечисляемом объекте уже может присутствовать свойство с именем hasOwnProperty:
Object::inherited = ->
object =
own: 1
hasOwnProperty: -> @
for i of object
i if object.hasOwnProperty i
# inherited, own, hasOwnProperty
Безусловно это не тот результат который мы хотели получить. Так как же быть?
Разрешить такую ситуацию можно очень просто, для этого достаточно вызвать метод hasOwnProperty относительно Object.prototype в контексте требуемого объекта:
Object::inherited = ->
object =
own: 1
hasOwnProperty: -> @
for i of object
i if Object::hasOwnProperty.call object, i
# own, hasOwnProperty
Такой способ вызова метода .hasOwnProperty является наиболее предпочтительным. Во-первых, это разрешает возможный конфликт имен, а во-вторых, обеспечивает более быструю фильтрацию, за счет прямой ссылки на метод.
Как уже упоминалось ранее, если свойство объекта не может быть найдено среди собственных, то поиск продолжается далее по цепочке прототипов.
Это значит что неявно в перечисление попадут и унаследованные свойства. А т.к. метод hasOwnProperty() является унаследованным, его поиск будет осуществляться как минимум среди следующих свойств:
Object.getOwnPropertyNames Object.prototype
[
'toString',
'toLocaleString',
'hasOwnProperty',
'valueOf',
'constructor',
'propertyIsEnumerable',
'isPrototypeOf',
]
В зависимости от реализации этот список может быть расширен. К примеру для движков V8, Presto, Gecko это будут следующие свойства:
'__lookupGetter__',
'__defineGetter__',
'__defineSetter__',
'__lookupSetter__'
В Gecko еще дополнительно будут свойств watch и unwatch.
Поэтому, чтобы не тратить ресурсы на поиск нужного свойства, всегда старайтесь явно указывать его местоположение и выносить определение переменных и ссылок на объекты за пределы тела цикла:
object = {}
own = Object::hasOwnProperty
for i of object
i if own.call object, i
Так как метод hasOwnProperty() довольно часто используется совместно с инструкцией for-of, в CoffeeScript предусмотрен специальный оператор own:
alert i for own i of object
Результат трансляции:
var i, __hasProp = {}.hasOwnProperty;
for (i in object) {
if (!__hasProp.call(object, i))
continue;
alert(i);
}
Более подробное описание оператора own и инструкции for-of смотрите в статье: CoffeeScript: Подробное руководство по циклам.
Напомню что для получения имен собственных свойств объекта есть методы Object.keys() и Object.getOwnPropertyNames(), которые больше подходит для этой задачи чем инструкция for-own-of.
Object.prototype.isPrototypeOf(object)
Проверят находится ли заданный объект в цепи прототипов. Возвращает логическое значение.
object = {}
Object::isPrototypeOf object # true
Object.isPrototypeOf object # false
Function::isPrototypeOf Object # true
Function::isPrototypeOf (new ->).constructor # true
fn = ->
instance = new fn
fn::.isPrototypeOf instance # true
Object.prototype.propertyIsEnumerable(object)
Проверяет является ли указанное свойство перечисляемым. Возвращает логическое значение.
object = property: 1
object.propertyIsEnumerable 'property' # true
object.propertyIsEnumerable 'toString' # false
object.propertyIsEnumerable 'prototype' # false
object.propertyIsEnumerable 'constructor' # false
list = ['']
list.propertyIsEnumerable 0 # true
list.propertyIsEnumerable 'length' # false
Заключение:
Мы полностью рассмотрели все методы объекта Object и их внутреннюю реализацию согласно спецификации. К сожалению реализовать многие методы из ECMAScript 5 для браузеров, которые их не поддерживают довольно проблематично. Частичную реализацию можно найти у меня на githab'e или тут
Eсли вы будете транслировать исходный код в JavaScript, то вам также следует ознакомиться с этой таблицей совместимости.
Автор: monolithed