Как найти значение свойств объектов

Материал, перевод которого мы сегодня публикуем, посвящён исследованию объектов — одной из ключевых сущностей JavaScript. Он рассчитан, преимущественно, на начинающих разработчиков, которые хотят упорядочить свои знания об объектах.

Объекты в JavaScript представляют собой динамические коллекции свойств, которые, кроме того, содержат «скрытое» свойство, представляющее собой прототип объекта. Свойства объектов характеризуются ключами и значениями. Начнём разговор о JS-объектах с ключей.

Ключи свойств объектов

Ключ свойства объекта представляет собой уникальную строку. Для доступа к свойствам можно использовать два способа: обращение к ним через точку и указание ключа объекта в квадратных скобках. При обращении к свойствам через точку ключ должен представлять собой действительный JavaScript-идентификатор. Рассмотрим пример:

let obj = {
  message : "A message"
}
obj.message //"A message"
obj["message"] //"A message"

При попытке обращения к несуществующему свойству объекта сообщения об ошибке не появится, но возвращено будет значение undefined:

obj.otherProperty //undefined

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

let french = {};
french["merci beaucoup"] = "thank you very much";

french["merci beaucoup"]; //"thank you very much"

Если в качестве ключей используются нестроковые значения, они автоматически преобразуются к строкам (с использованием, если это возможно, метода toString()):

et obj = {};
//Number
obj[1] = "Number 1";
obj[1] === obj["1"]; //true
//Object
let number1 = {
  toString : function() { return "1"; }
}
obj[number1] === obj["1"]; //true

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

Значения свойств объектов

Свойства объекта могут быть примитивными значениями, объектами или функциями.

▍Объект как значение свойства объекта

Объекты можно помещать в другие объекты. Рассмотрим пример:

let book = {
  title : "The Good Parts",
  author : {
    firstName : "Douglas",
    lastName : "Crockford"
  }
}
book.author.firstName; //"Douglas"

Подобный подход можно использовать для создания пространств имён:

let app = {};
app.authorService = { getAuthors : function() {} };
app.bookService = { getBooks : function() {} };

▍Функция как значение свойства объекта

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

У этого ключевого слова, однако, могут быть разные значения, что зависит от того, как именно была вызвана функция. Здесь можно почитать о ситуациях, в которых this теряет контекст.

Динамическая природа объектов

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

let obj = {};
obj.message = "This is a message"; //добавление нового свойства
obj.otherMessage = "A new message"; // добавление нового свойства
delete obj.otherMessage; //удаление свойства

Объекты как ассоциативные массивы

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

Прототипы объектов

У объектов есть «скрытая» ссылка, __proto__, указывающая на объект-прототип, от которого объект наследует свойства.

Например, объект, созданный с помощью объектного литерала, имеет ссылку на Object.prototype:

var obj = {};
obj.__proto__ === Object.prototype; //true

▍Пустые объекты

Как мы только что видели, «пустой» объект, {}, на самом деле, не такой уж и пустой, так как он содержит ссылку на Object.prototype. Для того чтобы создать по-настоящему пустой объект, нужно воспользоваться следующей конструкцией:

Object.create(null)

Благодаря этому будет создан объект без прототипа. Такие объекты обычно используют для создания ассоциативных массивов.

▍Цепочка прототипов

У объектов-прототипов могут быть собственные прототипы. Если попытаться обратиться к свойству объекта, которого в нём нет, JavaScript попытается найти это свойство в прототипе этого объекта, а если и там нужного свойства не окажется, будет сделана попытка найти его в прототипе прототипа. Это будет продолжаться до тех пор, пока нужное свойство не будет найдено, или до тех пор, пока не будет достигнут конец цепочки прототипов.

Значения примитивных типов и объектные обёртки

JavaScript позволяет работать со значениями примитивных типов как с объектами, в том смысле, что язык позволяет обращаться к их свойствам и методам.

(1.23).toFixed(1); //"1.2"
"text".toUpperCase(); //"TEXT"
true.toString(); //"true"

При этом, конечно, значения примитивных типов объектами не являются.

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

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

Встроенные прототипы

Объекты-числа наследуют свойства и методы от прототипа Number.prototype, который является наследником Object.prototype:

var no = 1;
no.__proto__ === Number.prototype; //true
no.__proto__.__proto__ === Object.prototype; //true

Прототипом объектов-строк является String.prototype. Прототипом объектов-логических значений является Boolean.prototype. Прототипом массивов (которые тоже являются объектами), является Array.prototype.

Функции в JavaScript тоже являются объектами, имеющими прототип Function.prototype. У функций есть методы наподобие bind(), apply() и call().

Все объекты, функции, и объекты, представляющие значения примитивных типов (за исключением значений null и undefined) наследуют свойства и методы от Object.prototype. Это ведёт к тому, что, например, у всех них есть метод toString().

Расширение встроенных объектов с помощью полифиллов

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

▍Использование полифиллов

Например, существует полифилл для метода Object.assign(). Он позволяет добавить в Object новую функцию в том случае, если она в нём недоступна.

То же самое относится и к полифиллу Array.from(), который, в том случае, если в объекте Array нет метода from(), оснащает его этим методом.

▍Полифиллы и прототипы

С помощью полифиллов новые методы можно добавлять к прототипам объектов. Например, полифилл для String.prototype.trim() позволяет оснастить все строковые объекты методом trim():

let text = "   A text  ";
text.trim(); //"A text"

Полифилл для Array.prototype.find() позволяет оснастить все массивы методом find(). Похожим образом работает и полифилл для Array.prototype.findIndex():

let arr = ["A", "B", "C", "D", "E"];
arr.indexOf("C"); //2

Одиночное наследование

Команда Object.create() позволяет создавать новые объекты с заданным объектом-прототипом. Эта команда используется в JavaScript для реализации механизма одиночного наследования. Рассмотрим пример:

let bookPrototype = {
  getFullTitle : function(){
    return this.title + " by " + this.author;
  }
}
let book = Object.create(bookPrototype);
book.title = "JavaScript: The Good Parts";
book.author = "Douglas Crockford";
book.getFullTitle();//JavaScript: The Good Parts by Douglas Crockford

Множественное наследование

Команда Object.assign() копирует свойства из одного или большего количества объектов в целевой объект. Её можно использовать для реализации схемы множественного наследования. Вот пример:

let authorDataService = { getAuthors : function() {} };
let bookDataService = { getBooks : function() {} };
let userDataService = { getUsers : function() {} };
let dataService = Object.assign({},
 authorDataService,
 bookDataService,
 userDataService
);
dataService.getAuthors();
dataService.getBooks();
dataService.getUsers();

Иммутабельные объекты

Команда Object.freeze() позволяет «заморозить» объект. В такой объект нельзя добавлять новые свойства. Свойства нельзя удалять, нельзя и изменять их значения. Благодаря использованию этой команды объект становится неизменяемым или иммутабельным:

"use strict";
let book = Object.freeze({
  title : "Functional-Light JavaScript",
  author : "Kyle Simpson"
});
book.title = "Other title";//Ошибка: Cannot assign to read only property 'title'

Команда Object.freeze() выполняет так называемое «неглубокое замораживание» объектов. Это означает, что объекты, вложенные в «замороженный» объект, можно изменять. Для того чтобы осуществить «глубокую заморозку» объекта, нужно рекурсивно «заморозить» все его свойства.

Клонирование объектов

Для создания клонов (копий) объектов можно использовать команду Object.assign():

let book = Object.freeze({
  title : "JavaScript Allongé",
  author : "Reginald Braithwaite"
});
let clone = Object.assign({}, book);

Эта команда выполняет неглубокое копирование объектов, то есть — копирует только свойства верхнего уровня. Вложенные объекты оказываются, для объектов-оригиналов и их копий, общими.

Объектный литерал

Объектные литералы дают разработчику простой и понятный способ создания объектов:

let timer = {
  fn : null,
  start : function(callback) { this.fn = callback; },
  stop : function() {},
}

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

timer.fn;//null 
timer.start = function() { console.log("New implementation"); }

Метод Object.create()

Решить две вышеозначенные проблемы можно благодаря совместному использованию методов Object.create() и Object.freeze().

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

let timerPrototype = Object.freeze({
  start : function() {},
  stop : function() {}
});
let timer = Object.create(timerPrototype);
timer.__proto__ === timerPrototype; //true

Если прототип защищён от изменений, объект, являющийся его наследником, не сможет изменять свойства, определённые в прототипе. Теперь методы start() и stop() переопределить нельзя:

"use strict";
timer.start = function() { console.log("New implementation"); } //Ошибка: Cannot assign to read only property 'start' of object

Конструкцию Object.create(timerPrototype) можно использовать для создания множества объектов с одним и тем же прототипом.

Функция-конструктор

В JavaScript существуют так называемые функции-конструкторы, представляющие собой «синтаксический сахар» для выполнения вышеописанных действий по созданию новых объектов. Рассмотрим пример:

function Timer(callback){
  this.fn = callback;
}
Timer.prototype = {
  start : function() {},
  stop : function() {}
}
function getTodos() {}
let timer = new Timer(getTodos);

В качестве конструктора можно использовать любую функцию. Конструктор вызывают с использованием ключевого слова new. Объект, созданный с помощью функции-конструктора с именем FunctionConstructor, получит прототип FunctionConstructor.prototype:

let timer = new Timer();
timer.__proto__ === Timer.prototype;

Тут, для предотвращения изменения прототипа, опять же, можно прототип «заморозить»:

Timer.prototype = Object.freeze({
  start : function() {},
  stop : function() {}
});

▍Ключевое слово new

Когда выполняется команда вида new Timer(), производятся те же действия, которые выполняет представленная ниже функция newTimer():

function newTimer(){
  let newObj = Object.create(Timer.prototype);
  let returnObj = Timer.call(newObj, arguments);
  if(returnObj) return returnObj;
    
  return newObj;
}

Здесь создаётся новый объект, прототипом которого является Timer.prototype. Затем вызывается функция Timer, устанавливающая поля для нового объекта.

Ключевое слово class

В ECMAScript 2015 появился новый способ выполнения вышеописанных действий, представляющий собой очередную порцию «синтаксического сахара». Речь идёт о ключевом слове class и о соответствующих конструкциях, связанных с ним. Рассмотрим пример:

class Timer{
  constructor(callback){
    this.fn = callback;
  }
  
  start() {}
  stop() {}  
}
Object.freeze(Timer.prototype);

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

let timer= new Timer();
timer.__proto__ === Timer.prototype;

Использование классов не делает прототипы неизменными. Их, если это нужно, придётся «замораживать» так же, как мы это уже делали:

Object.freeze(Timer.prototype);

Наследование, основанное на прототипах

В JavaScript объекты наследуют свойства и методы от других объектов. Функции-конструкторы и классы — это «синтаксический сахар» для создания объектов-прототипов, содержащих все необходимые методы. С их использованием создают новые объекты являющиеся наследниками прототипа, свойства которого, специфичные для конкретного экземпляра, устанавливают с помощью функции-конструктора или с помощью механизмов класса.

Хорошо было бы, если бы функции-конструкторы и классы могли бы автоматически делать прототипы неизменными.

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

▍Проблема отсутствия встроенных механизмов инкапсуляции

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

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

function logProperty(name){
  console.log(name); //имя свойства
  console.log(obj[name]); //значение свойства
}
Object.keys(obj).forEach(logProperty);

Существует один паттерн, имитирующий приватные свойства, полагающийся на то, что разработчики не будут обращаться к тем свойствам, имена которых начинаются с символа подчёркивания (_):

class Timer{
  constructor(callback){
    this._fn = callback;
    this._timerId = 0;
  }
}

Фабричные функции

Инкапсулированные объекты в JavaScript можно создавать с использованием фабричных функций. Выглядит это так:

function TodoStore(callback){
    let fn = callback;
    
    function start() {},
    function stop() {}
    
    return Object.freeze({
       start,
       stop
    });
}

Здесь переменная fn является приватной. Общедоступными являются лишь методы start() и stop(). Эти методы нельзя модифицировать извне. Здесь не используется ключевое слово this, поэтому при использовании данного метода создания объектов проблема потеря контекста this оказывается неактуальной.

В команде return используется объектный литерал, содержащий лишь функции. Более того, эти функции объявлены в замыкании, они совместно пользуются общим состоянием. Для «заморозки» общедоступного API объекта используется уже известная вам команда Object.freeze().

Здесь мы, в примерах, использовали объект Timer. В этом материале можно найти его полную реализацию.

Итоги

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

Уважаемые читатели! Если вы пришли в JavaScript из других языков, просим рассказать нам о том, что вам нравится или не нравится в JS-объектах, в сравнении с реализацией объектов в уже известных вам языках.

Перевод статьи 3 Ways To Access Object Properties in JavaScript.

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

Получить доступ к свойствам объекта в JavaScript возможно с использованием следующих трех методов доступа:

  1. С использованием точки (точечная нотация): object.property;
  2. С использованием квадратных скобок: object['property'];
  3. Деструктуризация объекта (деструктурирующее присваивание): const { property } = object.

Далее в статье мы подробно рассмотрим, как работает каждый из этих типов синтаксиса доступа к свойствам объекта. А также разберемся как и когда , в зависимости от конкретной ситуации, целесообразно их использовать на практике.

Содержание

  • 1. Метод доступа с использованием точки
      • 1.1 Метод доступа с использованием точки требует использования корректных идентификаторов
    • 2. Метод доступа к свойствам объекта, использующий квадратные скобки
    • 3. Деструктуризация объекта
      • 3.1 Используем псевдоним переменной для деструктуризации
      • 3.2 Динамическое имя свойства
    • 4. Случай когда свойство объекта не существует
    • 5. Вывод

1. Метод доступа с использованием точки

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

expression.identifier

Здесь expression содержит или возвращает некоторый объект, а identifier является именем его свойства, к значению которого вы хотите получить доступ.

Например, давайте получим доступ к свойству name объекта hero:

const hero = {
  name: 'Batman'
};

// Доступ к значению свойства с использованием точки
hero.name; // => 'Batman'

Таким образом, hero.name — это метод доступа к свойству с использованием точки, который в нашем случае получает значение свойства name объекта hero.

Для доступа к свойствам, находящимся на более глубоком уровне вложенности можно использовать это метод по цепочке: object.prop1.prop2.

Используйте метод доступа к свойству с синтаксисом точки, в том случае если вам заранее известно имя свойства . Для свойств, имена которых состоят из нескольких слов, доступ к их значению «через точку» не работает.

1.1 Метод доступа с использованием точки требует использования корректных идентификаторов

Так метод доступа к свойствам, использующий точку работает правильно, если имя свойства объекта является допустимым идентификатором. Так корректный идентификатор в языке JavaScript может содержать символы Unicode, $, _ и цифры 0..9, но не может начинаться с цифры.

Обычно это не является проблемой, потому что на практике мы в качестве имен свойств чаще всего используем корректные идентификаторы, например, name, address, street, createdBy (название, адрес, улица, создан).

Но иногда подобранные по этому принципу названия свойств не будут корректными как идентификаторы:

const weirdObject = {
  'prop-3': 'three',
  '3': 'three'
};

console.log(weirdObject.prop-3); // => NaN    
console.log(weirdObject.3); // исключение: SyntaxError: Unexpected number

Поскольку prop-3 и 3 не являются допустимыми идентификаторами, то наш метод доступа к свойству с использованием точечной нотации не работает:

  • значение свойства weirdObject.prop-3 определяется как NaN вместо ожидаемого ‘tree’;
  • weirdObject.3 приводит к возбуждению исключения SyntaxError (ошибка синтаксиса).

Подумайте, почему результат выполнения выражения weirdObject.prop-3 равен NaN? Пожалуйста, напишите свой ответ в комментарии ниже!

Тем не менее для получения доступа к значениям свойств, имена которых представляют собой некорректные идентификаторы Javascript, можно использовать такой метод, как доступ с “квадратными скобками” (его мы рассмотрим ниже):

const weirdObject = {
  'prop-3': 'three',
  '3': 'three'
};

console.log(weirdObject['prop-3']); // => 'three'
console.log(weirdObject[3]);        // => 'three' 

Используя синтаксис метода доступа, использующего квадратные скобки, мы без проблем получим значения нужных свойств и даже с такими достаточно специфическими именами: weirdObject['prop-3'] и weirdObject[3].

2. Метод доступа к свойствам объекта, использующий квадратные скобки

Метод доступа с квадратными скобками имеет следующий синтаксис:

expression1[expression2]

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

Рассмотрим следующий пример:

const property = 'name';
const hero = {
  name: 'Batman'
};

// Square brackets property accessor:
console.log(hero['name']);   // => 'Batman'
console.log(hero[property]); // => 'Batman'

Обе инструкции hero['name'] и hero[property] корректно считывают свойство name , используя синтаксис с квадратными скобками.

Используйте метод доступа к свойствам с квадратными скобками, если имя свойства является динамическим, то есть определяется во время выполнения вашего кода.

Базовый синтаксис деструктуризации объекта довольно прост:

const { identifier } = expression;

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

Рассмотрим следующий пример:

const hero = {
  name: 'Batman'
};

// Деструктуризация объекта
const { name } = hero;
console.log(name); // => 'Batman'

Инструкция const { name } = heroвыполняет деструктуризацию объекта. Деструктуризация определяет переменную name, которой передается значение свойства name объекта hero.

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

Используйте деструктуризацию объекта, если вы хотите создать переменную со значением его свойства.

Обратите внимание, что вы можете извлечь значения стольких свойств, сколько захотите:

const { identifier1, identifier2, .., identifierN } = expression;

3.1 Используем псевдоним переменной для деструктуризации

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

const { identifier: aliasIdentifier } = expression;

Здесь identifier имя свойства к которому мы хотим получить доступ, aliasIdentifier имя, создаваемой переменной, а expression соответственно содержит или возвращает объект. После деструктуризации переменная aliasIdentifier будет содержать значение свойства identifier.

Рассмотрим следующий пример:

const hero = {
  name: 'Batman'
};

// Деструктуризация объекта:
const { name: heroName } = hero;
console.log(heroName); // => 'Batman'

В инструкции const { name: heroName } = hero происходит деструктуризация объекта hero. В ходе его деструктуризации определяется новая переменная heroName (вместо имени name соответствующего названию свойства, как в предыдущем примере) и присваивает heroName значение hero.name.

3.2 Динамическое имя свойства

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

const { [expression1]: identifier } = expression2;

Первое выражение expression1 возвращает имя свойства к которому мы хотим получить доступ. В identifier указывается имя переменной, создающейся после деструктуризации, в которую затем будет передано извлекаемое значение. Второе выражение expression2 должно возвращать или содержать объект, который мы хотели бы деструктурировать.

Рассмотрим следующий пример:

const property = 'name';
const hero = {
  name: 'Batman'
};

// Деструтуризация объекта:
const { [property]: name } = hero;
console.log(name); // => 'Batman'

Так в инструкции const {[property]: name} = hero мы деструктурируем объект hero и далее динамически, во время выполнения кода, определяем название его свойства, которое нам необходимо извлечь.

4. Случай когда свойство объекта не существует

Если свойство объекта, к значению которого нам необходимо получить доступ, не существует, то все три, рассмотренных нами выше, синтаксиса методов доступа будут возвращать значение undefined:

const hero = {
  characterName: 'Batman'
};

console.log(hero.name);    // => undefined
console.log(hero['name']); // => undefined
const { name } = hero;
console.log(name);         // => undefined

Свойства name не существует в объекте hero. Поэтому результатом использования методов доступа с точечной нотацией hero.name, квадратными скобками hero ['name'], а также содержимым переменной name после деструктуризации будет значение undefined .

5. Вывод

Таким образом, мы узнали что JavaScript предоставляет множество отличных способов для доступа к свойствам объекта.

Синтаксис доступа к свойствам объекта с использованием точки object.property прекрасно работает, когда вам заранее известно название свойства property. А также для случая когда его имя представляет собой корректный идентификатор Javascript.

В случае если имя свойства определяется динамически или не является допустимым идентификатором, то лучшей альтернативой являются использование метода доступа с квадратными скобками: object [propertyName].

При деструктуризации объекта извлекается значение свойства непосредственно в создаваемую переменную: {property} = object. Кроме того, вы можете извлечь динамические имена свойств объекта (то есть определяемые во время выполнения кода): {[PropertName]: variable} = object.

Какой метод доступа к свойствам объектов вам выбрать зависит от конкретной задачи, которую решает ваш код. Надеюсь, что представленные на ваше рассмотрение материалы этой статьи помогут вам в будущем.

Depending on which browsers you have to support, this can be done in a number of ways. The overwhelming majority of browsers in the wild support ECMAScript 5 (ES5), but be warned that many of the examples below use Object.keys, which is not available in IE < 9. See the compatibility table.

ECMAScript 3+

If you have to support older versions of IE, then this is the option for you:

for (var key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
        var val = obj[key];
        // use val
    }
}

The nested if makes sure that you don’t enumerate over properties in the prototype chain of the object (which is the behaviour you almost certainly want). You must use

Object.prototype.hasOwnProperty.call(obj, key) // ok

rather than

obj.hasOwnProperty(key) // bad

because ECMAScript 5+ allows you to create prototypeless objects with Object.create(null), and these objects will not have the hasOwnProperty method. Naughty code might also produce objects which override the hasOwnProperty method.

ECMAScript 5+

You can use these methods in any browser that supports ECMAScript 5 and above. These get values from an object and avoid enumerating over the prototype chain. Where obj is your object:

var keys = Object.keys(obj);

for (var i = 0; i < keys.length; i++) {
    var val = obj[keys[i]];
    // use val
}

If you want something a little more compact or you want to be careful with functions in loops, then Array.prototype.forEach is your friend:

Object.keys(obj).forEach(function (key) {
    var val = obj[key];
    // use val
});

The next method builds an array containing the values of an object. This is convenient for looping over.

var vals = Object.keys(obj).map(function (key) {
    return obj[key];
});

// use vals array

If you want to make those using Object.keys safe against null (as for-in is), then you can do Object.keys(obj || {})....

Object.keys returns enumerable properties. For iterating over simple objects, this is usually sufficient. If you have something with non-enumerable properties that you need to work with, you may use Object.getOwnPropertyNames in place of Object.keys.

ECMAScript 2015+ (A.K.A. ES6)

Arrays are easier to iterate with ECMAScript 2015. You can use this to your advantage when working with values one-by–one in a loop:

for (const key of Object.keys(obj)) {
    const val = obj[key];
    // use val
}

Using ECMAScript 2015 fat-arrow functions, mapping the object to an array of values becomes a one-liner:

const vals = Object.keys(obj).map(key => obj[key]);

// use vals array

ECMAScript 2015 introduces Symbol, instances of which may be used as property names. To get the symbols of an object to enumerate over, use Object.getOwnPropertySymbols (this function is why Symbol can’t be used to make private properties). The new Reflect API from ECMAScript 2015 provides Reflect.ownKeys, which returns a list of property names (including non-enumerable ones) and symbols.

Array comprehensions (do not attempt to use)

Array comprehensions were removed from ECMAScript 6 before publication. Prior to their removal, a solution would have looked like:

const vals = [for (key of Object.keys(obj)) obj[key]];

// use vals array

ECMAScript 2017+

ECMAScript 2016 adds features which do not impact this subject. The ECMAScript 2017 specification adds Object.values and Object.entries. Both return arrays (which will be surprising to some given the analogy with Array.entries). Object.values can be used as is or with a for-of loop.

const values = Object.values(obj);

// use values array or:

for (const val of Object.values(obj)) {
    // use val
}

If you want to use both the key and the value, then Object.entries is for you. It produces an array filled with [key, value] pairs. You can use this as is, or (note also the ECMAScript 2015 destructuring assignment) in a for-of loop:

for (const [key, val] of Object.entries(obj)) {
    // use key and val
}

Object.values shim

Finally, as noted in the comments and by teh_senaus in another answer, it may be worth using one of these as a shim. Don’t worry, the following does not change the prototype, it just adds a method to Object (which is much less dangerous). Using fat-arrow functions, this can be done in one line too:

Object.values = obj => Object.keys(obj).map(key => obj[key]);

which you can now use like

// ['one', 'two', 'three']
var values = Object.values({ a: 'one', b: 'two', c: 'three' });

If you want to avoid shimming when a native Object.values exists, then you can do:

Object.values = Object.values || (obj => Object.keys(obj).map(key => obj[key]));

Finally…

Be aware of the browsers/versions you need to support. The above are correct where the methods or language features are implemented. For example, support for ECMAScript 2015 was switched off by default in V8 until recently, which powered browsers such as Chrome. Features from ECMAScript 2015 should be be avoided until the browsers you intend to support implement the features that you need. If you use babel to compile your code to ECMAScript 5, then you have access to all the features in this answer.

Содержание

  • Объект исследования
    • Как найти определенное свойство класса в C#
    • Как получить значение свойства объекта в C#
    • Как записать значение свойства объекта в C#
  • Итого

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

При разработке программ в C# бывает необходимым определить имеется ли у экземпляра класса определенное свойство, проверить или записать его значение. Для выполнения этих и других операций мы можем воспользоваться классом из System.Reflection PropertyInfo. Этот класс предоставляет подробную информацию о свойстве класса или объекта.

Объект исследования

Допишем наш класс Person, используемый в прошлой статье, следующим образом:

class Account
 { 
     public string Login { get; set; }
     public string Password { get; set; } 
 }

 class Person
 {
     public Person()
     {
         Account = new Account();
     }
     public Person(string name, string family, byte age):this()
     {
         Name = name;
         Family = family;
         Age = age;
     }
     public string Name { get; set; }
     public string Family { get; set; }
     private byte age;
     public byte Age 
     {
         get 
         {
             return age;
         }
         set
         { if (value > 100)
                 throw new Exception("Возраст человека не может быть больше 100 лет");
             else
                 age = value;
                     
         }
     }
     public Account Account { get; private set; }
 }

Как можно увидеть, у нашего класса определено свойство Account, которое представляет собой т.н. вложенный тип.

Как найти определенное свойство класса в C#

Чтобы найти определенное свойство класса можно воспользоваться методом GetProperty(), например, следующим образом:

Person user = new Person("Вася", "Пупкин", 18);
user.Account.Login = "Uasya";
user.Account.Password = "password";

Type type = user.GetType();
PropertyInfo property = type.GetProperty("AcCoUnt", BindingFlags.IgnoreCase | BindingFlags.Instance| BindingFlags.Public);
if (property != null)
    Console.WriteLine("Свойство найдено");
else
    Console.WriteLine("Свойство не найдено");

Здесь следует обратить внимание на выставленные флаги BindingFlags. Используя связку BindingFlags.IgnoreCase | BindingFlags.Instance| BindingFlags.Public, мы пробуем найти публичное свойство, игнорируя регистр букв в имени. Поэтому результатом выполнения этого кода будет следующая строка:

Если свойство обнаружено, то, используя методы и свойства класса PropertyInfo мы можем его детально исследовать:

Person user = new Person("Вася", "Пупкин", 18);
user.Account.Login = "Uasya";
user.Account.Password = "password";

Type type = user.GetType();
PropertyInfo property = type.GetProperty("AcCoUnt", BindingFlags.IgnoreCase | BindingFlags.Instance| BindingFlags.Public);
if (property != null)
{
    Console.WriteLine("Свойство найдено");
    Console.WriteLine($"Тип свойства: {property.PropertyType}");
    Console.WriteLine($"Доступно для чтения: {property.CanRead}");
    Console.WriteLine($"Доступно для записи: {property.CanWrite}");
    bool isClass = property.PropertyType.IsClass;
    Console.WriteLine($"Свойство является классом: {isClass}");
    if (isClass)
    {
        Console.WriteLine($"Перечисляем свойства класса {property.PropertyType.Name}");
        foreach (PropertyInfo info in property.PropertyType.GetProperties())
        {
            Console.WriteLine($"    {info.PropertyType.Name} {info.Name}");
        }
    }
}    
else
    Console.WriteLine("Свойство не найдено");

Результат работы:

Свойство найдено
Тип свойства: Reflection.Account
Доступно для чтения: True
Доступно для записи: True
Свойство является классом: True
Перечисляем свойства класса Account
String Login
String Password

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

Как получить значение свойства объекта в C#

Попробуем прочитать значение найденного свойства. Для этого воспользуемся методом GetValue() у PropertyInfo. Допишем наш пример следующим образом:

[...]
if (isClass)
{
    Console.WriteLine($"Перечисляем свойства класса {property.PropertyType.Name}");
    foreach (PropertyInfo info in property.PropertyType.GetProperties())
    {
        Console.WriteLine($"    {info.PropertyType.Name} {info.Name} Значение свойства: {info.GetValue(property.GetValue(user))}");
    }
}
[...]

Результат выполнения программы:

Свойство найдено
Тип свойства: Reflection.Account
Доступно для чтения: True
Доступно для записи: True
Свойство является классом: True
Перечисляем свойства класса Account
String Login Значение свойства: Uasya
String Password Значение свойства: password

Здесь стоит обратить внимание на то, как мы получили значение свойства. Для того, чтобы получить значение свойства мы должны обязательно передать в метод экземпляр (объект) исследуемого класса. В нашем случае, это объект с именем user. Таким образом, вначале мы использовали метод GetValue() для того, чтобы получить объект Account из объекта user и только затем мы передали значение свойства Account в метод GetValue(), чтобы прочитать значение свойств Login и Password.

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

private static PropertyInfo GetProperty(object t, string PropertName)
{
    if (t.GetType().GetProperties().Count(p => p.Name == PropertName.Split('.')[0]) == 0)
        throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", PropertName, t.ToString()));
    if (PropertName.Split('.').Length == 1)
        return t.GetType().GetProperty(PropertName);
    else
        return GetProperty(t.GetType().GetProperty(PropertName.Split('.')[0]).GetValue(t, null), PropertName.Split('.')[1]);
}

В приведенном методе необходимо указать ссылку на экземпляр класса (объект) и имя свойства, которое необходимо найти, при этом вложенность свойства определяется точками, например:

PropertyInfo property = GetProperty(user, "Account.Login");

В данном случае, мы пробуем получить информацию о свойстве Login, которое относится к объекту Account, который, в свою очередь является свойством объекта user.

Как записать значение свойства объекта в C#

Для записи значения свойства нам необходимо воспользоваться методом SetValue(). Например:

Person user = new Person("Вася", "Пупкин", 18);
user.Account.Login = "Uasya";
user.Account.Password = "password";

Type type = user.GetType();
PropertyInfo property = type.GetProperty("Name");
if (property != null)
{
    Console.WriteLine($"Старое значение свойства {property.GetValue(user)}");
    property.SetValue(user, "Петя");
    Console.WriteLine($"Новое значение свойства {property.GetValue(user)}");
}

Итого

Для работы со свойствами классов и объектов C# используется класс PropertyInfo. Используя экземпляры этого класса мы можем получить детальную информацию о свойстве, а также прочитать или записать его значение для определенного экземпляра класса, используя, соответственно, методы GetValue() и SetValue().

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Исследование полей и свойств с помощью рефлексии

Последнее обновление: 17.02.2022

Получение информации о полях

Для извлечения всех полей применяется метод GetFields(), который возвращает массив объектов класса
FieldInfo.

Некоторые основные свойства и методы класса FieldInfo:

  • Свойство IsFamily: возвращает true, если поле имеет модификатор доступа protected

  • Свойство IsFamilyAndAssembly: возвращает true, если поле имеет модификатор доступа private protected

  • Свойство IsFamilyOrAssembly: возвращает true, если поле имеет модификатор доступа protected internal

  • Свойство IsAssembly: возвращает true, если поле имеет модификатор доступа internal

  • Свойство IsPrivate: возвращает true, если поле имеет модификатор доступа private

  • Свойство IsPublic: возвращает true, если поле имеет модификатор доступа public

  • Свойство IsStatic: возвращает true, если поле статическое

  • Метод GetValue(): возвращает значение поля

  • Метод SetValue(): устанавливает значение поля

Например, получим все поля класса:

using System.Reflection;

Type myType = typeof(Person);

Console.WriteLine("Поля:");
foreach (FieldInfo field in myType.GetFields(
    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static))
{
    string modificator = "";

    // получаем модификатор доступа
    if (field.IsPublic)
        modificator += "public ";
    else if (field.IsPrivate)
        modificator += "private ";
    else if (field.IsAssembly)
        modificator += "internal ";
    else if (field.IsFamily)
        modificator += "protected ";
    else if (field.IsFamilyAndAssembly)
        modificator += "private protected ";
    else if (field.IsFamilyOrAssembly)
        modificator += "protected internal ";

    // если поле статическое
    if (field.IsStatic) modificator += "static ";

    Console.WriteLine($"{modificator}{field.FieldType.Name} {field.Name}");
}

class Person
{
    static int minAge = 0;
    string name;
    int age;
    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
    }
    public void Print() => Console.WriteLine($"{name} - {age}");
}

Чтобы получить и статические, и не статические, и публичные, и непубличные поля, в метод GetFields() передается набор
флагов

BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static

Консольный вывод:

Поля:
private String name
private Int32 age
private static Int32 minAge

Получение и изменение значения поля

Для получения одного поля по имени применяется метод GetField(), в который передается имя поля:

var name = myType.GetField("name", BindingFlags.Instance | BindingFlags.NonPublic);

В качестве второго необязательного параметра передается набор флагов.

Причем рефлексия позволяет получать значения и изменять их даже у привтаных полей. Например, получим и изменим значение поля name:

using System.Reflection;

Type myType = typeof(Person);
Person tom = new Person("Tom", 37);

// получаем приватное поле name
var name = myType.GetField("name", BindingFlags.Instance | BindingFlags.NonPublic);

// получаем значение поля name
var value =name?.GetValue(tom);
Console.WriteLine(value);   // Tom

// изменяем значение поля name
name?.SetValue(tom, "Bob");
tom.Print();    // Bob - 37

class Person
{
    static int minAge = 1;
    string name;
    int age;
    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
    }
    public void Print() => Console.WriteLine($"{name} - {age}");
}

Свойства

Для извлечения всех свойств типа применяется соответственно метод GetProperties(), который возвращает массив объектов PropertyInfo.
Для получения одного свойства по имени применяется метод GetProperty(), в который передается название свойства и который возвращает объект
PropertyInfo?.

Некоторый основной функционал класса PropertyInfo:

  • Свойство Attributes: возвращает коллекцию атрибутов свойства

  • Свойство CanRead: возвращает true, если свойство доступно для чтения

  • Свойство CanWrite: возвращает true, если свойство доступно для записи

  • Свойство GetMethod: возвращает get-акссесор в виде объекта MethodInfo?

  • Свойство SetMethod: возвращает set-акссесор в виде объекта MethodInfo?

  • Свойство PropertyType: возвращает тип свойства

  • Метод GetValue(): возвращает значение свойства

  • Метод SetValue(): устанавливает значение свойства

Используем некоторые свойства PropertyInfo для получения информации о свойствах:

using System.Reflection;

Type myType = typeof(Person);
foreach (PropertyInfo prop in myType.GetProperties(
    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static))
{
    Console.Write($"{prop.PropertyType} {prop.Name} {{");

    // если свойство доступно для чтения
    if (prop.CanRead) Console.Write("get;");
    // если свойство доступно для записи
    if (prop.CanWrite) Console.Write("set;");
    Console.WriteLine("}");
}

class Person
{
    public string Name { get; }
    public int Age { get; set; }
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    public void Print() => Console.WriteLine($"{Name} - {Age}");
}
System.String Name {get;}
System.Int32 Age {get;set;}

С помощью методов PropertyInfo можно манипулировать значением свойства. Например, получим и изменим значение свойства:

using System.Reflection;

Type myType = typeof(Person);
Person tom = new Person("Tom", 37);
// получаем свойство Age
var ageProp = myType.GetProperty("Age");
// получаем значение свойства Age у объекта tom
var age = ageProp?.GetValue(tom);
Console.WriteLine(age); // 37
// устанавливаем новое значение для свойства Age объекта tom
ageProp?.SetValue(tom, 22);
tom.Print();    // Tom - 22

class Person
{
    public string Name { get; }
    public int Age { get; set; }
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    public void Print() => Console.WriteLine($"{Name} - {Age}");
}

Для получения значения свойства в метод GetValue() объекта PropertyInfo передается объект, у которого вызывается свойства. Результатом метода
является значение свойства. Для установки значения в метод SetValue() объекта PropertyInfo передается объект, у которого устанавливается свойство, и
собственно новое значение свойства.

Добавить комментарий