Метод Object.assign
Метод Object.assign копирует свойства и значения исходного объекта, возвращая при этом новый объект. В первом параметре метода мы задаем целевой объект, в который копируем, а во втором — указываем объекты, которые нужно скопировать.
Синтаксис
Object.assign(куда копировать, объекты);
Пример
Давайте получим новый объект из исходного:
let obj = <'a': 1, 'b': 2, 'c': 3>; let newObj = Object.assign(<>, obj); console.log(newObj);'a':>
Результат выполнения кода:
Пример
А теперь получим новый объект из нескольких исходных, перечисляя их в параметре через запятую:
let obj1 = <'a': 1, 'b': 2>; let obj2 = ; let newObj = Object.assign(<>, obj1, obj2); console.log(newObj);'a':>
После выполнения кода мы увидим, что два исходных объекта слились в один новый объект:
Пример
Также метод Object.assign можно применять для получения нового массива, поскольку массив по своему типу также относится к объектам. Давайте сделаем новый массив, скопировав элементы из исходного:
let arr = [1, 2, 3, 4]; let newArr = Object.assign([], arr); console.log(newArr);
Результат выполнения кода:
Пример
При использовании метода Object.assign нужно внимательно придерживаться синтаксиса. К примеру, если не добавить в первый параметр целевой объект, а оставить только копируемый объект, то он и вернется обратно:
let obj = <1:'a', 2: 'b', 3: 'c'>; let newObj = Object.assign(obj); console.log(newObj === obj);1:'a',>
Результат выполнения кода:
Смотрите также
- метод Object.keys ,
который возвращает массив из свойств объекта - метод Object.values ,
который возвращает массив из значений объекта
object (объект)
Объекты — это экземпляры типов. Далее в этой статье речь пойдёт об использовании object в качестве базового класса. Понятие «объект» как таковое более подробно описано на странице «Объекты».
В отличие от классов старого стиля, объекты которых создавались на базе встроенного типа instance , классы нового стиля являются полноценными пользовательскими типами.
Для классов старого стиля type(x) выдаст instance — подразумевается, что класс не представляет собой самостоятельный тип, а лишь является экземпляром некого базового типа.
На заметку
object — это экземпляр типа type. Более того, обратное утверждение тоже верно. Это потому, что «всё — есть объект»: типы — это объекты, экземпляры типов — тоже объекты. Чтобы усугубить ощущение запутанности, скажем: тип — это подкласс объекта, однако обратное не верно.
isinstance(object, type) # True
isinstance(object, object) # True
isinstance(type, object) # True
isinstance(type, type) # True
issubclass(type, object) # True
issubclass(object, type) # False
Классы старого стиля упразднены в Python 3: теперь пользовательские типы, определённые без явного указания родителя, автоматически являются наследниками object . Переход на такую унифицированную объектную модель, помимо прочего, сделал возможным наследование от встроенных типов, применение дескрипторов, адекватное определение порядка разрешения методов при множественном наследовании.
# Следующие определения пользовательских типов
# эквивалентны.
# Python 2
class A(object): pass
# Python 3
class A: pass
Преобразование объектов в примитивы
Что произойдёт, если сложить два объекта obj1 + obj2 , вычесть один из другого obj1 — obj2 или вывести их на экран, воспользовавшись alert(obj) ?
JavaScript совершенно не позволяет настраивать, как операторы работают с объектами. В отличие от некоторых других языков программирования, таких как Ruby или C++, мы не можем реализовать специальный объектный метод для обработки сложения (или других операторов).
В случае таких операций, объекты автоматически преобразуются в примитивы, затем выполняется сама операция над этими примитивами, и на выходе мы получим примитивное значение.
Это важное ограничение: результатом obj1 + obj2 (или другой математической операции) не может быть другой объект!
К примеру, мы не можем создавать объекты, представляющие векторы или матрицы (или достижения или может ещё что-то), складывать их и ожидать в качестве результата «суммированный» объект. Такие архитектурные ходы автоматически оказываются «за бортом».
Итак, поскольку мы технически здесь мало что можем сделать, в реальных проектах нет математики с объектами. Если она всё же происходит, то за редким исключением, это из-за ошибок в коде.
В этой главе мы рассмотрим, как объект преобразуется в примитив и как это можно настроить.
У нас есть две цели:
- Это позволит нам понять, что происходит в случае ошибок в коде, когда такая операция произошла случайно.
- Есть исключения, когда такие операции возможны и вполне уместны. Например, вычитание или сравнение дат ( Date объекты). Мы встретимся с ними позже.
Правила преобразования
В главе Преобразование типов мы рассмотрели правила для числовых, строковых и логических преобразований примитивов. Но мы оставили пробел для объектов. Теперь, когда мы уже знаем о методах и символах, пришло время заполнить этот пробел.
- Не существует преобразования к логическому значению. В логическом контексте все объекты являются true , всё просто. Существует лишь их числовое и строковое преобразование.
- Числовое преобразование происходит, когда мы вычитаем объекты или применяем математические функции. Например, объекты Date (которые будут рассмотрены в главе Дата и время) могут быть вычтены, и результатом date1 — date2 будет разница во времени между двумя датами.
- Что касается преобразований к строке – оно обычно происходит, когда мы выводим на экран объект при помощи alert(obj) и в подобных контекстах.
Мы можем реализовать свои преобразования к строкам и числам, используя специальные объектные методы.
Теперь давайте углубимся в детали. Это единственный путь для того, чтобы разобраться в нюансах этой темы.
Хинты
Как JavaScript решает, какое преобразование применить?
Существует три варианта преобразования типов, которые происходят в различных ситуациях. Они называются «хинтами», как описано в спецификации:
Для преобразования объекта к строке, когда мы выполняем операцию над объектом, которая ожидает строку, например alert :
// вывод alert(obj); // используем объект в качестве ключа anotherObj[obj] = 123;
Для преобразования объекта к числу, в случае математических операций:
// явное преобразование let num = Number(obj); // математические (не считая бинарного плюса) let n = +obj; // унарный плюс let delta = date1 - date2; // сравнения больше/меньше let greater = user1 > user2;
Большинство встроенных математических функций также включают в себя такое преобразование.
Происходит редко, когда оператор «не уверен», какой тип ожидать.
Например, бинарный плюс + может работать как со строками (объединяя их в одну), так и с числами (складывая их). Поэтому, если бинарный плюс получает объект в качестве аргумента, он использует хинт «default» для его преобразования.
Также, если объект сравнивается с помощью == со строкой, числом или символом, тоже неясно, какое преобразование следует выполнить, поэтому используется хинт «default» .
// бинарный плюс использует хинт "default" let total = obj1 + obj2; // obj == number использует хинт "default" if (user == 1) < . >;
Операторы сравнения больше/меньше, такие как < >, также могут работать как со строками, так и с числами. Тем не менее, по историческим причинам, они используют хинт «number» , а не «default» .
Впрочем на практике, всё немного проще.
Все встроенные объекты, за исключением одного (объект Date , который мы рассмотрим позже), реализуют «default» преобразование тем же способом, что и «number» . И нам следует поступать так же.
Чтобы выполнить преобразование, JavaScript пытается найти и вызвать три следующих метода объекта:
- Вызвать obj[Symbol.toPrimitive](hint) – метод с символьным ключом Symbol.toPrimitive (системный символ), если такой метод существует,
- Иначе, если хинт равен «string»
- попробовать вызвать obj.toString() или obj.valueOf() , смотря какой из них существует.
- Иначе, если хинт равен «number» или «default»
- попробовать вызвать obj.valueOf() или obj.toString() , смотря какой из них существует.
Symbol.toPrimitive
Давайте начнём с первого метода. Есть встроенный символ с именем Symbol.toPrimitive , который следует использовать для обозначения метода преобразования, вот так:
obj[Symbol.toPrimitive] = function(hint) < // вот код для преобразования этого объекта в примитив // он должен вернуть примитивное значение // hint = чему-то из "string", "number", "default" >;
Если метод Symbol.toPrimitive существует, он используется для всех хинтов, и больше никаких методов не требуется.
Например, здесь объект user реализует его:
let user = < name: "John", money: 1000, [Symbol.toPrimitive](hint) < alert(`hint: $`); return hint == "string" ? `">` : this.money; > >; // демонстрация результатов преобразований: alert(user); // hint: string -> alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500
Как мы можем видеть из кода, user становится либо строкой со своим описанием, либо суммой денег в зависимости от преобразования. Единый метод user[Symbol.toPrimitive] обрабатывает все случаи преобразования.
toString/valueOf
Если нет Symbol.toPrimitive , тогда JavaScript пытается найти методы toString и valueOf :
- Для хинта «string» : вызвать метод toString , а если он не существует или возвращает объект вместо примитивного значения, то valueOf (таким образом, toString имеет приоритет при строковом преобразовании).
- Для других хинтов: вызвать метод valueOf , а если он не существует или возвращает объект вместо примитивного значения, то toString (таким образом, valueOf имеет приоритет для математических операций).
Методы toString и valueOf берут своё начало с древних времён. Это не символы (символов тогда ещё не было), а скорее просто «обычные» методы со строковыми именами. Они предоставляют альтернативный «старомодный» способ реализации преобразования.
Эти методы должны возвращать примитивное значение. Если toString или valueOf возвращает объект, то он игнорируется (так же, как если бы метода не было).
По умолчанию обычный объект имеет следующие методы toString и valueOf :
- Метод toString возвращает строку «[object Object]» .
- Метод valueOf возвращает сам объект.
Взгляните на пример:
let user = ; alert(user); // [object Object] alert(user.valueOf() === user); // true
Таким образом, если мы попытаемся использовать объект в качестве строки, как например в alert или вроде того, то по умолчанию мы увидим [object Object] .
Значение по умолчанию valueOf упоминается здесь только для полноты картины, чтобы избежать какой-либо путаницы. Как вы можете видеть, он возвращает сам объект и поэтому игнорируется. Не спрашивайте меня почему, это по историческим причинам. Так что мы можем предположить, что его не существует.
Давайте применим эти методы для настройки преобразования.
Для примера, используем их в реализации всё того же объекта user . Но уже используя комбинацию toString и valueOf вместо Symbol.toPrimitive :
let user = < name: "John", money: 1000, // для хинта равного "string" toString() < return `">`; >, // для хинта равного "number" или "default" valueOf() < return this.money; >>; alert(user); // toString -> alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500
Как видим, получилось то же поведение, что и в предыдущем примере с Symbol.toPrimitive .
Довольно часто нам нужно единое «универсальное» место для обработки всех примитивных преобразований. В этом случае мы можем реализовать только toString :
let user = < name: "John", toString() < return this.name; >>; alert(user); // toString -> John alert(user + 500); // toString -> John500
В отсутствие Symbol.toPrimitive и valueOf , toString обработает все примитивные преобразования.
Преобразование может вернуть любой примитивный тип
Важная вещь, которую следует знать обо всех методах преобразования примитивов, заключается в том, что они не обязательно возвращают подсказанный хинтом примитив.
Нет никакого контроля над тем, вернёт ли toString именно строку, или чтобы метод Symbol.toPrimitive возвращал именно число для хинта «number» .
Единственное обязательное условие: эти методы должны возвращать примитив, а не объект.
Историческая справка
По историческим причинам, если toString или valueOf вернёт объект, то ошибки не будет, но такое значение будет проигнорировано (как если бы метода вообще не существовало). Это всё потому, что в древние времена в JavaScript не было хорошей концепции «ошибки».
А вот Symbol.toPrimitive уже «четче», этот метод обязан возвращать примитив, иначе будет ошибка.
Дальнейшие преобразования
Как мы уже знаем, многие операторы и функции выполняют преобразования типов, например, умножение * преобразует операнды в числа.
Если мы передаём объект в качестве аргумента, то в вычислениях будут две стадии:
- Объект преобразуется в примитив (с использованием правил, описанных выше).
- Если необходимо для дальнейших вычислений, этот примитив преобразуется дальше.
let obj = < // toString обрабатывает все преобразования в случае отсутствия других методов toString() < return "2"; >>; alert(obj * 2); // 4, объект был преобразован к примитиву "2", затем умножение сделало его числом
- Умножение obj * 2 сначала преобразует объект в примитив (это строка «2» ).
- Затем «2» * 2 становится 2 * 2 (строка преобразуется в число).
А вот, к примеру, бинарный плюс в подобной ситуации соединил бы строки, так как он совсем не брезгует строк:
let obj = < toString() < return "2"; >>; alert(obj + 2); // 22 ("2" + 2), преобразование к примитиву вернуло строку => конкатенация
Итого
Преобразование объекта в примитив вызывается автоматически многими встроенными функциями и операторами, которые ожидают примитив в качестве значения.
Существует всего 3 типа (хинта) для этого:
- «string» (для alert и других операций, которым нужна строка)
- «number» (для математических операций)
- «default» (для некоторых других операторов, обычно объекты реализуют его как «number» )
Спецификация явно описывает для каждого оператора, какой ему следует использовать хинт.
Алгоритм преобразования таков:
- Сначала вызывается метод obj[Symbol.toPrimitive](hint) , если он существует,
- В случае, если хинт равен «string»
- происходит попытка вызвать obj.toString() и obj.valueOf() , смотря что есть.
- В случае, если хинт равен «number» или «default»
- происходит попытка вызвать obj.valueOf() и obj.toString() , смотря что есть.
Все эти методы должны возвращать примитив (если определены).
На практике часто бывает достаточно реализовать только obj.toString() в качестве универсального метода для преобразований к строке, который должен возвращать удобочитаемое представление объекта для целей логирования или отладки.
Object.values()
Метод Object.values() возвращает массив значений перечисляемых свойств объекта в том же порядке что и цикл for. in . Разница между циклом и методом в том, что цикл перечисляет свойства и из цепочки прототипов.
Интерактивный пример
Синтаксис
Object.values(obj)
Параметры
Объект, чьи значения перечисляемых свойств будут возвращены.
Возвращаемое значение
Массив содержащий значения перечисляемых свойств объекта.
Описание
Object.values() возвращает массив, чьи элементы это значения перечисляемых свойств найденных в объекте. Порядок такой же как если пройтись по объекту циклом вручную.
Примеры
var obj = foo: "bar", baz: 42 >; console.log(Object.values(obj)); // ['bar', 42] // Массив как объект var obj = 0: "a", 1: "b", 2: "c" >; console.log(Object.values(obj)); // ['a', 'b', 'c'] // Массив как объект со случайным порядком ключей // Когда мы используем нумерованные ключ, значения возвращаются в порядке возрастания var an_obj = 100: "a", 2: "b", 7: "c" >; console.log(Object.values(an_obj)); // ['b', 'c', 'a'] // getFoo неперечисляемое свойство var my_obj = Object.create( >, getFoo: value: function () return this.foo; >, >, >, ); my_obj.foo = "bar"; console.log(Object.values(my_obj)); // ['bar'] // Аргумент, не являющийся объектом, будет приведён к объекту console.log(Object.values("foo")); // ['f', 'o', 'o']
Полифил
Для добавления совместимости Object.values со старым окружением, которое не поддерживает нативно его, вы можете найти полифил здесь tc39/proposal-object-values-entries или в es-shims/Object.values репозитории.
Спецификации
| Specification |
|---|
| ECMAScript Language Specification # sec-object.values |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
- Перечисляемость и принадлежность свойств
- Object.keys()
- Object.entries() Экспериментальная возможность
- Object.prototype.propertyIsEnumerable()
- Object.create()
- Object.getOwnPropertyNames()
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.