Маньячная минимизация (в погоне за байтом)

    Hello World,

    Этот топик о том, каким образом можно предварительно зарефакторить код так, чтобы улучшить его минимизацию. Недавно я перед релизом минимизировал библиотеку Helios Kernel (о которой написал позавчера). Исходник библиотеки весит 28112 байт, в нём щедрые комментарии, и поэтому он с пол пинка ужимается YUI компрессором до 7083 байт. Не то что бы мне показалось, что 7 килобайт — слишком жирно. Но просто, посмотрев своими глазами на минимизированный код, я смог увидеть кучу мест, где можно было бы сэкономить ещё:



    Посмотрим, что можно сделать с кодом, чтобы превратить 7083 байт в 4009 3937.

    Но прежде чем начать, две оговорки:
    • Мы не будем использовать всякие «грязные» трюки (вроде var a=this или var f=false), которые теоретически приводят к замедлению. Предполагается, что быстродействие всё же важнее размера файла.
    • На каждом шаге я прогонял код через набор тестов. Часто бывает так, что после какого-то изменения всё перестаёт работать. Если в процессе ручной оптимизации вы не будете тестировать код (или если у вас вообще тестов нет), тогда тот код, который получится в итоге, скорее всего работать не будет.


    Выбор минимайзера


    Вообще, эта статья не про сравнение минимайзеров, но в процессе я заметил, что у YUI компрессора есть баг фича: он не убирает фигурные скобки у блоков кода, состящих из одной строки. Более того, он добавляет фигурные скобки, даже если в оригинале их не было (на первой картинке помечено тегом WTF). Я воспринял это как хамство и, не долго думая, перешёл на использование онлайн-минимайзера http://jscompress.com/. Впрочем, остальные рассуждения применимы к любому минимайзеру на ваш вкус.

    Большая Анонимная Функция


    Для начала, давайте обернём весь код в большую анонимную функцию, которая будет тут же вызываться (если изначально этого не сделано). Тогда мы сможем пользоваться локальной областью видимости этой функции. Как это позволит сэкономить байты, будет показано ниже. Самый компактный способ оборачивания кода в анонимную функцию выглядит так:
    было стало
    // код
    
    
    
    !function(){
        // код
    }()
    

    «Приватные» объекты


    Наверняка в коде имеется большое количество вспомогательных объектов, которые не включены в публичный API. Поскольку в Javascript нет нативного способа указать, что объект является приватным, обычно пользуются каким-то соглашением. Чаще всего такие объекты именуют начиная с символа подчёркивания: "_". Обычно минимайзер заменяет имена локальных переменных на однобуквенные, но оставляет неизменными имена «приватных» объектов, потому что не делает смелых предположений, относительно того, как мы обозначаем «приватные» объекты. Но нам не важно, как эти объекты будут называться в минимизированном коде, поэтому можно переименовать их вручную:
    было стало
    myObject._somethingPrivate = {
        // ...
    }
    
    myObject.a = {
        // ...
    }
    
    MyObj = function() {
        this.somePublicProperty = ...;
        this._somePrivateProperty = ...;
        this._anotherPrivateProperty = ...;
    }
    
    MyObj = function() {
        this.somePublicProperty = ...;
        this.a = ...;
        this.b = ...;
    }
    
    MyObj.prototype._privateMethod = function() {
        // ...
    }
    
    MyObj.prototype.c = function() {
        // ...
    }
    

    Здесь нужно быть аккуратным. Во-первых, не забывайте заменять имена приватных функций и переменных не только в объявлениях, но и там, где они используются. Во-вторых, нужно отслеживать логику кода, и не допускать пересечений имён. Например, если у какого-то типа в прототипе уже объявлена функция a, нельзя таким же именем называть приватное свойство этого объекта. Это очевидная вещь, но её легко упустить, если не обращать на это специального внимания.

    Кроме того, приватные объекты часто объявляются не только во всяких конструкторах/инициализаторах. Javascript позволяет дополнять объекты налету. По идее, все приватные идентификаторы в коде можно аккуратно позаменять на однобуквенные:
    было стало
    MyObj.prototype.getSomething = function() {
        if ( typeof this._prop == "undefined" ) {
            this._prop = 0;
        }
    
        return this._prop;
    }
    
    MyObj.prototype.getSomething = function() {
        if ( typeof this.x == "undefined" ) {
            this.x = 0;
        }
    
        return this.x;
    }
    

    «Публичные» объекты


    «Публичные» объекты — это такие, которые входят в API, и нам нужно, чтобы они назывались именно так, как они были названы изначально. Но если «публичный» объект используется внутри кода слишком часто (ну, скажем, хотя бы один раз), а его имя слишком длинное (ну, скажем, больше двух байт), тогда имеет смысл сделать ему алиас:
    было стало
    myObject = { ... }
    
    var a = myObject = { ... }
    

    В этом примере после такого изменения, переменная a будет объявлена как локальная, а переменная myObject — как глобальная (при условии, что идентификатор myObject используется впервые.

    Теперь можно пробежаться по коду, найти все объекты, которые не только объявляются, но и используются, и сделать им алиас:
    было стало
    MyObj = function() {
        this.somePublicProperty = ...;
        this.a = ...;
        this.b = ...;
    }
    
    var b = MyObj = function() {
        this.somePublicProperty = ...;
        this.a = ...;
        this.b = ...;
    }
    
    MyObj.prototype.someMethod = function() {
        // ...
    }
    
    b.prototype.d = b.prototype.someMethod = function() {
        // ...
    }
    
    someStorage.someMethod = function() {
        // ...
    }
    
    var c = someStorage.someMethod = function() {
        // ...
    }
    

    И опять, главное не запутаться в областях видимости и не называть переменные из одной области видимости одинаковыми именами. В примерах выше, у объекта типа MyObj уже есть приватное свойство b и приватный метод c, а новые локальные переменные b и c попадают в область видимости Большой Анонимной Функции, в которую мы обернули весь код в самом начале (обернули же, правда? не забыли?)

    Кроме того, мы можем сделать алиасы некоторым публичным свойствам, но только тем, которые содержат сложные объекты:
    было стало
    AnotherObj = function() {
        this.someProperty = [ 0, 0, 0 ]; // массив
        this.secondProperty = { a: 1 }; // хэш
        this.thirdProperty = 0; // число
        this.fourthProperty = true; // буль-буль
        this.fifthProperty = "hello"; // строка
    }
    
    AnotherObj = function() {
        this.a = this.someProperty = [ 0, 0, 0 ];
        this.b = this.secondProperty = { a: 1 };
        this.thirdProperty = 0;
        this.fourthProperty = true;
        this.fifthProperty = "hello";
    }
    

    Если мы сделаем алиасы для простых объектов, это приведёт к копированию содержимого, и алиас будет указывать на другой объект.

    Собираем var


    Теперь воспользуемся тем, что можно объявлять переменные через запятую, используя слово var один раз. В простейшем случае, это выглядит так:
    было стало
    someFunction = function() {
        var a = 0;
        var b = something();
        // ...
    }
    
    someFunction = function() {
        var a = 0, b = something();
        // ...
    }
    
    
    anotherFunction = function() {
        var c;
        // какой-то код
        var d = something();
        // ещё какой-то код
        for ( var i = 0; i < ...
        // и ещё какой-нибудь код
    }
    
    anotherFunction = function() {
        var c, d = something(), i = 0
        // какой-то код
        // ещё какой-то код
        for ( ; i < ...
        // и ещё какой-нибудь код
    }
    
    

    В общем, нужно вытащить все объявления в начало функции и написать их с использованием одного var. Про оптимизацию цикла for() я напишу ниже. И ещё нужно собрать все локальные объявления внутри нашей Большой Адронной Функции и тоже засунуть их под один var в начале. Это как раз те алиасы, которые мы насоздавали в предыдущем разделе. Весь код должен преобразоваться примерно таким образом:
    было стало
    !function(){
    
        // какой-то код
    
        var b = MyObj = function() {
            this.somePublicProperty = ...;
            this.a = ...;
            this.b = ...;
        }
    
        // ещё какой-то код
    
        var c = b.prototype.someMethod = function() {
            // ...
        }
    
        // и ещё какой-нибудь код
    
    }()
    
    
    !function(){
        var  b = MyObj = function() {
            this.somePublicProperty = ...;
            this.a = ...;
            this.b = ...;
        },
    
        c = b.prototype.someMethod = function() {
            // ...
        },
    
        // и так со всеми алиасами
    
        // какой-то код
    
        // ещё какой-то код
    
        // и ещё какой-нибудь код
    
    }()
    

    Обратите внимание, что в этом примере переменные b, c и им подобные остаются объявлены как локальные для Большой Функции. Таким образом, мы сэкономим столько var'ов, сколько их было в функции (ну, кроме одного).

    И ещё нужно следить, чтобы не поменялась логика кода. Мы ведь меняем порядок строк, поэтому теоретически может произойти так, что какой-то объект будет использоваться до того, как он проинициализирован, этого нельзя допустить.

    Прототипы


    Для каждого объявленного типа и его конструктора можно нехило сэкономить на слове protoype — уж слишком оно длинное. Для этого опишем весь прототип для будущих объектов этого типа в виде одного хэша:
    было стало
    MyObj = function() {
        // ...
    }
    
    MyObj.prototype.someMethod = function() {
        // ...
    }
    
    MyObj.prototype.anotherMethod = function() {
        // ...
    }
    
    MyObj.prototype.thirdMethod = function() {
        // ...
    }
    
    
    
    MyObj = function() {
        // ...
    }
    
    MyObj.prototype = {
        someMethod : function() {
            // ...
        },
    
        anotherMethod : function() {
            // ...
        },
    
        thirdMethod : function() {
            // ...
        }
    }
    

    Как видно, для этого нужно не забыть позаменять "=" на ":" и разделить объявления методов запятыми. Этот способ не сработает для случая, когда нужно дополнить какой-то прототип для конструктора типа, объявленного где-то в другом месте (потому что такой записью мы полностью переопределяем прототип).

    Оптимизация циклов и условий


    Почти все циклы и многие условия можно оптимизировать:
    было стало
    a--;
    if ( a == 0 ) {
        // ...
    }
    
    if ( --a == 0 ) {
        // ...
    }
    
    
    if ( --a == 0 ) {
        // ...
    }
    
    if ( !--a ) {
        // ...
    }
    
    for ( var i = 0; i < a; i++ ) {
        b( c[ i ] );
    }
    
    for ( var i = 0; i < a; ) {
        b( c[ i++ ] );
    }
    

    Но здесь тоже нужно быть внимательным, чтоб не нарушить логику кода.

    Частоиспользуемые значения


    Бывает, что есть значения, которые используются больше одного раза. Их тоже можно вынести в переменные:
    было стало
    // ...
    if ( typeof a == "undefined" ) ...
    // ...
    if ( typeof b == "undefined" ) ...
    // ...
    
    
    var z = "undefined";
    // ...
    if ( typeof a == z ) ...
    // ...
    if ( typeof b == z ) ...
    // ...
    
    if ( typeof a != "function" ) {
        a = function(){}
    }
    // ...
    if ( typeof b != "function" ) {
        b = function(){}
    }
    
    
    
    var f = "function", g = function(){}
    // ...
    if ( typeof a != f ) {
        a = g;
    }
    // ...
    if ( typeof b != f ) {
        b = g;
    }
    
    el = document.createElement( "script" );
    el.type = "text/javascript";
    
    
    var x = "script";
    el = document.createElement( x );
    el.type = "text/java" + x;
    


    Выбрасываем всё лишнее


    Часто бывает, что код содержит лишнюю информацию «для ясности», от которой можно избавиться. Но здесь, как и везде, нужно внимательно отслеживать, что мы удаляем:
    было стало
    if ( a.length > 0 ) {
        b = a.pop()
    }
    
    if ( a.length ) {
        b = a.pop()
    }
    
    var someEnum = { foo : 0, bar : 1, buz : 2 }
    
    // ...
    
    var a = [];
    for ( var i in someEnum ) {
        a[ someEnum[ i ] ] = 0;
    }
    
    // ...
    
    a[ someEnum.bar ] = getSomething();
    
    // ...
    
    if ( c.state == someEnum.foo ) {
        // ...
    }
    
    var someEnum = { foo : 0, bar : 1, buz : 2 }
    
    // ...
    
    var a = [ 0, 0, 0 ];
    
    
    
    
    // ...
    
    a[ 1 ] = getSomething();
    
    // ...
    
    if ( !c.state ) {
        // ...
    }
    

    Бонус: убираем var


    Это интересный трюк, который пригоден в тех случаях, когда внутри функции объявлена одна локальная переменная (или если переменная объявляется без инициализации). Здесь мы экономим на одном var'е, но нам приходится дублировать имя переменной:
    было стало
    doSomething = function( param1, param2 ) {
        var i = 0;
        // ....
    }
    
    doSomething = function( param1, param2, i ) {
        i = 0;
        // ....
    }
    
    doSomething = function( param1, param2 ) {
        var a, b, c;
        // ....
    }
    
    doSomething = function( param1, param2, a, b, c ) {
        // ....
    }
    
    

    Здесь мы используем параметры вместо локальных переменных, но ведут себя они точно так же. Этот трюк не пригоден в тех случаях, когда функция принимает не известное заранее число параметров. Чаще всего он позволяет избавится от почти всех var'ов в коде.

    Что получилось в итоге


    После обработки кода описанными способами, я скормил скрипт сервису jscompress.com. Немного подумав, он выдал мне вот такую кашу на 4009 байт. Приятного аппетита!



    Кстати, я раздам плюсы в карму тем, кто найдёт и опишет в комментах, что ещё можно урезать в этой каше :-)

    Update

    nano_freelancer предложил несколько правильных идей:
    • позаменять все true и false на 1 и 0 соответственно
    • for (initial;condition;loop statement) {statements}
      можно после loop statement поставить запятую и расположить все операторы из statements через запятую(вместо точки с запятой) — экономим 2 байта (фигурные скобки). Но это применимо только для случаев, когда statement само не содержит сложных операторов.

    Кроме того, большинство null'ов также можно заменить на 0 (но не все).

    Размер кода уменьшен до 3937 байт :-)

    Оффтопик: исходный и минимизированный коды, с которыми я работал, доступны для скачивания на домашней страничке проекта: http://home.gna.org/helios/kernel/
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 121

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

        Если использовать компрессию — почти полкило экономится для этого случая :-)
        • UFO just landed and posted this here
          • UFO just landed and posted this here
              0
              Я в своем комментарии ниже написал о том же.
              Однако вы приводите цифры для advanced оптимизаций, после которых данный код перестанет работать. Для advanced режима код нужно специально готовить.
              +1
              а запакованный исходный без «богатых коментариев»?
              0
              Мне кажется так делают скорее для обсфукации кода, потому что gzip все отлично и так сжимает.
              +3
              z=null; и заменить везде null на z, размер должен немного уменьшиться. Хотя, возможно, Вы назовете это грязным трюком…
                0
                Да, я думаю, что это трюк. Такой же, как и var me=this. Я не проводил тестов на скорость, но по идее это замедляет производительность, поскольку ключевые слова резолвятся непосредственно, а обращение к переменным приводит к линейному поиску по скопу.
                  –3
                  Можно определять их константами. Для IE использовать VB.
                    +2
                    Снимаю предложение. Потестил, хоть const и должен в конечном итоге быть тем же true после обработке парсером, похоже это странно реализовано. В IE очень сильно падает производительность.
                    +5
                  +16
                  очевидным будет замена true и false на 1 и 0 соответственно, т.к. strict equal я нигде не вижу — это даст 31 байт экономии; если автору требуется чтобы переменные содержали настоящий буль, то заменить на !0(true) и !1(false), что даст более скромный выигрыш в 22 байта
                    +1
                    кстати да, спасибо! -)
                      +3
                      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
                        –1
                        можно после 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 байт сэкономил

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

                          правда с ходу не соображу, даст ли это экономию при внедрении
                            –1
                            Здесь — нет, тут оно всего два раза используется. Вот если бы хотя бы три было… :-)
                              0
                              для нас нет ничего невозможного — сокращаем предложенный код копирования еще на 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х байт, т.к. скорее всего придется завести отдельную переменную для хранения исходной переменной
                            0
                            — верно, но это подходит только для простых случаев (без например ифов внутри).

                            Можно записать логические конструкции без использования самого 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))
                            


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

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


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

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


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

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

                                Экономия аж 8 байт (if со скобками, else). Правда такой способ, кажется, не применим, если внутри if, к примеру, цикл.
                                  0
                                  для 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 может подкачать :)
                                0
                                Я не проверял, но что, если добавить функцию var l=function(s){return s.length;} и все *.length заменить на l(*)?
                                  –1
                                  Сработает, но это немножко замедлит производительность: придётся вызывать дополнительную функцию каждый раз, со всеми накладными расходами.
                                    0
                                    Вы бы все-таки определились — используете вы эти переименования или нет. А то с одной стороны — «var a=this» и «var f=false» не нравятся, а с другой — «Бывает, что есть значения, которые используются больше одного раза. Их тоже можно вынести в переменные».
                                      0
                                      Ну тут дело в том, что обратиться к переменной a одинаково затратно, как и обратиться к переменной _myMagicVariable. А вот вызывать дополнительную функцию, которая будет вместо меня обращаться к переменной — уже дороже.

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

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

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

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

                                        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 не только полезно с точки зрения объема, но и с точки зрения производительности.
                                          0
                                          2.
                                          s.length 777,078,814
                                          l(s) 776,900,845
                                            0
                                            Ну, собственно, хорошо видно, как JIT работает в более современных браузерах — Chrome 13+, новых Firefox и т.д.
                                            +1
                                            Добавил заминусованый вариант с const:
                                            jsperf.com/renaming-a-variable/2
                                        0
                                        А ещё хотелось бы уточнить. Часто при ужимании JS вижу использование чего-то вроде:
                                        var l="length";
                                        str[l];

                                        вместо str.length и других length. Это тоже негативно сказывается на скорости?
                                        • UFO just landed and posted this here
                                      +2
                                      Как так получилось, что из
                                      if ( --a == 0 )
                                      стало
                                      if ( --a )
                                      ?
                                        –1
                                        оу. затупил. спасибо :-)
                                        +3
                                        А почему YUI, а не GCC? У меня он сильно лучше работал (правда, на несколько больших проектах — под 200кб). Да, для него надо готовить код (а лучше — заранее писать имея его в виду), а результат — обязательно тестировать, но зато результат…
                                          0
                                          GCC уже и с JavaScript работает? О_о видимо я многое пропустил :-)

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

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

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


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

                                                  Но я за собой заметил, что с некоторого времени почти спокойно воспринимаю и отлаживаю минифицированный код :-D
                                                    +1
                                                    Ну есть же JavaScript Beautifier. Названия функций и переменных он, конечно, не вернет, но отформатирует красиво.
                                                      0
                                                      Я как раз об убиении имён приватных методов имён и прочего.
                                                    +15
                                                    Может мне кто-то объяснить зачем это всё делать, зачем пытаться сэкономить пару килобайт на скриптах или стилях, если на странице сейчас графики на сотни килобайт!?.. Тем более что все браузеры довольно эффективно всё кэшируют!

                                                    Иногда проще сделать jpeg, который стоит фоном, с качеством 59% вместо 60%, чем делать себе больную голову с минимизацией стилей или шрифтов, и потом ещё проверять а не поломалось ли что-нибудь!
                                                      +2
                                                      ха. ну дык мы ж маньяки.

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

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

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

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

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

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

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

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

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

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

                                                                            Если вставить содержимое 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,{}))
                                                                              +2
                                                                              Вставить-то я его вставил, но, по-видимому, хабраразметка <source></source> (вопреки своему предназначению) губит код (например, заменяет последовательность символов «(», «c» и «)» на «©»).
                                                                                0
                                                                                Это существенно снизит скорость работы.
                                                                                  +1
                                                                                  У меня на одноядерном Celeron (2,4 ГГц) на распаковку ушло четыре миллисекунды.

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

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

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

                                                                                    по делу: за четыре года, Dean Edwards' Packer устарел во всех смыслах
                                                                                      +1
                                                                                      Однако этот пакер ужал исходник лучше чем YUI
                                                                                        0
                                                                                        …произведя обфускацию кода, о нежелании которой автор написал черным по белому
                                                                                          0
                                                                                          вам шашечки или ехать? минифицированный код не шибко лучше обфусцированного
                                                                                            0
                                                                                            наверное именно поэтому популярнейший фреймворк 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).
                                                                                  0
                                                                                  Есть отличный инструмент для сжатия JS кода — Jasob. Он в частности позволяет настраивать какие переменные заменять, а какие нет, проверяет их на допустимость, считает кол-во вызовов, чтобы определить, какие лучше делать однобуквенными, чтобы больше сэкономить и т.д. Только платный да.
                                                                                    0
                                                                                    Ну и да, обрабатывает вместе и html и css, при чем корректно отрабатывает встречающиеся название переменных в строках.
                                                                                    +10
                                                                                    Первое, что хочется сказать — не используйте 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 версии только в том, что такой код быстрее парсится и требует меньше памяти для хранения кода. Однако это существенно в основном только в случае когда его очень много и лишь для медленных мобильных браузеров.

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

                                                                                    ЗЫ Сорри, что-то улекся писаниной :)
                                                                                      +3
                                                                                      Да, кстати большой выигрыш можно получить на циклах, вот пара примеров:
                                                                                      =========================
                                                                                      +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};
                                                                                      
                                                                                        0
                                                                                        справедливости ради отмечу, что регулярки медленнее методов string. в примере будет не заметно, но при бо́льших объемах это существенно.
                                                                                          +1
                                                                                          Справедливости ради на чем основано ваше утверждение?
                                                                                          В оригинальном коде делается: получение двух новых строк (substr), два приведения к нижнему регистру (toLowerCase), два сравнения (==). В случае с регулярным выражением делается сравнение без учета регистра. Даже при условии, что регулярные выражения нужно компилировать и они «медленны», здесь выигрыш будет очевидным, так как регулярное выражение простое и в итоге делается меньше операций.
                                                                                          Чтобы не быть голословным я сделал тест, из которого выходит что в этой задаче использование регулярного выражение быстрее (в зависимости от браузера в два и более раз).
                                                                                          Если что-то утверждаете, подтверждайте это тестами.
                                                                                            0
                                                                                            думаю, это упырит ваш мел

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

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

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

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

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

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

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

                                                                                                зыж статья готова, но теперь ее уже надо править под новые реалии, соберусь с силами и рефакторну
                                                                                      +1
                                                                                      ну же, разработчики браузеров, даёшь байт-код в JS!
                                                                                        0
                                                                                        JS уже давно компилируется перед выполнением, в большинстве браузеров, и если не в машинный, то в какой-то свой байт-код.
                                                                                          +1
                                                                                          Да это понятно. Ждём возможность хранения скриптов сразу в байт-коде.
                                                                                          0
                                                                                          Именно. Тогда ещё и старший бит зря пропадать не станет.
                                                                                          0
                                                                                          for(a;b;){c;d} -> for(a;b;c,d);
                                                                                          особенно после перевода if'ов в троичный оператор.
                                                                                            0
                                                                                            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];
                                                                                              0
                                                                                              Тут уже выяснили, что еще короче будет
                                                                                              typeof a==«string»?d=false,a=[a]:0;
                                                                                                0
                                                                                                Не уверен, что это подходит в данном случае. Дело в том, что при невыполнении условия (type a=="string") будет выполнен оператор-заглушка 0; — а это немного уменьшит производительность.
                                                                                                  0
                                                                                                  Так-с, написал небольшой тест и проверил. Как оказалось, наличие заглушки 0; в моем случае никак не повлияла на скорость выполнения. По крайней мере так в FF 5, я многократно проверял на цикле 100000000 итераций. Так что мой коммент выше, пожалуй, можете игнорировать. Хотя, можете и сами поставить эксперимент и проверить, мало ли, может быть я ошибся на ночь глядя.
                                                                                              0
                                                                                              Не забываем про оператор in. При этом выигрываем несколько байт за счет того, что не нужно предварительно инициализировать переменную-индекс, и еще за счет того, что не нужно делать инкремент.

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

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

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

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

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

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

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

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

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


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

                                                                                                            this==e&&this.n()|o()

                                                                                                      Only users with full accounts can post comments. Log in, please.