Как стать автором
Обновить

Комментарии 121

Очень часто файлы отдаются веб сервером с использованием компрессии. Хотелось бы увидеть разницу в размере запакованного исходного файла и запакованного минимизированного. Она должна быть намного меньше.
Исходный: 28112 байт
Исходный запакованный: 7299 байт
Минимизированный и запакованный: 2038 байт
Оптимизированный по описанным методам, минимизированный и запакованный: 1653 байта

Если использовать компрессию — почти полкило экономится для этого случая :-)
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Я в своем комментарии ниже написал о том же.
Однако вы приводите цифры для advanced оптимизаций, после которых данный код перестанет работать. Для advanced режима код нужно специально готовить.
а запакованный исходный без «богатых коментариев»?
Мне кажется так делают скорее для обсфукации кода, потому что gzip все отлично и так сжимает.
z=null; и заменить везде null на z, размер должен немного уменьшиться. Хотя, возможно, Вы назовете это грязным трюком…
Да, я думаю, что это трюк. Такой же, как и var me=this. Я не проводил тестов на скорость, но по идее это замедляет производительность, поскольку ключевые слова резолвятся непосредственно, а обращение к переменным приводит к линейному поиску по скопу.
Можно определять их константами. Для IE использовать VB.
Снимаю предложение. Потестил, хоть const и должен в конечном итоге быть тем же true после обработке парсером, похоже это странно реализовано. В IE очень сильно падает производительность.
очевидным будет замена true и false на 1 и 0 соответственно, т.к. strict equal я нигде не вижу — это даст 31 байт экономии; если автору требуется чтобы переменные содержали настоящий буль, то заменить на !0(true) и !1(false), что даст более скромный выигрыш в 22 байта
кстати да, спасибо! -)
for(initial;condition;loop statement){statements}
можно после loop statement поставить запятую и расположить все операторы из statements через запятую(вместо точки с запятой) — экономим 2 байта(фигурные скобки)

if(i.length>0)
смело заменяем на
if(i.length)
тоже 2 байта

в коде дважды встречается [0,0,0,0,0,0] — логично вынести в отдельную переменную, даст еще 11 байт

опережая будущих комментаторов: замену a.charAt(0) на a[0] делать не стоит, т.к. ведет к потере ie6
можно после loop statement поставить запятую и расположить все операторы из statements через запятую(вместо точки с запятой) — экономим 2 байта(фигурные скобки)


— верно, но это подходит только для простых случаев (без например ифов внутри). Но один такой фор я всё-же нашёл

if(i.length>0)

смело заменяем на
if(i.length)

тоже 2 байта


— тоже да; забавно, что я описал это в топике, но забыл заметить в коде :-)

в коде дважды встречается [0,0,0,0,0,0] — логично вынести в отдельную переменную, даст еще 11 байт


— нет, это массив, он должен быть разным в каждом случае

опережая будущих комментаторов: замену a.charAt(0) на a[0] делать не стоит, т.к. ведет к потере ie6


— в моём конкретном случае поддержка ie6 уже убита («благодаря» window.postMessage и script.onload), так что, пожалуй, воспользуюсь

итого: ещё 9 байт сэкономил

— нет, это массив, он должен быть разным в каждом случае
копию массива можно получить, к примеру, так: g.slice()

правда с ходу не соображу, даст ли это экономию при внедрении
Здесь — нет, тут оно всего два раза используется. Вот если бы хотя бы три было… :-)
для нас нет ничего невозможного — сокращаем предложенный код копирования еще на 5 байт:
g = [0,0,0,0,0,0]; // исходный массив
v = [g][0]; // делаем копию
g = [1,0,0,0,0,0]; // меняем исходный
v; // [0,0,0,0,0,0]

в результате даст в районе 4х байт, т.к. скорее всего придется завести отдельную переменную для хранения исходной переменной
— верно, но это подходит только для простых случаев (без например ифов внутри).

Можно записать логические конструкции без использования самого if, например так:
if (i.length) {
  a = "z" + i;
  console.log(a, i);
}

можно заменить на
i.length && (a = "z" + i, console.log(a, i));


И тогда можно делать такие конструкции:
for (i = 0; i < 20; i++, a = 'z' + i, i % 2 && console.log(a, i))


За одно получаем ещё дополнительную экономию.
не знаю, как это укрылось от всевидящего ока, но:
a.substr(1,a.length-1);
заменяем на
a.slice(1);

-12 байт
typeof this.x == «undefined» => this.x === undefined. Ещё в функциях из одного ифа отлично использовать тернарник.
typeof this.x == «undefined» => this.x === undefined.


— экономит на использовании typeof. Но строка «undefined» толще, чем «typeof» :-) или это должно увеличить производительность?

Ещё в функциях из одного ифа отлично использовать тернарник.


Имеется в виду только случай if (a) return b; else return c;? В моём коде такого нет… Если с помощью тренарного оператора можно как-то по-другому сэкономить на if-else — подскажите, я наверное туплю сейчас и не вижу :-)
this.x===void 0
Undefined обычно принимают в замыкание, не передавая параметр, собственно можно его прийти в u.
if (condition) { statement1; statement2; } else { statement3; statement4; }

condition? (statement1, statement2): (statement3, statement4);

Экономия аж 8 байт (if со скобками, else). Правда такой способ, кажется, не применим, если внутри if, к примеру, цикл.
для typeof проверку на «undefined» можно сократить при обращении к свойству объекта:
if(typeof a.module!=q)
на
if(a.module)
и
if(typeof this.v==q)
на
if(!this.v)


также можем упростить конструкцию
if(typeof variable != "function")
до
if(!variable.call)
если верить тесту от 2001 года то проблем не возникнет в ie6 и mozilla 1.0rc1, а вот opera 6.01 может подкачать :)
Я не проверял, но что, если добавить функцию var l=function(s){return s.length;} и все *.length заменить на l(*)?
Сработает, но это немножко замедлит производительность: придётся вызывать дополнительную функцию каждый раз, со всеми накладными расходами.
Вы бы все-таки определились — используете вы эти переименования или нет. А то с одной стороны — «var a=this» и «var f=false» не нравятся, а с другой — «Бывает, что есть значения, которые используются больше одного раза. Их тоже можно вынести в переменные».
Ну тут дело в том, что обратиться к переменной a одинаково затратно, как и обратиться к переменной _myMagicVariable. А вот вызывать дополнительную функцию, которая будет вместо меня обращаться к переменной — уже дороже.

В примерах я заменял код на равнозначный по сложности.

Если забить на скорость, то много чего ещё можно повыкидывать. Но так не интересно :-)

Про this и false я уже объяснял чуть выше. Суть в том, что интерпретатор гораздо быстрее резолвит ключевые слова, чем имена переменных. Когда мы обращаемся к переменной, интерпретатор лезет в скоп и перебирает все переменные до тех пор, пока не найдёт ту, которая называется так, как мы запросили, и возвращает значение. Это называется линейный поиск. В случае с ключевыми словами этого не происходит, поэтому их не переименовывают при минимизации.
Ну, там на самом деле, конечно, не линейный поиск, но в целом вы правы.

А результаты на самом деле примерно такие:

1) переименовать true в t => jsperf.com/renaming-a-variable — падение на 9%
2) сделать так, как предлагалось выше с оборачиванием length в функциюjsperf.com/property-inside-function — падение в 2 раза
3) попереименовывать всякие window в w — jsperf.com/renaming-window/2 — наоборот, рост производительности от «на 25%» вплоть до «в 3-4 раза».

Т.е. переименовывать длинные идентификаторы типа window и document не только полезно с точки зрения объема, но и с точки зрения производительности.
2.
s.length 777,078,814
l(s) 776,900,845
Ну, собственно, хорошо видно, как JIT работает в более современных браузерах — Chrome 13+, новых Firefox и т.д.
А ещё хотелось бы уточнить. Часто при ужимании JS вижу использование чего-то вроде:
var l="length";
str[l];

вместо str.length и других length. Это тоже негативно сказывается на скорости?
НЛО прилетело и опубликовало эту надпись здесь
Как так получилось, что из
if ( --a == 0 )
стало
if ( --a )
?
оу. затупил. спасибо :-)
А почему YUI, а не GCC? У меня он сильно лучше работал (правда, на несколько больших проектах — под 200кб). Да, для него надо готовить код (а лучше — заранее писать имея его в виду), а результат — обязательно тестировать, но зато результат…
GCC уже и с JavaScript работает? О_о видимо я многое пропустил :-)

Но в любом случае, это пост не про минимайзеры, а про подготовку кода к минимизации. По идее, эти советы минимайзеро-независимы…
GCC в данном контексте — Google Closure Compiler.
Ого, а ведь вы тут правы все. GCC сходу ужал всё ещё на 100+ байт. Можно зачёркивать этот пост :-D

Шутка. GCC сработал, конечно, лучше, но предварительная оптимизация всё же улучшила результат на полкилобайта.
В advanced-режиме он делает немалую часть ваших преобразований автоматически.
advanced-режим сходу сгенерил неработающий код :-(

да, тут нужно поразбираться…
Это верно, код для advanced нужно готовить (а ещё лучше сразу писать нужным образом), но читаемость от этого почти не снижается, во отличии от ;)
Более того, я бы сказал, что она повышается, т.к. как минимум к некоторым кускам кода придется добавить комментарии JSDoc, хуже от которых никому еще не было. :-) Ну и в остальном тоже — иногда после того как все выдаваемые warning'и поправишь и полной работоспособности оптимизированного кода добьешься — считай рефакторинг кода сделал — все так миленько становится, аккуратненько, по полочкая разложено… :-)
Мне кажется, что sky_lord говорит про Google Closure Compiler — минимизатор, который использует Гугл в своих проектах
Именно, тем более что автор вручную (!!!) переименовывает приватные функции, с чем Google Closure Compiler всё равно справляется лучше человека.
Как из
var a = [];
for ( var i in someEnum ) {
a[ i ] = 0;
}


Получилось
var a = [ 0, 0, 0 ];
?
Или в цикле должно быть a[someEnum[i]] = 0?
да, вы правы, спасибо
Мрак мрак мрак. После того уже невозможно работать с исходным, до минимизации, кодом — он превратился в нечитаемую кашу.
Зачем вообще тогда их разделять? Работайте и пишите сразу как выход js minimizer :)
Предполагается, что исходный код уже отлажен. Конечно, если он будет изменяться, все эти манипуляции придётся проделывать ещё раз.

Но я за собой заметил, что с некоторого времени почти спокойно воспринимаю и отлаживаю минифицированный код :-D
Ну есть же JavaScript Beautifier. Названия функций и переменных он, конечно, не вернет, но отформатирует красиво.
Я как раз об убиении имён приватных методов имён и прочего.
НЛО прилетело и опубликовало эту надпись здесь
ха. ну дык мы ж маньяки.

а ещё на домашней страничке минимизируемого проекта нету никаких сотен килобайт графики. и jpeg'а фоном тоже нету. да мне просто больше нечего минимизировать! :-D
НЛО прилетело и опубликовало эту надпись здесь
код можно посмотреть на домашней страничке проекта. экономлю я всё остальное.
вот я на днях при проблемах с инетом решил заглянуть на сайт провайдера через резервный канал (gprs) на предмет наличия каких-либо объявлений.
разумеется, вырубив картинки.
за те несколько минут (!), что грузилась страница, испытал лютую НЕНАВИСТЬ к любителям засунуть лишние пару сотен кило жаваскриптового говна ради какой-нибудь ерунды типа выпадающей менюшки (которую и без js спокойно можно было сделать).
подход «у кого 10+ мегабит анлима со 100% аптаймом нет — уже недочеловек» должен умереть, по крайней мере для обычных сайтов (речь не идет о сложных приложениях типа google docs)
А если сжать с помощью uglify-js?
Google Closure Compiler пока в лидерах :-)

хотя, да. топик не про минимайзеры.
Существует индийский контест, Time Limit Exceeded, который награждает не за правильность решения (хотя это необходимое условие), а за его длину, количество использованных точек с запятой, требует решения-палиндрома и т.д. Может, вам понравится? :-) Правда, там только C/C++.
в минимизации JS кода, помимо забавы, есть ещё и практическая польза :-)
Функции window.funcName можно заменить на просто funcName
Или можно в замыкание передать window как параметр:
!function(window){
// код
}(window)
тогда уж ~function(w){}(window);
Не обязательно. Компрессор все равно заменит window внутри замыкания на короткое имя.
Jed, автор «фремворка» (fab) недавно устраивал конкурс — «фреймворк в твите» 140byt.es/, и к нему давал набор рекомендаций о том, как можно минимизировать руками код.

Очень рекомендую прочитать внимательно каждый пунктик: https://github.com/jed/140bytes/wiki/Byte-saving-techniques

Я уверен, найдете как еще ужать код :)

Ну и там есть просто охренительные твиты, несколько из которых я уже использую активно :)
Об организаторах.
Будем сокращать имена переменных в Javascript, чтобы втиснуть код в 140 байт, но при этом оставлять красивый indent лесенкой из пробелов в HTML, выводить граватарки по 14 килобайт.

Здесь нет здравого смысла!
Не думаю, что есть смысл смешивать сам сайт и суть проекта — на сайте там, где есть инденты и граватарки именна переменных не сокращены. А в гистах по 140 байт нет индентов и граватарок.

Тут важна сама идея, гисты и описание то, как и чем достигнуты 140 байт, чем то, как сделан сайт.
var = true; var = !0;
var = false; var = !1;

var = parseInt('12'); var = +'12';
Ну вы меня поняли))
Про тру/фолс есть по той ссылке что я привел.

С парсинтом ваабще отдельная история — я сравнивал по скорости разные варианты, это "+", "|0", "~~" и т.д. и очень интересные результаты выходили: см. например
jsperf.com/number-vs-plus-vs-toint-vs-tofloat/10

как оказалось в Chrome parseFloat почти в два раза быстрее pareInt :)
Вот это поворот!)
у jsperf всегда интересные результаты
вот более правильные: goo.gl/Px3Vi
GWT вам в руки :)
Дайте прямую гиперссылку на сырой неупакованный код, пожалуйста.
Впрочем, нашёл.

Если вставить содержимое http://home.gna.org/helios/kernel/helios-kernel-0.9.tar.gz в упаковщик Эдвардса, работающий в режиме «Base62 encode» и «Shrink variables», то код сокращается до 3380 символов:

eval(function(p,a,c,k,e,r){e=function©{return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e©]=k[c]||e©;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e©+'\\b','g'),k[c]);return p}('!A(){O a={},b=a.10=A(a){6.o=6.11=a;6.D=0;6.p=6.12=[];6.q=6.13=[];6.r=[];6.s=[];6.t=[];d.F(6);f.F(6);6.u=[];6.g(g)},c=14,d=[],e=0,f=[],g=[0,0,0,0,0,0],h=A(a,b,c){i.F([a,b||0,c]);I.15(k,"*")},i=[],j="16",k=j+(V 17).18(),l=A(a,b){9(a.19==I&&a.1a==k)9(i.B){b=i.P();b[0].1b(b[1],b[2])}},m=A(a,b,c){9(a.K(0,7).W()!=j+"://"&&a.K(0,8).W()!=j+"s://")a=b.K(0,b.1c("/")+1)+a;c=a;1d{a=c;c=a.1e(/[\\w\\.~]*\\/\\.\\.\\//,"")}J(c!=a);C a},n=A(a,c){c=u(a);9(!c){c=V b(a);h(A(){9(!e)o()})}C c},o=A(){Q=R=0;e=f.S()||0;9(e)e.a()},p=A(){},q="1f",r="1g",s=a.1h=A(a,b,c){9(H b!=v)b=p;9(H c!=v)c=p;O d=1;9(H a=="1i"){d=0;a=[a]}O e=a.B,f=A(){9(!--e)b()},g=0,h=A(a){9(!g){g=1;t(i);c(a)}},i=[],j="",k,l=0;E(;l<a.B;k=n(j),i.F(k.l(f,h)))j=a[l++];C d?i:i[0]},t=a.1j=A(a,b){9(H a.L!=q){a.L.m(a);C}E(b=0;b<a.B;b++)a[b].L.m(a[b])},u=a.1k=A(a,b){E(b=0;b<d.B;b++)9(d[b].o==a)C d[b];C M},v="A";9(H X==q){X=a}I.1l("1m",l,1);a.1n={1o:0,1p:1,1q:2,1r:3,1s:4,1t:5};a.1u=A(){C d};a.Y=A(){C g};b.1v={Y:A(){9(H 6.v==q)6.g(6.v=[0,0,0,0,0,0]);C 6.v},a:A(a,b){6.h(1);a=6.o,b=6;9(a[0]=="/")a=a.K(1,a.B-1);6.w=Z.1w®;6.w.1x="1y/1z"+r;6.w.1A=a;6.w.N=A(){e.c()};6.x=1B(A(){e.b(b.o)},c);Z.1C("1D").1E(0).1F(6.w)},b:A(a,b){9(6.D==M)C;J(6.s.B)h(6.s.P(),0,[a]);J(6.p.B){b=6.p.S();h(b.b,b,[a])}6.f();9(6==e){6.n();o()}},c:A(){6.y=Q;6.z=R;6.n();h(o);9(!6.j())6.f();G 9(6.k())6.d();G 6.h(2)},d:A(){6.h(3);h(A(a){9(6.y)6.y();9(6.j()){6.h(4);J(6.r.B)6.r.P()();E(a=0;a<6.p.B;a++)9(6.p[a].D==2&&6.p[a].k())6.p[a].d()}G 6.e()},6)},e:A(){6.h(5);h(A(){9(6.z)6.z();9(6.j())6.d();G 6.f()},6)},f:A(a,b){6.h(M);J(6.q.B){a=6.q.S();E(b=0;b<a.p.B;b++)9(a.p[b]==6){a.p.T(b,1);U}9(!a.j())9(a.D==2)h(a.f,a);G 9(a.D==4)a.e()}E(b=0;b<d.B;b++)9(d[b]==6){d.T(b,1);U}6.y=6.z=6.u=6.r=6.s=0},g:A(a,b){E(b=0;b<6.u.B;)9(6.u[b++]==a)C;6.u.F(a);a[6.D]++;E(b=0;b<6.q.B;)6.q[b++].g(a)},h:A(a,b,c){b=6.D,c=0;6.D=a;E(;c<6.u.B;c++){6.u[c][b]--;9(6.D!==M)6.u[c][6.D]++}},i:A(a,b){E(b=0;b<6.p.B;b++)9(6.p[b].o==a||6.p[b].i(a))C 1;C 0},j:A(){C 6.t.B||6.p.B},k:A(a){E(a=0;a<6.q.B;)9(6.q[a++].D!=4)C 0;C 1},l:A(a,b,c){c={L:6};6.t.F©;9(6.D==4){9(a)h(a)}G{9(a)6.r.F(a);9(b)6.s.F(b)}C c},m:A(a,b){E(b=0;b<6.t.B;b++)9(6.t[b]==a){6.t.T(b,1);U}9(!6.j())9(6.D==2)6.f();G 9(6.D==4)6.e()},n:A(){1G(6.x);6.w.1H.1I(6.w);6.w=6.w.N=6.x=0;1J 6.w}};9(!I.N)I.N=A(){s("/1K.1L")};1M=A(a,b,c,d){b=e,c=0;9(!b)C;a=m(a,b.o);9(b.i(a))b.b(a);G{d=n(a);d.p.F(b);b.q.F(d);E(;c<b.u.B;)d.g(b.u[c++])}};Q=R=0}()',62,111,'||||||this|||if|||||||||||||||||||||||||||function|length|return|state|for|push|else|typeof|window|while|substr|module|null|onload|var|shift|init|uninit|pop|splice|break|new|toLowerCase|kernel|getStatistics|document|Module|path|children|parents|3e3|postMessage|http|Date|getTime|source|data|apply|lastIndexOf|do|replace|undefined|script|require|string|release|getModule|addEventListener|message|moduleStates|created|parsing|waiting|initializing|ready|uninitializing|getModulesList|prototype|createElement|type|text|java|src|setTimeout|getElementsByTagName|head|item|appendChild|clearTimeout|parentNode|removeChild|delete|main|js|include'.split('|'),0,{}))
Вставить-то я его вставил, но, по-видимому, хабраразметка <source></source> (вопреки своему предназначению) губит код (например, заменяет последовательность символов «(», «c» и «)» на «©»).
Это существенно снизит скорость работы.
У меня на одноядерном Celeron (2,4 ГГц) на распаковку ушло четыре миллисекунды.

Само по себе это не очень заметно.

Правда, и дополнительный выигрыш нескольких сотен байт также не особенно заметен на высокоскоростном канале связи с Интернетом.

Пусть каждый сам выбирает, что важнее.
Когда кода несколько сотен килобайт (сжатого) то на распаковку тратится гораздо больше времени чем хотелось бы — браузер может подвиснуть на несколько секунд (пока распаковывается). Так что packer подходит только для небольших скриптов.
Не минифицированный код он ужал до 4302 байта
а xpostman конечно же о нем не знал

по делу: за четыре года, Dean Edwards' Packer устарел во всех смыслах
Однако этот пакер ужал исходник лучше чем YUI
…произведя обфускацию кода, о нежелании которой автор написал черным по белому
вам шашечки или ехать? минифицированный код не шибко лучше обфусцированного
наверное именно поэтому популярнейший фреймворк jquery отказался от packed-версии в пользу minified?

Finally, a few users noticed that we no longer provide a “packed” version of jQuery (a version of jQuery run through Dean Edwards’ Packer with Base62 encoding turned on). We did this for a couple reasons:

Packed scripts are significantly harder to debug (even harder than minifed scripts).
Packed scripts aren’t able to run on all platforms without issue (such as Adobe AIR and Caja-capable environments).
But most importantly: Packed scripts are slower for the user than what you would get from using just minification. This may seem counter-intuitive since a packed script’s file size is smaller than a minified script but the final load time ends up being much higher (due to the decompression step it must go through). We have some data regarding the loading performance of minified scripts vs. packed scripts, for those that are interested.

The minifed copy of jQuery that we provide, run through the YUI Compressor, should be the optimal form of jQuery to use in a production environment (served using gzipping, if possible).
Есть отличный инструмент для сжатия JS кода — Jasob. Он в частности позволяет настраивать какие переменные заменять, а какие нет, проверяет их на допустимость, считает кол-во вызовов, чтобы определить, какие лучше делать однобуквенными, чтобы больше сэкономить и т.д. Только платный да.
Ну и да, обрабатывает вместе и html и css, при чем корректно отрабатывает встречающиеся название переменных в строках.
Первое, что хочется сказать — не используйте YUI компрессор, он практически не делает оптимизаций, да и вообще проект не развивается. Могу посоветовать Google Closure Compiler или UglifyJs (по ссылке кстати есть сравнительная таблица минимизаторов), сам пользуюсь первым.
Closure дает такие результаты (воспользуйтесь online-версией если хотите)
Original Size: 24.69KB (6.85KB gzipped)
Compiled Size: 6.65KB (1.95KB gzipped)
или для Advanced оптимизаций
Compiled Size: 4.18KB (1.67KB gzipped)
Последний вариант, впрочем, скорей всего будет неработоспособным, так как код должен быть специальным образом написан, чтобы сохранить публичные свойства и методы. Однако показывает вам чего вы можете добиться «на ровном месте», просто используя правильный инструмент и оставляя ваш код читаемым что важно для его дальнейшего развития.
Так, например, многие оптимизации такие как сокращение имен переменных и сведение к одному var делают оптимизаторы, и более эффективно — поэтому нет смысла это делать самому.
Плохая сжимаемость вашего кода (а он безусловно еще может быть сжат больше) обусловлена в первую очередь тем, что он сам по себе плох и плох для минификаторов. А ваши оптимизации это экономия на спичках или не экономия вообще.
Например вы оптимизируете код:
kernel._getAbsolute = function( path, childPath ) {
    // concatinating path with the child's path (without the filename)
    // path starting from "http://" or "https://" treated as absolute
    if ( path.substr( 0, 7 ).toLowerCase() != "http://" &&
         path.substr( 0, 8 ).toLowerCase() != "https://" ) {
        path = childPath.substr( 0, childPath.lastIndexOf("/") + 1 )
            + path;
    }

    // resolving (clearing) up-dir sequences such as "foo/../"
    var newPath = path;
    do {
        path = newPath;
        newPath = path.replace( /[\w\.~]*\/\.\.\//, "" );
    } while ( newPath != path );

    return path;
}

Таким образом:
var ... , http = "http" ...
kernel._getAbsolute = function( path, childPath, newPath ) {
    // concatinating path with the child's path (without the filename)
    // path starting from "http://" or "https://" treated as absolute
    if ( path.substr( 0, 7 ).toLowerCase() != http  + "://" &&
         path.substr( 0, 8 ).toLowerCase() != http  + "https://" ) {
        path = childPath.substr( 0, childPath.lastIndexOf("/") + 1 )
            + path;
    }

    // resolving (clearing) up-dir sequences such as "foo/../"
    newPath = path;
    do {
        path = newPath;
        newPath = path.replace( /[\w\.~]*\/\.\.\//, "" );
    } while ( newPath != path );

    return path;
}

Посмотрим что вы сэкономили (при условии что все переменные станут однобуквенными).
На «http», было:
"http://""https://"

стало:
,x="http"x+"://"x+"s://"

Не хочу вас расстраивать, но вы потеряли 5 байт. А на newPath вы сэкономили лишь 2 байта, убрали var и пробел, но в объявлении функции добавилось имя переменной и запятая.
Итого выигрыш -3 байта. То есть вы сделали код больше на 3 байта.
Ок. Перепишем немного:
kernel._getAbsolute = function( path, childPath ) {
    // concatinating path with the child's path (without the filename)
    // path starting from "http://" or "https://" treated as absolute
    if (!/^https?:\/\//.test(path)) {
        path = childPath.replace(/[^\/]+$/, '') + path;
    }

    // resolving (clearing) up-dir sequences such as "foo/../"
    var prevPath;
    while (prevPath != path){
        prevPath = path;
        path = path.replace( /[\w\.~]*\/\.\.\//, "" );
    }

    return path;
}

И получим 153 байта, то есть сэкономили 76 байт (по сравнению с вашим вариантом).
Результаты:
// оригинал
// Original Size:	558 bytes (334 bytes gzipped)
// Compiled Size:	226 bytes (187 bytes gzipped)

kernel._getAbsolute=function(a,c){a.substr(0,7).toLowerCase()!="http://"&&a.sub
str(0,8).toLowerCase()!="https://"&&(a=c.substr(0,c.lastIndexOf("/")+1)+a);var 
b=a;do a=b,b=a.replace(/[\w\.~]*\/\.\.\//,"");while(b!=a);return a};

// ваша оптимизация
// Original Size:	571 bytes (342 bytes gzipped)
// Compiled Size:	229 bytes (187 bytes gzipped)

x="http";kernel._getAbsolute=function(a,c,b){a.substr(0,7).toLowerCase()!=x+":/
/"&&a.substr(0,8).toLowerCase()!=x+"s://"&&(a=c.substr(0,c.lastIndexOf("/")+1)+
a);b=a;do a=b,b=a.replace(/[\w\.~]*\/\.\.\//,"");while(b!=a);return a};

// моя оптимизация
// Original Size:	450 bytes (296 bytes gzipped)
// Compiled Size:	153 bytes (147 bytes gzipped)

kernel._getAbsolute=function(a,c){/^https?:\/\//.test(a)||(a=c.replace(/[^\/]+$
/,"")+a);for(var b;b!=a;)b=a,a=a.replace(/[\w\.~]*\/\.\.\//,"");return a};


Еще одна функция:
kernel.Module.prototype._parse = function() {
    this._setState( this._states.parsing );

    var path = this.path;
    if ( path.charAt(0) == "/" ) {
        path = path.substr( 1, path.length - 1 );
    }

    this._script = document.createElement("script");
    this._script.type = "text/javascript";
    this._script.src = path;
    this._script.onload = function() {
        kernel._activeModule._finalizeParsing();
    }

    var me = this;
    this._invalidTimeout = setTimeout(
        function() {
            kernel._activeModule._invalidate( me.path );
        },
        kernel._invalidateTimeout
    );

    document.getElementsByTagName("head").item(0).appendChild(this._script);
}

// скомпилированный closure вариант
// Original Size:	592 bytes (321 bytes gzipped)
// Compiled Size:	509 bytes (292 bytes gzipped)

kernel.Module.prototype._parse=function(){this._setState(this._states.parsing);v
ar a=this.path;a.charAt(0)=="/"&&(a=a.substr(1,a.length-1));this._script=documen
t.createElement("script");this._script.type="text/javascript";this._script.src=a
;this._script.onload=function(){kernel._activeModule._finalizeParsing()};var b=t
his;this._invalidTimeout=setTimeout(function(){kernel._activeModule._invalidate(
b.path)},kernel._invalidateTimeout);document.getElementsByTagName("head").item(0
).appendChild(this._script)};

// ваша оптимизация
// Compiled Size:	333 bytes (241 bytes gzipped)

x="script";kernel.Module.prototype._parse=function(a,b){this.h(1);a=this.o;b=thi
s;a[0]=="/"&&(a=a.substr(1,a.length-1));this.w=document.createElement(x);this.w.
type="text/java"+x;this.w.src=a;this.w.onload=function(){e.c()};this.x=setTimeou
t(function(){e.b(b.o)},c);document.getElementsByTagName("head").item(0).appendCh
ild(this.w)};

// Насчет замены "script"
// было:
"script""text/javascript"
// cтало:
,x="script"x"text/java"+x
// вы ничего не выиграли и не проиграли, сколько было, столько и осталось

// Немного перепишем функцию

kernel.Module.prototype._parse = function() {
    var me = this;
    var script = me._script = document.createElement("script");

    script.type = "text/javascript";
    script.src = me.path.replace(/^\/(.*).$/, '$1');
    script.onload = function() {
        me._finalizeParsing();
    }

    me._setState( STATE_PARSING );

    me._invalidTimeout = setTimeout(
        function() {
            me._invalidate( me.path );
        },
        INVALIDATE_TIMEOUT
    );

    document.getElementsByTagName("head")[0].appendChild(script);
}

// получаем 
// Original Size:	453 bytes (304 bytes gzipped)
// Compiled Size:	371 bytes (278 bytes gzipped)

// А если проделать ваши замены
// Compiled Size:	283 bytes (223 bytes gzipped)

kernel.Module.prototype._parse=function(){var a=this,b=a.w=document.createElemen
t("script");b.type="text/javascript";b.src=a.o.replace(/^\/(.*).$/,"$1");b.onloa
d=function(){e.c()};a.h(1);a.x=setTimeout(function(){e.b(a.o)},c);document.getEl
ementsByTagName("head")[0].appendChild(b)};


Таким образом получили меньше вашего на 50 байт.
То есть ре-организовав код можно получить намного больший выигрыш, чем на выжимании байта другого на экономии var. Ваш код рефакторить и рефакторить.
Вывод: вы не то оптимизируете (по крайней мере в этих примерах), потратьте больше времени на рефакторинг (у вас там много лишнего).

Лучше всего на минимизацию влияет изолированность кода. Вы правильно начали оборачивать модуль в функцию и сливать свойства/методы в объекты, вместо поочередного их присвоения прототипу или объекту. Стоит так же завести еще функцию extend, которая помогла бы копировать свойства одного объекта в другой. Так же, все константы нужно вынести в отдельные переменные, вместо того чтобы хранить их свойствами объекта. Каркас вашего модуля должен быть примерно таким:
!function(){
  function extend(a, b){
    for (var key in b)
      a[key] = b[key];
  }

  var STATE_CREATED = 0;
  var STATE_PARSING = 1;
  // etc

  var Module = function(){};
  Module.prototype = {
    property: value,
    method: function(){
       this.setState(STATE_PARSING);
    }
    // ...
  };

  var _kernel = {
    Module: Module,
    property: value,
    method: function(){
    }
    // ...
  };
  extend(kernel, _kernel);
}()

В таком коде минификатор заменит константы на их значения и сократит имена переменных. Посмотрите как изменил этот пример google closure (код для читабельности пропущен через jsbeautifier)
(function () {
  var a = function () {};
  a.prototype = {
    property: value,
    method: function () {
      this.setState(1)
    }
  };
  (function (a, b) {
    for (var c in b) a[c] = b[c]
  })(kernel, {
    Module: a,
    property: value,
    method: function () {}
  })
})();

Можете еще разобраться с правилами написания кода для Advanced режима сборки, и получите дополнительную экономию.
Ваши оптимизации еще плохи тем, что они не автоматические.
И в целом: все выигрыши по объему обычно нивелируются при упаковке gzip. Код может быть меньше, но при этом хуже сжиматься gzip или сжатые gzip версии будут малоразличимы. Разница в 1-2-3 килобайта gzip версий несущественна, а сравнивать нужно именно эти версии.
Основной плюс от оптимизации объема не-gzip версии только в том, что такой код быстрее парсится и требует меньше памяти для хранения кода. Однако это существенно в основном только в случае когда его очень много и лишь для медленных мобильных браузеров.

Так что главный совет — пишите выразительный код, а оптимизации оставьте автоматическим средствам.

ЗЫ Сорри, что-то улекся писаниной :)
Да, кстати большой выигрыш можно получить на циклах, вот пара примеров:
=========================
+32 bytes

/* orginal */
function (a, b) {
      if (this.state == null) return;

      while (this.s.length) 
        h(this.s.shift(), 0, [a]);

      while (this.p.length) {
        b = this.p.pop();
        h(b.b, b, [a])
      }

      this.f();

      if (this == e) {
        this.n();
        o()
      }
    }

/* optimized */
function (path) {
  if (this.state) {
    var item;
    while (item = this.s.shift()) {
      h(item, null, [path]);
    }

    while (item = this.p.pop()) {
      h(item.b, item, [path]);
    }

    this.f();

    if (this == e) {
      this.n();
      o();
    }
  }
}

/* orginal */
function(a,b){if(this.state==null)return;while(this.s.length)h(this.s.shift(),0,
[a]);while(this.p.length){b=this.p.pop();h(b.b,b,[a])}this.f();if(this==e){this.
n();o()}};

/* optimized */
function(b){if(this.state){for(var a;a=this.s.shift();)h(a,null,[b]);for(;a=this
.p.pop();)h(a.b,a,[b]);this.f();this==e&&(this.n(),o())}};

=========================
+27 bytes

/* orginal */
for (var a = 0; a < this.p.length; a++)
  if (this.p[a].state == 2 && this.p[a].k())
    this.p[a].d()

/* optimized */
for (var a = 0, x; x = this.p[a++];)
  if (x.state == 2 && x.k())
    x.d()

/* original */ 
for(var a=0;a<this.p.length;a++)this.p[a].state==2&&this.p[a].k()&&this.p[a].d();

/* optimized  */ 
for(var a=0,x;x=this.p[a++];)x.state==2&&x.k()&&x.d();


==========================
+28 bytes

/* original */
function f(a, b) {
  for (b = 0; b < this.p.length; b++)
    if (this.p[b].o == a || this.p[b].i(a))
      return 1;
  return 0
}

/* optimized */
function f(a) {
  for (var i = 0, obj; obj = this.p[i++];)
    if (obj.o == a || obj.i(a))
      return 1;
}

/* original */
function f(b,a){for(a=0;a<this.p.length;a++)if(this.p[a].o==b||this.p[a].i(b))re
turn 1;return 0};

/* optimized */
function f(b){for(var c=0,a;a=this.p[c++];)if(a.o==b||a.i(b))return 1};
справедливости ради отмечу, что регулярки медленнее методов string. в примере будет не заметно, но при бо́льших объемах это существенно.
Справедливости ради на чем основано ваше утверждение?
В оригинальном коде делается: получение двух новых строк (substr), два приведения к нижнему регистру (toLowerCase), два сравнения (==). В случае с регулярным выражением делается сравнение без учета регистра. Даже при условии, что регулярные выражения нужно компилировать и они «медленны», здесь выигрыш будет очевидным, так как регулярное выражение простое и в итоге делается меньше операций.
Чтобы не быть голословным я сделал тест, из которого выходит что в этой задаче использование регулярного выражение быстрее (в зависимости от браузера в два и более раз).
Если что-то утверждаете, подтверждайте это тестами.
думаю, это упырит ваш мел

что не отменяет того, что ваша регулярка действительно лучше исходного кода, ибо
получение двух новых строк (substr), два приведения к нижнему регистру (toLowerCase), два сравнения (==).
действительно никуда не годится

ps. утверждал не голословно, год назад проводил тесты, статья до сих пор в черновиках
Смотря какая задача решается. Не утверждаю что регулярные выражения всегда быстрее строковых операций, у каждого метода есть своя ахилесова пята.
Насчет вашего теста. Во-первых, ваше решение показало лучший результат только в одном браузере, что нельзя назвать успехом. Во-вторых, ваше решение определяет что на 4-й или 5-й позиции есть подстрока "://", что неверно, так как в оригинале проверялось, что перед этой подстрокой должна быть подстрока «http» или «https», а у вас это не проверяется. Я добавил в words значение, которое будет давать неверный ответ для вашего решения. Да, и если решать вашим подходом, то substr не нужен, можно обойтись одним indexOf, тогда будет еще быстрее — но все равно не совсем корректно.
Так же если сохранить регулярное выражение отдельно, то можно получить прирост производительности.
Дополненный тест

Ко всему прочему, в данном случае используемый метод никак не повлияет на скорость, так как будет вызываться ничтожно мало кол-во раз, чтобы сказаться на скорости в целом. К тому же задача ставилась — минимизировать объем кода.

ЗЫ А статью заканчивайте и публикуйте — обсудим ;)
Во-первых, ваше решение показало лучший результат только в одном браузере, что нельзя назвать успехом
ie6/7,fx6 — в два раза быстрее, чем rx
ie8 — почти в 4 раза

Во-вторых, ваше решение определяет что на 4-й или 5-й позиции есть подстрока "://", что неверно, так как в оригинале проверялось, что перед этой подстрокой должна быть подстрока «http» или «https», а у вас это не проверяется.
отталкиваемся все же от задачи: в оригинале этого не требуется — нам всего лишь надо знать, абсолютный путь или относительный, приведенный мною код с этим полностью справляется

протокол «error», не не слышал ;)

и я не спорил, что ваш способ короче(и в данном контексте лучше подходит), я указывал на меньшую эффективность regexp'ов при больших объемах данных

зыж статья готова, но теперь ее уже надо править под новые реалии, соберусь с силами и рефакторну
ну же, разработчики браузеров, даёшь байт-код в JS!
JS уже давно компилируется перед выполнением, в большинстве браузеров, и если не в машинный, то в какой-то свой байт-код.
Да это понятно. Ждём возможность хранения скриптов сразу в байт-коде.
Именно. Тогда ещё и старший бит зря пропадать не станет.
for(a;b;){c;d} -> for(a;b;c,d);
особенно после перевода if'ов в троичный оператор.
if(a){b,c} заменяем на if(a)b,c; — выигрываем 1 или 2 байта. Точнее, выигрываем 2 байта за счет фигурных скобок, и теряем один байт за счет финальной точки с запятой (или не теряем, если это в конце блока).

Из примера автора: if(typeof a==«string»){d=false;a=[a]} заменяем на if(typeof a==«string»)d=false,a=[a];
Тут уже выяснили, что еще короче будет
typeof a==«string»?d=false,a=[a]:0;
Не уверен, что это подходит в данном случае. Дело в том, что при невыполнении условия (type a=="string") будет выполнен оператор-заглушка 0; — а это немного уменьшит производительность.
Так-с, написал небольшой тест и проверил. Как оказалось, наличие заглушки 0; в моем случае никак не повлияла на скорость выполнения. По крайней мере так в FF 5, я многократно проверял на цикле 100000000 итераций. Так что мой коммент выше, пожалуй, можете игнорировать. Хотя, можете и сами поставить эксперимент и проверить, мало ли, может быть я ошибся на ночь глядя.
Не забываем про оператор in. При этом выигрываем несколько байт за счет того, что не нужно предварительно инициализировать переменную-индекс, и еще за счет того, что не нужно делать инкремент.

Из примера автора: l=0;for(;l<a.length;){j=a[l++];… заменяем на for(l in a)j=a[l],…
Могут возникнуть проблемы с expando-properties — все-таки обход свойств объекта (for… in ...) — это не то же самое, что обход элементов массива.
Меняем длинную и сложную конструкцию if(a)b;else c — на короткую и простую a?b:c. Выигрываем 8 байт.

Из примера автора:
if(this.j())this.d();else this.f()
->
this.j()?this.d():this.f()

Очень сомневаюсь, что это хоть как-то может уменьшить производительность, зато сокращение кода явно видно.

Кстати, после ряда этих упрощений имеет смысл приступать к избавлению от фигурных скобок в циклах и условных операторах, используя запятую. Это даст дополнительный выигрыш.
Прошу прощения, я написал вышестоящее сообщение не видя Ваш комментарий от 01:37. Забыл обновить комменты перед постом.
this.j()?this.d():this.f()
если вызов и проверка именно такие, то можно это заменить на
this[this.j()?'d':'f']()

два байта как-никак:)
и все же если заменить некоторые совойства и методы
var l="length",p="push",s="state",m="module",...

this.u.push(a) -> this.u[p](a)

не думаю что это сильно замедлит конкретно ваш код, который тут рассматривается — ну максимум на 1мс!!! +-
а размер кода станет меньше с 3937 до 3808 только на приведенных заменах — гарантирую ))
1 мс нужно помножить на 100500 раз, которые этот код будет прорабатывать. Поэтому грязные трюки и называются грязными :-)
что то вы преувиличиваете

l=«length»,p=«push»,s=«state»,m=«module» выполняется один раз
а на этом this.u.push(a) -> this.u[p](a) хорошо если за все 100500 раз набежит 1мс

а если вы собираетесь вызывать 100500 раз _require(), то вам не надо ниче оптимизировать )
if(this.y)this.y();
this.y&&this.y();
-2 байта )
и соот все такие места по -2 байта
if(this==e){this.n();o()}
this==e&&(this.n(),o())
this==e&&(this.n(),o())
this==e&&this.n()&&o()


=)
Виноват, не учел что функция может вернуть false? поэтому правильнее и короче вот так:

this==e&&this.n()|o()
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории