Javascript.RU

Улучшаем сжимаемость Javascript-кода.

Update: Более новый материал по этой теме находится по адресу https://learn.javascript.ru/better-minification.

При сжатии javascript-кода минификатор делает две основные вещи.

  1. удаляет заведомо лишние символы: пробелы, комментарии и т.п.
  2. заменяет локальные переменные более короткими.

В статье рассматриваются минификаторы YUI Compressor и ShrinkSafe.
На момент написания это лучшие минификаторы javascript.

Есть несколько несложных приемов программирования, которые могут увеличить сжимаемость JS-кода.

Минификатор заменяет все локальные переменные на более короткие
(Также о сжатии в статье Сжатие Javascript и CSS).

Например, вот такой скрипт:

function flyToMoon(moon) {
  var spaceShip = new SpaceShip()
  spaceShip.fly(moon.getDistance())
}
</div>

После минификации станет:

function flyToMoon(A) {
  var B = new SpaceShip()
  B.fly(A.getDistance())
}

Заведомо локальные переменные moon, spaceShip могут быть безопасно заменены на более короткие A,B. Минификатор использует патченный открытый интерпретатор javascript, написанный на java: Rhino, он разбирает код, анализирует и собирает обратно с заменой, поэтому все делается корректно.

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

По тем же причинам не сжимается вызов SpaceShip и методы fly, getDistance().

Итак, сделаем какой-нибудь более реальный скрипт и напустим на него YUI Compressor.

function SpaceShip(fuel) {
    
    this.fuel = fuel
    
    this.inflight = false
        
    this.showWarning = function(message) {
        var warningBox = document.createElement('div')
        with (warningBox) {
            innerHTML = message
            className = 'warningBox'
        }
        document.body.appendChild(warningBox)        
    }
    
    this.showInfo = function(message) {
        var messageBox = document.createElement('div')
        messageBox.innerHTML = message
        document.body.appendChild(messageBox)        
    }
    
    this.fly = function(distance) {
        if (distance<this.fuel) {
            this.showWarning("Мало топлива!")
        } else {            
            this.inflight = true
            this.showInfo("Взлетаем")
        }
    }
    
}

function flyToMoon() {
    var spaceShip = new SpaceShip()
    spaceShip.fly(1000)
}

flyToMoon()

К сожалению, в YUI Compressor нельзя отключить убивание переводов строки, поэтому получилось такое:

function SpaceShip(fuel){this.fuel=fuel;
this.inflight=false;this.showWarning=function(message){
var warningBox=document.createElement("div");
with(warningBox){innerHTML=message;
className="warningBox"
}document.body.appendChild(warningBox)
};this.showInfo=function(message){
var messageBox=document.createElement("div");
messageBox.innerHTML=message;
document.body.appendChild(messageBox)
};this.fly=function(distance){
if(distance<this.fuel){this.showWarning("Мало топлива!")
}else{this.inflight=true;
this.showInfo("Взлетаем")
}}}function flyToMoon(){var A=new SpaceShip();
A.fly(1000)};flyToMoon()

Если посмотреть внимательно - видно, что локальные переменные вообще не сжались внутри SpaceShip, а сжались только в функции flyToMoon.

ShrinkSafe замечательно сжимает все, что только может.

function SpaceShip(_1){
this.fuel=_1;
this.inflight=false;
this.showWarning=function(_3){
var _4=document.createElement("div");
with(_4){
innerHTML=_3
className="warningBox";
}
document.body.appendChild(_4);
};
this.showInfo=function(_5){
var _6=document.createElement("div");
_6.innerHTML=_5;
document.body.appendChild(_6);
};
this.fly=function(_7){
if(_7<this.fuel){
this.showWarning("Мало топлива!");
}else{
this.inflight=true;
this.showInfo("Взлетаем");
}
};
};
function flyToMoon(){
var _8=new SpaceShip();
_8.fly(1000);
};
flyToMoon();

Все локальные переменные заменены на более короткие _варианты.

Почему YUI не сжал, а ShrinkSafe справился?

Дело в конструкции with() { ... } в методе showWarning. Эти два компрессора по-разному к ней относятся.

ShrinkSafe игнорирует нелокальные названия переменных внутри with:

// Было
with(val) {
  prop = val
}
// Стало
with(_1) {
  prop = _1
}

К сожалению, в последней версии ShrinkSafe есть баг: если переменная prop объявлена локально, то она заменяется:

// Было
var prop
with(val) {
  prop = val
}
// Стало
var _1
with(_2) {
  _1 = _2
}

Например, если val = { prop : 5 }, то сжатая таким образом конструкция with сработает неверно из-за сжатия prop до _1.

Впрочем, никогда не слышал, чтобы кто-то на такой баг реально напоролся. Видимо, люди локальные переменные не объявляют одноименные со свойствами аргумента with, чтобы код понятнее был.

Внутри оператора with(obj) никогда нельзя точно сказать: будет ли переменная взята из obj или из внешней области видимости.

Поэтому никакие переменные внутри with сжимать нельзя.

А раз так - получается, что локальные переменные с именами, упомянутыми внутри with тоже сжимать нельзя.

YUI Compressor почему-то (почему? есть идеи?) пошел еще дальше: он не минифицирует вообще никаких локальных переменных даже в соседних функциях.

Может быть, это баг (версия 2.3.5), а может - фича, не знаю. Будут идеи - пишите в комментариях. Например, локальные переменные функции fly вполне можно было минифицировать.

Вывод:

YUI категорически не любит with.

ShrinkSafe любит, но с багофичей.

Если заменить функцию showWarning на вариант без with, то YUI сожмет код без проблем:

// вариант без with
this.showWarning = function(message) {
        var warningBox = document.createElement('div')
        warningBox.innerHTML = message
        warningBox.className = 'warningBox'        
        document.body.appendChild(warningBox)        
}

Результат сжатия YUI без with:

function SpaceShip(A){this.fuel=A;
this.inflight=false;this.showWarning=function(B){var C=document.createElement("div");
C.innerHTML=B;C.className="warningBox";
document.body.appendChild(C)
};this.showInfo=function(B){var C=document.createElement("div");
C.innerHTML=B;document.body.appendChild(C)
};this.fly=function(B){if(B<this.fuel){this.showWarning("Мало топлива!")
}else{this.inflight=true;
this.showInfo("Взлетаем")
}}}function flyToMoon(){var A=new SpaceShip();
A.fly(1000)}

В примере не сжались вызовы к объекту document.

Для того, чтобы сжатие сработало, надо заменить обращение к глобальной переменной document вызовом локальной функции.

Например, вот так:

function SpaceShip(fuel) {
    /* сокращенные локальные вызовы */
    var doc = document
    var createElement = function(str) {
        return doc.createElement(str)
    }
    var appendChild = function(elem) {
        doc.body.appendChild(elem)
    }

    this.fuel = fuel
    
    this.inflight = false
        
    this.showWarning = function(message) {
        var warningBox = createElement('div')
        warningBox.innerHTML = message
        warningBox.className = 'warningBox'        
        appendChild(warningBox)        
    }
    
    this.showInfo = function(message) {
        var messageBox = createElement('div')
        messageBox.innerHTML = message
        appendChild(messageBox)        
    }
    
    this.fly = function(distance) {
        if (distance<this.fuel) {
            this.showWarning("Мало топлива!")
        } else {            
            this.inflight = true
            this.showInfo("Взлетаем")
        }
    }    
}

Обращение к document осталось в одном месте, что тут же улучшает сжатие:

(Здесь и дальше для сжатия использован ShrinkSafe, т.к он оставляет переводы строки.
Результаты YUI - по сути, такие же)

function SpaceShip(_1){
var _2=document;
var _3=function(_4){
return _2.createElement(_4);
};
var _5=function(_6){
_2.body.appendChild(_6);
};
this.fuel=_1;
this.inflight=false;
this.showWarning=function(_7){
var _8=_3("div");
_8.innerHTML=_7;
_8.className="warningBox";
_5(_8);
};
this.showInfo=function(_9){
var _a=_3("div");
_a.innerHTML=_9;
_5(_a);
};
this.fly=function(_b){
if(_b<this.fuel){
this.showWarning("Мало топлива!");
}else{
this.inflight=true;
this.showInfo("Взлетаем");
}
};

Как правило, в интерфейсах достаточно много обращений к document, и все они длинные, поэтому этот подход может уменьшить сразу код эдак на 10-20%.

Функции объявлены через var, а не function:

var createElement = function(str) { // (1)
  return doc.createElement(str)
}
// а не
function createElement(str) {  // (2)
  return doc.createElement(str)
}

Это нужно для ShrinkSafe, который сжимает только определения (1). Для YUI - без разницы, как объявлять функцию, сожмет и так и так.

Существуют различные способы объявления объектов.
Один из них - фабрика объектов, когда для создания не используется оператор new.

Общая схема фабрики объектов:

function object() {
  var private = 1  // приватная переменная для будущего объекта

  return {   // создаем объект прямым объявлением в виде { ... }
    increment: function(arg) {  // открытое свойство объекта
        arg += private // доступ к приватной переменной
        return arg
    }
  }
}
// вызов не new object(), а просто
var obj = object()

Прелесть тут состоит в том, что приватные переменные являются локальными, и поэтому могут быть сжаты. Кроме того, убираются лишние this.

function SpaceShip(fuel) {
    var doc = document

    var createElement = function(str) {
        return doc.createElement(str)
    }
    var appendChild = function(elem) {
        doc.body.appendChild(elem)
    }
    
    var inflight = false    
    
    var showWarning = function(message) {
        var warningBox = createElement('div')
        warningBox.innerHTML = message
        warningBox.className = 'warningBox'        
        appendChild(warningBox)        
    }
    
    var showInfo = function(message) {
        var messageBox = createElement('div')
        messageBox.innerHTML = message
        appendChild(messageBox)        
    }
    
    return {
        fly: function(distance) {
            if (distance<this.fuel) {
                showWarning("Мало топлива!")
            } else {            
                inflight = true
                showInfo("Взлетаем")
            }
        }
    }
    
}

function flyToMoon() {
    var spaceShip = SpaceShip()
    spaceShip.fly(1000)
}
function SpaceShip(_1){
var _2=document;
var _3=function(_4){
return _2.createElement(_4);
};
var _5=function(_6){
_2.body.appendChild(_6);
};
var _7=false;
var _8=function(_9){
var _a=_3("div");
_a.innerHTML=_9;
_a.className="warningBox";
_5(_a);
};
var _b=function(_c){
var _d=_3("div");
_d.innerHTML=_c;
_5(_d);
};
return {fly:function(_e){
if(_e<this.fuel){
_8("Мало топлива!");
}else{
_7=true;
_b("Взлетаем");
}
}};
};
function flyToMoon(){
var _f=SpaceShip();
_f.fly(1000);
};
flyToMoon();

Максимально возможное использование локальных переменных, собственно, и улучшает минификацию. А некоторые подходы в этой статье - лишь иллюстрации.


Автор: sores (не зарегистрирован), дата: 20 июня, 2008 - 11:53
#permalink

ВОопщето вот лучший минимизатор!
http://dean.edwards.name/packer/


Автор: Илья Кантор, дата: 20 июня, 2008 - 12:36
#permalink

Это неправда.

packer не следует вообще использовать, если на сервере стоит mod_gzip/deflate/compress.


Автор: sunnybear (не зарегистрирован), дата: 23 июля, 2008 - 21:36
#permalink

Однозначно сказать нельзя. В общем случае, да. Но не всегда


Автор: Илья Кантор, дата: 23 июля, 2008 - 23:36
#permalink

Приведите, пожалуйста, хоть один пример скрипта, на котором результат packer + gzip весит меньше, чем просто gzip.


Автор: Гость (не зарегистрирован), дата: 25 января, 2009 - 19:32
#permalink
jQuery JavaScript Library v1.3:
-----------------
jquery.pack                                        js │   38058│
jquery.pack.js                                     gz │   18997│

*(gzip -9)


Автор: Гость (не зарегистрирован), дата: 25 января, 2009 - 19:36
#permalink
jquery.pack.js         │   38058
jquery.js.gz           │   34278
jquery.pack.js.gz      │   18997

Автор: Илья Кантор, дата: 27 февраля, 2009 - 09:45
#permalink

Дополнил статью по сжатию - смотрите в конец, там мои результаты по сжатию jQuery 1.3.2 с использованием php-packer 1.1, yui 2.4.2(он же - родной минифай) и gzip.

Да, действительно, gzip + packer могут работать вместе. Предполагаю, что это благодаря той части packer, которая реализует регэкспы. Регэкспы используют знание о структуре кода, которого нет у gzip.

Возможно, gzip + packer работали бы еще лучше вместе, если б из packer убрать псевдо-гзип и оставить только регэкспы.. Но тогда уже получится минифай вместо пакера.

Итого, лучше всех: yui + gzip.

P.S. Если еще есть что обсудить - лучше там, здесь все же статья по оптимизации для сжатия, а не по самому сжатию.


Автор: Гость (не зарегистрирован), дата: 20 июня, 2008 - 14:17
#permalink

В Опере страничка отображается неправильно


Автор: Илья Кантор, дата: 20 июня, 2008 - 23:43
#permalink

Да, совсем забыл об этом

Оказались непротестаны изменения верстки в опере. Поправил.


Автор: sunnybear (не зарегистрирован), дата: 23 июля, 2008 - 21:37
#permalink

К сожалению, все эти фишки актуальны только при отсутствии сжатия. Само сжатия уменьшает код раз в 5, поэтому дальнейшая оптимизация зачастую бессмысленна.


Автор: Илья Кантор, дата: 23 июля, 2008 - 23:36
#permalink

Зачастую сжатие в 5 раз еще не означает сжатие до нуля


Автор: Zebooka (не зарегистрирован), дата: 29 сентября, 2008 - 11:00
#permalink

Собственно не верное утверждение.
Ибо эти фишки можно использовать как обфускатор. Для запутывания кода.


Автор: Гость (не зарегистрирован), дата: 21 сентября, 2008 - 13:13
#permalink

А может лучше просто самому писать короткий код?


Автор: миркус (не зарегистрирован), дата: 26 января, 2009 - 16:41
#permalink

а после тебя трынь трава?
другой разработчик будет сидеть голову ломать?

имхо,писать лучше читабельным кодом.
с комментами.

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


Автор: imsha, дата: 7 декабря, 2011 - 10:53
#permalink

Коммент неудачно написал уровнем ниже....

А сам через месяц когда вернешься поправить баг и сразу в ступор.
Мы на работе договорились даже об определенной семантике названия объектов и т.п. Пусть даже они будут длинными. Зато поддерживать удобнее. При факте - что вероятнее всего другой разработчик будет этим заниматься.

ТС, за статью спасибо. Буквально на днях скрещивал Tortoise Svn с батниками и YUI Compressor. Теперь есть о чем подумать и как все это дело допилить еще лучше.


Автор: Dima (не зарегистрирован), дата: 28 октября, 2008 - 22:32
#permalink

Для конкретного кода подходят разные типы сжатия кода.
Вообще, понравилась статья, узнал кое-что новое.


Автор: Мариана (не зарегистрирован), дата: 13 ноября, 2008 - 15:55
#permalink

Зачастую сжатие в 5 раз еще не означает сжатие до нуля


Автор: Нина (не зарегистрирован), дата: 22 ноября, 2008 - 13:33
#permalink

а для чего вообще сжимать, зачем такие сложности?


Автор: Илья Кантор, дата: 22 ноября, 2008 - 20:21
#permalink

Чтобы быстрее загружалось


Автор: gps (не зарегистрирован), дата: 13 августа, 2009 - 16:42
#permalink

Спасиб отличный сайт...


Автор: Гость (не зарегистрирован), дата: 3 сентября, 2009 - 13:51
#permalink

Отличный пакер!
Сделал простую обертку для запуска этого crunchy из командной строки:

<package>
<job>
<script language="javascript">
window = {};
var WS = WScript, oFSO = new ActiveXObject("Scripting.FileSystemObject"),
function alert(arg){
    WS.echo(arg)
};
function readFile(fileName) {
    var stream = oFSO.OpenTextFile(fileName),
        text = stream.ReadAll();
    stream.Close()
    return text
};

function createAndWriteFile(fileName, text) {
    var streem = oFSO.CreateTextFile(fileName);
    streem.Write(text);
};

function pack(sourceText) {
    sourceText = sourceText.replace(/\r*\n/g, "\n").replace(/\r/g, "\n");
    try {
        return Crunchy.crunch(sourceText);
    } catch (error) {

    }
};

</script>
<script src="web-crunchy.js" language="javascript"></script>
<script language="javascript">

var args = WS.arguments;
if( args.length < 1 ) WS.Quit(1);

var inputFileName = args.item(0);
var outputFileName = args.length < 2 ?
    inputFileName.replace(/(\.js$)|$/, "(crunchy-compressed).js") :
    args.item(1);

createAndWriteFile(outputFileName, pack(readFile(inputFileName)));

</script>
</job>
</package>

Автор: Гость (не зарегистрирован), дата: 4 октября, 2018 - 12:54
#permalink

Защитить код можно достаточно просто, помогут статьи.


Автор: Bob Williams (не зарегистрирован), дата: 22 октября, 2019 - 10:34
#permalink

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

Gun Mayhem 2


Автор: osama shk (не зарегистрирован), дата: 28 января, 2020 - 18:46
#permalink

Interesting post. I Have Been wondering about this issue, so thanks for posting. Pretty cool post.It 's really very nice and Useful post.Thanks
click this site


Автор: qagaty (не зарегистрирован), дата: 31 января, 2020 - 14:31
#permalink

I’ve read some good stuff here. Definitely worth bookmarking for revisiting. I surprise how much effort you put to create such a great informative website.


Автор: osama shk (не зарегистрирован), дата: 1 февраля, 2020 - 12:20
#permalink

I recently came across your blog and have been reading along. I thought I would leave my first comment. I don't know what to say except that I have enjoyed reading. Nice blog. I will keep visiting this blog very often.
bitcoin news


Автор: osama kji (не зарегистрирован), дата: 2 февраля, 2020 - 18:25
#permalink

That appears to be excellent however i am still not too sure that I like it. At any rate will look far more into it and decide personally!
ataque de pánico


Автор: sond (не зарегистрирован), дата: 10 февраля, 2020 - 22:51
#permalink

I have a hard time describing my thoughts on content, but I really felt I should here. Your article is really great. I like the way you wrote this information.
Digital Marketing Agency


Автор: osama shk (не зарегистрирован), дата: 12 февраля, 2020 - 18:36
#permalink

Great articles and great layout. Your blog post deserves all of the positive feedback it’s been getting.
https://champagne-pools.com


Автор: osama shk (не зарегистрирован), дата: 15 февраля, 2020 - 14:27
#permalink

I am very happy to discover your post as it will become on top in my collection of favorite blogs to visit.
rico chandra kusuma


Автор: osama shk (не зарегистрирован), дата: 15 февраля, 2020 - 16:52
#permalink

Just admiring your work and wondering how you managed this blog so well. It’s so remarkable that I can't afford to not go through this valuable information whenever I surf the internet!
go here


Автор: osama shk (не зарегистрирован), дата: 15 февраля, 2020 - 18:10
#permalink

If you set out to make me think today; mission accomplished! I really like your writing style and how you express your ideas. Thank you.
https://worldsbestdogfoods.org/


Автор: osama shk (не зарегистрирован), дата: 15 февраля, 2020 - 19:00
#permalink

I wanted to thank you for this great read!! I definitely enjoying every little bit of it I have you bookmarked to check out new stuff you post.
divorce lawyers colorado springs


Автор: osama shk (не зарегистрирован), дата: 16 февраля, 2020 - 20:15
#permalink

I like this post,And I guess that they having fun to read this post,they shall take a good site to make a information,thanks for sharing it to me.
https://sportstv.io/en/watch-live/all-sports/upcoming


Автор: osama shk (не зарегистрирован), дата: 17 февраля, 2020 - 17:14
#permalink

I have read your blog it is very helpful for me. I want to say thanks to you. I have bookmark your site for future updates.
read the article


Автор: osama shk (не зарегистрирован), дата: 17 февраля, 2020 - 18:10
#permalink

Great job for publishing such a beneficial web site. Your web log isn’t only useful but it is additionally really creative too. There tend to be not many people who can certainly write not so simple posts that artistically. Continue the nice writing
learn here


Автор: osama shk (не зарегистрирован), дата: 17 февраля, 2020 - 18:51
#permalink

Thank you so much for sharing this great blog.Very inspiring and helpful too.Hope you continue to share more of your ideas.I will definitely love to read.
look at this


Автор: osama shk (не зарегистрирован), дата: 17 февраля, 2020 - 19:49
#permalink

Thank you for some other informative blog. Where else could I get that type of information written in such an ideal means? I have a mission that I’m just now working on, and I have been at the look out for such information.
TheFancyVoyager


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

Приветствуются комментарии:
  • Полезные.
  • Дополняющие прочитанное.
  • Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены.
    Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Содержание этого поля является приватным и не предназначено к показу.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Разрешены HTML-таги: <strike> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <u> <i> <b> <pre> <img> <abbr> <blockquote> <h1> <h2> <h3> <h4> <h5> <p> <div> <span> <sub> <sup>
  • Строки и параграфы переносятся автоматически.
  • Текстовые смайлы будут заменены на графические.

Подробнее о форматировании

CAPTCHA
Антиспам
1 + 0 =
Введите результат. Например, для 1+3, введите 4.
 
Текущий раздел
Поиск по сайту
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние комментарии
Последние темы на форуме
Forum