Javascript сравнение объектов: Разбираемся с объектами в JavaScript / Блог компании Wirex / Хабр
Сравнение объектов в JavaScript — javascript
Вот мое прокомментированное решение в ES3 (кровавые подробности после кода):
Object.equals = function( x, y ) {
if ( x === y ) return true;
// if both x and y are null or undefined and exactly the same
if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
// if they are not strictly equal, they both need to be Objects
if ( x.constructor !== y.constructor ) return false;
// they must have the exact same prototype chain, the closest we can do is
// test there constructor.
for ( var p in x ) {
if ( ! x.hasOwnProperty( p ) ) continue;
// other properties were tested using x.constructor === y.constructor
if ( ! y.hasOwnProperty( p ) ) return false;
// allows to compare x[ p ] and y[ p ] when set to undefined
if ( x[ p ] === y[ p ] ) continue;
// if they have the same strict value or identity then they are equal
if ( typeof( x[ p ] ) !== "object" ) return false;
// Numbers, Strings, Functions, Booleans must be strictly equal
if ( ! Object.equals( x[ p ], y[ p ] ) ) return false;
// Objects and Arrays must be tested recursively
}
for ( p in y ) {
if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
// allows x[ p ] to be set to undefined
}
return true;
}
При разработке этого решения я уделил особое внимание угловым случаям, эффективности, но все же попытался дать простое решение, которое работает, надеюсь, с некоторой элегантностью. JavaScript допускает как null , так и неопределенные свойства и объекты имеют цепочки прототипов , которые могут привести к очень разным поведениям,если их не проверять.
Во-первых , я решил расширить объект вместо Object.prototype , главным образом потому , что null не может быть одним из объектов сравнения и что я считаю, что null должен быть допустимым объектом для сравнения с другим. Есть также и другие законные опасения, отмеченные другими участниками относительно расширения Object.prototype в отношении возможных побочных эффектов для чужого кода.
Особое внимание должно быть уделено возможности того, что JavaScript допускает, что свойства объекта могут быть установлены в неопределенное значение, т. е. существуют свойства, значения которых имеют значение undefined . Приведенное выше решение проверяет, что оба объекта имеют одинаковые свойства, установленные в значение undefined , чтобы сообщить о равенстве. Это можно сделать, только проверив наличие свойств с помощью Object.hasOwnProperty (property_name) . Также обратите внимание, что JSON.stringify() удаляет свойства, которым присвоено значение undefined , и поэтому сравнения с использованием этой формы будут игнорировать свойства, присвоенные значению undefined .
Функции следует считать равными только в том случае, если они имеют одну и ту же ссылку, а не просто один и тот же код, поскольку это не будет учитывать прототип этих функций. Таким образом, сравнение строк кода не гарантирует, что они имеют один и тот же объект-прототип.
Эти два объекта должны иметь одну и ту же цепочку прототипов , а не просто одни и те же свойства. Это можно проверить только в кросс-браузере, сравнив конструктор обоих объектов для строгого равенства. ECMAScript 5 позволит протестировать их реальный прототип с использованием Object.getPrototypeOf() . Некоторые веб-браузеры также предлагают свойство __ proto__ , которое делает то же самое. Возможное усовершенствование приведенного выше кода позволит использовать один из этих методов при любой возможности.
Использование строгих сравнений здесь имеет первостепенное значение , поскольку 2 не следует считать равным «2.0000» , а false не следует считать равным null , неопределенному или 0 .
Соображения эффективности приводят меня к тому, чтобы сравнить для равенства свойств как можно скорее. Затем, только если это не удалось , найдите тип этих свойств. Скорость boost может быть значительной для больших объектов с большим количеством свойств scalar.
Не требуется больше двух циклов, первый для проверки свойств из левого объекта, второй для проверки свойств из правого и проверки только существования (не значения), чтобы поймать эти свойства, которые определены с неопределенным значением.
В целом этот код обрабатывает большинство угловых случаев только в 16 строках кода (без комментариев).
Обновление (8/13/2015). Я реализовал лучшую версию, так как функция value_equals() , которая работает быстрее, правильно обрабатывает угловые случаи, такие как NaN и 0, отличающиеся от -0, опционально применяя порядок свойств объектов и тестирование циклических ссылок, подкрепленное более чем 100 автоматическими тестами в составе набора тестов проекта Toubkal .
Сравнение объектов в JavaScript – 10 Ответов
Вот мое прокомментированное решение в ES3 (подробности после кода):
Object.equals = function( x, y ) {
if ( x === y ) return true;
// if both x and y are null or undefined and exactly the same
if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
// if they are not strictly equal, they both need to be Objects
if ( x.constructor !== y.constructor ) return false;
// they must have the exact same prototype chain, the closest we can do is
// test there constructor.
for ( var p in x ) {
if ( ! x.hasOwnProperty( p ) ) continue;
// other properties were tested using x.constructor === y.constructor
if ( ! y.hasOwnProperty( p ) ) return false;
// allows to compare x[ p ] and y[ p ] when set to undefined
if ( x[ p ] === y[ p ] ) continue;
// if they have the same strict value or identity then they are equal
if ( typeof( x[ p ] ) !== "object" ) return false;
// Numbers, Strings, Functions, Booleans must be strictly equal
if ( ! Object.equals( x[ p ], y[ p ] ) ) return false;
// Objects and Arrays must be tested recursively
}
for ( p in y ) {
if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
// allows x[ p ] to be set to undefined
}
return true;
}
Разрабатывая это решение, я особенно внимательно посмотрел на угловые случаи, эффективность, но пытаясь дать простое решение, которое работает, надеюсь, с некоторой элегантностью. JavaScript допускает, что у нулевых и неопределенных свойств и объектов есть прототипы, которые могут привести к очень разному поведению, если не проверены.
Сначала я решил расширить Object вместо Object.prototype, главным образом потому, что null не может быть одним из объектов сравнения и что я считаю, что null должен быть действительным объектом для сравнения с другим. Есть и другие законные проблемы, отмеченные другими в отношении расширения Object.prototype относительно возможных побочных эффектов для другого кода.
Необходимо проявлять особую осторожность, чтобы разрешить JavaScript, чтобы свойства объекта могли быть установлены как неопределенные, т.е. Существуют свойства, значения которых установлены как неопределенные. Вышеприведенное решение подтверждает, что оба объекта имеют одинаковые свойства, которые не определены для сообщения о равенстве. Это может быть достигнуто только путем проверки существования свойств с использованием Object.hasOwnProperty(property_name). Также обратите внимание, что JSON.stringify() удаляет свойства, которые установлены как неопределенные, и поэтому сравнения с использованием этой формы будут игнорировать свойства, установленные для значения undefined.
Функции должны считаться равными, только если они используют одну и ту же ссылку, а не только тот же код, потому что это не будет учитывать прототип этих функций. Поэтому сравнение строки кода не работает, чтобы гарантировать, что у них есть один и тот же объект-прототип.
Оба объекта должны иметь одну и ту же цепочку прототипов, а не одни и те же свойства. Это может быть проверено только кросс-браузером, сравнивая конструктор обоих объектов для строгого равенства. ECMAScript 5 позволит проверить их фактический прототип с помощью Object.getPrototypeOf(). Некоторые веб-браузеры также предлагают свойство __proto__, которое делает то же самое. Возможное улучшение вышеуказанного кода позволит использовать один из этих методов всякий раз, когда это возможно.
Использование строгих сравнений здесь имеет первостепенное значение, потому что 2 не следует считать равным «2.0000», а false должно считаться равным нулю, неопределенным или 0.
Соображения эффективности приводят к тому, что я как можно скорее сравню для равенства свойств. Затем, только если это не удалось, найдите тип этих свойств. Повышение скорости может быть значительным для больших объектов с большим количеством скалярных свойств.
Не более двух циклов требуется, первый для проверки свойств из левого объекта, второй для проверки свойств справа и проверки только существования (не значения), чтобы поймать эти свойства, которые определены с неопределенным значением.
В целом этот код обрабатывает большинство угловых случаев только в 16 строках кода (без комментариев).
Обновление (8/13/2015). Я реализовал более эффективную версию, так как функция value_equals() работает быстрее, обрабатывает правильные угловые случаи, такие как NaN и 0, отличные от -0, необязательно применяя порядок и тестирование свойств объектов для циклических ссылок, поддерживаемых более чем 100 автоматическими тесты как часть тестового набора проектов Toubkal.
javascript: Сравнение объектов в JavaScript
Вот мое закомментированное решение в ES3 (подробности после кода):
Object.equals = function( x, y ) {
if ( x === y ) return true;
// if both x and y are null or undefined and exactly the same
if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
// if they are not strictly equal, they both need to be Objects
if ( x.constructor !== y.constructor ) return false;
// they must have the exact same prototype chain, the closest we can do is
// test there constructor.
for ( var p in x ) {
if ( ! x.hasOwnProperty( p ) ) continue;
// other properties were tested using x.constructor === y.constructor
if ( ! y.hasOwnProperty( p ) ) return false;
// allows to compare x[ p ] and y[ p ] when set to undefined
if ( x[ p ] === y[ p ] ) continue;
// if they have the same strict value or identity then they are equal
if ( typeof( x[ p ] ) !== "object" ) return false;
// Numbers, Strings, Functions, Booleans must be strictly equal
if ( ! Object.equals( x[ p ], y[ p ] ) ) return false;
// Objects and Arrays must be tested recursively
}
for ( p in y ) {
if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
// allows x[ p ] to be set to undefined
}
return true;
}
Разрабатывая это решение, я обратил особое внимание на угловые случаи, эффективность, но в то же время пытался найти простое решение, которое работает, надеюсь, с некоторой элегантностью. JavaScript допускает как нулевые, так и неопределенные свойства, и объекты имеют цепочки прототипов, которые могут приводить к очень разным поведениям, если их не проверять.
Сначала я решил расширить Object вместо Object.prototype , главным образом потому, что null не может быть одним из объектов сравнения, и я считаю, что null должен быть допустимым объектом для сравнения с другим. Есть также другие законные проблемы, отмеченные другими в отношении расширения Object.prototype относительно возможных побочных эффектов на код другого.
Особое внимание необходимо уделить тому, чтобы JavaScript позволял устанавливать свойства объекта неопределенными , т. Е. Существуют свойства, значения которых установлены неопределенными . Приведенное выше решение проверяет, что оба объекта имеют одинаковые свойства, для которых установлено значение undefined, чтобы сообщить о равенстве. Это может быть достигнуто только путем проверки существования свойств с помощью Object.hasOwnProperty (property_name) . Также обратите внимание, что JSON.stringify () удаляет свойства, для которых установлено значение undefined , и поэтому при сравнении с использованием этой формы игнорируются свойства, для которых установлено значение undefined .
Функции следует считать равными только в том случае, если они имеют одну и ту же ссылку, а не только один и тот же код, поскольку это не будет учитывать прототип этих функций. Таким образом, сравнение строки кода не дает гарантии, что у них один и тот же объект-прототип.
Два объекта должны иметь одинаковую цепочку прототипов , а не только одинаковые свойства. Это можно проверить только в кросс-браузерном режиме, сравнив конструктор обоих объектов на предмет строгого равенства. ECMAScript 5 позволит протестировать их фактический прототип, используя Object.getPrototypeOf () . Некоторые веб-браузеры также предлагают свойство __proto__, которое делает то же самое. Возможное улучшение приведенного выше кода позволит использовать один из этих методов, когда это возможно.
Использование строгих сравнений здесь имеет первостепенное значение, потому что 2 не следует считать равным «2,0000» , а ложное не следует считать равным нулю , неопределенному или 0 .
Соображения эффективности побуждают меня сравнивать равенство свойств как можно скорее. Тогда, только если это не удалось, ищите typeof этих свойств. Увеличение скорости может быть значительным для больших объектов с большим количеством скалярных свойств.
Не требуется более двух циклов: первый проверяет свойства из левого объекта, второй проверяет свойства справа и проверяет только существование (не значение), чтобы перехватить эти свойства, которые определены с неопределенным значением.
В целом этот код обрабатывает большинство угловых случаев только в 16 строках кода (без комментариев).
Обновление (13.08.2015) . Я реализовал лучшую версию, так как функция value_equals (), которая работает быстрее, правильно обрабатывает угловые случаи, такие как NaN и 0, отличные от -0, опционально предписывая порядок свойств объектов и проверяя циклические ссылки, опираясь на более чем 100 автоматических тестов. как часть тестового пакета проекта Toubkal .
Автор: Jean Vincent
Размещён: 15.07.2011 10:21
Как сравнить массивы в JavaScript?
Практический Путь
я думаю, что неправильно говорить, что конкретная реализация — это » правильный путь™», если это только «правильное» («правильное») в отличие от «неправильного» решения. Решение Томаша является явным улучшением по сравнению со строковым сравнением массивов, но это не означает, что оно объективно «правильно». Что такое право? Это самый быстрый? Он самый гибкий? Это легче всего понять? Это самый быстрый для отладки? Использует ли он наименьшие операции? Делает у него есть побочные эффекты? Ни одно решение не может иметь лучшего из всех.
Томаш мог бы сказать, что его решение быстро, но я бы также сказал, что это бесполезно сложно. Он пытается быть все-в-одном решение, которое работает для всех массивов, вложенных или нет. Фактически, он даже принимает больше, чем просто массивы в качестве входных данных, и все еще пытается дать «действительный» ответ.
дженерики предлагают возможность повторного использования
мой ответ будет подойти к проблеме по-другому. Я начну с общего arrayCompare
процедура, которая касается только шага через массивы. Оттуда мы построим наши другие основные функции сравнения, такие как arrayEqual
и arrayDeepEqual
, etc
// arrayCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayCompare = f => ([x,...xs]) => ([y,...ys]) =>
x === undefined && y === undefined
? true
: Boolean (f (x) (y)) && arrayCompare (f) (xs) (ys)
на мой взгляд, лучший вид кода даже не нуждается в комментариях, и это не исключение. Здесь так мало происходит, что вы можете понять поведение этой процедуры почти без каких-либо усилий. Конечно, некоторые синтаксисы ES6 могут показаться вам чуждыми, но это только потому, что ES6 относительно новый.
как подсказывает тип,arrayCompare
принимает функцию сравнения, f
, и два входных массива,xs
и ys
. По большей части, все, что мы делаем, это называем f (x) (y)
для каждого элемента входного массива. Мы возвращаемся рано false
если пользовательская f
возвращает false
– спасибо &&
оценка короткого замыкания. Так что да, это означает, что компаратор может остановить итерацию раньше и предотвратить цикл через остальную часть входной массив при необходимости.
строгое сравнение
далее используя наш arrayCompare
функция, мы можем легко создать другие функции, которые нам могут понадобиться. Начнем с элементарного arrayEqual
…
// equal :: a -> a -> Bool
const equal = x => y =>
x === y // notice: triple equal
// arrayEqual :: [a] -> [a] -> Bool
const arrayEqual =
arrayCompare (equal)
const xs = [1,2,3]
const ys = [1,2,3]
console.log (arrayEqual (xs) (ys)) //=> true
// (1 === 1) && (2 === 2) && (3 === 3) //=> true
const zs = ['1','2','3']
console.log (arrayEqual (xs) (zs)) //=> false
// (1 === '1') //=> false
просто. arrayEqual
можно определить с помощью arrayCompare
и функция компаратора, которая сравнивает a
to b
используя ===
(строгое равенство).
обратите внимание, что мы также определить equal
как это собственная функция. Этот подчеркивает роль arrayCompare
как функция более высокого порядка для использования нашего компаратора первого порядка в контексте другого типа данных (массива).
Свободная сравнения
мы могли бы так же легко определено arrayLooseEqual
С помощью ==
вместо. Теперь при сравнении 1
(номер) в '1'
(строка), результат будет true
…
// looseEqual :: a -> a -> Bool
const looseEqual = x => y =>
x == y // notice: double equal
// arrayLooseEqual :: [a] -> [a] -> Bool
const arrayLooseEqual =
arrayCompare (looseEqual)
const xs = [1,2,3]
const ys = ['1','2','3']
console.log (arrayLooseEqual (xs) (ys)) //=> true
// (1 == '1') && (2 == '2') && (3 == '3') //=> true
глубокое сравнение (рекурсивное)
вы, вероятно, заметили, что это только поверхностное сравнение tho. Конечно, решение Томаша — » правильный путь™», потому что оно делает неявное глубокое сравнение, верно ?
наши arrayCompare
процедура достаточно универсальна, чтобы использовать таким образом, что делает глубокий тест равенства ветер…
// isArray :: a -> Bool
const isArray =
Array.isArray
// arrayDeepCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayDeepCompare = f =>
arrayCompare (a => b =>
isArray (a) && isArray (b)
? arrayDeepCompare (f) (a) (b)
: f (a) (b))
const xs = [1,[2,[3]]]
const ys = [1,[2,['3']]]
console.log (arrayDeepCompare (equal) (xs) (ys)) //=> false
// (1 === 1) && (2 === 2) && (3 === '3') //=> false
console.log (arrayDeepCompare (looseEqual) (xs) (ys)) //=> true
// (1 == 1) && (2 == 2) && (3 == '3') //=> true
просто. Мы строим глубокий компаратор, используя другое функция более высокого порядка. На этот раз мы оборачиваемся arrayCompare
используя пользовательский компаратор, который проверит, если a
и b
массивы. Если так, apply arrayDeepCompare
иначе сравниваем a
и b
на указанный компаратор (f
). Это позволяет нам отделить поведение глубокого сравнения от того, как мы фактически сравниваем отдельные элементы. Т. е., как показано в примере выше, мы можем глубоко сравнить, используя equal
, looseEqual
, или любой другой компаратор мы делаем.
, потому что arrayDeepCompare
Карри, мы можем частично применить его, как и в предыдущих примерах тоже
// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
arrayDeepCompare (equal)
// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
arrayDeepCompare (looseEqual)
для меня это уже явное улучшение по сравнению с решением Томаша, потому что я могу явно выберите мелкое или глубокое сравнение для моих массивов, по мере необходимости.
сравнение объектов (пример)
а что, если у вас есть массив объектов или что-то ? Возможно, вы хотите считать эти массивы «равными», если каждый объект имеет одинаковое id
значение …
// idEqual :: {id: Number} -> {id: Number} -> Bool
const idEqual = x => y =>
x.id !== undefined && x.id === y.id
// arrayIdEqual :: [a] -> [a] -> Bool
const arrayIdEqual =
arrayCompare (idEqual)
const xs = [{id:1}, {id:2}]
const ys = [{id:1}, {id:2}]
console.log (arrayIdEqual (xs) (ys)) //=> true
// (1 === 1) && (2 === 2) //=> true
const zs = [{id:1}, {id:6}]
console.log (arrayIdEqual (xs) (zs)) //=> false
// (1 === 1) && (2 === 6) //=> false
просто. Здесь я использовал объекты vanilla JS, но этот тип компаратора мог работа для любой тип объекта; даже пользовательские объекты. Решение Томаша необходимо будет полностью переработать, чтобы поддержать такой тест на равенство
глубокий массив с объектами? Не проблема. Мы построили очень универсальные, общие функции, поэтому они будут работать в самых разных случаях использования.
const xs = [{id:1}, [{id:2}]]
const ys = [{id:1}, [{id:2}]]
console.log (arrayCompare (idEqual) (xs) (ys)) //=> false
console.log (arrayDeepCompare (idEqual) (xs) (ys)) //=> true
произвольное сравнение (пример)
или что, если вы хотите сделать какой-то другой вид совершенно произвольного сравнения ? Возможно Я хочу знать, если каждый x
больше, чем друг y
…
// gt :: Number -> Number -> Bool
const gt = x => y =>
x > y
// arrayGt :: [a] -> [a] -> Bool
const arrayGt = arrayCompare (gt)
const xs = [5,10,20]
const ys = [2,4,8]
console.log (arrayGt (xs) (ys)) //=> true
// (5 > 2) && (10 > 4) && (20 > 8) //=> true
const zs = [6,12,24]
console.log (arrayGt (xs) (zs)) //=> false
// (5 > 6) //=> false
меньше
вы можете видеть, что мы на самом деле делаем больше с меньшим кодом. Нет ничего сложного в arrayCompare
сам и каждый из пользовательских компараторов, которые мы сделали, имеют очень простую реализацию.
с легкостью мы можем точно определить, как мы хотим, чтобы два массива сравнивались-мелкие, глубокие, строгие, свободные, некоторые свойства объекта или некоторые произвольные вычисления или любая их комбинация … —122—>все с помощью одной процедуры, arrayCompare
. Может быть, даже выдумать RegExp
компаратор ! Я знаю, как дети любят эти регэкспы …
это самый быстрый? Нет. Но, возможно, и не нужно. Если скорость-единственная метрика, используемая для измерения качества нашего кода, много действительно отличного кода будет выброшено — вот почему я называю этот подход Практический Путь. Или может быть более справедливым, A Практичный Способ. Это описание подходит для этого ответа, потому что я не говорю, что этот ответ только практичен по сравнению с каким-то другим ответом; это объективно верно. Мы достигли высокой степени практичности с очень небольшим кодом, о котором очень легко рассуждать. Никакой другой код не может сказать, что мы не заслужили этого описания.
это делает его «правильным» решением для вас ? Это для вы решать. И никто другой не может этого сделать. только вы знаете, что вам нужно. Почти во всех случаях я ценю простой, практичный и универсальный код над умным и быстрым видом. То, что вы цените, может отличаться, поэтому выберите то, что работает для вас.
Edit
мой старый ответ был больше сосредоточен на разложении arrayEqual
на мелкие процедуры. Это интересное упражнение, но не самый лучший (самый практичный) способ подойти к этой проблеме. Если вы заинтересованы, вы можете увидеть эту историю.
Как сравнить 2 объекта в JavaScript 🎉
Объекты являются ссылочными типами, поэтому вы не можете просто использовать ===
или ==
для сравнения двух объектов. Один из быстрых способов сравнить, имеют ли два объекта одинаковое значение ключа, — использовать JSON.stringify
. Другой способ — использовать функцию Lodash isEqual
👏
Deep Nested Comparison
Да, два способа также работают для глубоких вложенных объектов.
Какой из них использовать?
Ну это зависит.Для JSON.stringify ()
порядок имеет значение. Поэтому, если пара «ключ-значение» упорядочена по-разному в двух объектах, но они одинаковы, она вернет false. Принимая во внимание, что в Lodash это не имеет значения isEqual
, оно вернет истину, если существует пара «ключ-значение».
Вот моя рекомендация. Для быстрого и грязного решения я бы использовал JSON.stringify ()
. Но для более надежного решения, охватывающего большее количество странных случаев, используйте способ Lodash.
ES6 Способ сравнения 2 объектов
Это решение, предложенное @ mustafauzun0.Однако следует отметить несколько моментов: он не работает с вложенными объектами, и порядок ключей важен. Идея, лежащая в основе этого, похожа на способ стрингификации. Он превращает объект в строку и сравнивает, совпадают ли строки. По сути, это сравнение равенства двух строк. Вот почему порядок имеет значение.
Спасибо: @ mustafauzun0
JSON.stringify против isEqual Performance Lodash
Коул Тернер: Стоит отметить, что объекты не гарантируют порядок сортировки, а stringify будет стоить дороже в производительности, потому что она должна сериализовать объект целиком, тогда как lodash может выйти раньше, если обнаружит несоответствующий ключ.Это всплывает в интервью 🙂
Спасибо: @coleturner
blnkdotspace: Lodash значительно быстрее. Потому что lodash завершится, как только достигнет первой разницы, но stringify без необходимости идет до конца. Если у вас есть небольшие массивы, это нормально, но для моих личных случаев использования с более чем 1000 событий / продуктов в списке я бы никогда не использовал Stringify.
Спасибо: @blnkdotspace
Ресурсы
.
Сравнение объектов javascript: равно и строго равно
Переполнение стека
- Около
Продукты
- Для команд
Переполнение стека
Общественные вопросы и ответыПереполнение стека для команд
Где разработчики и технологи делятся частными знаниями с коллегамиВакансии
Программирование и связанные с ним технические возможности карьерного ростаТалант
Нанимайте технических специалистов и создавайте свой бренд работодателяРеклама
Обратитесь к разработчикам и технологам со всего мира- О компании
Загрузка…
.