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