[Перевод] Реверс-инжиниринг одной строчки JavaScript |
Reverse Engineering One Line of JavaScript : https://t.co/QsTzYBvWbu cc @akras14
— Binni Shah (@binitamshah) July 13, 2017
code.js
, а p
закавычил в id="p"
.
k
— просто константа, так что убрал её из строчки и переименовал в delay
.var delay = 64;
var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var n = setInterval(draw, delay);
var draw
был просто строкой, которая исполнялась как функция eval
с периодичностью setInterval, поскольку setInterval может принимать и функции, и строки. Я перенёс var draw
в явную функцию, но сохранил изначальную строку для справки на всякий случай.p
в действительности ссылался на элемент DOM с идентификатором p
, объявленным в HTML, который я недавно закавычил. Оказывается, на элементы в JavaScript можно ссылаться по их идентификатору, если id состоит только из букв и цифр. Я добавил document.getElementById("p")
, чтобы сделать код понятнее.var delay = 64;
var p = document.getElementById("p"); // < --------------
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
for (n += 7, i = delay, P = 'p.\n'; i -= 1 / delay; P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
j = delay / i; p.innerHTML = P;
}
};
var n = setInterval(draw, delay);
i
, p
и j
и перенёс их в начало функции.var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
var i = delay; // < ---------------
var P ='p.\n';
var j;
for (n += 7; i > 0 ;P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
j = delay / i; p.innerHTML = P;
i -= 1 / delay;
}
};
var n = setInterval(draw, delay);
for
и преобразовал его в цикл while
. Из трёх частей прежнего for
осталась только одна часть CHECK_EVERY_LOOP, а всё остальное (RUNS_ONCE_ON_INIT; DO_EVERY_LOOP) перенёс за пределы цикла.var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
var i = delay;
var P ='p.\n';
var j;
n += 7;
while (i > 0) { // <----------------------
//Update HTML
p.innerHTML = P;
j = delay / i;
i -= 1 / delay;
P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2];
}
};
var n = setInterval(draw, delay);
( condition ? do if true : do if false) in P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2];
.i%2
проверяет, является переменная i
чётной или нечётной. Если она четная, то просто возвращает 2. Если нечётная, то возвращает «магическое» значение magic (i % 2 * j - j + n / delay ^ j) & 1;
(подробнее об этом чуть позже).index
и превратим строку в P += P[index];
.var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
var i = delay;
var P ='p.\n';
var j;
n += 7;
while (i > 0) {
//Update HTML
p.innerHTML = P;
j = delay / i;
i -= 1 / delay;
let index;
let iIsOdd = (i % 2 != 0); // <---------------
if (iIsOdd) { // <---------------
index = (i % 2 * j - j + n / delay ^ j) & 1;
} else {
index = 2;
}
P += P[index];
}
};
var n = setInterval(draw, delay);
& 1
из значения index = (i % 2 * j - j + n / delay ^ j) & 1
в ещё один оператор if
.&
— это побитовый оператор AND. Он работает так:something & 1
преобразует "something" в двоичное представление, а также добивает перед единицей необходимое количество нулей, чтобы соответствовать размеру "something", и возвращает просто результат AND последнего бита. Например, 5 в двоичном формате равняется 101
, так что если мы применим на ней логическую операцию AND с единицей, то получится следующее: 101
AND 001
001
0 & 1 // 0 - even return 0
1 & 1 // 1 - odd return 1
2 & 1 // 0 - even return 0
3 & 1 // 1 - odd return 1
4 & 1 // 0 - even return 0
5 & 1 // 1 - odd return 1
index
в magic
, так что код с развёрнутым &1
будет выглядеть следующим образом:var delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
var i = delay;
var P ='p.\n';
var j;
n += 7;
while (i > 0) {
//Update HTML
p.innerHTML = P;
j = delay / i;
i -= 1 / delay;
let index;
let iIsOdd = (i % 2 != 0);
if (iIsOdd) {
let magic = (i % 2 * j - j + n / delay ^ j);
let magicIsOdd = (magic % 2 != 0); // &1 < --------------------------
if (magicIsOdd) { // &1 <--------------------------
index = 1;
} else {
index = 0;
}
} else {
index = 2;
}
P += P[index];
}
};
var n = setInterval(draw, delay);
P += P[index];
в оператор switch
. К этому моменту стало понятно, что index
может принимать только одно из трёх значений — 0, 1 или 2. Также понятно, что переменная P
всегда инициализируется со значениями var P ='p.\n';
, где 0 указывает на p
, 1 указывает на .
, а 2 указывает на \n
— символ новой строкиvar delay = 64;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
var draw = function() {
var i = delay;
var P ='p.\n';
var j;
n += 7;
while (i > 0) {
//Update HTML
p.innerHTML = P;
j = delay / i;
i -= 1 / delay;
let index;
let iIsOdd = (i % 2 != 0);
if (iIsOdd) {
let magic = (i % 2 * j - j + n / delay ^ j);
let magicIsOdd = (magic % 2 != 0); // &1
if (magicIsOdd) { // &1
index = 1;
} else {
index = 0;
}
} else {
index = 2;
}
switch (index) { // P += P[index]; <-----------------------
case 0:
P += "p"; // aka P[0]
break;
case 1:
P += "."; // aka P[1]
break;
case 2:
P += "\n"; // aka P[2]
}
}
};
var n = setInterval(draw, delay);
var n = setInterval(draw, delay);
. Метод setInterval возвращает целые числа, начиная с единицы, увеличивая значение при каждом вызове. Это целое число может использоваться для clearInterval (то есть для отмены). В нашем случае setInterval вызывается всего один раз, а переменная n
просто установилась в значение 1.delay
в DELAY
для напоминания, что это всего лишь константа.i % 2 * j - j + n / DELAY ^ j
для указания, что у ^
(побитового XOR) меньший приоритет, чем у операторов %
, *
, -
, +
и /
. Другими словами, сначала выполнятся все вышеупомянутые вычисления, а уже потом ^
. То есть получается (i % 2 * j - j + n / DELAY) ^ j)
.p.innerHTML = P; //Update HTML
в цикл, так что я убрал его оттуда.const DELAY = 64; // approximately 15 frames per second 15 frames per second * 64 seconds = 960 frames
var n = 1;
var p = document.getElementById("p");
// var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
/**
* Draws a picture
* 128 chars by 32 chars = total 4096 chars
*/
var draw = function() {
var i = DELAY; // 64
var P ='p.\n'; // First line, reference for chars to use
var j;
n += 7;
while (i > 0) {
j = DELAY / i;
i -= 1 / DELAY;
let index;
let iIsOdd = (i % 2 != 0);
if (iIsOdd) {
let magic = ((i % 2 * j - j + n / DELAY) ^ j); // < ------------------
let magicIsOdd = (magic % 2 != 0); // &1
if (magicIsOdd) { // &1
index = 1;
} else {
index = 0;
}
} else {
index = 2;
}
switch (index) { // P += P[index];
case 0:
P += "p"; // aka P[0]
break;
case 1:
P += "."; // aka P[1]
break;
case 2:
P += "\n"; // aka P[2]
}
}
//Update HTML
p.innerHTML = P;
};
setInterval(draw, 64);
i
установлено на 64 посредством var i = DELAY;
, а затем каждый цикл оно уменьшается на 1/64 (0,015625) через i -= 1 / DELAY;
. Цикл продолжается, пока i
больше нуля (код while (i > 0) {
). Поскольку за каждый проход i
уменьшается на 1/64, то требуется 64 цикла, прежде чем оно уменьшится на единицу (64/64 = 1). В целом уменьшение i
произойдёт 64x64 = 4096 раз, чтобы уменьшиться до нуля.i
может быть чётным (не нечётным let iIsOdd = (i % 2 != 0);
), если i
является строго чётным числом. Такое произойдёт 32 раза, когда оно равняется 64, 62, 60 и т. д. Эти 32 раза index
примет значение 2 index = 2;
, а к строке добавится символ новой строки: P += "\n"; // aka P[2]
. Остальные 127 символов в строке примут значения p
или .
.p
, а когда .
?.
при нечётном значении let magic = ((i % 2 * j - j + n / DELAY) ^ j);
, или установить p
, если «магия» чётная.var P ='p.\n';
...
if (magicIsOdd) { // &1
index = 1; // second char in P - .
} else {
index = 0; // first char in P - p
}
magic
чётное, а когда нечётное? Это вопрос на миллион долларов. Перед тем как перейти к нему, давайте определим ещё кое-что.+ n/DELAY
из let magic = ((i % 2 * j - j + n / DELAY) ^ j);
, то получится статическая картинка, на которой вообще ничего не двигается:magic
без + n/DELAY
. Как получилась эта красивая картинка?(i % 2 * j - j) ^ j
j = DELAY / i;
i -= 1 / DELAY;
j
через конечное i
как j = DELAY/ (i + 1/DELAY)
. Но поскольку 1/DELAY слишком малое число, то для этого примера можно отбросить + 1/DELAY
и упростить выражение до j = DELAY/i = 64/i
.(i % 2 * j - j) ^ j
как i % 2 * 64/i - 64/i) ^ 64/i
.i%2
.64/i
, то получим такой график:(i % 2 * j - j) ^ j
принимает чётное значение, то нужно добавить p
, а для нечётного числа нужно добавить .
.i
имеет значения от 64 до 32.Math.floor
, который округляет число в меньшую сторону.j
начинается с единицы и медленно продвигается к двойке, останавливаясь прямо около неё, так что можем считать её всегда единицей при округлении в меньшую сторону (Math.floor(1.9999) === 1
), и нам нужна ещё одна единица с левой стороны, чтобы получить в результате ноль и дать нам p
.(i % 2 * j - j) ^ j
, она же i % 2 * i/64 — i/64
, то есть зелёная диагональ, тоже будет выше 1 или ниже -1.1 ^ 1 // 0 - even p
1.1 ^ 1.1 // 0 - even p
0.9 ^ 1 // 1 - odd .
0 ^ 1 // 1 - odd .
-1 ^ 1 // -2 - even p
-1.1 ^ 1.1 // -2 - even p
p
). Следующая выходит чуть дальше за эти границы, третья — ещё чуть дальше и т. д. Линия номер 16 едва удерживается в границах между 2 и -2. После линии 16 мы видим, что наш статический график меняет свой характер.j
пересекает лимит 2, так что меняется ожидаемый результат. Теперь мы получим чётное число, если зелёная диагональная линия выше 2 или ниже -2, или внутри рамок 1 и -1, но не соприкасается с ними. Вот почему мы видим на картинке две или больше групп символов p
начиная с 17-й строки.+ n/DELAY
. В коде мы видим, что значение n
начинается с 8 (1 от setInteval и плюс 7 на каждый вызов метода). Затем оно увеличивается на 7 при каждом срабатывании setInteval. j
по-прежнему находится около единицы, но теперь левая половина красной диагонали в пределах примерно 62-63 находится примерно около нуля, а правая половина в пределах примерно 63-64 — около единицы. Поскольку наши символы появляются в убывающем порядке от 64 к 62, то можно ожидать, что правая половина диагонали в районе 63-64 (1 ^ 1 = 0 // even) добавит кучку символов p
, а левая половина диагонали в районе 62-63 (1 ^ 0 = 1 // odd) добавит кучку точек. Всё это будет нарастать слева направо, как обычный текст.n
в редакторе CodePen и посмотреть). Это совпадает с нашими ожиданиями.p
выросло до постоянной величины. Например, в первом ряду половина всех значений всегда будут чётными. Теперь символы p
и .
будут только меняться местами.n
увеличивается на 7 на следующем вызове setInterval, график немного изменится.n
будет равняться 64+9x7.j
по-прежнему равняется 1. Теперь верхняя половина красной диагонали около отметки 64 примерно упирается в два, а нижний конец около единицы. Это переворачивает картинку в другую сторону, поскольку теперь 1^2 = 3 // odd - .
и 1 ^ 1 = 0 //even - p
. Так что можно ожидать кучу точек, за которыми пойдут символы p
.Комментировать | « Пред. запись — К дневнику — След. запись » | Страницы: [1] [Новые] |