[Medium] 📄 Примитивные и ссылочные типы
1. Что такое примитивные и ссылочные типы (Primitive Types и Reference Types)?
Что такое примитивные и ссылочные типы?
Типы данных JavaScript можно разделить на две категории: примитивные типы и ссылочные типы. Они принципиально различаются в поведении памяти и семантике передачи.
Примитивные типы (Primitive Types)
Характеристики:
- Хранятся как непосредственные значения (концептуально в стеке)
- Передаются копированием значения
- Неизменяемы (immutable)
7 примитивных типов:
// 1. String
const str = 'hello';
// 2. Number
const num = 42;
// 3. Boolean
const bool = true;
// 4. Undefined
let undef;
// 5. Null
const n = null;
// 6. Symbol (ES6)
const sym = Symbol('unique');
// 7. BigInt (ES2020)
const bigInt = 9007199254740991n;
Ссылочные типы (Reference Types)
Характеристики:
- Объекты размещаются в куче (heap)
- Переменные хранят ссылки (адреса)
- Изменяемы (mutable)
Примеры:
// 1. Object
const obj = { name: 'John' };
// 2. Array
const arr = [1, 2, 3];
// 3. Function
const func = function () {};
// 4. Date
const date = new Date();
// 5. RegExp
const regex = /abc/;
// 6. Map, Set, WeakMap, WeakSet (ES6)
const map = new Map();
const set = new Set();
2. Передача по значению vs передача по ссылке (Call by Value vs Call by Reference)
Передача по значению vs передача по ссылке
Передача по значению (поведение примитивов)
Поведение: значение копируется; изменение копии не влияет на оригинал.
let a = 10;
let b = a; // копирование значения
b = 20;
console.log(a); // 10
console.log(b); // 20
Диаграмма памяти:
┌─────────┐
│ Стек │
├─────────┤
│ a: 10 │ <- н езависимое значение
├─────────┤
│ b: 20 │ <- независимое значение после копирования/обновления
└─────────┘
Поведение ссылок (объекты)
Поведение: копируется ссылка; обе переменные могут указывать на один и тот же объект.
let obj1 = { name: 'John' };
let obj2 = obj1; // копирование ссылки
obj2.name = 'Jane';
console.log(obj1.name); // 'Jane'
console.log(obj2.name); // 'Jane'
console.log(obj1 === obj2); // true
Диаграмма памяти:
┌─────────┐ ┌──────────────────┐
│ Стек │ │ Куча │
├─────────┤ ├──────────────────┤
│ obj1 ───┼───────────────────>│ { name: 'Jane' } │
├─────────┤ │ │
│ obj2 ───┼───────────────────>│ (тот же объект) │
└─────────┘ └──────────────────┘
3. Распространённые задачи (Common Quiz Questions)
Распространённые задачи
Задача 1: передача примитивных значений
function changeValue(x) {
x = 100;
console.log('Inside function x:', x);
}
let num = 50;
changeValue(num);
console.log('Outside function num:', num);
Нажмите, чтобы увидеть ответ
// Inside function x: 100
// Outside function num: 50
Объяснение:
num— примитив (Number)- аргумент функции получает скопированное значение
- изменение
xне влияет наnum
// процесс
let num = 50; // Стек: num = 50
changeValue(num); // Стек: x = 50 (копия)
x = 100; // изменяется только x
console.log(num); // всё ещё 50
Задача 2: передача объектов
function changeObject(obj) {
obj.name = 'Changed';
console.log('Inside function obj.name:', obj.name);
}
let person = { name: 'Original' };
changeObject(person);
console.log('Outside function person.name:', person.name);
Нажмите, чтобы увидеть о твет
// Inside function obj.name: Changed
// Outside function person.name: Changed
Объяснение:
person— ссылочный тип (Object)- аргумент функции копирует ссылку
objиpersonуказывают на один и тот же объект
// схема памяти
let person = { name: 'Original' }; // куча @0x001
changeObject(person); // obj -> @0x001
obj.name = 'Changed'; // мутация @0x001
console.log(person.name); // чтение из @0x001
Задача 3: переприсваивание vs мутация свойства
function test1(obj) {
obj.name = 'Modified'; // мутация свойства
}
function test2(obj) {
obj = { name: 'New Object' }; // переприсваивание локального параметра
}
let person = { name: 'Original' };
test1(person);
console.log('A:', person.name);
test2(person);
console.log('B:', person.name);
Нажмите, чтобы увидеть ответ
// A: Modified
// B: Modified (не 'New Object')
Объяснение:
test1: мутация свойства
function test1(obj) {
obj.name = 'Modified'; // мутирует оригинальный объект
}
test2: переприсваивание
function test2(obj) {
obj = { name: 'New Object' }; // изменяет только локальную привязку
}
// person по-прежнему указывает на оригинальный объект
Схема памяти:
// до test1
person ---> { name: 'Original' }
obj ---> { name: 'Original' } (тот же)
// после test1
person ---> { name: 'Modified' }
obj ---> { name: 'Modified' } (тот же)
// внутри test2
person ---> { name: 'Modified' } (не изменился)
obj ---> { name: 'New Object' } (новый объект)
// после test2
person ---> { name: 'Modified' }
// локальный obj удалён
Задача 4: передач а массива
function modifyArray(arr) {
arr.push(4);
console.log('1:', arr);
}
function reassignArray(arr) {
arr = [5, 6, 7];
console.log('2:', arr);
}
let numbers = [1, 2, 3];
modifyArray(numbers);
console.log('3:', numbers);
reassignArray(numbers);
console.log('4:', numbers);
Нажмите, чтобы увидеть ответ
// 1: [1, 2, 3, 4]
// 3: [1, 2, 3, 4]
// 2: [5, 6, 7]
// 4: [1, 2, 3, 4]
Объяснение:
modifyArray: мутирует оригинальный массивreassignArray: только перепривязывает локальный параметр
Задача 5: сравнение на равенство
// примитивы
let a = 10;
let b = 10;
console.log('A:', a === b);
// ссылки
let obj1 = { value: 10 };
let obj2 = { value: 10 };
let obj3 = obj1;
console.log('B:', obj1 === obj2);
console.log('C:', obj1 === obj3);
Нажмите, чтобы увидеть ответ
// A: true
// B: false
// C: true
Объяснение:
Примитивы сравниваются по значению; объекты сравниваются по ссылке.
obj1 === obj2; // false (разные ссылки)
obj1 === obj3; // true (одна ссылка)
4. Поверхностное копирование vs глубокое копирование (Shallow Copy vs Deep Copy)
Поверхностное копирование vs глубокое копирование
Поверхностное копирование (Shallow Copy)
Определение: копируется только верхний уровень; вложенные объекты остаются общими ссылками.
Способ 1: оператор spread
const original = {
name: 'John',
address: { city: 'Taipei' },
};
const copy = { ...original };
copy.name = 'Jane';
console.log(original.name); // 'John'
copy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Kaohsiung' (затронут)
Способ 2: Object.assign()
const original = { name: 'John', age: 30 };
const copy = Object.assign({}, original);
copy.name = 'Jane';
console.log(original.name); // 'John'
Способ 3: поверхностное копирование массива
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
const arr3 = arr1.slice();
const arr4 = Array.from(arr1);
arr2[0] = 999;
console.log(arr1[0]); // 1
Глубокое копирование (Deep Copy)
Определение: все уровни копируются рекурсивно.
Способ 1: JSON.parse(JSON.stringify(...))
const original = {
name: 'John',
address: { city: 'Taipei' },
hobbies: ['reading', 'gaming'],
};
const copy = JSON.parse(JSON.stringify(original));
copy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Taipei'
copy.hobbies.push('coding');
console.log(original.hobbies); // ['reading', 'gaming']
Ограничения:
const obj = {
date: new Date(), // -> строка
func: () => {}, // игнорируется
undef: undefined, // игнорируется
symbol: Symbol('test'), // игнорируется
regexp: /abc/, // -> {}
circular: null, // циклическая ссылка выбрасывает ошибку
};
obj.circular = obj;
JSON.parse(JSON.stringify(obj)); // ошибка или потеря данных
Способ 2: structuredClone()
const original = {
name: 'John',
address: { city: 'Taipei' },
date: new Date(),
};
const copy = structuredClone(original);
console.log(copy.date instanceof Date); // true
Плюсы:
- Поддерживает Date, RegExp, Map, Set и т.д.
- Поддерживает циклические ссылки
- Обычно лучшая производительность, чем ручное глубокое клонирование
Ограничения:
- Не клонирует функции
- Не клонирует значения Symbol во всех паттернах использования
Способ 3: рекурсивное глубокое клонирование
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item));
}
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
const original = {
name: 'John',
address: { city: 'Taipei' },
hobbies: ['reading'],
date: new Date(),
};
const copy = deepClone(original);
copy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Taipei'
Способ 4: Lodash
import _ from 'lodash';
const original = {
name: 'John',
address: { city: 'Taipei' },
};
const copy = _.cloneDeep(original);