Javascript.RU

Оптимизации стандартного режима

Оптимизации Google Closure Compiler делятся на два типа.

Первый тип - стандартные. Они задуманы как безопасные. Эти оптимизации делают внутренние преобразования функций и кода, которые не должны влиять на результат выполнения.

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

Эти оптимизации применяются в режиме --compilation_level SIMPLE_OPTIMIZATIONS, установленном по умолчанию.

Главная операция в этом режиме - переименование локальных переменных.

Например, если внутри функции объявлено длинное имя переменной nodeToHide:

function hide(nodeToHide) {
  nodeToHide.style.display = 'none'
}

... То ничего страшного не случится, если заменить его на более короткое n:

function hide(n) {
  n.style.display = 'none'
}

А размер функции при этом уменьшился.

Перед переименованием оптимизатор сортирует все переменные по частоте применения, так что более часто используемые переменные получат более короткие имена.

Эта оптимизация составляет основу работы современных оптимизаторов Javascript, включая ShrinkSafe, YUI Compressor и Google Closure Compiler.

К совсем безопасным оптимизациям также относятся вырезание из кода комментариев и лишних пробелов.

Далее мы рассмотрим список дополнительных оптимизаций, применяемых Google Closure Compiler.

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

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

Например: 1+2 станет 3, "my"+'string' станет "mystring", 1 && 0 станет просто 0.

До оптимизации:

function test(name, value) {
   run(name, 'my'+'string', 60*60*5, 1 && 0, value && 0)
}

После:

function test(a, b) {
  run(a, "mystring", 18000, 0, b && 0)
}

Обратите внимание, что последний аргумент run остался неизменным: b && 0, хотя по логике его заведомо можно заменить на 0. Это потому, что объединение констант оперирует только с константами.

Рассмотрим следующий код:

function test(nodeId) {
  var elem = document.getElementsById(nodeId)
}

После сжатия:

function test(a) {
  a = document.getElementsById(a)
}

Как видите, в исходном коде было 2 переменные: nodeId и elem, а в получившемся - осталась только одна.

Это потому, что google closure compiler ищет и использует заново те имена переменных, для которых допустимо повторное использование. Например:

var x = 1
print(x)
var y = 2
print(y)

Переменная x, начиная со 2й строки, не используется, и поэтому этот код вполне можно заменить на:

var a = 1
print(a)
a = 2
print(a)

Таким образом, мы с одной стороны экономим объявление var, с другой - уменьшаем общее количество переменных.

В чем еще бонус уменьшения количества переменных?

Допустим, у нас было 40 локальных переменных. Каждую хотелось бы заменить на короткое имя, желательно в один символ. Итого, для переименования потребовалось бы 40 символов. Столько нету, поэтому некоторые переменные будут заменены на сочетания из двух символов. А после уменьшения количества переменных - их может остаться всего 20, так что вполне хватит по одному символу на каждую.

При выполнении этой оптимизации google code compiler опирается на построенный по коду граф взаимодействий, в котором содержится информация о присвоении значений переменным.

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

Посмотрим на это в следующем примере:

function test(node) {
  node.innerHTML = 'newValue'
  node = 123 // лишнее присваивание
}

Стало:

function test(a) {
  a.innerHTML = "newValue"
}
;

Переменная node не используется после присваивания, поэтому присваивание было удалено.

Другой пример:

function test(node) {
  node.innerHTML = 'newValue'
  node = createElement('div')
}

Стало:

function test(a) {
  a.innerHTML = "newValue";
  createElement("div")
}

Как видите, присваивание было убрано, а вызов внешнего метода оставлен. Это в целях безопасности, т.к. оптимизация не знает, что еще может делать вызов createElement, не имеет ли этот вызов side effects, т.е. не делает ли он что-то, кроме возврата результата. Не меняет ли этот вызов документ, и т.п.

Аннотация @nosideeffects

В документации к google closure compiler есть возможность указывать отсутствие side effects путем аннотации:

/** @nosideeffects */
function createElement(name) {
  return document.createElement(name)
}
 
function test(node) {
  node.innerHTML = 'newValue'
  node = createElement('div')
}

Естественно было бы ожидать, что после такого описания вызов node = createElement('div') будет полностью удален из кода как неиспользуемый и без дополнительных эффектов.

Однако, на момент написания статьи эта аннотация никак не влияет на оптимизацию.

В исходных кодах google closure compiler есть соответствующие классы и методы (см. computeFunctionSideEffects и markPureFunctions), которые могли бы помочь, но они никак не используются. Возможно, это в связи с невычищенными багами.

Эта простая оптимизация объединяет несколько объявлений var в одно.

До оптимизаии:

function test() {
  var x = 1
  var y = 2

  return x+y
}

После:

function test() {
  var a = 1, b = 2;
  return a + b
}

Эта оптимизация отыскивает все обращения к свойствам вида object["prop"] и меняет их на object.prop.

До:

function test(node) {
  node["innerHTML"] = 'test'
}

После:

function test(a) {
  a.innerHTML = "test"
}

Google Closure Compiler вовсю использует тернарный оператор и вызов через && для укорачивания операций if:

Было:

if (a) {
  alert(1)
} else {
  alert(2)
}

Станет:

a?alert(1):alert(2);

Еще пример.
Было:

if (a) {
  alert(1)
}

Станет:

a&&alert(1);

Такому преобразованию подвергаются только однострочные блоки if..else.

Все метки анализируются. Если метка используется, она переименовывается на более короткую. Если нет - удаляется.

Например:

function test() {
  label: { 
    var a = 5
  }
}

function test2() {
  label: for(var i=0;i<5; i++) { 
    var a = 5
  }
}

function test3() {
  label: for(var i=0;i<5; i++) { 
    var a = 5
    break label;
  }
}

После оптимизации:

function test() {
  var a = 5
}
function test2() {
  var a = 0;
  for(;a < 5;a++)var b = 5
}
function test3() {
  var a = 0;
  a:for(;a < 5;) {
    a = 5;
    break a
  }
}

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

Весь код после оператора return удаляется.

Кроме того, удаляются заведомо безопасные фрагменты, например Class.prototype.property; или true;.

Например, до оптимизации:

function test() {
  test.a.b; 

  alert("test") 

  return;

  window.callMe()
}

После:

function test() {
  alert("test")
}

Google closure compiler удалил обращение к свойству test.a.b и заведомо невыполнимый код после return.

Эта оптимизация не совсем безопасна, т.к., например, Firefox позволяет определять функции-геттеры для свойств. При этом обращение object.prop будет вызывать функцию-геттер, которая потенциально может что-то сделать.

Но если вы не используете геттеры, либо ваши геттеры не имеют side effects (как и должно быть) - то для вас такая оптимизация безопасна.

Кроме того, оптимизатором могут быть удалены заведомо лишние блоки try..catch и объявленные, но не используемые переменные внутри функций.

Также будут удалены блоки вида:

if (0) { .... }

Это довольно удобно, поскольку такого вида проверки часто используются для комментариев. Например, пусть у нас был такой код:

function Animal(name) {
  if(name) {
     doThis()
  } else {
     doThat()
  }
  this.name = name
}

Нам захотелось по-быстрому отключить ветку if(name), но менять структуру кода не хотим. Обычно это делается добавлением && 0 к условию:

function Animal(name) {
  if(name && 0) {
     doThis()
  } else {
     doThat()
  }
  this.name = name
}

После оптимизации этот код превратится в:

function Animal(a) {
  doThat();
  this.name = a
}

Ветка if(name && 0) была удалена вместе с конструкцией if.

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

Google Closure Compiler меняет форму объявления функции, делая его короче:

До:

var test = function() { return 1 }

После:

function test() {  return 1 }

Эта оптимизация потенциально небезопасна, так как эти две формы записи функции неэквивалентны.

Функция, объявленная как function name(...) {...}, видна везде в области видимости, а объявленная через присвоение переменной - лишь после присвоения.

С другой стороны, на практике эта "багофича" компилятора вреда не приносит. Расширилась немного область видимости функции, ну и что?

Компилятор старается сделать эту оптимизацию максимально безопасно. Например, если одна и та же функция объявляется 2 раза - он переименует только первое объявление.

function go(node) {

	var process = function(node) {
		node.style.display = 'none'
	}

	process(node)

	var process = function(node) {
		node.innerHTML = 'newText'
	}

	process(node)

}

После сжатия:

function go(c) {
  function a(b) {
    b.style.display = "none"
  }
  a(c);
  a = function(b) {
    b.innerHTML = "newText"
  };
  a(c)
}
;

Как видно, переименовано только первое объявление, так что конфликтов не возникает.

Если переменные-функции объявляются в более сложной структуре, внутри вложенных if..else и т.п., то переименования может вообще не быть.

Использование with всегда было проблемой оптимизаторов javascript. Эта конструкция создает область видимости из объекта. В результате любые обращения к переменным внутри with могут как относиться как к объекту, так и к переменным вне with.

Например:

function changePosition(elem) {
  with (elem.style) {
    position = 'absolute'
  }
}

C виду - простая функция. Что делает - тоже понятно: меняет elem.style.position.

Но на этапе компиляции и анализа кода - непонятно, будет ли position меняться в самом объекте, а не в глобальной области.

Хотя это знание потенциально можно было бы извлечь из типа elem (Closure Compiler умеет обращаться с типами), но вообще говоря - не известно точно, какое свойство на момент выполнения этого кода будет в объекте, а какое - нет.

Так как компилятор не знает наверняка, с каким объектом будет иметь дело with, и какие у этого объекта будут свойства, то он не может переименовать position.

После сжатия:

function changePosition(a) {
  with(a.style)position = "absolute"
}
;

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

Было:

function changePosition(elem) {
  var position

  with (elem.style) {
    position = 'absolute'
  }

}

Станет:

function changePosition(a) {
  with(a.style);
}
;

Как видно, компилятор "безопасно" удалил лишнюю переменную. Получился кривой код. Выглядит как ошибка компилятора, более детальный анализ кода позволил бы ее избежать.

Чтобы не напороться на эту ошибку и другие подобные грабли - проще всего не применять конструкцию with. Google Closure Compiler выводит warning, если видит with.

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

Поэтому было бы безопасно не переименовывать переменные в областях видимости, где выполняется eval, чтобы обращения к локальным переменным по старым именам прошли успешно.

Однако, Google Closure Compiler переименовывает переменные, не обращая внимания на eval.

Это нужно иметь в виду, если ваш eval меняет локальные переменные или выполняет (формат JSONP) функции.

Условная компиляция (Conditional Compilation) - фишка IE, которая позволяет выполнить разный код в зависимости от ОС и версии браузера.

Как правило, они используются для определения версии браузера:

Пример: Определение версии через CC
function getBrowser() {
	var browser = navigator.userAgent
/*@cc_on
   /*@if (@_win32)
	browser = "ieWin"
   @else @*/
	browser = "ieNoWin"
   /*@end
@*/
	return browser
}

Собственно, этот способ надежнее разбора userAgent, т.к. в userAgent прописываются разные плагины, что иногда приводит к глюкам распознавания.

Google Closure Compiler понятия не имеет об условной компиляции (в отличие, например, от YUI compressor). Поэтому он просто удалит все от /* до */:

function getBrowser() {
  var a = navigator.userAgent;
  return a = "ieNoWin"
}
;

Это совсем не тот результат, который мы хотели рассчитывали получить.

Поэтому вывод здесь простой: не стоит жать скрипт с условной компиляцией google closure compiler'ом.

В этой статье описаны основные серьезные оптимизации, которые являются "визитной карточкой" стандартного режима сжатия. Кроме них, google closure compiler проводит еще ряд мелких изменений, например:

  • Убирает лишние кавычки у ключей

    {"prop" : "val" }   =>  {prop:"val"}
    
  • Упрощает пустые вызовы Array/Object
    a = new Array()   =>  a = []
    o = new Object()  => o = {}
    
  • Убирает лишние апострофы
    a = "a'c"  =>  a="a'c"
    
  • ...И еще некоторые мелкие изменения кода...

Google Closure Compiler в стандартном режиме работает почти безопасно (если не использовать with/eval/CC).

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

Можно смело использовать этот режим как замену вашему привычному упаковщику.


Автор: Dmitry A. Soshnikov, дата: 30 ноября, 2009 - 16:59
#permalink

Функция, объявленная как function name(...) {...}, видна везде в области видимости, а объявленная через присвоение переменной - лишь после присвоения.

С другой стороны, на практике эта "багофича" компилятора вреда не приносит. Расширилась немного область видимости функции, ну и что?

Кстати, это, действительно, получается небезопасное сжатие, зря они так сделали (даже, несмотря на то, что меняется только первая функция). Функции могут создаваться по условию в if-aх, и в данном случае не учитываются баги реализаций (например, IE), да и всех остальных, кроме Gecko.


Автор: goldserg, дата: 26 апреля, 2010 - 16:11
#permalink

И еще вариант, правда вы меня можете поправить если это не так.

Когда функция объявляется в закрытой области видимости.

var fun;

(function(){
  function main() {
   ....
  }
  fun = function() { return main()};
}())

Автор: Гость (не зарегистрирован), дата: 9 октября, 2013 - 09:20
#permalink

Функция, объявленная как function name(...) {...}, видна везде в области видимости, а объявленная через присвоение переменной - лишь после присвоения.

Я не понял при чем тут область видимости. Если присваивать переменной функцию --- переменная и так будет сразу в области видимости, просто будет undefined.


Автор: goldserg, дата: 26 апреля, 2010 - 15:52
#permalink

document.getElementsById(nodeId)

фунция getElementById


Автор: Гость (не зарегистрирован), дата: 30 мая, 2010 - 22:10
#permalink

Русские символы заменяются кодами, типа \u0418\u0437\u0432.
Это лечится?


Автор: aulizko, дата: 22 декабря, 2010 - 06:22
#permalink

@Гость

Это не баг, это фича.
Вот эти последовательности символов - \u0418\u0437\u0432 - это javascript-escapes symbols, т.е. они гарантировано будут работать в любом окружении и на любом движке.

Ну и кроме того, бывают моменты, когда js-файл лежит не в кодировке utf-8, а, скажем, windows-1251, а страничка в кодировке utf-8.
Предположим, что у нас есть функция, которая создает div с каким-то русским текстом в нем. Если бы вместо этих последовательностей в файле были русские символы, пользователь увидел бы кракозябли. С этими кодами - все нормально работает.
Кстати, не смейтесь, но я такое видел у довольно серьезных компаний-разработчиков, где по 100+ программистов - админы и программисты общаются через менеджеров, итог закономерен.


Автор: Гость (не зарегистрирован), дата: 4 февраля, 2011 - 12:38
#permalink

А если в этом примере:

function Animal(name) {
  if(name && 0) {
     doThis()
  } else {
     doThat()
  }
  this.name = name
}

После оптимизации этот код превратится в:

function Animal(a) {
	  doThat();
	  this.name = a
	}

У нас в функции doThat использовалась name? После оптимизации она пропала.

Ну т.е. общий код такой:

function Animal(name) {
          if(name) {
             doThis()
          } else {
             doThat()
          }
          this.name = name
        }
        
        function doThis() {
          alert(name);
        }
        
        Animal('111');

Автор: lazycommit (не зарегистрирован), дата: 8 декабря, 2011 - 14:00
#permalink

Внимательнее читайте. Условие "откомментировано" с использованием "&& 0"


Автор: ikokostya (не зарегистрирован), дата: 3 ноября, 2011 - 02:34
#permalink

В разделе Мелкие оптимизации

Убирает лишние апострофы

a = "a'c"  =>  a="a'c"

Какая здесь трансформация?


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

Приветствуются комментарии:
  • Полезные.
  • Дополняющие прочитанное.
  • Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены.
    Для остальных вопросов и обсуждений есть форум.
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
Антиспам
10 + 0 =
Введите результат. Например, для 1+3, введите 4.
 
Текущий раздел
Поиск по сайту
Реклама
Содержание

Учебник javascript

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

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

Интерфейсы

Все об AJAX

Оптимизация

Разное

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

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