Работа с данными в языке 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, используйте строгое сравнение.