Хабрахабр

[Перевод] Новшества объектных литералов в JavaScript ES6

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

Стандарт ES2015 (ES6) упрощает работу с объектами при создании приложений для современных браузеров (кроме IE) и для платформы Node.js. JavaScript обладает мощной и удобной возможностью создания объектов с использованием объектных литералов.

Основы

Создание объектов в некоторых языках может требовать больших затрат ресурсов, под которыми мы имеем в виду и рабочее время программиста, и вычислительные ресурсы систем. В частности, речь идёт о том, что, прежде чем создавать объекты, необходимо описывать классы (скажем, с помощью ключевого слова class). В JavaScript объекты можно создавать очень быстро и просто, без необходимости выполнения каких-либо предварительных действий. Рассмотрим пример:

// ES5
var myObject =
}; myObject.output(); // hello world

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

Инициализация объектов из переменных

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

// ES5
var a = 1, b = 2, c = 3; obj = { a: a, b: b, c: c }; // obj.a = 1, obj.b = 2, obj.c = 3

В ES6 больше не нужно повторять имена переменных:

// ES6
const a = 1, b = 2, c = 3; obj = { a, b, c }; // obj.a = 1, obj.b = 2, obj.c = 3

Этот приём может оказаться полезным для возвращаемых объектов при использовании паттерна Revealing Module, который позволяет создавать пространства имён для различных фрагментов кода для того, чтобы избежать конфликтов имён. Например:

// ES6
const lib = (() => { function sum(a, b) { return a + b; } function mult(a, b) { return a * b; } return { sum, mult }; }()); console.log( lib.sum(2, 3) ); // 5
console.log( lib.mult(2, 3) ); // 6

Возможно, вы видели как этот приём используется в ES6-модулях:

// lib.js
function sum(a, b) { return a + b; }
function mult(a, b) { return a * b; } export { sum, mult };

Сокращённый синтаксис объявления методов объектов

При объявлении методов объектов в ES5 необходимо использовать ключевое слово function:

// ES5
var lib = { sum: function(a, b) { return a + b; }, mult: function(a, b) { return a * b; }
}; console.log( lib.sum(2, 3) ); // 5
console.log( lib.mult(2, 3) ); // 6

Теперь, в ES6, так больше можно не делать. Здесь допустим следующий сокращённый способ объявления методов:

// ES6
const lib = { sum(a, b) { return a + b; }, mult(a, b) { return a * b; }
}; console.log( lib.sum(2, 3) ); // 5
console.log( lib.mult(2, 3) ); // 6

Надо отметить, что здесь нельзя использовать стрелочные функции ES6 (=>), так как у методов должны быть имена. Однако стрелочные функции можно использовать если явно назначать имена методам (как в ES5). Например:

// ES6
const lib = { sum: (a, b) => a + b, mult: (a, b) => a * b
}; console.log( lib.sum(2, 3) ); // 5
console.log( lib.mult(2, 3) ); // 6

Динамические ключи

В ES5 нельзя было использовать переменные в качестве имён ключей, хотя ключ, имя которого задаётся переменной, можно было добавить после создания объекта. Например:

// ES5
var key1 = 'one', obj = { two: 2, three: 3 }; obj[key1] = 1; // obj.one = 1, obj.two = 2, obj.three = 3

В ES6 ключи можно назначать динамически, помещая выражение, определяющее имя, в квадратные скобки ([]). Например:

// ES6
const key1 = 'one', obj = { [key1]: 1, two: 2, three: 3 }; // obj.one = 1, obj.two = 2, obj.three = 3

Для создания ключа можно использовать любое выражение:

// ES6
const i = 1, obj = { ['i' + i]: i }; console.log(obj.i1); // 1

Динамические ключи можно использовать и для методов, и для свойств:

// ES6
const i = 2, obj = { ['mult' + i]: x => x * i }; console.log( obj.mult2(5) ); // 10

Другой вопрос — надо ли создавать свойства и методы с динамически генерируемыми именами. Читабельность кода, в котором используется этот приём, может ухудшиться. Возможно, если вы сталкиваетесь с ситуациями, в которых динамические имена кажутся уместными, лучше будет подумать о применении для создания объектов фабричных функций или классов.

Деструктурирование

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

// ES5
var myObject = { one: 'a', two: 'b', three: 'c'
}; var one = myObject.one, // 'a' two = myObject.two, // 'b' three = myObject.three; // 'c'

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

// ES6
const myObject = { one: 'a', two: 'b', three: 'c'
}; const { one, two, three } = myObject;
// one = 'a', two = 'b', three = 'c'

Переменные, в которые попадают значения свойств объекта, могут, на самом деле, иметь любые имена, но в том случае, если они отличаются от имён свойств, необходимо пользоваться конструкцией { propertyName: newVariable }:

// ES6
const myObject = { one: 'a', two: 'b', three: 'c'
}; const { one: first, two: second, three: third } = myObject;
// first = 'a', second = 'b', third = 'c'

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

// ES6
const meta = { title: 'Enhanced Object Literals', pageinfo: { url: 'https://www.sitepoint.com/', description: 'How to use object literals in ES2015 (ES6).', keywords: 'javascript, object, literal' }
}; const { title : doc, pageinfo: { keywords: topic }
} = meta; /* doc = 'Enhanced Object Literals' topic = 'javascript, object, literal'
*/

Поначалу всё это может показаться сложным, однако, с этим не так уж и тяжело разобраться, главное — помнить следующее:

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

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

{ a, b, c } = myObject; // неправильно

Эта конструкция нормально воспринимается системой при объявлении переменных:

const { a, b, c } = myObject; // правильно

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

let a, b, c;
({ a, b, c } = myObject); // правильно

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

Деструктурирование — это приём, который может пригодиться во многих ситуациях.

Параметры функций по умолчанию

Если функция нуждается в длинном списке аргументов, обычно проще передать ей один объект с параметрами. Например:

prettyPrint( { title: 'Enhanced Object Literals', publisher: { name: 'SitePoint', url: 'https://www.sitepoint.com/' }
} );

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

// ES5, назначение значений по умолчанию
function prettyPrint(param) { param = param || {}; var pubTitle = param.title || 'No title', pubName = (param.publisher && param.publisher.name) || 'No publisher'; return pubTitle + ', ' + pubName; }

В ES6 любым параметрам можно назначать значения по умолчанию:

// ES6 - значения параметров по умолчанию
function prettyPrint(param = {}) { ... }

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

// ES6 деструктурированное значение по умолчанию
function prettyPrint( { title: pubTitle = 'No title', publisher: { name: pubName = 'No publisher' } } = {}
) { return `${pubTitle}, ${pubName}`; }

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

Разбор объектов, возвращаемых функциями

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

// ES5
var obj = getObject(), one = obj.one, two = obj.two, three = obj.three;

Деструктурирование упрощает этот процесс. Теперь всё это можно сделать без необходимости сохранения объекта в отдельной переменной и последующего его разбора:

// ES6
const { one, two, three } = getObject();

Возможно, вы видели нечто подобное в программах для Node.js. Например, если вам нужны только методы readFile() и writeFile() модуля fs, получить ссылки на них можно так:

// ES6 Node.js
const { readFile, writeFile } = require('fs'); readFile('file.txt', (err, data) => { console.log(err || data);
}); writeFile('new.txt', 'new content', err => { console.log(err || 'file written');
});

Синтаксис оставшихся параметров и оператор расширения ES2018 (ES9)

=В ES2015 синтаксис оставшихся параметров и оператор расширения (и тот и другой выглядят как три точки, ) применялись лишь при работе с массивами. В ES2018 похожий функционал можно использовать и для работы с объектами:

const myObject = { a: 1, b: 2, c: 3
}; const { a, ...x } = myObject;
// a = 1
// x = { b: 2, c: 3 }

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

function restParam({ a, ...x }) { // a = 1 // x = { b: 2, c: 3 }
} restParam({ a: 1, b: 2, c: 3
});

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

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

const obj1 = { a: 1, b: 2, c: 3 }, obj2 = { ...obj1, z: 26 }; // obj2 is { a: 1, b: 2, c: 3, z: 26 }

Оператор расширения допустимо применять для клонирования объектов (obj2 = { ...obj1 };), однако тут надо учитывать то, что при таком подходе выполняется мелкая копия объекта. Если свойствами объектов являются другие объекты, клон объекта будет ссылаться на те же самые вложенные объекты.

В настоящий момент ими, без дополнительных усилий, можно пользоваться в браузерах Chrome и Firefox, и при разработке для платформы Node.js версии 8. Синтаксис оставшихся параметров и оператор расширения пока пользуются не слишком широкой поддержкой. 6 и выше.

Итоги

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

Уважаемые читатели! Какими способами создания JS-объектов вы пользуетесь чаще всего?

Теги
Показать больше

Похожие статьи

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть