Суть Читай короткие сниппеты TypeScript — aliasing, копирование vs reference, scope shadowing и swap — и предсказывай, какое значение держит каждая переменная и почему.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min
Быстрее всего проверить, уложилась ли модель памяти, — протрассировать реальный код вручную. Для каждого сниппета следи за ячейками: где значение, где reference, к какому binding разрешается имя и какие записи — это mutation общего объекта. Предскажи вывод, прежде чем смотреть варианты.
Цель
Потренируйся трассировать, что держит каждая переменная после каждой строки — отличая скопированный примитив от разделяемого reference, внутренний binding от внешнего и mutation на месте от свежего выделения памяти.
Сниппет 1 — общий объект
const original = { count: 0 };const copy = original; // присваивает reference, а не объектcopy.count = 5; // mutation через copyconsole.log(original.count); // ?
Викторина
Completed
Что выведет последняя строка и почему?
Heads-up Assignment копирует reference, а не объект. Объект один; оба имени — его алиасы. Чтобы получить независимый дубликат, надо копировать явно, например { ...original }.
Heads-up const запечатывает binding (нельзя copy = что-то другое), но не замораживает объект. Mutation свойства объекта по reference при const разрешена.
Heads-up Assignment никогда не очищает источник. Он копирует значение-reference в ячейку copy; original сохраняет свой reference и поле, которое затем mutation меняет на 5.
Сниппет 2 — примитив и объект рядом
let n = 10;let m = n; // копирует значение 10m = m + 1; // меняет собственную ячейку mlet p = { v: 10 };let q = p; // копирует referenceq.v = q.v + 1; // мутирует общий объектconsole.log(n, q === p, p.v); // ?
Викторина
Completed
Что будет выведено?
Heads-up Примитивы копируются по значению; m и n — отдельные ячейки. Изменение m никогда не трогает n. Разделяется только объектная сторона, поэтому меняется p.v, а не n.
Heads-up 'let q = p' копирует reference, поэтому q и p указывают на один объект: q === p — true, а mutation через q видна как p.v === 11.
Heads-up Если q === p, за обоими именами ровно один объект. Mutation через любое имя — та же запись, поэтому p.v становится 11.
Сниппет 3 — scope shadowing
let x = 1;{ let x = 2; // новый binding во внутреннем блоке x = x + 10; // пишет в ячейку внутреннего x console.log(x); // внутренний}console.log(x); // внешний
Викторина
Completed
Что выведут две строки console.log, по порядку?
Heads-up 'let x' внутри блока создаёт новый binding на новую ячейку. Внутри блока имя x разрешается в эту внутреннюю ячейку; внешний x сохраняет своё значение 1.
Heads-up Внутренний x — обычный let; 'x = x + 10' читает 2 и пишет 12 во внутреннюю ячейку. Блок свободно мутирует свой binding.
Heads-up Повторное объявление x во вложенном блоке легально: у каждого scope свой binding. Внутреннее объявление затеняет внешнее, а не конфликтует с ним.
Сниппет 4 — swap
let a = 1;let b = 2;a = b;b = a;console.log(a, b); // ?
Викторина
Completed
Что будет выведено и какой фикс, если задумывался обмен a и b?
Heads-up После 'a = b' старое значение a (1) пропало. 'b = a' читает новое a (2), поэтому оба заканчиваются на 2. Корректный swap сначала сохраняет исходное во временную.
Heads-up Они не гасятся. 'a = b' перезаписывает a на месте на 2; исходная 1 потеряна до выполнения 'b = a', поэтому b становится 2, а не 1.
Heads-up Assignment пишет правое значение в левую ячейку, а не наоборот. 'a = b' делает a равным 2; оба заканчиваются на 2.
Итог
Трассировка вручную — это и есть навык: скопированный примитив (m, n) живёт в своей ячейке и игнорирует изменения другого; скопированный reference (p, q) делает оба имени алиасами одного объекта, поэтому mutation распространяется и === — true; внутренний ‘let’ создаёт новый binding, который затеняет внешнее имя, не трогая его ячейку; а наивный ‘a = b; b = a’ проваливается, потому что каждый assignment — разрушающая перезапись, поэтому реальный swap требует временную. Следи за ячейками — и ответ никогда не будет сюрпризом.