Работа с данными в языке JavaScript: от объявления до глубокого копирования

Раздел: JavaScript -> Основы JavaScript

Данные в JavaScript: примитивы и объекты

Основа эффективной работы с данными в JavaScript - понимание двух категорий типов: примитивных (числа, строки, булевы значения, null, undefined, Symbol, BigInt) и ссылочных (объекты, массивы, функции). Примитивы хранятся и передаются по значению, объекты - по ссылке. Это различие определяет поведение при присваивании, сравнении и копировании.


let a = 10;
let b = a;
b = 20;
console.log(a); // 10 - копия значения

let obj1 = { x: 1 };
let obj2 = obj1;
obj2.x = 2;
console.log(obj1.x); // 2 - ссылка
  

Js json (json в javascript)

Типичная ошибка:

Неявное изменение объекта через другую переменную. Например, передача объекта в функцию и мутация внутри неё может привести к неожиданным побочным эффектам.

Как объявить переменную с нужным типом данных?

Используйте let для изменяемых, const для неизменяемых ссылок. var устарел. Тип определяется значением, а не объявлением.


let age = 25;        // number
const name = 'Анна'; // string
let isActive = true; // boolean
let data = null;     // null (typeof вернёт 'object')
let undef;           // undefined
let sym = Symbol('id'); // Symbol
let big = 123n;      // BigInt
  

Js данные (данные в javascript)

Частая проблема:

Попытка изменить значение, объявленное через const, для примитива вызовет ошибку. Для объектов const защищает только саму ссылку, не содержимое.

Как преобразовать один тип данных в другой?

Явное преобразование: Number(), String(), Boolean(). Неявное происходит при операциях (сложение строк, логические контексты).


let str = '123';
let num = Number(str);     // 123
let bool = Boolean(num);   // true
let againStr = String(num); // '123'

// Неявное
console.log('5' * 2); // 10 (строка преобразуется в число)
console.log(!!0);     // false (0 -> false)
  

Js значение (значения в javascript)

Ошибка:

При сложении строк и чисел '5' + 2 даёт '52', а не 7. Неявное преобразование может быть неочевидным.

Решение: всегда использовать явное приведение типов для ясности.

Как правильно проверить тип данных?

Основные способы: typeof (для примитивов, но с оговорками), instanceof (для объектов), Object.prototype.toString.call() (точная диагностика).


console.log(typeof 42);           // 'number'
console.log(typeof 'hello');      // 'string'
console.log(typeof true);         // 'boolean'
console.log(typeof undefined);    // 'undefined'
console.log(typeof null);         // 'object' - известная проблема
console.log(typeof Symbol());     // 'symbol'
console.log(typeof 123n);         // 'bigint'

console.log([] instanceof Array); // true
console.log({} instanceof Object);// true

console.log(Object.prototype.toString.call([])); // '[object Array]'
console.log(Object.prototype.toString.call(null)); // '[object Null]'
  

Javascript let (оператор let в javascript)

Ошибка:

typeof null === 'object' - баг языка, который не исправлен. Для точной проверки на null используйте value === null.

Также typeof для массивов вернёт 'object', поэтому применяйте Array.isArray().

Как корректно сравнивать данные разных типов?

Нестрогое равенство == приводит типы, строгое === - нет. Для сравнения объектов сравниваются ссылки, не содержимое. Используйте Object.is() для специальных случаев (NaN, -0).


console.log(5 == '5');   // true (приведение)
console.log(5 === '5');  // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
console.log(+0 === -0);  // true
console.log(Object.is(+0, -0)); // false

let a = { x: 1 };
let b = { x: 1 };
console.log(a === b);    // false (разные ссылки)
  

Js code (код на javascript)

Типичная ошибка:

Сравнение объектов через === даёт false, если это разные экземпляры. Для сравнения содержимого используйте ручную проверку или библиотеки (lodash.isEqual).

Как скопировать объект или массив без ссылки?

Поверхностное копирование: Object.assign() или спред-оператор. Глубокое копирование: structuredClone() (современный метод) или JSON.parse(JSON.stringify()) (с ограничениями).


let original = { a: 1, b: { c: 2 } };

// Поверхностное
let shallow = { ...original };
shallow.b.c = 99;
console.log(original.b.c); // 99 - мутация вложенного объекта

// Глубокое через structuredClone
let deep = structuredClone(original);
deep.b.c = 77;
console.log(original.b.c); // 99 - не изменилось

// Через JSON (не работает с функциями, undefined, Symbol)
let jsonCopy = JSON.parse(JSON.stringify(original));
  

Проблема:

JSON.parse(JSON.stringify()) теряет функции, undefined, Symbol, даты преобразует в строки. structuredClone не поддерживается в старых средах, но современный браузеры и Node.js 17+ имеют.

Как отличить null от undefined и правильно с ними работать?

null - явное указание на отсутствие значения, undefined - значение не присвоено. Проверка: value === null или value === undefined. Используйте опциональную цепочку ?. для безопасного доступа.


let user = { name: 'John', address: null };
console.log(user.address?.city); // undefined (без ошибки)
console.log(user?.profile?.age); // undefined

if (user.address === null) {
  console.log('Адрес не указан');
}
  

Ошибка:

Попытка доступа к свойству у null или undefined вызывает TypeError. Всегда проверяйте значения перед доступом, особенно при работе с API.

Зачем нужны Symbol и BigInt и как их использовать?

Symbol создаёт уникальные ключи для свойств объектов, предотвращая коллизии. BigInt позволяет работать с целыми числами произвольной точности.


let sym1 = Symbol('id');
let sym2 = Symbol('id');
console.log(sym1 === sym2); // false

let obj = { [sym1]: 123 };
console.log(obj[sym1]); // 123

let big = 123456789012345678901234567890n;
console.log(big + 1n); // 123456789012345678901234567891n
  

Проблема:

BigInt нельзя смешивать с обычными числами в арифметических операциях (нужно явное преобразование). Symbol не сериализуется в JSON - свойства с Symbol ключами будут пропущены при JSON.stringify().

Расширенные примеры работы с данными

Глубокое копирование через structuredClone vs JSON

Пример

// structuredClone поддерживает Date, Map, Set, RegExp, Blob и др.
const original = {
  date: new Date(2023, 0, 1),
  map: new Map([['a', 1]]),
  nested: { arr: [1, 2, [3]] }
};

const clone = structuredClone(original);
console.log(clone.date instanceof Date); // true
console.log(clone.map instanceof Map);  // true
clone.nested.arr[2][0] = 99;
console.log(original.nested.arr[2][0]); // 3 - не изменилось
// Результат: копирование прошло успешно, вложенные объекты не связаны.

Сравнение содержимого объектов с помощью кастомной функции

Пример

function deepEqual(a, b) {
  if (a === b) return true;
  if (typeof a !== 'object' || a === null ||
      typeof b !== 'object' || b === null) return false;
  const keysA = Object.keys(a), keysB = Object.keys(b);
  if (keysA.length !== keysB.length) return false;
  for (let key of keysA) {
    if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
  }
  return true;
}

console.log(deepEqual({ x: 1, y: { z: 2 } }, { x: 1, y: { z: 2 } })); // true
console.log(deepEqual({ x: 1 }, { x: '1' })); // false
// Результат: функция корректно сравнивает вложенные объекты.

Неявное преобразование в условии if

Пример

// falsy: false, 0, '', null, undefined, NaN
// всё остальное truthy
if ('0') console.log('truthy'); // выведется
if (0) console.log('falsy');     // не выведется
if ([]) console.log('truthy');  // выведется (пустой массив truthy)

// Сравнение с помощью == может привести к неожиданностям
console.log([] == false);  // true (пустой массив преобразуется в '', затем в 0, false -> 0)
console.log([] === false); // false
// Результат: неявное приведение типов может давать неинтуитивные результаты.

Использование Symbol как уникальных метаданных

Пример

const WITHDRAW = Symbol('withdraw');
class BankAccount {
  constructor(balance) {
    this.balance = balance;
    this[WITHDRAW] = function(amount) {
      if (amount <= this.balance) {
        this.balance -= amount;
        return true;
      }
      return false;
    };
  }
}

const acc = new BankAccount(100);
console.log(acc[WITHDRAW](30)); // true
console.log(acc.balance);       // 70
// Свойства с ключами Symbol не перечисляются в for...in и Object.keys
console.log(Object.keys(acc)); // ['balance']
// Результат: Symbol позволяет скрыть метод от случайного доступа.

BigInt и потеря точности

Пример

// Обычное число double может точно представить только до 2^53
console.log(9007199254740992 + 1); // 9007199254740992 (потеря точности)

// BigInt решает проблему
console.log(9007199254740992n + 1n); // 9007199254740993n

// Преобразование между BigInt и Number требует явного указания
let large = 12345678901234567890n;
console.log(Number(large)); // 12345678901234567000 (потеря точности!)
console.log(BigInt(123));   // 123n
// Результат: BigInt обеспечивает точность, но преобразование в Number может дать округление.

typeof null и обходной путь

Пример

const value = null;
console.log(typeof value); // 'object' - историческая ошибка

// Корректная проверка на null
if (value === null) {
  console.log('Это null');
}

// Проверка на null или undefined
if (value == null) {
  console.log('null или undefined');
}
// Результат: typeof не работает для null, используйте строгое сравнение.

Данные в JavaScript - comments

En
Js данные (javascript)