minor renovations, beautify round 2 (final)

This commit is contained in:
Ilya Kantor 2015-03-12 10:26:02 +03:00
parent fad6615c42
commit 8410ce6421
212 changed files with 1981 additions and 1717 deletions

View file

@ -104,16 +104,16 @@ elem.menu = menu; // такая привязка или что-то подобн
var elem = document.createElement('div'); // любой элемент
function leak() {
elem.innerHTML = '<table><tr><td>1</td></tr></table>';
elem.innerHTML = '<table><tr><td>1</td></tr></table>';
*!*
elem.firstChild.rows[0]; // просто доступ через rows[] приводит к утечке
// при том, что мы даже не сохраняем значение в переменную
elem.firstChild.rows[0]; // просто доступ через rows[] приводит к утечке
// при том, что мы даже не сохраняем значение в переменную
*/!*
elem.removeChild(elem.firstChild); // удалить таблицу (*)
// alert(elem.childNodes.length) // выдал бы 0, elem очищен, всё честно
elem.removeChild(elem.firstChild); // удалить таблицу (*)
// alert(elem.childNodes.length) // выдал бы 0, elem очищен, всё честно
}
```
@ -132,7 +132,7 @@ function leak() {
```js
function empty(elem) {
while(elem.firstChild) elem.removeChild(elem.firstChild);
while (elem.firstChild) elem.removeChild(elem.firstChild);
}
```
@ -146,12 +146,12 @@ function empty(elem) {
```js
function leak() {
var xhr = new XMLHttpRequest();
var xhr = new XMLHttpRequest();
xhr.open('GET', '/server.do', true);
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == 200) {
if (xhr.readyState == 4 && xhr.status == 200) {
// ...
}
}
@ -181,12 +181,12 @@ function leak() {
function leak() {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'something.js?'+Math.random(), true);
xhr.open('GET', 'something.js?' + Math.random(), true);
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if(xhr.status == 200) {
if (xhr.status == 200) {
document.getElementById('test').innerHTML++;
}

View file

@ -12,6 +12,7 @@
Например:
```js
//+ no-beautify
// присвоить
$(document).data('prop', { anything: "любой объект" })
@ -27,13 +28,14 @@ jQuery-вызов `elem.data(prop, val)` делает следующее:
<li>Элемент получает уникальный идентификатор, если у него такого еще нет:
```js
elem[ jQuery.expando ] = id = ++jQuery.uuid; // средствами jQuery
elem[jQuery.expando] = id = ++jQuery.uuid; // средствами jQuery
```
`jQuery.expando` -- это случайная строка, сгенерированная jQuery один раз при входе на страницу. Уникальное свойство, чтобы ничего важного не перезаписать.</li>
<li>...А сами данные сохраняются в специальном объекте `jQuery.cache`:
```js
//+ no-beautify
jQuery.cache[id]['prop'] = { anything: "любой объект" };
```
@ -55,20 +57,37 @@ jQuery.cache[id]['prop'] = { anything: "любой объект" };
## Примеры утечек в jQuery
Следующий код создает jQuery-утечку во всех браузерах:
Следующая функция `leak` создает jQuery-утечку во всех браузерах:
```js
$('<div/>')
.html(new Array(1000).join('text')) // div с текстом, возможна AJAX-загрузка
.click(function() { })
.appendTo('#data')
document.getElementById('data').innerHTML = ''; // (*)
```html
<!--+ run -->
<script src="http://code.jquery.com/jquery.min.js"></script>
<div id="data"></div>
<script>
function leak() {
*!*
$('<div/>')
.html(new Array(1000).join('text'))
.click(function() {})
.appendTo('#data');
document.getElementById('data').innerHTML = '';
*/!*
}
var interval = setInterval(leak, 10)
</script>
Утечка идёт...
<input type="button" onclick="clearInterval(interval)" value="stop" />
```
Полный пример:
[codetabs src="jquery-leak"]
Утечка происходит потому, что обработчик события в jQuery хранится в данных элемента. В строке `(*)` элемент удален очисткой родительского `innerHTML`, но в `jQuery.cache` данные остались.
@ -79,9 +98,9 @@ document.getElementById('data').innerHTML = ''; // (*)
Этот код также создает утечку:
```js
function go() {
function leak() {
$('<div/>')
.click(function() { })
.click(function() {})
}
```

View file

@ -1,34 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script src="http://code.jquery.com/jquery.min.js"></script>
</head>
<body>
<div id="data"></div>
<script>
function go() {
$('<div/>')
.html(new Array(1000).join('text'))
.click(function() {})
.appendTo('#data');
document.getElementById('data').innerHTML = '';
}
var interval = setInterval(go, 10)
</script>
Утечка идёт...
<input type="button" onclick="clearInterval(interval)" value="stop" />
</body>
</html>

View file

@ -89,7 +89,7 @@ dot -Tsvg my.dot -o my.svg
function User(name) {
this.sayHi = function() {
alert(name);
alert( name );
};
}
@ -139,13 +139,14 @@ var minIEVersion = 8;
```js
function test(a, b) {
run(a, 'my'+'string', 600*600*5, 1 && 0, b && 0)
run(a, 'my' + 'string', 600 * 600 * 5, 1 && 0, b && 0)
}
```
После:
```js
//+ no-beautify
function test(a,b){run(a,"mystring",18E5,0,b&&0)};
```
@ -169,6 +170,7 @@ function sayHi(*!*name*/!*, *!*message*/!*) {
После оптимизации:
```js
//+ no-beautify
function sayHi(a,b){alert(a+" сказал: "+b)};
```
@ -186,13 +188,14 @@ function sayHi(a,b){alert(a+" сказал: "+b)};
function test(nodeId) {
var elem = document.getElementsById(nodeId);
var parent = elem.parentNode;
alert(parent);
alert( parent );
}
```
После оптимизации GCC:
```js
//+ no-beautify
function test(a){a=document.getElementsById(a).parentNode;alert(a)};
```
@ -210,22 +213,23 @@ function test(a){a=document.getElementsById(a).parentNode;alert(a)};
```js
function test(node) {
var parent = node.parentNode;
if (0) {
alert("Привет с параллельной планеты");
alert( "Привет с параллельной планеты" );
} else {
alert("Останется только один");
alert( "Останется только один" );
}
return;
alert(1);
alert( 1 );
}
```
После оптимизации:
```js
//+ no-beautify
function test(){alert("Останется только один")}
```
@ -244,25 +248,26 @@ function test(){alert("Останется только один")}
```js
var i = 0;
while (i++ < 10) {
alert(i);
alert( i );
}
if (i) {
alert(i);
alert( i );
}
if (i=='1') {
alert(1);
} else if(i == '2') {
alert(2);
if (i == '1') {
alert( 1 );
} else if (i == '2') {
alert( 2 );
} else {
alert(i);
alert( i );
}
```
После оптимизации:
```js
//+ no-beautify
for(var i=0;10>i++;)alert(i);i&&alert(i);"1"==i?alert(1):"2"==i?alert(2):alert(i);
```
@ -299,9 +304,9 @@ function sayHi(message) {
После оптимизации (переводы строк также будут убраны):
```js
function sayHi(b){
var a=document.createElement("div");
a.innerHTML=b;
function sayHi(b) {
var a = document.createElement("div");
a.innerHTML = b;
document.body.appendChild(a)
};
```
@ -323,43 +328,44 @@ function sayHi(b){
window.sayHi = function() {
if (isVisible) {
alert(hi);
alert(hi);
alert(hi);
alert(hi);
alert(hi);
alert(hi);
alert(hi);
alert(hi);
alert(hi);
alert(hi);
alert(hi);
alert(hi);
}
alert( hi );
alert( hi );
alert( hi );
alert( hi );
alert( hi );
alert( hi );
alert( hi );
alert( hi );
alert( hi );
alert( hi );
alert( hi );
alert( hi );
}
}
})();
```
После оптимизации:
```js
(function(){
window.sayHi=function(){
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
alert("Привет вам из JavaScript");
};
}})();
(function() {
window.sayHi = function() {
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
alert( "Привет вам из JavaScript" );
};
}
})();
```
<ul>
@ -385,6 +391,7 @@ function sayHi(b){
<li>Убираются лишние кавычки у ключей
```js
//+ no-beautify
{"prop" : "val" } => {prop:"val"}
```
@ -392,6 +399,7 @@ function sayHi(b){
<li>Упрощаются простые вызовы `Array/Object`
```js
//+ no-beautify
a = new Array() => a = []
o = new Object() => o = {}
```
@ -414,6 +422,7 @@ o = new Object() => o = {}
Рассмотрим код:
```js
//+ no-beautify
function changePosition(style) {
var position, test;
@ -432,6 +441,7 @@ function changePosition(style) {
Можно ли в такой ситуации заменить локальную переменную на более короткую? Очевидно, нет:
```js
//+ no-beautify
function changePosition(style) {
var a, b;
@ -446,6 +456,7 @@ function changePosition(style) {
Такая же опасность для сжатия кроется в использованном `eval`. Ведь `eval` может обращаться к локальным переменным:
```js
//+ no-beautify
function f(code) {
var myVar;
@ -481,32 +492,22 @@ function f(code) {
```js
//+ run
var isIE /*@cc_on =true@*/;
var isIE /*@cc_on =true@*/ ;
alert(isIE); // true в IE.
alert( isIE ); // true в IE10-
```
Там же доступны и дополнительные директивы: `@_jscript_version`, `@if` и т.п., но речь здесь не о том.
Для минификаторов этот "условный" комментарий -- всего лишь обычный комментарий. Они его удалят. Получится, что код не поймёт, где же IE.
Что делать?
<ol>
<li>Первое и наиболее корректное решение -- не использовать условную компиляцию.</li>
<li>Второе, если уж очень надо -- применить хак, завернуть его в `eval` или `new Function` (чтобы сжиматель не ругался):
Можно хитро сделать, чтобы комментарий остался, например так:
```js
//+ run
var isIE = new Function('', '/*@cc_on return true@*/')();
alert(isIE); // true в IE.
alert( isIE ); // true в IE.
```
</li>
</ol>
Ещё раз заметим, что в современных IE11+ эта компиляция не работает в любом случае.
...Однако, с учётом того, что в современных IE11+ эта компиляция не работает в любом случае, лучше избавиться от неё вообще.
В следующих главах мы посмотрим, какие продвинутые возможности есть в минификаторах, как сделать сжатие более эффективным.

View file

@ -28,12 +28,12 @@
function User(firstName, lastName) {
var fullName = firstName + ' ' + lastName;
this.sayHi = function() {
this.sayHi = function() {
showMessage(fullName);
}
function showMessage(msg) {
alert('**' + msg + '**');
alert( '**' + msg + '**' );
}
}
```
@ -46,13 +46,13 @@ function User(firstName, lastName) {
this._lastName = lastName;
}
User.prototype.sayHi = function() {
User.prototype.sayHi = function() {
this._showMessage(this._fullName);
}
User.prototype._showMessage = function(msg) {
alert('**' + msg + '**');
User.prototype._showMessage = function(msg) {
alert( '**' + msg + '**' );
}
```
@ -71,21 +71,21 @@ User.prototype._showMessage = function(msg) {
Проще всего это сделать локальной переменной в модуле:
```js
(function($) {
(function($) {
*!*
/** @const */
var platform = 'IE';
/** @const */
var platform = 'IE';
*/!*
// .....
if (platform == 'IE') {
alert('IE');
} else {
alert('NON-IE');
}
// .....
if (platform == 'IE') {
alert( 'IE' );
} else {
alert( 'NON-IE' );
}
})(jQuery);
```
@ -108,16 +108,16 @@ UglifyJS и GCC позволяют задать значение глобаль
```js
// my.js
if (isIE) {
alert("Привет от IE");
} else {
alert("Не IE :)");
alert( "Привет от IE" );
} else {
alert( "Не IE :)" );
}
```
Сжатие вызовом `uglifyjs -d isIE my.js` даст:
```js
alert("Привет от IE");
alert( "Привет от IE" );
```
..Ну а чтобы код работал в обычном окружении, нужно определить в нём значение переменной по умолчанию. Это обычно делается в каком-то другом файле (на весь проект), так как если объявить `var isIE` в этом, то флаг `-d isIE` не сработает.
@ -147,24 +147,24 @@ var uglify = require('uglify-js');
var pro = uglify.uglify;
function ast_squeeze_console(ast) {
var w = pro.ast_walker(),
walk = w.walk,
scope;
return w.with_walkers({
"stat": function(stmt) {
if(stmt[0] === "call" && stmt[1][0] == "dot" && stmt[1][1] instanceof Array && stmt[1][1][0] == 'name' && stmt[1][1][1] == "log") {
return ["block"];
}
return ["stat", walk(stmt)];
},
"call": function(expr, args) {
if(expr[0] == "dot" && expr[1] instanceof Array && expr[1][0] == 'name' && expr[1][1] == "console") {
return ["atom", "0"];
}
}
}, function() {
return walk(ast);
});
var w = pro.ast_walker(),
walk = w.walk,
scope;
return w.with_walkers({
"stat": function(stmt) {
if (stmt[0] === "call" && stmt[1][0] == "dot" && stmt[1][1] instanceof Array && stmt[1][1][0] == 'name' && stmt[1][1][1] == "log") {
return ["block"];
}
return ["stat", walk(stmt)];
},
"call": function(expr, args) {
if (expr[0] == "dot" && expr[1] instanceof Array && expr[1][0] == 'name' && expr[1][1] == "console") {
return ["atom", "0"];
}
}
}, function() {
return walk(ast);
});
};
```

View file

@ -35,7 +35,7 @@ java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js my.js
```js
function test(n) {
alert("this is my test number " + n);
alert( "this is my test number " + n );
}
test(1);
test(2);
@ -121,13 +121,17 @@ first:2 --module second:1:first
Например:
```js
document.onkeyup = function(event) { alert(event.type) }
document.onkeyup = function(event) {
alert(event.type)
}
```
После продвинутого сжатия:
```js
document.onkeyup = function(a) { alert(a.type) }
document.onkeyup = function(a) {
alert(a.type)
}
```
Как видите, переименованной оказалась только переменная `event`. Такое переименование заведомо безопасно, т.к. `event` -- локальная переменная.
@ -135,13 +139,17 @@ document.onkeyup = function(a) { alert(a.type) }
Почему компилятор не тронул остального? Попробуем другой вариант:
```js
document.blabla = function(event) { alert(event.megaProperty) }
document.blabla = function(event) {
alert(event.megaProperty)
}
```
После компиляции:
```js
document.a = function(a) { alert(a.b) }
document.a = function(a) {
alert(a.b)
}
```
Теперь компилятор переименовал и <code>blabla</code> и <code>megaProperty</code>.
@ -175,9 +183,9 @@ window.a = function(a) {
```js
window['User'] = function(name, type, age) {
this.name = name
this.type = type
this.age = age
this.name = name
this.type = type
this.age = age
}
```
@ -253,9 +261,9 @@ SayWidget.prototype = {
init: function() {
this.elem.style.display = 'none'
},
setSayHandler: function() {
this.elem.onclick = function() {
this.elem.onclick = function() {
alert("hi")
};
}
@ -268,6 +276,7 @@ SayWidget.prototype['setSayHandler'] = SayWidget.prototype.setSayHandler;
После сжатия:
```js
//+ no-beautify
function a(b) {
this.a = b;
this.b()
@ -428,6 +437,7 @@ MyFramework.publicOne();
Результат компиляции в обычном режиме:
```js
//+ no-beautify
// java -jar compiler.jar --js myframework.js --formatting PRETTY_PRINT
(function(a) {
a = a.MyFramework = {};

View file

@ -51,7 +51,7 @@ function f1(id) {
}
/** @param {string} id */
function f2(id) { }
function f2(id) {}
```
Такой вызов приведёт к предупреждению со стороны минификатора:
@ -122,13 +122,13 @@ required: (Node|null)
/** @param {!Node} node */
*/!*
function removeNode(node) {
node.parentNode.removeChild(node)
node.parentNode.removeChild(node)
}
```
Восклицательный знак означает, что параметр обязатален.
Найти описания встроенных типов и объектов javascript вы можете в файле экстернов: <code>externs.zip</code> находится в корне архива <code>compiler.jar</code>, или в соответствующей директории SVN: <a href="http://closure-compiler.googlecode.com/svn/trunk/externs/">http://closure-compiler.googlecode.com/svn/trunk/externs/</a>.
Найти описания встроенных типов и объектов javascript вы можете в файле экстернов: <code>externs.zip</code> находится в корне архива <code>compiler.jar</code>.
## Интеграция с проверками типов из Google Closure Library
@ -138,7 +138,9 @@ Google Closure Compiler знает о них и понимает, что вну
```js
var goog = {
isFunction: function(f) { return typeof f == 'function' }
isFunction: function(f) {
return typeof f == 'function'
}
}
if (goog.isFunction(func)) {
@ -168,7 +170,7 @@ required: (Object|null|undefined)
Также можно указывать количество и тип параметров функции, ключевого слова <code>this</code>, объявлять классы, приватные методы и интерфейсы.
Проверка типов javascript, предоставляемая Google Closure Compiler - пожалуй, самая продвинутая из существующих на сегодняшний день.
Проверка типов javascript, предоставляемая Google Closure Compiler -- пожалуй, самая продвинутая из существующих на сегодняшний день.
C ней аннотации, документирующие типы и параметры, становятся не просто украшением, а реальным средством проверки, уменьшающим количество ошибок на production.

View file

@ -36,9 +36,9 @@ var COMPILED = false;
```js
goog.require = function(rule) {
// ...
// ...
if (!COMPILED) {
// основное тело функции
// основное тело функции
}
}
```
@ -49,14 +49,14 @@ goog.require = function(rule) {
/** @define {boolean} */
var COMPILED = false
Framework = { }
Framework = {}
Framework.sayCompiled = function() {
if (!COMPILED) {
alert("Not compressed")
} else {
alert("Compressed")
}
if (!COMPILED) {
alert("Not compressed")
} else {
alert("Compressed")
}
}
```
@ -65,7 +65,7 @@ Framework.sayCompiled = function() {
```js
Framework = {};
Framework.sayCompiled = Framework.a = function() {
alert("Compressed");
alert( "Compressed" );
};
```
@ -102,13 +102,12 @@ Google Closure Library умеет преобразовывать классы CS
Например, следующая функция задает такой список.
```js
goog.setCssNameMapping({
goog.setCssNameMapping({
"goog-menu": "a",
"goog-menu-disabled": "a-b",
"CSS_LOGO": "b",
"hidden": "c"
});
});
```
Тогда следующий вызов преобразуется в "a a-b":
@ -151,9 +150,8 @@ Google Closure Compiler производит соответствующие пр
```js
/** @export */
function Widget() {
}
/** @export */
function Widget() {}
/** @export */
Widget.prototype.hide = function() {
this.elem.style.display = 'none'
}
@ -162,8 +160,7 @@ Widget.prototype.hide = function() {
После компиляции в продвинутом режиме:
```js
function a() {
}
function a() {}
goog.d("Widget", a);
a.prototype.a = function() {
this.b.style.display = "none"

View file

@ -9,6 +9,7 @@
```html
<html>
<body>
<div>
<ul>
@ -17,6 +18,7 @@
Сосед
</div>
</body>
</html>
```
@ -75,9 +77,9 @@ document.body.innerHTML = "";
var list = document.getElementsByTagName('ul')[0];
document.body.innerHTML = ''; // удалили DIV
alert(list.parentNode); // цела ли ссылка UL -> DIV ?
alert(list.nextSibling); // живы ли соседи UL ?
alert(list.children.length); // живы ли потомки UL ?
alert( list.parentNode ); // цела ли ссылка UL -> DIV ?
alert( list.nextSibling ); // живы ли соседи UL ?
alert( list.children.length ); // живы ли потомки UL ?
</script>
```