Во время каникул я потратил некоторое время на изучение событий в ES6 (следующая версия JavaScript). При рассмотрении некоторых предложений, таких как определения минимального класса , Proxy API и Weak Maps ; Я заметил, что большинство этих улучшений широко используют функции управления объектами, представленные в ES5 (т. Е. ECMAScript5 — текущий стандарт JavaScript).
Одним из основных направлений ES5 было улучшение структуры объектов JavaScript и манипулирования ими. Представленные в нем функции имеют большой смысл, особенно если вы работаете с большими и сложными приложениями.
Мы очень неохотно принимаем функции ES5, особенно из-за проблем совместимости браузера. Мы редко видим производственный код, который использует эти функции. Тем не менее, все современные браузеры (т.е. IE9, FF4, Opera 12 и Chrome) имеют механизмы JavaScript, которые реализуют стандарт ES5. Кроме того, функции ES5 можно использовать в проектах на базе Node.js. Поэтому я думаю, что было бы целесообразно вернуться к объектным функциям ES5 и посмотреть, как они могут быть полезны в реальных сценариях.
Данные и свойства доступа
ES5 представляет два вида свойств объекта — данные и средства доступа. Свойство данных непосредственно отображает имя на значение (например. Целое число, строка, логический, массив, объект или функция). Свойство аксессора отображает имя к определенному геттеру и / или функции сеттера.
var square = { length: 10, get area() { return this.length * this.length }, set area(val) { this.length = Math.sqrt(val) } }
Здесь мы определили square
объект length
как свойство данных и свойство area
доступа.
> square.length 10 > square.area 100 > square.area = 400 400 > square.length 20
Когда мы получаем доступ к area
свойству, его получатель вычисляет и возвращает значение в терминах length
свойства. Кроме того, когда мы присваиваем значение area
, его функция установки меняет length
свойство.
Дескриптор свойства
ES5 позволяет вам более детально контролировать свойства, определенные в объекте. С каждым свойством связана специальная коллекция атрибутов, известная как дескриптор свойства .
Вы можете проверить атрибуты, связанные со свойством, вызвав Object.getOwnPropertyDescriptor
метод.
> Object.getOwnPropertyDescriptor(square, "length") { configurable: true enumerable: true value: 20 writable: true } > Object.getOwnPropertyDescriptor(square, "area") { configurable: true enumerable: true get: function area() { return this.length * this.length } set: function area(val) { this.length = Math.sqrt(val) } }
Как видно из приведенных выше двух примеров — value
и writeable
атрибуты определены только для дескрипторов свойств данных , а get
и / или set
определены для дескрипторов свойств аксессоров . И атрибуты, configurable
и enumerable
атрибуты применяются к любому виду дескриптора свойства.
writable
Атрибут указать , является ли значение может быть присвоено свойству. Если writable
есть false
, свойство становится доступным только для чтения. Как следует из названия, configurable
указывает, можно ли настраивать атрибуты свойства, а также можно ли удалить свойство из объекта (используя delete
операцию). enumerable
Атрибут определяет , должно ли свойство быть видимым в for..in
петле или Object.keys
методах.
Мы можем изменить эти атрибуты в дескрипторе свойства , используя Object.defineProperty
метод.
Object.defineProperty(square, "length", { value: 10, writable: false });
Это сделает length
свойство square
доступным только для чтения и постоянно установленным в 10
.
> square.length 10 > square.area = 400 400 > square.length 10 > square.area 100
Взломостойкие объекты
В некоторых случаях вам нужно сохранять объекты в их текущем состоянии во время выполнения без каких-либо дополнительных расширений или модификаций свойств. ES5 предоставляет три уровня управления, которые вы можете применить к объектам.
Вызов preventExtensions
метода сделает объект нерасширяемым. Это означает, что никакие дополнительные свойства не могут быть определены для объекта.
> Object.preventExtensions(square); > Object.defineProperty(square, "text", { value: "hello" }); TypeError: Cannot define property:text, object is not extensible.
Запечатывание объекта предотвратит как определение новых свойств, так и удаление существующих свойств в объекте.
> Object.seal(square); > delete square.length false
Если мы сделаем еще один шаг и остановим объект, это также не позволит изменить существующие значения свойств в объекте. В этот момент весь объект эффективно становится постоянным.
> Object.freeze(square); > square.length = 20 20 > square.length 10
Вы можете использовать методы Object.isSealed
, Object.isFrozen
и Object.isExtensible
программно проверить состояние объекта.
Даже если объект защищен, его прототип все равно можно расширить. Проверьте следующий пример:
var obj = Object.create({}, { onlyProp: { value: true } }); Object.preventExtensions(obj); var proto = Object.getPrototypeOf(obj); proto.anotherProp = true; > obj.anotherProp true
Перечисления
Часто мы используем объекты JavaScript в качестве ассоциативных массивов или коллекций. В таких случаях мы склонны использовать for...in
циклы для перечисления свойств. Однако цикл будет проходить по всем перечислимым свойствам, доступным в цепочке прототипов объекта, что приведет к нежелательным результатам.
Чтобы избежать таких побочных эффектов, JSLint предлагает вручную проверить, определено ли данное свойство в объекте.
for (name in object) { if (object.hasOwnProperty(name)) { .... } }
ES5 предоставляет Object.keys
метод, который бы возвращал массив собственных перечисляемых свойств объекта.
Мы можем использовать этот метод для безопасной итерации по списку свойств:
Object.keys(obj).forEach( function(key) { console.log(key); });
Примечание. Array.forEach также является новой функцией, представленной в ES5.
наследование
Мы знаем, что JavaScript обеспечивает повторное использование поведения с точки зрения наследования прототипов . Тем не менее, отсутствие прямого механизма создания нового объекта с использованием другого объекта в качестве прототипа, было одной из любимых мозгов в языке.
Стандартный способ создания нового объекта — использование функции конструктора. Таким образом, вновь созданный объект унаследует прототип функции конструктора.
var Person = function(first_name, last_name) { this.first_name = first_name; this.last_name = last_name; } Person.prototype = { say: function(msg) { return this.first_name + " says " + msg; } } var ron = new Person("Ron", "Swanson");
Если кто-то вызывает функцию конструктора без new
оператора, это может привести к нежелательным побочным эффектам во время выполнения. Кроме того, нет семантической связи между функцией конструктора и ее прототипом, что может привести к путанице при попытке понять код.
Для тех, кто предпочитает использовать альтернативный синтаксис, ES5 предоставляет Object.create
метод. Он принимает объект-прототип и дескриптор свойства в качестве аргументов.
Вот альтернативная реализация, которая может быть использована для создания Person
объектов с использованием шаблонаObject.create
и модуля .
var Person = (function() { var proto = { say: function(msg) { return this.first_name + " says " + msg; } } return { init: function(first_name, last_name) { return Object.create(proto, { first_name: { value: first_name, enumerable: true }, last_name: { value: last_name, enumerable: true } }); } } })(); var ron = Person.init("Ron", "Swanson");
Однако по сравнению с функциями конструктора использование Object.create
может быть значительно медленным . Поэтому выберите, какую реализацию вы хотите использовать, в зависимости от контекста и требований.
Даже если вы предпочитаете использовать функции конструктора, Object.create
они пригодятся, если вы хотите иметь несколько уровней наследования.
var Person = function(first_name, last_name) { this.first_name = first_name; this.last_name = last_name; }; Person.prototype = { say: function(msg) { return this.first_name + " says " + msg; } }; var Employee = function(first_name, last_name) { Person.call(this, first_name, last_name); } Employee.prototype = Object.create(Person.prototype, { department: { value: "", enumerable: true }, designation:{ value: "", enumerable: true } }); var ron = new Employee("Ron", "Swanson");
Мы расширили Person
прототип, чтобы создать прототип Employee
.
Клонирование объектов
Наконец, давайте посмотрим, как создать мелкий клон объекта, используя методы объекта ES5.
var clone = function(obj) { // create clone using given object's prototype var cloned_obj = Object.create(Object.getPrototypeOf(obj)); // copy all properties var props = Object.getOwnPropertyNames(obj); props.forEach(function(prop) { var propDescriptor = Object.getOwnPropertyDescriptor(obj, prop); Object.defineProperty(cloned_obj, prop, propDescriptor); }); return cloned_obj; }
Здесь мы извлекаем прототип данного объекта и используем его для создания клона. Затем мы просматриваем все свойства, определенные в объекте (включая не перечисляемые свойства), и копируем дескрипторы их свойств в клон.
Дальнейшее чтение
Если вы хотите узнать больше об объектах JavaScript и о том, как ими манипулировать, я бы порекомендовал вам ознакомиться со следующими ресурсами: