среда, 24 февраля 2010 г.

Занимательное JavaScript программирование

Привет!

Недавно мой сосед показал мне главную страничку ВКонтакта со счётчиком идиотовзарегистрированных пользователей и обратил внимание на то, что инет отключен, а счётчик работает дальше. Ясен пень, что реализовано сиё счастье с помощью JavaScript, потому и работает в оффлайне. Сосед получил разъяснения и успокоился, а у меня зачесались руки — стало интересно, как же работают такие счётчики.

Я взял код главной ВКонтакта, а также login-страницы GMail, на которой присутствует счётчик предоставляемого пользователям места, и проанализировал используемый там код. Если вам всё ещё интересен весь этот бред — читайте дальше :)

Начнём с гугла.
Код:
// Estimates of nanite storage generation over time.
var CP = [
 [ 1199433600000, 6283 ],
 [ 1224486000000, 7254 ],
 [ 2144908800000, 10996 ],
 [ 2147328000000, 43008 ],
 [ 46893711600000, Number.MAX_VALUE ]
];


function updateQuota() {
  if (!quota_elem) {
  return;
  }
  var now = (new Date()).getTime();
  var i;
  for (i = 0; i < CP.length; i++) {
    if (now < CP[i][0]{
      break;
    }
  }
  if (i == 0) {
    setTimeout(updateQuota, 1000);
  } else if (i == CP.length) {
    quota_elem.innerHTML = CP[i - 1][1];
  } else {
    var ts = CP[i - 1][0];
    var bs = CP[i - 1][1];
    quota_elem.innerHTML = format(((now-ts) / (CP[i][0]-ts) * (CP[i][1]-bs)) + bs);
    setTimeout(updateQuota, 1000);
  } 
}
Теперь пошагово разберём процесс получения надписи вроде “Over 2757.272164 megabytes (and counting) of free storage”.

Итак, часть первая — массив CP. Комментарий любезно подсказывает, что в массиве этом содержится ничто иное, как прикидки на количество места, которое Гугл будет предоставлять в определённый момент времени. Дальше идёт несколько пар значений вида «таймштамп UNIX (в миллисекундах)»—«мегабайты».

Часть вторая — собственно функция, которая обновляет элемент с мегабайтами. Вот код, но уже с моими комментариями:
function updateQuota() {
  // существует ли элемент, в который надо выводить мегабайты?
  if (!quota_elem) {
  // элемента нет — закругляемся
  return;
  }

  // получаем текущее время в виде UNIX timestamp в миллисекундах
  var now = (new Date()).getTime();

  // объявляем переменную для цикла
  var i;

  // перебираем все записи в CP
  for (i = 0; i < CP.length; i++) {
    // если текущий таймштамп меньше того, что в массиве — прерываем цикл
    if (now < CP[i][0]{
      break;
    }
  }


  if (i == 0) {
    // если таймштамп, на котором прервались — первый в массиве, то…
    // вызвать данную функцию снова через секунду
    setTimeout(updateQuota, 1000);
    // по-русски это звучит как «ждать наступления февраля 2008-го года»
  } else if (i == CP.length) {
    // если вышеприведённое условие не сработало и
    // если все таймштампы в массиве меньше текущего времени, то
    // записать количество мегабайт из последней записи в массиве
    quota_elem.innerHTML = CP[i - 1][1];
    // это на случай, если код долго не будут апдейтить — тогда в качестве мегабайт будет принято
    // самое большое число, допустимое в JavaScript — это около 1.7976931348623157e+308
  } else {
    // если ни одно из вышеприведённых условий не сработало, то
    // переменной ts присвоить таймштамп последней записи,
    // а переменной bs — кол-во мегабайт, соответствующих этому таймштампу
    var ts = CP[i - 1][0];
    var bs = CP[i - 1][1];
    // обновить запись с помощью нехитрой математики:
    // now-ts — разность между текущим временем и используемым таймштампом
    // CP[i][0]-ts — это разность во времени между текущим и следующим таймштампами
    // CP[i][1]-bs — разность в мегабайтах между текущим и следующим таймштампами
    // ((now-ts) / (CP[i][0]-ts) * (CP[i][1]-bs)) — сколько дополнительного места начал
    //     давать Гугл относительно текущего таймштампа
    // +bs — относительную величину (прирост мегабайт) превращаем в абсолютную (сколько же
    //     мегабайт выдают теперь)
    quota_elem.innerHTML = format(((now-ts) / (CP[i][0]-ts) * (CP[i][1]-bs)) + bs);
    // выставляем таймер — через секунду опять обновить значение
    setTimeout(updateQuota, 1000);
  } 
}


Так-то. Теперь перейдём к ВКонтакту. Программисты Дурова, очевидно, очень любят математику, в особенности статистику и генерацию случайных чисел. Вот их код:
var memCount = 63301534;
var memPerSec = 2.16273601705;
function updateCount() {
 next = -(1000 / memPerSec)*Math.log(Math.random());
 memCountString = '' + memCount;
 len = memCountString.length;
 memCountString = memCountString.substr(0,len-6)+'<span style="font-size:8px"> </span>'+memCountString.substr(len-6,3)+'<span style="font-size:8px"> </span>'+memCountString.substr(len-3,3);
 ge('memCount').innerHTML = memCountString;
 memCount = memCount + 1;
 setTimeout(updateCount, next);
}
Как видите, всё очень лаконично: сейчас насчитывается memCount пиплов, каждую секунду к ним присоединяется ещё memPerSec тел. Вот только незадача: каждую секунду добавлять по чуть более чем два человека некрасиво. Лучше уж почаще, но по одному целому. Т.к. люди несовершенны, они не регистрируются каждые, скажем, 500 милисекунд — это тоже надо учесть. Короче, пришлось им придумать специальную формулу, которую я подробно рассматриваю в коде ниже:
// текущее количество пользователей Вконтакта
var memCount = 63301534;
// примерное количество регистрирующихся в секунду
var memPerSec = 2.16273601705;

// собственно функция, обновляющая счётчик
function updateCount() {
 // хитрая математическая формула, вычисляющая длинну промежутков между
 //     регистрациями
 // 1000 / memPerSec — среднее время между регистрациями
 // random возвращает дробное число от 0 до 1
 // логарифм используется для того, чтобы хотя бы иногда получать время
 //    _больше_ среднего
 // минус перед формулой компенсирует тот факт, что логарифм числа из
 //    диапазона 0..1 отрицателен
 next = -(1000 / memPerSec)*Math.log(Math.random());
 // конвертируем число в строку
 memCountString = '' + memCount;
 // вычисляем длинну строки, чтобы потом красиво разбить его на разряды
 len = memCountString.length;
 // собственно разбивка на разряды
 // жду миллиардного пользователя, на котором эта конструкция обломается и выдаст «1000 000 000»
 memCountString = memCountString.substr(0,len-6)+'<span style="font-size:8px"> </span>'+memCountString.substr(len-6,3)+'<span style="font-size:8px"> </span>'+memCountString.substr(len-3,3);
 // выводим данные
 ge('memCount').innerHTML = memCountString;
 // наконец, добавляем к счётчику свежезарегистрировавшегося пользователя
 memCount = memCount + 1;
 // выставляем таймер, чтобы через next миллисекунд обновить счётчик
 setTimeout(updateCount, next);
}


Не знаю, насколько полезно такое вот спонтанное и бессистемное ковыряние в коде, но мне это нравится. Надеюсь, я не один такой, и этот пост будет интересен кому-то ещё ;)

Копируете статью — поставьте ссылку!

8 комментариев:

Kona-chan комментирует...

Капитан разбушевался.

Анонимный комментирует...

и шо?

Артем комментирует...

меня напрягает множество ЦП в коде

Анонимный комментирует...

В описании дважды гугловский код FAIL!

Реквестирую качественное капитанство, так-то.

Minoru комментирует...

Спасибо комментаторам за их замечания, исправил.

Ежели к «капитанству» было отнесено обилие комментариев или сам факт существования этого поста, то извиняйте — волен писать о том, о чём хочу. Может быть, какому-то начинающему web-дизайнеру будет интересно почитать, как такие счётчики делать.

Alexey Romanenko комментирует...

Почему Вы считаете, что программисты Дурова любят генерацию случайных чисел?

Minoru комментирует...

2 Alexey Romanenko:
Потому что мне кажется, что 2*rand — более очевидное решение, нежели log(rand).

А вообще абзац перед кодом ВКонтакта нужно воспринимать шутливо — там ни капли злобы или наезда, клянусь.

Alexey Romanenko комментирует...

не-не-не, я вовсе не защищаю ВК, просто там же все очевидно. Когда нужно показать статические данные, либо сложные мат формулы, либо аякс =)

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.

 
Blogger logo Debian logo Creative Commons License FeedBurner Subscribers Counter