Суть Прочитайте четыре маленькие программы и протрассируйте их вручную: предскажите число итераций loop и вывод, найдите off-by-one на границе и разберите short-circuit как цепочку conditional jump.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min
Единственный надёжный способ узнать, что делает branch или loop, — протрассировать его так, как это делает CPU: следить за program counter и переменными, по одному шагу. Прочитайте каждый сниппет, предскажите результат вручную, затем проверьте себя.
Цель
Отработайте главный навык unit: проходить conditional и loop вручную, точно считать итерации, находить off-by-one, переворачивающий границу, и рассуждать о short-circuit как о цепочке conditional jump, в которую он компилируется.
Сниппет 1 — посчитайте итерации
let count = 0;let i = 1;while (i <= 5) { // обратите внимание: <= , не < count++; i++;}// чему равно count?
Викторина
Completed
Каково итоговое значение count?
Heads-up Проверка — это i <= 5, что включает 5, поэтому тело исполняется для i = 1..5 (пять раз). Четыре раза было бы только если бы проверка была i < 5.
Heads-up Когда i становится 6, while-проверка i <= 5 ложна, поэтому тело для 6 не исполняется. Backward jump не берётся, и loop выходит, не увеличив count снова.
Heads-up i++ выполняется каждую итерацию, поэтому сравниваемое значение меняется и проверка в итоге проваливается при i = 6. Loop завершается.
Сниппет 2 — граница off-by-one
const a = [10, 20, 30]; // длина 3, валидные индексы 0,1,2let i = 0;while (i <= a.length) { // <= length, не < length console.log(a[i]); i++;}
Викторина
Completed
Сколько раз исполняется тело loop и в чём баг?
Heads-up i <= a.length истинно для i = 0,1,2 и ещё для i = 3 (ведь 3 <= 3). Тело исполняется четыре раза и читает a[3], которого не существует.
Heads-up i++ выполняется каждый проход, поэтому i доходит до 3, затем до 4; при i = 4 проверка 4 <= 3 ложна и loop выходит. Не бесконечный — просто на один проход больше.
Heads-up Проверка сравнивает i с a.length, а не индексирует им. Первая проверка — 0 <= 3 (истина), поэтому тело исполняется; ошибка границы в конце, а не в начале.
Сниппет 3 — short-circuit как цепочка jump
function check(user) { // && останавливается на первом ложном операнде if (user && user.active && user.age >= 18) { return "allowed"; } return "denied";}check(null); // user равен null
Викторина
Completed
check(null) возвращает 'denied'. Почему user.active никогда не вычисляется, в терминах машинного уровня?
Heads-up && делает short-circuit: останавливается на первом ложном операнде. Чтение user.active у null-user на самом деле бросило бы ошибку — short-circuit именно это и предотвращает, перепрыгивая эти чтения.
Heads-up Операнды вычисляются слева направо. user проверяется первым; будучи null (ложным), conditional jump пропускает остальное до того, как user.active или user.age вообще прочитаны.
Heads-up Он никогда не вычисляется. Conditional jump после проверки user полностью переносит управление за эти чтения, поэтому на null-user ошибка не бросается.
Сниппет 4 — вложенный loop и conditional
let sum = 0;for (let i = 0; i < 3; i++) { // внешний: i = 0,1,2 for (let j = 0; j < 3; j++) { // внутренний: j = 0,1,2 if (i === j) { // только диагональ sum += 1; } }}// чему равно sum?
Викторина
Completed
Каково итоговое значение sum?
Heads-up Внутреннее тело исполняется 9 раз, но sum += 1 защищён if (i === j). Только 3 диагональные пары проходят проверку; остальные 6 берут jump пропуска.
Heads-up Всё наоборот: sum увеличивают только диагональные пары (где i === j). 6 внедиагональных пар пропускаются conditional jump.
Heads-up i === j истинно раз на каждую внешнюю итерацию, а их 3, поэтому это истинно 3 раза: (0,0), (1,1), (2,2).
Итог
Трассировка вручную — это весь навык. Число итераций loop — ровно столько, сколько раз его проверка остаётся истинной, а оператор границы (< против <=) — то место, где живут баги off-by-one: здесь чтение на один элемент за концом массива из 3 элементов. Short-circuit && компилируется в цепочку conditional jump, уходящих на ложный путь в момент, когда операнд становится ложным, — поэтому последующие операнды даже не читаются (и поэтому проверка на null защищает чтения после неё). Вложенные loop перемножают число итераций, но защищающий if всё равно срабатывает только при своём условии. Если сомневаетесь — проводите program counter и переменные по одному шагу.