From 5b9be7d7c2bf3b918193a9ccca5e5d95c15d05c4 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Thu, 9 Apr 2015 23:07:12 +0300 Subject: [PATCH] renovations --- .../11-modifying-document/article.md | 2 +- 2-ui/1-document/4-traversing-dom/article.md | 10 +- .../2-tree/solution.md | 18 - .../2-tree/solution.view/index.html | 66 --- .../2-tree/source.view/index.html | 54 -- .../5-searching-elements-dom/2-tree/task.md | 14 - .../6-searching-elements-internals/article.md | 8 +- 4-ajax/6-xhr-onprogress/article.md | 65 ++- 4-ajax/8-xhr-longpoll/article.md | 2 +- 6-optimize/1-memory-leaks/article.md | 317 ----------- 6-optimize/1-memory-leaks/chrome.png | Bin 18285 -> 0 bytes 6-optimize/1-memory-leaks/goodluck.png | Bin 17574 -> 0 bytes 6-optimize/1-memory-leaks/ie1.png | Bin 3679 -> 0 bytes 6-optimize/1-memory-leaks/ie2.png | Bin 6387 -> 0 bytes 6-optimize/1-memory-leaks/ie9_disable1.png | Bin 42545 -> 0 bytes 6-optimize/1-memory-leaks/ie9_disable2.png | Bin 18133 -> 0 bytes .../1-memory-leaks/leak-ie8-2.view/index.html | 35 -- .../leak-ie8-table.view/index.html | 34 -- .../leak-ie8-widget.view/index.html | 40 -- .../leak-ie8-xhr.view/index.html | 37 -- .../1-memory-leaks/leak-ie8.view/index.html | 39 -- 6-optimize/1-memory-leaks/leak-xhr-2.svg | 37 -- 6-optimize/1-memory-leaks/leak-xhr.svg | 43 -- 6-optimize/2-memory-leaks-jquery/article.md | 144 ----- 6-optimize/3-minification/article.md | 513 ----------------- 6-optimize/3-minification/my.svg | 1 - 6-optimize/4-better-minification/article.md | 172 ------ .../5-gcc-advanced-optimization/article.md | 530 ------------------ 6-optimize/6-gcc-check-types/article.md | 177 ------ 6-optimize/7-gcc-closure-library/article.md | 180 ------ .../8-memory-removechild-innerhtml/article.md | 147 ----- .../html-innerhtml.png | Bin 8262 -> 0 bytes .../html-list.png | Bin 4180 -> 0 bytes .../8-memory-removechild-innerhtml/html.png | Bin 4400 -> 0 bytes .../6-clickjacking/article.md | 5 +- 35 files changed, 51 insertions(+), 2639 deletions(-) delete mode 100644 2-ui/1-document/5-searching-elements-dom/2-tree/solution.md delete mode 100644 2-ui/1-document/5-searching-elements-dom/2-tree/solution.view/index.html delete mode 100644 2-ui/1-document/5-searching-elements-dom/2-tree/source.view/index.html delete mode 100644 2-ui/1-document/5-searching-elements-dom/2-tree/task.md delete mode 100644 6-optimize/1-memory-leaks/article.md delete mode 100644 6-optimize/1-memory-leaks/chrome.png delete mode 100644 6-optimize/1-memory-leaks/goodluck.png delete mode 100644 6-optimize/1-memory-leaks/ie1.png delete mode 100644 6-optimize/1-memory-leaks/ie2.png delete mode 100644 6-optimize/1-memory-leaks/ie9_disable1.png delete mode 100644 6-optimize/1-memory-leaks/ie9_disable2.png delete mode 100644 6-optimize/1-memory-leaks/leak-ie8-2.view/index.html delete mode 100644 6-optimize/1-memory-leaks/leak-ie8-table.view/index.html delete mode 100644 6-optimize/1-memory-leaks/leak-ie8-widget.view/index.html delete mode 100644 6-optimize/1-memory-leaks/leak-ie8-xhr.view/index.html delete mode 100644 6-optimize/1-memory-leaks/leak-ie8.view/index.html delete mode 100644 6-optimize/1-memory-leaks/leak-xhr-2.svg delete mode 100644 6-optimize/1-memory-leaks/leak-xhr.svg delete mode 100644 6-optimize/2-memory-leaks-jquery/article.md delete mode 100644 6-optimize/3-minification/article.md delete mode 100644 6-optimize/3-minification/my.svg delete mode 100644 6-optimize/4-better-minification/article.md delete mode 100644 6-optimize/5-gcc-advanced-optimization/article.md delete mode 100644 6-optimize/6-gcc-check-types/article.md delete mode 100644 6-optimize/7-gcc-closure-library/article.md delete mode 100644 6-optimize/8-memory-removechild-innerhtml/article.md delete mode 100644 6-optimize/8-memory-removechild-innerhtml/html-innerhtml.png delete mode 100644 6-optimize/8-memory-removechild-innerhtml/html-list.png delete mode 100644 6-optimize/8-memory-removechild-innerhtml/html.png diff --git a/2-ui/1-document/11-modifying-document/article.md b/2-ui/1-document/11-modifying-document/article.md index 9fe5ea49..00d3ad84 100644 --- a/2-ui/1-document/11-modifying-document/article.md +++ b/2-ui/1-document/11-modifying-document/article.md @@ -87,7 +87,7 @@ div.innerHTML = "Ура! Вы прочитали это важ
`parentElem.insertBefore(elem, nextSibling)`
-
Вставляет `elem` в список дочерних `parentElem` перед элементом `nextSibling`. +
Вставляет `elem` в коллекцию детей `parentElem`, перед элементом `nextSibling`. Следующий код вставляет новый элемент перед вторым `
  • `: diff --git a/2-ui/1-document/4-traversing-dom/article.md b/2-ui/1-document/4-traversing-dom/article.md index 4e153737..e84f525f 100644 --- a/2-ui/1-document/4-traversing-dom/article.md +++ b/2-ui/1-document/4-traversing-dom/article.md @@ -274,7 +274,7 @@ for (var key in elems) { ```js elem.firstElementChild === elem.children[0] -elem.lastElementChild === body.children[body.children.length - 1] +elem.lastElementChild === elem.children[elem.children.length - 1] ``` @@ -301,19 +301,19 @@ elem.lastElementChild === body.children[body.children.length - 1]
    `TABLE`
      -
    • **`table.rows`** -- список строк `TR` таблицы.
    • +
    • **`table.rows`** -- коллекция строк `TR` таблицы.
    • `table.caption/tHead/tFoot` -- ссылки на элементы таблицы `CAPTION`, `THEAD`, `TFOOT`.
    • -
    • `table.tBodies` -- список элементов таблицы `TBODY`, по спецификации их может быть несколько.
    • +
    • `table.tBodies` -- коллекция элементов таблицы `TBODY`, по спецификации их может быть несколько.
    `THEAD/TFOOT/TBODY`
      -
    • `tbody.rows` -- список строк `TR` секции.
    • +
    • `tbody.rows` -- коллекция строк `TR` секции.
    `TR`
      -
    • **`tr.cells`** -- список ячеек `TD/TH`
    • +
    • **`tr.cells`** -- коллекция ячеек `TD/TH`
    • **`tr.sectionRowIndex`** -- номер строки в текущей секции `THEAD/TBODY`
    • `tr.rowIndex` -- номер строки в таблице
    diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree/solution.md b/2-ui/1-document/5-searching-elements-dom/2-tree/solution.md deleted file mode 100644 index 55b45737..00000000 --- a/2-ui/1-document/5-searching-elements-dom/2-tree/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -Сделаем цикл по узлам `
  • `: - -```js -var lis = document.getElementsByTagName('li'); - -for (i = 0; i < lis.length; i++) { - ... -} -``` - -В цикле для каждого `lis[i]` можно получить текст, используя свойство `firstChild`. Ведь первым в `
  • ` является как раз текстовый узел, содержащий текст названия. - -Также можно получить количество потомков, используя `lis[i].getElementsByTagName('li')`. - -Напишите код с этой подсказкой. - -Если уж не выйдет -- тогда откройте решение. - diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree/solution.view/index.html b/2-ui/1-document/5-searching-elements-dom/2-tree/solution.view/index.html deleted file mode 100644 index e9b5d644..00000000 --- a/2-ui/1-document/5-searching-elements-dom/2-tree/solution.view/index.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - -
      -
    • Животные -
        -
      • Млекопитающие -
          -
        • Коровы
        • -
        • Ослы
        • -
        • Собаки
        • -
        • Тигры
        • -
        -
      • -
      • Другие -
          -
        • Змеи
        • -
        • Птицы
        • -
        • Ящерицы
        • -
        -
      • -
      -
    • -
    • Рыбы -
        -
      • Аквариумные -
          -
        • Гуппи
        • -
        • Скалярии
        • -
        - -
      • -
      • Морские -
          -
        • Морская форель
        • -
        -
      • -
      -
    • -
    - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree/source.view/index.html b/2-ui/1-document/5-searching-elements-dom/2-tree/source.view/index.html deleted file mode 100644 index 2f45460c..00000000 --- a/2-ui/1-document/5-searching-elements-dom/2-tree/source.view/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - -
      -
    • Животные -
        -
      • Млекопитающие -
          -
        • Коровы
        • -
        • Ослы
        • -
        • Собаки
        • -
        • Тигры
        • -
        -
      • -
      • Другие -
          -
        • Змеи
        • -
        • Птицы
        • -
        • Ящерицы
        • -
        -
      • -
      -
    • -
    • Рыбы -
        -
      • Аквариумные -
          -
        • Гуппи
        • -
        • Скалярии
        • -
        - -
      • -
      • Морские -
          -
        • Морская форель
        • -
        -
      • -
      -
    • -
    - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree/task.md b/2-ui/1-document/5-searching-elements-dom/2-tree/task.md deleted file mode 100644 index 91980462..00000000 --- a/2-ui/1-document/5-searching-elements-dom/2-tree/task.md +++ /dev/null @@ -1,14 +0,0 @@ -# Дерево - -[importance 5] - -Есть дерево [edit src="source"]в песочнице[/edit]. - -Напишите код, который для каждого элемента списка(`LI`) выведет: -
      -
    1. Текст в нём.
    2. -
    3. Количество вложенных в него элементов.
    4. -
    - -[demo src="solution"] - diff --git a/2-ui/1-document/6-searching-elements-internals/article.md b/2-ui/1-document/6-searching-elements-internals/article.md index bc5c7cfa..d2935b7c 100644 --- a/2-ui/1-document/6-searching-elements-internals/article.md +++ b/2-ui/1-document/6-searching-elements-internals/article.md @@ -52,11 +52,11 @@ ``` -Как видно, длина коллекции, найденной через `querySelectorAll`, осталась прежней. А длина списка, возвращённого `getElementsByTagName`, изменилась. +Как видно, длина коллекции, найденной через `querySelectorAll`, осталась прежней. А длина коллекции, возвращённой `getElementsByTagName`, изменилась. -Дело в том, что результат запросов `getElementsBy*` -- это не массив, а специальный объект, имеющий тип NodeList или HTMLCollection. Он похож на массив, так как имеет нумерованные элементы и длину, но внутри это не готовый список, а "живой поисковой запрос". +Дело в том, что результат запросов `getElementsBy*` -- это не массив, а специальный объект, имеющий тип NodeList или HTMLCollection. Он похож на массив, так как имеет нумерованные элементы и длину, но внутри это не готовая коллекция, а "живой поисковой запрос". -Собственно поиск выполняется только при обращении к элементам списка или к его длине. +Собственно поиск выполняется только при обращении к элементам коллекции или к её длине. ## Алгоритмы getElementsBy* @@ -132,7 +132,7 @@ alert( elems.length ); Если точнее -- будут сброшены все коллекции, привязанные к элементам вверх по иерархии от непосредственного родителя нового `div` и выше, то есть такие, которые потенциально могли измениться. И только они.
  • Во-вторых, если добавлен только `div`, то не будут сброшены запомненные элементы для поиска по другим тегам, например `elem.getElementsByTagName('a')`.
  • -
  • ...И, конечно же, не любые изменения DOM приводят к сбросу кешей, а только те, которые могут повлиять на список. Если где-то добавлен новый атрибут -- с поиском по тегу ничего не произойдёт.
  • +
  • ...И, конечно же, не любые изменения DOM приводят к сбросу кешей, а только те, которые могут повлиять на коллекцию. Если где-то добавлен новый атрибут элементу -- с кешем для `getElementsByTagName` ничего не произойдёт, так как атрибут никак не может повлиять на результат поиска по тегу.
  • Прочие поисковые методы, такие как `getElementsByClassName` тоже сбрасывают кеш при изменениях интеллектуально. diff --git a/4-ajax/6-xhr-onprogress/article.md b/4-ajax/6-xhr-onprogress/article.md index f4e5d844..07f532c9 100644 --- a/4-ajax/6-xhr-onprogress/article.md +++ b/4-ajax/6-xhr-onprogress/article.md @@ -2,21 +2,19 @@ Запрос `XMLHttpRequest` состоит из двух фаз:
      -
    1. Стадия закачки (upload). На ней данные загружаются на сервер. Эта фаза может быть долгой для POST-запросов.
    2. -
    3. Стадия скачивания (download). После того, как данные загружены, браузер скачивает ответ с сервера. Если он большой, то это может занять существенное время.
    4. +
    5. Стадия закачки (upload). На ней данные загружаются на сервер. Эта фаза может быть долгой для POST-запросов. Для отслеживания прогресса на стадии закачки существует объект типа [XMLHttpRequestUpload](https://xhr.spec.whatwg.org/#xmlhttprequesteventtarget), доступный как `xhr.upload` и события на нём.
    6. +
    7. Стадия скачивания (download). После того, как данные загружены, браузер скачивает ответ с сервера. Если он большой, то это может занять существенное время. На этой стадии используется обработчик `xhr.onprogress`.
    -Для каждой стадии предусмотрены события, "рассказывающие" о процессе выполнения. +Далее -- обо всём по порядку. [cut] -## XMLHttpRequestUpload +## Стадия закачки -Для отслеживания прогресса на стадии закачки существует объект [XMLHttpRequestUpload](https://xhr.spec.whatwg.org/#xmlhttprequesteventtarget), доступный как `xhr.upload`. +На стадии закачки для получения информации используем объект `xhr.upload`. У этого объекта нет методов, он только генерирует события в процессе закачки. А они-то как раз и нужны. -У него нет методов, он предназначен исключительно для обработки событий при закачке. - -Вот их полный список: +Вот полный список событий:
    • `loadstart`
    • `progress`
    • @@ -43,9 +41,13 @@ xhr.upload.onerror = function() { } ``` -После того, как загрузка завершена, будет начато скачивание ответа. +## Стадия скачивания -На этой фазе `xhr.upload` уже не нужен, а в дело вступают обработчики событий на самом объекте `xhr`, в частности `xhr.onprogress`: +После того, как загрузка завершена, и сервер соизволит ответить на запрос, `XMLHttpRequest` начнёт скачивание ответа сервера. + +На этой фазе `xhr.upload` уже не нужен, а в дело вступают обработчики событий на самом объекте `xhr`. В частности, событие `xhr.onprogress` содержит информацию о количестве принятых байт ответа. + +Пример обработчика: ```js xhr.onprogress = function(event) { @@ -55,13 +57,13 @@ xhr.onprogress = function(event) { Все события, возникающие в этих обработчиках, имеют тип [ProgressEvent](https://xhr.spec.whatwg.org/#progressevent), то есть имеют свойства `loaded` -- количество уже пересланных данных в байтах и `total` -- общее количество данных. -## Загрузка файла с индикатором прогресса +## Демо: загрузка файла с индикатором прогресса Современный `XMLHttpRequest` позволяет отправить на сервер всё, что угодно. Текст, файл, форму. Мы, для примера, рассмотрим загрузку файла с индикацией прогресса. Это требует от браузера поддержки [File API](http://www.w3.org/TR/FileAPI/), то есть исключает IE9-. -File API позволяет получить доступ к содержимому файла, который перенесён в браузер при помощи Drag'n'Drop или выбран в поле формы. +File API позволяет получить доступ к содержимому файла, который перенесён в браузер при помощи Drag'n'Drop или выбран в поле формы, и отправить его при помощи `XMLHttpRequest`. Форма для выбора файла с обработчиком `submit`: @@ -73,7 +75,8 @@ File API позволяет получить доступ к содержимо ``` -Здесь мы почти не пользуемся File API. Его роль -- получить файл из формы через свойство `files` элемента `` и далее мы сразу передадим его `xhr.send` в функции `upload`: +Мы получаем файл из формы через свойство `files` элемента `` и передаём его в функцию `upload`: ```js function upload(file) { var xhr = new XMLHttpRequest(); - // обработчики можно объединить в один, + // обработчик для закачки + xhr.upload.onprogress = function(event) { + log(event.loaded + ' / ' + event.total); + } + + // обработчики успеха и ошибки // если status == 200, то это успех, иначе ошибка xhr.onload = xhr.onerror = function() { if (this.status == 200) { @@ -101,10 +109,6 @@ function upload(file) { } }; - // обработчик для закачки - xhr.upload.onprogress = function(event) { - log(event.loaded + ' / ' + event.total); - } xhr.open("POST", "upload", true); xhr.send(file); @@ -112,15 +116,21 @@ function upload(file) { } ``` +Этот код отправит файл на сервер и будет сообщать о прогрессе при его закачке (`xhr.upload.onprogress`), а также об окончании запроса (`xhr.onload`, `xhr.onerror`). + Полный пример, основанный на коде выше: [codetabs src="progress"] ## Событие onprogress в деталях -Событие `onprogress` имеет одинаковый вид при закачке на сервер (`xhr.upload.onprogress`) и при получении ответа (`xhr.onprogress`). +При обработке события `onprogress` есть ряд важных тонкостей. -Оно получает объект `event` типа [ProgressEvent](https://xhr.spec.whatwg.org/#progressevent) со свойствами: +Можно, конечно, их игнорировать, но лучше бы знать. + +Заметим, что событие, возникающее при `onprogress`, имеет одинаковый вид на стадии закачки (в обработчике `xhr.upload.onprogress`) и при получении ответа (в обработчике `xhr.onprogress`). + +Оно представляет собой объект типа [ProgressEvent](https://xhr.spec.whatwg.org/#progressevent) со свойствами:
      `loaded`
      @@ -132,10 +142,11 @@ function upload(file) {
      `total`
      Общее количество байт для пересылки, если известно. -При HTTP-запросах оно передаётся в заголовке `Content-Length`. +А может ли оно быть неизвестно? +
        -
      • При закачке на сервер браузер всегда знает полный размер пересылаемых данных. Поэтому в `xhr.upload.onprogress` значение `lengthComputable` всегда будет `true`, а `total` -- содержать этот размер.
      • -
      • При скачивании данных -- уже задача сервера поставить этот заголовок. Если его нет, то `total = 0`, а для того, чтобы понять, что данных не `0`, а неизвестное количество -- служит `lengthComputable`, которое в данном случае равно `false`.
      • +
      • При закачке на сервер браузер всегда знает полный размер пересылаемых данных, так что `total` всегда содержит конкретное количество байт, а значение `lengthComputable` всегда будет `true`.
      • +
      • При скачивании данных -- обычно сервер в начале сообщает их общее количество в HTTP-заголовке `Content-Length`. Но он может и не делать этого, например если сам не знает, сколько данных будет или если генерирует их динамически. Тогда `total` будет равно `0`. А чтобы отличить нулевой размер данных от неизвестного -- как раз служит `lengthComputable`, которое в данном случае равно `false`.
      @@ -147,10 +158,10 @@ function upload(file) { Это обозначено в [спецификации progress notifications](http://www.w3.org/TR/XMLHttpRequest/#make-progress-notifications). -
    • **При получении данных доступен `xhr.responseText`.** +
    • **В процессе получения данных, ещё до их полной передачи, доступен `xhr.responseText`.** Можно до окончания запроса заглянуть в него и прочитать текущие полученные данные. Важно, что при пересылке строки в кодировке UTF-8 русские символы кодируются 2 байтами. Возможно, что в конце одного пакета данных окажется первая половинка символа, а в начале следующего -- вторая. Поэтому полагаться на то, что до окончания запроса в `responoseText` находится корректная строка нельзя. Исключение -- заведомо однобайтные символы, например цифры.
    • -
    • **При закачки `xhr.upload.onprogress` не гарантирует обработку загрузки сервером.** +
    • **Сработавшее событие `xhr.upload.onprogress` не гарантирует, что данные дошли.** Событие `xhr.upload.onprogress` срабатывает, когда данные отправлены браузером. Но оно не гарантирует, что сервер получил, обработал и записал данные на диск. Он говорит лишь о самом факте отправки. @@ -174,5 +185,5 @@ formData.append("myfile", file); xhr.send(formData); ``` -Данные будут отправлены в кодировке `multipart/form-data`. Серверный фреймворк обработает это как обычную форму. +Данные будут отправлены в кодировке `multipart/form-data`. Серверный фреймворк увидит это как обычную форму с файлом. Практически все серверные технологии имеют их встроенную поддержку. diff --git a/4-ajax/8-xhr-longpoll/article.md b/4-ajax/8-xhr-longpoll/article.md index 717f2f3c..0d31af5f 100644 --- a/4-ajax/8-xhr-longpoll/article.md +++ b/4-ajax/8-xhr-longpoll/article.md @@ -1,4 +1,4 @@ -# XMLHttpRequest: длинные опросы +# COMET с XMLHttpRequest: длинные опросы В этой главе мы рассмотрим способ организации COMET, то есть непрерывного получения данных с сервера, который очень прост и подходит в 90% реальных случаев. diff --git a/6-optimize/1-memory-leaks/article.md b/6-optimize/1-memory-leaks/article.md deleted file mode 100644 index 08ed1610..00000000 --- a/6-optimize/1-memory-leaks/article.md +++ /dev/null @@ -1,317 +0,0 @@ -# Утечки памяти - -*Утечки памяти* происходят, когда браузер по какой-то причине не может освободить память от недостижимых объектов. - -Обычно это происходит автоматически ([](/memory-management)). Кроме того, браузер освобождает память при переходе на другую страницу. Поэтому утечки в реальной жизни проявляют себя в двух ситуациях: - -[cut] -
        -
      1. Приложение, в котором посетитель все время на одной странице и работает со сложным JavaScript-интерфейсом. В этом случае утечки могут постепенно съедать доступную память.
      2. -
      3. Страница регулярно делает что-то, вызывающее утечку памяти. Посетитель (например, менеджер) оставляет компьютер на ночь включенным, чтобы не закрывать браузер с кучей вкладок. Приходит утром -- а браузер съел всю память и рухнул и сильно тормозит.
      4. -
      -Утечки бывают из-за ошибок браузера, ошибок в расширениях браузера и, гораздо реже, по причине ошибок в архитектуре JavaScript-кода. Мы разберём несколько наиболее частых и важных примеров. - -## Коллекция утечек в IE - -### Утечка DOM ↔ JS в IE8- - -IE до версии 8 не умел очищать циклические ссылки, появляющиеся между DOM-объектами и объектами JavaScript. В результате и DOM и JS оставались в памяти навсегда. - -В браузере IE8 была проведена серьёзная работа над ошибками, но утечка в IE8- появляется, если круговая ссылка возникает "через объект". - -Чтобы было понятнее, о чём речь, посмотрите на следующий код. Он вызывает утечку памяти в IE8-: - -``` -function leak() { - // Создаём новый DIV, добавляем к BODY - var elem = document.createElement('div'); - document.body.appendChild(elem); - - // Записываем в свойство жирный объект - elem.__expando = { - bigAss: new Array(1000000).join('lalala') - }; - -*!* - // Создаём круговую ссылку. Без этой строки утечки не будет. - elem.__expando.__elem = elem; -*/!* - - // Удалить элемент из DOM. Браузер должен очистить память. - elem.parentElement.removeChild(elem); -} -``` - -Полный пример (только для IE8-, а также IE9 в режиме совместимости с IE8): - -[codetabs src="leak-ie8"] - - -Круговая ссылка и, как следствие, утечка может возникать и неявным образом, через замыкание: - -```js -function leak() { - var elem = document.createElement('div'); - document.body.appendChild(elem); - - elem.__expando = { - bigAss: new Array(1000000).join('lalala'), -*!* - method: function() {} // создаётся круговая ссылка через замыкание -*/!* - }; - - // Удалить элемент из DOM. Браузер должен очистить память. - elem.parentElement.removeChild(elem); -} -``` - -Полный пример (IE8-, IE9 в режиме совместимости с IE8): - -[codetabs src="leak-ie8-2"] - -Без привязки метода `method` к элементу здесь утечки не возникнет. - -Бывает ли такая ситуация в реальной жизни? Или это -- целиком синтетический пример, для заумных программистов? - -Да, конечно бывает. Например, при разработке графических компонент -- бывает удобно присвоить DOM-элементу ссылку на JavaScript-объект, который представляет собой компонент. Это упрощает делегирование и, в общем-то, логично, что DOM-элемент знает о компоненте на себе. Но в IE8- прямая привязка ведёт к утечке памяти! - -Примерно так: -```js -function Menu(elem) { - elem.onclick = function() {}; -} - -var menu = new Menu(elem); // Menu содержит ссылку на elem -*!* -elem.menu = menu; // такая привязка или что-то подобное ведёт к утечке в IE8 -*/!* -``` - -Полный пример (IE8-, IE9 в режиме совместимости с IE8): - -[codetabs src="leak-ie8-widget"] - -### Утечка IE8 при обращении к коллекциям таблицы - -Эта утечка происходит только в IE8 в стандартном режиме. В нём при обращении к табличным псевдо-массивам (напр. `rows`) создаются и не очищаются внутренние ссылки, что приводит к утечкам. - -Также воспроизводится в новых IE в режиме совместимости с IE8. - -Код: - -```js -var elem = document.createElement('div'); // любой элемент - -function leak() { - - elem.innerHTML = '
      1
      '; - -*!* - elem.firstChild.rows[0]; // просто доступ через rows[] приводит к утечке - // при том, что мы даже не сохраняем значение в переменную -*/!* - - elem.removeChild(elem.firstChild); // удалить таблицу (*) - // alert(elem.childNodes.length) // выдал бы 0, elem очищен, всё честно -} -``` - -Полный пример (IE8): - -[codetabs src="leak-ie8-table"] - -Особенности: -
        -
      • Если убрать отмеченную строку, то утечки не будет.
      • -
      • Если заменить строку `(*)` на `elem.innerHTML = ''`, то память будет очищена, т.к. этот способ работает по-другому, нежели просто `removeChild` (см. главу [](/memory-management)).
      • -
      • Утечка произойдёт не только при доступе к `rows`, но и к другим свойствам, например `elem.firstChild.tBodies[0]`.
      • -
      - -Эта утечка проявляется, в частности, при удалении детей элемента следующей функцией: - -```js -function empty(elem) { - while (elem.firstChild) elem.removeChild(elem.firstChild); -} -``` - -Если идёт доступ к табличным коллекциям и регулярное обновление таблиц при помощи DOM-методов -- утечка в IE8 будет расти. - -Более подробно вы можете почитать об этой утечке в статье [Утечки памяти в IE8, или страшная сказка со счастливым концом](http://habrahabr.ru/post/141451/). - -### Утечка через XmlHttpRequest в IE8- - -Следующий код вызывает утечки памяти в IE8-: - -```js -function leak() { - var xhr = new XMLHttpRequest(); - - xhr.open('GET', '/server.do', true); - - xhr.onreadystatechange = function() { - if (xhr.readyState == 4 && xhr.status == 200) { - // ... - } - } - - xhr.send(null); -} -``` - -Как вы думаете, почему? Если вы внимательно читали то, что написано выше, то имеете информацию для ответа на этот вопрос.. - -Посмотрим, какая структура памяти создается при каждом запуске: - - - -Когда запускается асинхронный запрос `xhr`, браузер создаёт специальную внутреннюю ссылку (internal reference) на этот объект. находится в процессе коммуникации. Именно поэтому объект `xhr` будет жив после окончания работы функции. - -Когда запрос завершен, браузер удаляет внутреннюю ссылку, `xhr` становится недостижимым и память очищается... Везде, кроме IE8-. - - -Полный пример (IE8): - -[codetabs src="leak-ie8-xhr"] - -Чтобы это исправить, нам нужно разорвать круговую ссылку `XMLHttpRequest ↔ JS`. Например, можно удалить `xhr` из замыкания: - -```js -function leak() { - var xhr = new XMLHttpRequest(); - - xhr.open('GET', 'something.js?' + Math.random(), true); - - xhr.onreadystatechange = function() { - if (xhr.readyState != 4) return; - - if (xhr.status == 200) { - document.getElementById('test').innerHTML++; - } - -*!* - xhr = null; // по завершении запроса удаляем ссылку из замыкания -*/!* - } - - xhr.send(null); -} -``` - - - -Теперь циклической ссылки нет -- и не будет утечки. - - -## Объемы утечек памяти - -Объем "утекающей" памяти может быть небольшим. Тогда это почти не ощущается. Но так как замыкания ведут к сохранению переменных внешних функций, то одна функция может тянуть за собой много чего ещё. - -Представьте, вы создали функцию, и одна из ее переменных содержит очень большую по объему строку (например, получает с сервера). - -```js -function f() { - var data = "Большой объем данных, например, переданных сервером" - - /* делаем что-то хорошее (ну или плохое) с полученными данными */ - - function inner() { - // ... - } - - return inner; -} -``` - -Пока функция `inner` остается в памяти, `LexicalEnvironment` с переменной большого объема внутри висит в памяти. - -Висит до тех пор, пока функция `inner` жива. - -Как правило, JavaScript не знает, какие из переменных функции `inner` будут использованы, поэтому оставляет их все. Исключение -- виртуальная машина V8 (Chrome, Opera, Node.JS), она часто (не всегда) видит, что переменная не используется во внутренних функциях, и очистит память. - -В других же интерпретаторах, даже если код спроектирован так, что никакой утечки нет, по вполне разумной причине может создаваться множество функций, а память будет расти потому, что функция тянет за собой своё замыкание. - -Сэкономить память здесь вполне можно. Мы же знаем, что переменная `data` не используется в `inner`. Поэтому просто обнулим её: - -```js -function f() { - var data = "Большое количество данных, например, переданных сервером" - - /* действия с data */ - - function inner() { - // ... - } - -*!* - data = null; // когда data станет не нужна - -*/!* - - return inner; -} -``` - -## Поиск и устранение утечек памяти - -### Проверка на утечки - -Существует множество шаблонов утечек и ошибок в браузерах, которые могут приводить к утечкам. Для их устранения сперва надо постараться изолировать и воспроизвести утечку. - -
        -
      • **Необходимо помнить, что браузер может очистить память не сразу когда объект стал недостижим, а чуть позже.** Например, сборщик мусора может ждать, пока не будет достигнут определенный лимит использования памяти, или запускаться время от времени.
      • - -Поэтому если вы думаете, что нашли проблему и тестовый код, запущенный в цикле, течёт -- подождите примерно минуту, добейтесь, чтобы памяти ело стабильно и много. Тогда будет понятно, что это не особенность сборщика мусора.
      • **Если речь об IE, то надо смотреть "Виртуальную память" в списке процессов, а не только обычную "Память".** Обычная может очищаться за счет того, что перемещается в виртуальную (на диск).
      • -
      • Для простоты отладки, если есть подозрение на утечку конкретных объектов, в них добавляют большие свойства-маркеры. Например, подойдет фрагмент текста: `new Array(999999).join('leak')`.
      • -
      - -### Настройка браузера - -Утечки могут возникать из-за расширений браузера, взимодействющих со страницей. Еще более важно, что **утечки могут быть следствием конфликта двух браузерных расширений** Например, было такое: память текла когда включены расширения Skype и плагин антивируса одновременно. - -Чтобы понять, в расширениях дело или нет, нужно отключить их: - -
        -
      1. Отключить Flash.
      2. -
      3. Отключить анивирусную защиту, проверку ссылок и другие модули и дополнения.
      4. -
      5. Отключить плагины. Отключить ВСЕ плагины. -
          -
        • - Для IE есть параметр коммандной строки: - -``` -"C:\Program Files\Internet Explorer\iexplore.exe" -extoff -``` - -Кроме того необходимо отключить сторонние расширения в свойствах IE. - - - - -
        • -
        • Firefox необходимо запускать с чистым профилем. Используйте следующую команду для запуска менеджера профилей и создания чистого пустого профиля: - -``` -firefox --profilemanager -``` - -
        • -
        -
      6. -
      - -## Инструменты - -Пожалуй, единственный браузер с поддержкой отладки памяти -- это Chrome. В инструментах разработчика вкладка Timeline -- Memory показывает график использования памяти. - - - -Можем посмотреть, сколько памяти и куда он использует. - -Также в Profiles есть кнопка Take Heap Snapshot, здесь можно сделать и исследовать снимок текущего состояния страницы. Снимки можно сравнивать друг с другом, выяснять количество новых объектов. Можно смотреть, почему объект не очищен и кто на него ссылается. - -Замечательная статья на эту тему есть в документации: [Chrome Developer Tools: Heap Profiling](http://code.google.com/chrome/devtools/docs/heap-profiling.html). - -Утечки памяти штука довольно сложная. В борьбе с ними вам определенно понадобится одна вещь: *Удача!* - - diff --git a/6-optimize/1-memory-leaks/chrome.png b/6-optimize/1-memory-leaks/chrome.png deleted file mode 100644 index 0c88b10b7694e51565fb6b950c1408ddec4b09e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18285 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 diff --git a/6-optimize/1-memory-leaks/ie2.png b/6-optimize/1-memory-leaks/ie2.png deleted file mode 100644 index 93617434906bcb3d7210fed5b26306fb190ba25c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6387 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

      ct5pU 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 - - - - - - - - - - - - -

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

      - - - - - \ No newline at end of file diff --git a/6-optimize/1-memory-leaks/leak-ie8-table.view/index.html b/6-optimize/1-memory-leaks/leak-ie8-table.view/index.html deleted file mode 100644 index e0017dce..00000000 --- a/6-optimize/1-memory-leaks/leak-ie8-table.view/index.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - -

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

      - - - - - - \ No newline at end of file diff --git a/6-optimize/1-memory-leaks/leak-ie8-widget.view/index.html b/6-optimize/1-memory-leaks/leak-ie8-widget.view/index.html deleted file mode 100644 index 1bdd947c..00000000 --- a/6-optimize/1-memory-leaks/leak-ie8-widget.view/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - -

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

      - - - - - - \ No newline at end of file diff --git a/6-optimize/1-memory-leaks/leak-ie8-xhr.view/index.html b/6-optimize/1-memory-leaks/leak-ie8-xhr.view/index.html deleted file mode 100644 index 67329b91..00000000 --- a/6-optimize/1-memory-leaks/leak-ie8-xhr.view/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - -

      Страница создаёт объект XMLHttpRequest каждые 50мс.

      - -

      Нажмите на кнопку и смотрите на память, она течёт в IE<9.

      - - - - - -
      Количество запросов: 0
      - - - - \ No newline at end of file diff --git a/6-optimize/1-memory-leaks/leak-ie8.view/index.html b/6-optimize/1-memory-leaks/leak-ie8.view/index.html deleted file mode 100644 index c2c1a4fc..00000000 --- a/6-optimize/1-memory-leaks/leak-ie8.view/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - -

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

      - - - - - - \ No newline at end of file diff --git a/6-optimize/1-memory-leaks/leak-xhr-2.svg b/6-optimize/1-memory-leaks/leak-xhr-2.svg deleted file mode 100644 index e6faf68c..00000000 --- a/6-optimize/1-memory-leaks/leak-xhr-2.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - leak-xhr-2.svg - Created with bin/sketchtool. - - - - - - - - xhr - - - function - - - LexicalEnvironment - - - - - - - - - onreadystatechange - - - [[Scope]] - - - - - - \ No newline at end of file diff --git a/6-optimize/1-memory-leaks/leak-xhr.svg b/6-optimize/1-memory-leaks/leak-xhr.svg deleted file mode 100644 index 967a4b3c..00000000 --- a/6-optimize/1-memory-leaks/leak-xhr.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - leak-xhr.svg - Created with bin/sketchtool. - - - - - - - - xhr - - - function - - - LexicalEnvironment - - - - - - - - - - - property - - - onreadystatechange - - - внутренняя ссылка - - - [[Scope]] - - - - \ No newline at end of file diff --git a/6-optimize/2-memory-leaks-jquery/article.md b/6-optimize/2-memory-leaks-jquery/article.md deleted file mode 100644 index 70b76c45..00000000 --- a/6-optimize/2-memory-leaks-jquery/article.md +++ /dev/null @@ -1,144 +0,0 @@ - -# Утечки памяти при использовании jQuery - -В jQuery для хранения обработчиков событий и других вспомогательных данных, связанных с DOM-элементами, используется внутренний объект, который в jQuery 1 доступен через $.data. - -В jQuery 2 доступ к нему закрыт через замыкание, он стал локальной переменной внутри jQuery с именем `data_priv`, но в остальном всё работает точно так, как описано, и с теми же последствиями. - -## $.data - -Встроенная функция `$.data` позволяет хранить привязывать произвольные значения к DOM-узлам. - -Например: - -```js -//+ no-beautify -// присвоить -$(document).data('prop', { anything: "любой объект" }) - -// прочитать -alert( $(document).data('prop').anything ) // любой объект -``` - -Реализована она хитрым образом. Данные не хранятся в самом элементе, а во внутреннем объекте jQuery. - -jQuery-вызов `elem.data(prop, val)` делает следующее: - -
        -
      1. Элемент получает уникальный идентификатор, если у него такого еще нет: - -```js -elem[jQuery.expando] = id = ++jQuery.uuid; // средствами jQuery -``` - -`jQuery.expando` -- это случайная строка, сгенерированная jQuery один раз при входе на страницу. Уникальное свойство, чтобы ничего важного не перезаписать.
      2. -
      3. ...А сами данные сохраняются в специальном объекте `jQuery.cache`: - -```js -//+ no-beautify -jQuery.cache[id]['prop'] = { anything: "любой объект" }; -``` - -
      4. -
      - -Когда данные считываются из элемента: - -
        -
      1. Уникальный идентификатор элемента извлекается из `id = elem[ jQuery.expando]`. -
      2. Данные считываются из `jQuery.cache[id]`.
      3. -
      - -Смысл этого API в том, что DOM-элемент никогда не ссылается на JavaScript объект напрямую. Задействуется идентификатор, а сами данные хранятся в `jQuery.cache`. Утечек в IE не будет. - -К тому же все данные известны библиотеке, так что можно клонировать с ними и т.п. - -Как побочный эффект -- возникает утечка памяти, если элемент удален из DOM без дополнительной очистки. - -## Примеры утечек в jQuery - -Следующая функция `leak` создает jQuery-утечку во всех браузерах: - - -```html - - - -
      - - - -Утечка идёт... - - -``` - - -Утечка происходит потому, что обработчик события в jQuery хранится в данных элемента. В строке `(*)` элемент удален очисткой родительского `innerHTML`, но в `jQuery.cache` данные остались. - -Более того, система обработки событий в jQuery устроена так, что вместе с обработчиком в данных хранится и ссылка на элемент, так что в итоге оба -- и обработчик и элемент -- остаются в памяти вместе со всем замыканием! - -Ещё более простой пример утечки: - -Этот код также создает утечку: - -```js -function leak() { - $('
      ') - .click(function() {}) -} -``` - -...То есть, мы создаём элемент, вешаем на него обработчик... И всё. - -Такой код ведёт к утечке памяти как раз потому, что элемент `
      ` создан, но нигде не размещен :). После выполнения функции ссылка на него теряется. Но обработчик события `click` уже сохранил данные в `jQuery.cache`, которые застревают там навсегда. - -## Используем jQuery без утечек - -Чтобы избежать утечек, описанных выше, для удаления элементов используйте функции jQuery API, а не чистый JavaScript. - -Методы remove(), empty() и html() проверяют дочерние элементы на наличие данных и очищают их. Это несколько замедляет процедуру удаления, но зато освобождается память. - -К счастью обнаружить такие утечки легко. Проверьте размер `$.cache`. Если он большой и растет, то изучите кэш, посмотрите, какие записи остаются и почему. - -## Улучшение производительности jQuery - -У способа организации внутренних данных, применённого в jQuery, есть важный побочный эффект. - -Функции, удаляющие элементы, также должны удалить и связанные с ними внутренние данные. Для этого нужно для каждого удаляемого элемента проверить -- а нет ли чего во внутреннем хранилище? И, если есть -- удалить. - -Представим, что у нас есть большая таблица ``, и мы хотим обновить её содержимое на новое. Вызов `$('table').html(новые данные)` перед вставкой новых данных аккуратно удалит старые: пробежит по всем ячейкам и проверит внутреннее хранилище. - -Если это большая таблица, то обработчики, скорее всего, стоят не на ячейках, а на самом элементе `
      `, то есть используется делегирование. А, значит, тратить время на проверку всех подэлементов ни к чему. - -Но jQuery-то об этом не знает! - -Чтобы "грязно" удалить элемент, без чистки, мы можем сделать это через "обычные" DOM-вызовы или воспользоваться методом detach(). Его официальное назначение -- в том, чтобы убрать элемент из DOM, но сохранить возможность для вставки (и, соответственно, оставить на нём все данные). А неофициальное -- быстро убрать элемент из DOM, без чистки. - -Возможен и промежуточный вариант: никто не мешает сделать `elem.detach()` и поместить вызов `elem.remove()` в `setTimeout`. В результате очистка будет происходить асинхронно и незаметно. - -## Итого - -
        -
      • Утечки памяти при использовании jQuery возможны, если через DOM-методы удалять элементы, к которым привязаны данные или обработчики.
      • -
      • Чтобы утечки не было, достаточно убедиться, что элемент удаляется при помощи методов jQuery.
      • -
      • Побочный эффект -- при удалении элементов jQuery должна проверить наличие данных для них. Это сильно замедляет процесс удаления большого поддерева DOM.
      • -
      • Если мы значем, что обработчиков и данных нет -- гораздо быстрее удалять элементы при помощи вызова `detach` или обычного DOM.
      • -
      - - diff --git a/6-optimize/3-minification/article.md b/6-optimize/3-minification/article.md deleted file mode 100644 index 22cb1700..00000000 --- a/6-optimize/3-minification/article.md +++ /dev/null @@ -1,513 +0,0 @@ -# Как работают сжиматели JavaScript - -Перед выкладыванием JavaScript на "боевую" машину -- пропускаем его через минификатор (также говорят "сжиматель"), который удаляет пробелы и по-всякому оптимизирует код, уменьшая его размер. - -В этой статье мы посмотрим, как работают современные минификаторы, за счёт чего они укорачивают код и какие с ними возможны проблемы. -[cut] - -## Современные сжиматели - -Рассматриваемые в этой статье алгоритмы и подходы относятся к минификаторам последнего поколения. - -Вот их список: - -
        -
      • [Google Closure Compiler](https://developers.google.com/closure/compiler/)
      • -
      • [UglifyJS](https://github.com/mishoo/UglifyJS)
      • -
      • [Microsoft AJAX Minifier](http://ajaxmin.codeplex.com/)
      • -
      -Самые широко используемые -- первые два, поэтому будем рассматривать в первую очередь их. - -Наша цель -- понять, как они работают, и что интересного с их помощью можно сотворить. - -## С чего начать? - -Для GCC: - -
        -
      1. Убедиться, что стоит [Java](http://java.oracle.com)
      2. -
      3. Скачать и распаковать [](http://closure-compiler.googlecode.com/files/compiler-latest.zip), нам нужен файл `compiler.jar`.
      4. -
      5. Сжать файл `my.js`: `java -jar compiler.jar --charset UTF-8 --js my.js --js_output_file my.min.js`
      6. -
      - -Обратите внимание на флаг `--charset` для GCC. Без него русские буквы будут закодированы во что-то типа `\u1234`. - -Google Closure Compiler также содержит [песочницу](http://closure-compiler.appspot.com/home) для тестирования сжатия и [веб-сервис](https://developers.google.com/closure/compiler/docs/gettingstarted_api?hl=ru), на который код можно отправлять для сжатия. Но скачать файл обычно гораздо проще, поэтому его редко где используют. - -Для UglifyJS: - -
        -
      1. Убедиться, что стоит [Node.js](http://nodejs.org)
      2. -
      3. Поставить `npm install -g uglify-js`.
      4. -
      5. Сжать файл `my.js`: `uglifyjs my.js -o my.min.js`
      6. -
      - -## Что делает минификатор? - -Все современные минификаторы работают следующим образом: -
        -
      1. Разбирают JavaScript-код в синтаксическое дерево. - -Также поступает любой интерпретатор JavaScript перед тем, как его выполнять. Но затем, вместо исполнения кода...
      2. -
      3. Бегают по этому дереву, анализируют и оптимизируют его.
      4. -
      5. Записывают из синтаксического дерева получившийся код.
      6. -
      - -## Как выглядит дерево? - -Посмотреть синтаксическое дерево можно, запустив компилятор со специальным флагом. - -Для GCC есть даже способ вывести его: - -
        -
      1. Сначала сгенерируем дерево в формате [DOT](http://en.wikipedia.org/wiki/DOT_language): - -``` -java -jar compiler.jar --js my.js --use_only_custom_externs --print_tree >my.dot -``` - -Здесь флаг `--print_tree` выводит дерево, а `--use_only_custom_externs` убирает лишнюю служебную информацию. -
      2. -
      3. Файл в этом формате используется в различных программах для графопостроения. - -Чтобы превратить его в обычную картинку, подойдёт утилита `dot` из пакета [Graphviz](http://www.graphviz.org/): - -``` -// конвертировать в формат png -dot -Tpng my.dot -o my.png - -// конвертировать в формат svg -dot -Tsvg my.dot -o my.svg -``` - -
      4. -
      - -Пример кода `my.js`: - -```js -function User(name) { - - this.sayHi = function() { - alert( name ); - }; - -} -``` - -Результат, получившееся из `my.js` дерево: - - - -В узлах-эллипсах на иллюстрации выше стоит тип, например `FUNCTION` (функция) или `NAME` (имя переменной). Комментарии к ним на русском языке добавлены мной вручную. - -Кроме него к каждому узлу привязаны конкретные данные. Сжиматель умеет ходить по этому дереву и менять его, как пожелает. - -[smart header="Комментарии JSDoc"] -Обычно когда код превращается в дерево -- из него естественным образом исчезают комментарии и пробелы. Они не имеют значения при выполнении, поэтому игнорируются. - -Но Google Closure Compiler добавляет в дерево информацию из *комментариев JSDoc*, т.е. комментариев вида `/** ... */`, например: - -```js -*!* -/** - * Номер минимальной поддерживаемой версии IE - * @const - * @type {number} - */ -*/!* -var minIEVersion = 8; -``` - -Такие комментарии не создают новых узлов дерева, а добавляются в качестве информации к существующем. В данном случае -- к переменной `minIEVersion`. - -В них может содержаться информация о типе переменной (`number`) и другая, которая поможет сжимателю лучше оптимизировать код (`const` -- константа). - -[/smart] - -## Оптимизации - -Сжиматель бегает по дереву, ищет "паттерны" -- известные ему структуры, которые он знает, как оптимизировать, и обновляет дерево. - -В разных минификаторах реализован разный набор оптимизаций, сами оптимизации применяются в разном порядке, поэтому результаты работы могут отличаться. В примерах ниже даётся результат работы GCC. - - -
      -
      Объединение и сжатие констант
      -
      -До оптимизации: - -```js -function test(a, b) { - 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)}; -``` - -
        -
      • `'my' + 'string'` -> `"mystring"`.
      • -
      • `600 * 600 * 5` -> `18E5` (научная форма числа, для краткости).
      • -
      • `1 && 0` -> `0`.
      • -
      • `b && 0` -> без изменений, т.к. результат зависит от `b`.
      • -
      -
      -
      Укорачивание локальных переменных
      -
      -До оптимизации: - -```js -function sayHi(*!*name*/!*, *!*message*/!*) { - alert(name +" сказал: " + message); -} -``` - -После оптимизации: - -```js -//+ no-beautify -function sayHi(a,b){alert(a+" сказал: "+b)}; -``` - -
        -
      • Локальная переменная заведомо доступна только внутри функции, поэтому обычно её переименование безопасно (необычные случаи рассмотрим далее).
      • -
      • Также переименовываются локальные функции.
      • -
      • Вложенные функции обрабатываются корректно.
      • -
      -
      -
      Объединение и удаление локальных переменных
      -
      -До оптимизации: - -```js -function test(nodeId) { - var elem = document.getElementsById(nodeId); - var parent = elem.parentNode; - alert( parent ); -} -``` - -После оптимизации GCC: - -```js -//+ no-beautify -function test(a){a=document.getElementsById(a).parentNode;alert(a)}; -``` - -
        -
      • Локальные переменные были переименованы.
      • -
      • Лишние переменные убраны. Для этого сжиматель создаёт вспомогательную внутреннюю структуру данных, в которой хранятся сведения о "пути использования" каждой переменной. Если одна переменная заканчивает свой путь и начинает другая, то вполне можно дать им одно имя.
      • -
      • Кроме того, операции `elem = getElementsById` и `elem.parentNode` объединены, но это уже другая оптимизация.
      • -
      -
      -
      Уничтожение недостижимого кода, разворачивание `if`-веток
      -
      - -До оптимизации: - -```js -function test(node) { - var parent = node.parentNode; - - if (0) { - alert( "Привет с параллельной планеты" ); - } else { - alert( "Останется только один" ); - } - - return; - - alert( 1 ); -} -``` - -После оптимизации: - -```js -//+ no-beautify -function test(){alert("Останется только один")} -``` - -
        -
      • Если переменная присваивается, но не используется, она может быть удалена. В примере выше эта оптимизация была применена к переменной `parent`, а затем и к параметру `node`.
      • -
      • Заведомо ложная ветка `if(0) { .. }` убрана, заведомо истинная -- оставлена. - -То же самое будет с условиями в других конструкциях, например `a = true ? c : d` превратится в `a = c`.
      • -
      • Код после `return` удалён как недостижимый.
      • -
      -
      -
      Переписывание синтаксических конструкций
      -
      -До оптимизации: - -```js -var i = 0; -while (i++ < 10) { - alert( i ); -} - -if (i) { - alert( i ); -} - -if (i == '1') { - alert( 1 ); -} else if (i == '2') { - alert( 2 ); -} else { - 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); -``` - -
        -
      • Конструкция `while` переписана в `for`.
      • -
      • Конструкция `if (i) ...` переписана в `i&&...`.
      • -
      • Конструкция `if (cond) ... else ...` была переписана в `cond ? ... : ...`.
      • -
      -
      -
      Инлайнинг функций
      -
      -*Инлайнинг функции* -- приём оптимизации, при котором функция заменяется на своё тело. - -До оптимизации: - -```js -function sayHi(message) { - - var elem = createMessage('div', message); - showElement(elem); - - function createMessage(tagName, message) { - var el = document.createElement(tagName); - el.innerHTML = message; - return el; - } - - function showElement(elem) { - document.body.appendChild(elem); - } -} -``` - -После оптимизации (переводы строк также будут убраны): - -```js -function sayHi(b) { - var a = document.createElement("div"); - a.innerHTML = b; - document.body.appendChild(a) -}; -``` - -
        -
      • Вызовы функций `createMessage` и `showElement` заменены на тело функций. В данном случае это возможно, так как функции используются всего по разу.
      • -
      • Эта оптимизация применяется не всегда. Если бы каждая функция использовалась много раз, то с точки зрения размера выгоднее оставить их "как есть".
      • -
      -
      -
      Инлайнинг переменных
      -
      Переменные заменяются на значение, если оно заведомо известно. - -До оптимизации: - -```js -(function() { - var isVisible = true; - var hi = "Привет вам из JavaScript"; - - 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 ); - } - } - -})(); -``` - -После оптимизации: - -```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" ); - }; - } -})(); -``` - -
        -
      • Переменная `isVisible` заменена на `true`, после чего `if` стало возможным убрать.
      • -
      • Переменная `hi` заменена на строку.
      • -
      - -Казалось бы -- зачем менять `hi` на строку? Ведь код стал ощутимо длиннее! - -...Но всё дело в том, что минификатор знает, что дальше код будет сжиматься при помощи gzip. Во всяком случае, все правильно настроенные сервера так делают. - -[Алгоритм работы gzip](http://www.gzip.org/algorithm.txt) заключается в том, что он ищет повторы в данных и выносит их в специальный "словарь", заменяя на более короткий идентификатор. Архив как раз и состоит из словаря и данных, в которых дубликаты заменены на идентификаторы. - -Если вынести строку обратно в переменную, то получится как раз частный случай такого сжатия -- взяли `"Привет вам из JavaScript"` и заменили на идентификатор `hi`. Но gzip справляется с этим лучше, поэтому эффективнее будет оставить именно строку. Gzip сам найдёт дубликаты и сожмёт их. - -Плюс такого подхода станет очевиден, если сжать gzip оба кода -- до и после минификации. Минифицированный gzip-сжатый код в итоге даст меньший размер. -
      - -
      Разные мелкие оптимизации
      -
      Кроме основных оптимизаций, описанных выше, есть ещё много мелких: - -
        -
      • Убираются лишние кавычки у ключей - -```js -//+ no-beautify -{"prop" : "val" } => {prop:"val"} -``` - -
      • -
      • Упрощаются простые вызовы `Array/Object` - -```js -//+ no-beautify -a = new Array() => a = [] -o = new Object() => o = {} -``` - -Эта оптимизация предполагает, что `Array` и `Object` не переопределены программистом. Для включения её в UglifyJS нужен флаг `--unsafe`. -
      • -
      • ...И еще некоторые другие мелкие изменения кода...
      • -
      - - -
      -
      - -## Подводные камни - -Описанные оптимизации, в целом, безопасны, но есть ряд подводных камней. - -### Конструкция with - -Рассмотрим код: - -```js -//+ no-beautify -function changePosition(style) { - var position, test; - -*!* - with (style) { - position = 'absolute'; - } -*/!* -} -``` - -Куда будет присвоено значение `position = 'absolute'`? - -Это неизвестно до момента выполнения: если свойство `position` есть в `style` -- то туда, а если нет -- то в локальную переменную. - -Можно ли в такой ситуации заменить локальную переменную на более короткую? Очевидно, нет: - -```js -//+ no-beautify -function changePosition(style) { - var a, b; - -*!* - with (style) { // а что, если в style нет такого свойства? - position = 'absolute';// куда будет осуществлена запись? в window.position? - } -*/!* -} -``` - -Такая же опасность для сжатия кроется в использованном `eval`. Ведь `eval` может обращаться к локальным переменным: - -```js -//+ no-beautify -function f(code) { - var myVar; - - eval(code); // а что, если будет присвоение eval("myVar = ...") ? - - alert(myVar); -``` - -Получается, что при наличии `eval` мы не имеем права переименовывать локальные переменные. Причём (!), если функция является вложенноой, то и во внешних функциях тоже. - -А ведь сжатие переменных -- очень важная оптимизация. Как правило, она уменьшает размер сильнее всего. - -Что делать? Разные минификаторы поступают по-разному. - -
        -
      • UglifyJS -- не будет переименовывать переменные. Так что наличие `with/eval` сильно повлияет на степень сжатие кода.
      • -
      • GCC -- всё равно сожмёт локальные переменные. Это, конечно же, может привести к ошибкам, причём в сжатом коде, отлаживать который не очень-то удобно. Поэтому он выдаст предупреждение о наличии опасной конструкции.
      • -
      - -Ни тот ни другой вариант нас, по большому счёту, не устраивают. - -**Для того, чтобы код сжимался хорошо и работал правильно, не используем `with` и `eval`.** - -Либо, если уж очень надо использовать -- делаем это с оглядкой на поведение минификатора, чтобы не было проблем. - -### Условная компиляция IE10- - -В IE10- поддерживалось [условное выполнение JavaScript](http://msdn.microsoft.com/en-us/library/121hztk3.aspx). - -Синтаксис: `/*@cc_on код */`. - -Такой код выполнится в IE10-, например: - -```js -//+ run -var isIE /*@cc_on =true@*/ ; - -alert( isIE ); // true в IE10- -``` - -Можно хитро сделать, чтобы комментарий остался, например так: - -```js -//+ run -var isIE = new Function('', '/*@cc_on return true@*/')(); - -alert( isIE ); // true в IE. -``` - - -...Однако, с учётом того, что в современных IE11+ эта компиляция не работает в любом случае, лучше избавиться от неё вообще. - -В следующих главах мы посмотрим, какие продвинутые возможности есть в минификаторах, как сделать сжатие более эффективным. - diff --git a/6-optimize/3-minification/my.svg b/6-optimize/3-minification/my.svg deleted file mode 100644 index a04ec2c4..00000000 --- a/6-optimize/3-minification/my.svg +++ /dev/null @@ -1 +0,0 @@ -ASTnode0BLOCKnode1SCRIPTnode0->node1node0->node1UNCONDRETURNRETURNnode0->RETURNSYN_BLOCKnode2FUNCTIONфункцияnode1->node2node1->RETURNUNCONDnode3NAMEимяnode2->node3node4PARAM_LISTсписок параметровnode2->node4node6BLOCKтело функцииnode2->node6UNCONDnode2->node6node5NAMEимяnode4->node5node7EXPR_RESULTвыражениеnode6->node7UNCONDnode6->node7node8ASSIGNприсвоитьnode7->node8node7->RETURNUNCONDnode9GETPROPполучить свойствоnode8->node9node12FUNCTIONфункцияnode8->node12node10THISnode9->node10node11STRINGстрока-константаnode9->node11node13NAMEnode12->node13node14PARAM_LISTnode12->node14node15BLOCKnode12->node15node16EXPR_RESULTnode15->node16node17CALLвызов функцииnode16->node17node18NAMEимя(функции)node17->node18node19NAMEимя(параметр)node17->node19 \ No newline at end of file diff --git a/6-optimize/4-better-minification/article.md b/6-optimize/4-better-minification/article.md deleted file mode 100644 index 5220f678..00000000 --- a/6-optimize/4-better-minification/article.md +++ /dev/null @@ -1,172 +0,0 @@ -# Улучшаем сжатие кода - -Здесь мы обсудим разные приёмы, которые используются, чтобы улучшить сжатие кода. -[cut] -## Больше локальных переменных - -Например, код jQuery обёрнут в функцию, запускаемую "на месте". - -```js -(function(window, undefined) { - // ... - var jQuery = ... - - window.jQuery = jQuery; // сделать переменную глобальной - -})(window); -``` - -Переменные `window` и `undefined` стали локальными. Это позволит сжимателю заменить их на более короткие. - -## ООП без прототипов - -Приватные переменные будут сжаты и заинлайнены. - -Например, этот код хорошо сожмётся: - -```js -function User(firstName, lastName) { - var fullName = firstName + ' ' + lastName; - - this.sayHi = function() { - showMessage(fullName); - } - - function showMessage(msg) { - alert( '**' + msg + '**' ); - } -} -``` - -..А этот -- плохо: - -```js -function User(firstName, lastName) { - this._firstName = firstName; - this._lastName = lastName; -} - -User.prototype.sayHi = function() { - this._showMessage(this._fullName); -} - - -User.prototype._showMessage = function(msg) { - alert( '**' + msg + '**' ); -} -``` - -Сжимаются только локальные переменные, свойства объектов не сжимаются, поэтому эффект от сжатия для второго кода будет совсем небольшим. - -При этом, конечно, нужно иметь в виду общий стиль ООП проекта, достоинства и недостатки такого подхода. - -## Сжатие под платформу, define - -Можно делать разные сборки в зависимости от платформы (мобильная/десктоп) и браузера. - -Ведь не секрет, что ряд функций могут быть реализованы по разному, в зависимости от того, поддерживает ли среда выполнения нужную возможность. - -### Способ 1: локальная переменная - -Проще всего это сделать локальной переменной в модуле: - -```js -(function($) { - -*!* - /** @const */ - var platform = 'IE'; -*/!* - - // ..... - - if (platform == 'IE') { - alert( 'IE' ); - } else { - alert( 'NON-IE' ); - } - -})(jQuery); -``` - -Нужное значение директивы можно вставить при подготовке JavaScript к сжатию. - -Сжиматель заинлайнит её и оптимизирует соответствующие IE. - -### Способ 2: define - -UglifyJS и GCC позволяют задать значение глобальной переменной из командной строки. - -В GCC эта возможность доступна лишь в "продвинутом режиме" работы оптимизатора, который мы рассмотрим далее (он редко используется). - -Удобнее в этом плане устроен UglifyJS. В нём можно указать флаг `-d SYMBOL[=VALUE]`, который заменит все переменные `SYMBOL` на указанное значение `VALUE`. Если `VALUE` не указано, то оно считается равным `true`. - -Флаг не работает, если переменная определена явно. - -Например, рассмотрим код: - -```js -// my.js -if (isIE) { - alert( "Привет от IE" ); -} else { - alert( "Не IE :)" ); -} -``` - -Сжатие вызовом `uglifyjs -d isIE my.js` даст: - -```js -alert( "Привет от IE" ); -``` - -..Ну а чтобы код работал в обычном окружении, нужно определить в нём значение переменной по умолчанию. Это обычно делается в каком-то другом файле (на весь проект), так как если объявить `var isIE` в этом, то флаг `-d isIE` не сработает. - -Но можно и "хакнуть" сжиматель, объявив переменную так: - -```js -// объявит переменную при отсутствии сжатия -// при сжатии не повредит -window.isIE = window.isIE || getBrowserVersion(); -``` - -## Убираем вызовы console.* - -Минификатор имеет в своём распоряжении дерево кода и может удалить ненужные вызовы. - -Для UglifyJS это делают опции компилятора: -
        -
      • `drop_debugger` -- убирает вызовы `debugger`.
      • -
      • `drop_console` -- убирает вызовы `console.*`.
      • -
      - -Можно написать и дополнительную функцию преобразования, которая убирает другие вызовы, например для `log.*`: - -```js -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); - }); -}; -``` - -Эту функцию следует вызвать на результате `parse`, и она пройдётся по дереву и удалит все вызовы `log.*`. - diff --git a/6-optimize/5-gcc-advanced-optimization/article.md b/6-optimize/5-gcc-advanced-optimization/article.md deleted file mode 100644 index f2a84d14..00000000 --- a/6-optimize/5-gcc-advanced-optimization/article.md +++ /dev/null @@ -1,530 +0,0 @@ -# GCC: продвинутые оптимизации - -Продвинутый режим оптимизации google closure compiler включается опцией --compilation_level ADVANCED_OPTIMIZATIONS. - -Слово "продвинутый" (advanced) здесь, пожалуй, не совсем подходит. Было бы более правильно назвать его "супер-агрессивный-ломающий-ваш-неподготовленный-код-режим". Кардинальное отличие применяемых оптимизаций от обычных (simple) -- в том, что они небезопасны. - -Чтобы им пользоваться -- надо уметь это делать. -[cut] - -## Основной принцип продвинутого режима - -
        -
      • Если в обычном режиме переименовываются только локальные переменные внутри функций, то в "продвинутом" -- на более короткие имена заменяется все.
      • -
      • Если в обычном режиме удаляется недостижимый код после return, то в продвинутом -- вообще весь код, который не вызывается в явном виде.
      • -
      - -Например, если запустить продвинутую оптимизацию на таком коде: - -```js -// my.js -function test(node) { - node.innerHTML = "newValue" -} -``` - -Строка запуска компилятора: - -``` -java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js my.js -``` - -...То результат будет -- пустой файл. Google Closure Compiler увидит, что функция test не используется, и с чистой совестью вырежет ее. - -А в следующем скрипте функция сохранится: - -```js -function test(n) { - alert( "this is my test number " + n ); -} -test(1); -test(2); -``` - -После сжатия: - -```js -function a(b) { - alert("this is my test number " + b) -} -a(1); -a(2); -``` - -Здесь в скрипте присутствует явный вызов функции, поэтому она сохранилась. - -Конечно, есть способы, чтобы сохранить функции, вызов которых происходит вне скрипта, и мы их обязательно рассмотрим. - -**Продвинутый режим сжатия не предусматривает сохранения глобальных переменных. Он переименовывает, инлайнит, удаляет вообще все символы, кроме зарезервированных.** - -Иначе говоря, продвинутый режим (ADVANCED_OPTIMIZATIONS), в отличие от простого (SIMPLE_OPTIMIZATIONS -- по умолчанию), вообще не заботится о доступности кода извне и сохранении ссылочной целостности относительно внешних скриптов. - -Единственное, что он гарантирует -- это внутреннюю ссылочную целостность, и то -- при соблюдении ряда условий и практик программирования. - -Собственно, за счет такого агрессивного подхода и достигается дополнительный эффект оптимизации и сжатия скриптов. - -[summary] -То есть, продвинутый режим - это не просто "улучшенный обычный", а принципиально другой, небезопасный и обфусцирующий подход к сжатию. - -Этот режим является "фирменной фишкой" Google Closure Compiler, недоступной при использовании других компиляторов. -[/summary] - -Для того, чтобы эффективно сжимать Google Closure Compiler в продвинутом режиме, нужно понимать, что и как он делает. Это мы сейчас обсудим. - -### Сохранение ссылочной целостности - -Чтобы использовать сжатый скрипт, мы должны иметь возможность вызывать функции под теми именами, которые им дали. - -То есть, перед нами стоит задача *сохранения ссылочной целостности*, которая заключается в том, чтобы обеспечить доступность нужных функций для обращений по исходному имени извне скрипта. - -Существует два способа сохранения внешней ссылочной целостности: экстерны и экспорты. Мы в подробностях рассмотрим оба, но перед этим необходимо упомянуть о модулях -- другой важнейшей возможности GCC. - -### Модули - -При сжатии GCC можно указать одновременно много JavaScript-файлов. "Эка невидаль, " -- скажете вы, и будете правы. Да, пока что ничего особого. - -Но в дополнение к этому можно явно указать, какие исходные файлы сжать в какие файлы результата. То есть, разбить итоговую сборку на модули. - -Так что страницы могут грузить модули по мере надобности. Например, по умолчанию -- главный, а дополнительная функциональность -- загружаться лишь там, где она нужна. - -Для такой сборки используется флаг компилятора `--module имя:количество файлов`. - -Например: - -``` -java -jar compiler.jar --js base.js --js main.js --js admin.js --module -first:2 --module second:1:first -``` - -Эта команда создаст модули: first.js и second.js. - -Первый модуль, который назван "first", создан из объединённого и оптимизированного кода первых двух файлов (`base.js` и `main.js`). - -Второй модуль, который назван "second", создан из `admin.js` -- это следующий аргумент `--js` после включенных в первый модуль. - -Второй модуль в нашем случае зависит от первого. Флаг `--module second:1:first` как раз означает, что модуль `second` будет создан из одного файла после вошедших в предыдущий модуль (`first`) и зависит от модуля `first`. - -А теперь -- самое вкусное. - -**Ссылочная целостность между всеми получившимися файлами гарантируется.** - -Если в одном функция `doFoo` заменена на `b`, то и в другом тоже будет использоваться `b`. - -Это означает, что проблем между JS-файлами не будет. Они могут свободно вызывать друг друга без экспорта, пока находятся в единой модульной сборке. - -### Экстерны - -Экстерн (extern) -- имя, которое числится в специальном списке компилятора. Он должен быть определен вне скрипта, в файле экстернов. - -**Компилятор никогда не переименовывает экстерны.** - -Например: - -```js -document.onkeyup = function(event) { - alert(event.type) -} -``` - -После продвинутого сжатия: - -```js -document.onkeyup = function(a) { - alert(a.type) -} -``` - -Как видите, переименованной оказалась только переменная `event`. Такое переименование заведомо безопасно, т.к. `event` -- локальная переменная. - -Почему компилятор не тронул остального? Попробуем другой вариант: - -```js -document.blabla = function(event) { - alert(event.megaProperty) -} -``` - -После компиляции: - -```js -document.a = function(a) { - alert(a.b) -} -``` - -Теперь компилятор переименовал и blabla и megaProperty. - -Дело в том, что названия, использованные до этого, были во внутреннем списке экстернов компилятора. Этот список охватывает основные объекты браузеров и находится (под именем externs.zip) в корне архива compiler.jar. - -**Компилятор переименовывает имя списка экстернов только когда так названа локальная переменная.** - -Например: - -```js -window.resetNode = function(node) { - var innerHTML = "test"; - node.innerHTML = innerHTML; -} -``` - -На выходе: - -```js -window.a = function(a) { - a.innerHTML = "test" -}; -``` - -Как видите, внутренняя переменная innerHTML не просто переименована - она заинлайнена (заменена на значение). Так как переменная локальна, то любые действия внутри функции с ней безопасны. - -А свойство innerHTML не тронуто, как и объект window -- так как они в списке экстернов и не являются локальными переменными. - -Это приводит к следующему побочному эффекту. Иногда свойства, которые следовало бы сжать, не сжимаются. Например: - -```js -window['User'] = function(name, type, age) { - this.name = name - this.type = type - this.age = age -} -``` - -После сжатия: - -```js -window.User = function(a, b, c) { - this.name = a; - this.type = b; - this.a = c -}; -``` - -Как видно, свойство age сжалось, а name и type -- нет. Это побочный эффект экстернов: name и type -- в списке объектов браузера, и компилятор просто старается не наломать дров. - -Поэтому отметим еще одно полезное правило оптимизации: - -**Названия своих свойств не должны совпадать с зарезервированными словами (экстернами). Тогда они будут хорошо сжиматься.** - -Для задания списка экстернов их достаточно перечислить в файле и указать этот файл флагом --externs <файл экстернов.js>. - -При перечислении объектов в файле экстернов - объявляйте их и перечисляйте свойства. Все эти объявления никуда не идут, они используются только для создания списка, который обрабатывается компилятором. - -Например, файл `myexterns.js`: - -```js -var dojo = {} -dojo._scopeMap; -``` - -Использование такого файла при сжатии (опция --externs myexterns.js) приведет к тому, что все обращения к символам dojo и к dojo._scopeMap будут не сжаты, а оставлены "как есть". - - -### Экспорт - -*Экспорт* -- программный ход, основанный на следующем правиле поведения компилятора. - -**Компилятор заменяет обращения к свойствам через кавычки на точку, и при этом не трогает название свойства.** - -Например, window['User'] превратится в window.User, но не дальше. - -Таким образом можно *"экспортировать"* нужные функции и объекты: - -```js -function SayWidget(elem) { - this.elem = elem - this.init() -} -window['SayWidget'] = SayWidget; -``` - -На выходе: - -```js -function a(b) { - this.a = b; - this.b() -} -window.SayWidget = a; -``` - -Обратим внимание -- сама функция SayWidget была переименована в a. Но затем -- экспортирована как window.SayWidget, и таким образом доступна внешним скриптам. - -Добавим пару методов в прототип: - -```js -function SayWidget(elem) { - this.elem = elem; - this.init(); -} - -SayWidget.prototype = { - init: function() { - this.elem.style.display = 'none' - }, - - setSayHandler: function() { - this.elem.onclick = function() { - alert("hi") - }; - } -} - -window['SayWidget'] = SayWidget; -SayWidget.prototype['setSayHandler'] = SayWidget.prototype.setSayHandler; -``` - -После сжатия: - -```js -//+ no-beautify -function a(b) { - this.a = b; - this.b() -} -a.prototype = {b:function() { - this.a.style.display = "none" -}, c:function() { - this.a.onclick = function() { - alert("hi") - } -}}; -window.SayWidget = a; -a.prototype.setSayHandler = a.prototype.c; -``` - -Благодаря строке - -```js -SayWidget.prototype['setSayHandler'] = SayWidget.prototype.setSayHandler -``` - -метод setSayHandler экспортирован и доступен для внешнего вызова. - -Сама строка экспорта выглядит довольно глупо. По виду -- присваиваем свойство самому себе. - -Но логика сжатия GCC работает так, что такая конструкция является экспортом. Справа переименование свойства setSayHandler происходит, а слева -- нет. - -[smart header="Планируйте жизнь после сжатия"] - -Рассмотрим следующий код: - -```js -window['Animal'] = function() { - this.blabla = 1; - this['blabla'] = 2; -} -``` - -После сжатия: - -```js -window.Animal = function() { - this.a = 1; - this.blabla = 2 -}; -``` - -Как видно, первое обращение к свойству blabla сжалось, а второе (как и все аналогичные) -- преобразовалось в синтаксис через точку. -В результате получили некорректное поведение кода. - -Так что, используя продвинутый режим оптимизации, планируйте поведение кода после сжатия. - -**Если где-то возможно обращение к свойствам через квадратные скобки по полному имени -- такое свойство должно быть экспортировано.** -[/smart] - -### goog.exportSymbol и goog.exportProperty - -В библиотеке [Google Closure Library](https://developers.google.com/closure/library/) для экспорта есть специальная функция goog.exportSymbol. Вызывается так: - -```js -goog.exportSymbol('my.SayWidget', SayWidget) -``` - -Эта функция по сути работает также, как и рассмотренная выше строка с присвоением свойства, но при необходимости создает нужные объекты. - -Она аналогична коду: - -```js -window['my'] = window['my'] || {} -window['my']['SayWidget'] = SayWidget -``` - -То есть, если путь к объекту не существует -- exportSymbol создаст нужные пустые объекты. - -Функция goog.exportProperty экспортирует свойство объекта: - -```js -goog.exportProperty(SayWidget.prototype, 'setSayHandler', SayWidget.prototype.setSayHandler) -``` - -Строка выше - то же самое, что и: - -```js -SayWidget.prototype['setSayHandler'] = SayWidget.prototype.setSayHandler -``` - -Зачем они нужны, если все можно сделать простым присваиванием? - -Основная цель этих функций -- во взаимодействии с Google Closure Compiler. Они дают информацию компилятору об экспортах, которую он может использовать. - -Например, есть недокументированная внутренняя опция externExportsPath, которая генерирует из всех экспортов файл экстернов. Таким образом можно распространять откомпилированный JavaScript-файл как внешнюю библиотеку, с файлом экстернов для удобного внешнего связывания. - -Кроме того, экспорт через эти функциями удобен и нагляден. - -Если вы используете продвинутый режим оптимизации, то можно взять их из файла base.js Google Closure Library. Можно и подключить этот файл целиком -- оптимизатор при продвинутом сжатии вырежет из него почти всё лишнее, так что overhead будет минимальным. - -### Отличия экспорта от экстерна - -Между экспортом и экстерном есть кое-что общее. И то и другое дает возможность доступа к объектам под исходным именем, до переименования. - -Но, в остальном, это совершенно разные вещи. - -
      - - - - - - - - - - - - - - - - - - - - -
      ЭкстернЭкспорт
      Служит для тотального запрета на переименование всех обращений к свойству. -Задумано для сохранения обращений к стандартным объектам браузера, внешним библиотекам.Служит для открытия доступа к свойству извне под указанным именем. -Задумано для открытия внешнего интерфейса к сжатому скрипту.
      Работает со свойством, объявленным вне скрипта. -Вы не можете объявить новое свойство в скрипте и сделать его экстерном.Создает ссылку на свойство, объявленное в скрипте.
      Если window - экстерн, то все обращения к window в скрипте останутся как есть.Если user экспортируется, то создается только одна ссылка под полным именем, а все остальные обращения будут сокращены.
      - - -## Стиль разработки - -Посмотрим, как сжиматель поведёт себя на следующем, типичном, объявлении библиотеки: - -```js -(function(window, undefined) { - - // пространство имен и локальная перменная для него - var MyFramework = window.MyFramework = {}; - - // функция фреймворка, доступная снаружи - MyFramework.publicOne = function() { - makeElem(); - }; - - // приватная функция фреймворка - function makeElem() { - var div = document.createElement('div'); - document.body.appendChild(div); - } - - // еще какая-то функция - MyFramework.publicTwo = function() {}; - -})(window); - -// использование -MyFramework.publicOne(); -``` - -Результат компиляции в обычном режиме: - -```js -//+ no-beautify -// java -jar compiler.jar --js myframework.js --formatting PRETTY_PRINT -(function(a) { - a = a.MyFramework = {}; - a.publicOne = function() { - var a = document.createElement("div"); - document.body.appendChild(a) - }; - a.publicTwo = function() { - } -})(window); -MyFramework.publicOne(); -``` - -Это -- примерно то, что мы ожидали. Неиспользованный метод `publicTwo` остался, локальные свойства переименованы и заинлайнены. - -А теперь продвинутый режим: - -```js -// --compilation_level ADVANCED_OPTIMIZATIONS -window.a = {}; -MyFramework.b(); -``` - -Оно не работает! Компилятор попросту не разобрался, что и как вызывается, и превратил рабочий JS-файл в один сплошной баг. - -В зависимости от версии GCC у вас может быть и что-то другое. - -Всё дело в том, что такой стиль объявления нетипичен для инструментов, которые в самом Google разрабатываются и сжимаются этим минификатором. - -Типичный правильный стиль: - -```js -// пространство имен и локальная перменная для него -var MyFramework = {}; - -MyFrameWork._makeElem = function() { - var div = document.createElement('div'); - document.body.appendChild(div); -}; - -MyFramework.publicOne = function() { - MyFramework._makeElem(); -}; - -MyFramework.publicTwo = function() {}; - -// использование -MyFramework.publicOne(); -``` - -Обычное сжатие здесь будет бесполезно, а вот продвинутый режим идеален: - -```js -// в зависимости от версии GCC результат может отличаться -MyFrameWork.a = function() { - var a = document.createElement("div"); - document.body.appendChild(a) -}; -MyFrameWork.a(); -``` - -Google Closure Compiler не только разобрался в структуре и удалил лишний метод - он заинлайнил функции, чтобы итоговый размер получился минимальным. - -Как говорится, преимущества налицо. - -## Резюме - -Продвинутый режим оптимизации сжимает, оптимизирует и, при возможности, удаляет все свойства и методы, за исключением экстернов. - -Это является принципиальным отличием, по сравнению с другими упаковщиками. - -Отказ от сохранения внешней ссылочной целостности с одной стороны позволяет увеличить уровень сжатия, но требует поддержки со стороны разработчика. - -Основная проблема этого сжатия -- усложнение разработки. Добавляется дополнительный уровень возможных проблем: сжатие. Конечно, можно отлаживать и сжатый код, для этого придуманы [Source Maps](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/), но клиентская разработка и без того достаточно сложна. - -Поэтому его используют редко. - -Как правило, есть две причины для использования продвинутого режима: -
        -
      1. **Обфускация кода.** - -Если в коде после обычного сжатия ещё как-то можно разобраться, то после продвинутого -- уже нет. Всё переименовано и заинлайнено. В теории это, конечно, возможно, но "порог входа" в такой код несоизмеримо выше. - -Судя по виду скриптов на сайтах, созданных Google, сам Google жмет свои скрипты именно продвинутым режимом оптимизации. И библиотека Google Closure Library тоже рассчитана на него.
      2. -
      3. **Хорошие сжатие виджетов, счётчиков.** - -Небольшой код, который отдаётся наружу, может быть сжат в продвинутом режиме. Так как он небольшой -- все ошибки можно легко исправить, а продвинутый режим гарантирует наилучшее сжатие.
      4. -
      diff --git a/6-optimize/6-gcc-check-types/article.md b/6-optimize/6-gcc-check-types/article.md deleted file mode 100644 index 8c625359..00000000 --- a/6-optimize/6-gcc-check-types/article.md +++ /dev/null @@ -1,177 +0,0 @@ -# GCC: статическая проверка типов - -Google Closure Compiler, как и любой кошерный компилятор, старается проверить правильность кода и предупредить о возможных ошибках. - -Первым делом он, разумеется, проверяет структуру кода и сразу же выдает такие ошибки как пропущенная скобка или лишняя запятая. - -Но, кроме этого, он умеет проверять типы переменных, используя как свои собственные знания о встроенных javascript-функциях и преобразованиях типов, -так и информацию о типах из JSDoc, указываемую javascript-разработчиком. - -Это обеспечивает то, чем так гордятся компилируемые языки -- статическую проверку типов, что позволяет избежать лишних ошибок во время выполнения. -[cut] - -Для вывода предупреждений при проверки типов используется флаг `--jscomp_warning checkTypes`. - -## Задание типа при помощи аннотации - -Самый очевидный способ задать тип -- это использовать аннотацию. Полный список аннотаций вы найдете в документации. - -В следующем примере параметр id функции f1 присваивается переменной boolVar другого типа: - -```js -/** @param {number} id */ -function f(id) { - /** @type {boolean} */ - var boolVar; - - boolVar = id; // (!) -} -``` - -Компиляция с флагом `--jscomp_warning checkTypes` выдаст предупреждение: - -``` -f.js:6: WARNING - assignment -found : number -required: boolean - boolVar = id; // (!) - ^ -``` - -Действительно: произошло присвоение значения типа number переменной типа boolean. - -Типы отслеживаются по цепочке вызовов. - -Еще пример, на этот раз вызов функции с некорректным параметром: - -```js -/** @param {number} id */ -function f1(id) { - f2(id); // (!) -} - -/** @param {string} id */ -function f2(id) {} -``` - -Такой вызов приведёт к предупреждению со стороны минификатора: - -``` -f2.js:3: WARNING - actual parameter 1 of f2 does not match formal parameter -found : number -required: string - f2(id); // (!) - ^ -``` - -Действительно, вызов функции f2 произошел с числовым типом вместо строки. - -**Отслеживание приведений и типов идёт при помощи графа взаимодействий и выведению (infer) типов, который строит GCC по коду.** - -## Знания о преобразовании типов - -Google Closure Compiler знает, как операторы javascript преобразуют типы. Такой код уже не выдаст ошибку: - -```js -/** @param {number} id */ -function f1(id) { - /** @type {boolean} */ - var boolVar; - - boolVar = !!id -} -``` - -Действительно - переменная преобразована к типу boolean двойным оператором НЕ. -А код boolVar = 'test-'+id выдаст ошибку, т.к. конкатенация со строкой дает тип string. - -## Знание о типах встроенных функций, объектные типы - -Google Closure Compiler содержит описания большинства встроенных объектов и функций javascript вместе с типами параметров и результатов. - -Например, объектный тип Node соответствует узлу DOM. - -Пример некорректного кода: - -```js -/** @param {Node} node */ -function removeNode(node) { - node.parentNode.removeChild(node) -} -document.onclick = function() { - removeNode("123") -} -``` - -Выдаст предупреждение - -``` -f3.js:7: WARNING - actual parameter 1 of removeNode does not match formal parameter -found : string -required: (Node|null) - removeNode("123") - ^ -``` - -Обратите внимание - в этом примере компилятор выдает required: Node|null. Это потому, что указание объектного типа (не элементарного) подразумевает, что в функцию может быть передан null. - -В следующем примере тип указан жестко, без возможности обнуления: - -```js -*!* -/** @param {!Node} node */ -*/!* -function removeNode(node) { - node.parentNode.removeChild(node) -} -``` - -Восклицательный знак означает, что параметр обязатален. - -Найти описания встроенных типов и объектов javascript вы можете в файле экстернов: externs.zip находится в корне архива compiler.jar. - -## Интеграция с проверками типов из Google Closure Library - -В Google Closure Library есть функции проверки типов: goog.isArray, goog.isDef, goog.isNumber и т.п. - -Google Closure Compiler знает о них и понимает, что внутри следующего if переменная может быть только функцией: - -```js -var goog = { - isFunction: function(f) { - return typeof f == 'function' - } -} - -if (goog.isFunction(func)) { - func.apply(1, 2) -} -``` - -Сжатие с проверкой выдаст предупреждение: - -``` -f.js:6: WARNING - actual parameter 2 of Function.apply does not match formal parameter -found : number -required: (Object|null|undefined) - func.apply(1, 2) - ^ ^ -``` - -То есть, компилятор увидел, что код, использующий func находится в `if (goog.isFunction(func))` и сделал соответствующий вывод, что это в этой ветке `func` является функцией, а значит вызов `func.apply(1,2)` ошибочен (второй аргумент не может быть числом). - -Дело тут именно в интеграции с Google Closure Library. Если поменять `goog` на `g` -- предупреждения не будет. - -## Резюме - -Из нескольких примеров, которые мы рассмотрели, должна быть понятна общая логика проверки типов. - -Соответствующие различным типам и ограничениям на типы аннотации вы можете найти в Документации Google. В частности, возможно указание нескольких возможных типов, типа undefined и т.п. - -Также можно указывать количество и тип параметров функции, ключевого слова this, объявлять классы, приватные методы и интерфейсы. - -Проверка типов javascript, предоставляемая Google Closure Compiler -- пожалуй, самая продвинутая из существующих на сегодняшний день. - -C ней аннотации, документирующие типы и параметры, становятся не просто украшением, а реальным средством проверки, уменьшающим количество ошибок на production. - -Очень подробно проверка типов описана в книге [Closure: The Definitive Guide](http://www.ozon.ru/context/detail/id/6089988/), автора Michael Bolin. \ No newline at end of file diff --git a/6-optimize/7-gcc-closure-library/article.md b/6-optimize/7-gcc-closure-library/article.md deleted file mode 100644 index 0c02e685..00000000 --- a/6-optimize/7-gcc-closure-library/article.md +++ /dev/null @@ -1,180 +0,0 @@ -# GCC: интеграция с Google Closure Library - -Google Closure Compiler содержит ряд специальных возможностей для интеграции с Google Closure Library. - -Здесь важны две вещи. -
        -
      1. Для их использования возможно использовать минимум от Google Closure Library. Например, взять одну или несколько функций из библиотеки.
      2. -
      3. GCC -- расширяемый компилятор, можно добавить к нему свои "фазы оптимизации" для интеграции с другими инструментами и фреймворками.
      4. -
      -[cut] - -Интеграция с Google Closure Library подключается флагом --process_closure_primitives, который по умолчанию установлен в true. То есть, она включена по умолчанию. - -Этот флаг запускает специальный проход компилятора, описанный классом ProcessClosurePrimitives и подключает дополнительную проверку типов ClosureReverseAbstractInterpreter. - -Мы рассмотрим все действия, которые при этом происходят, а также некоторые опции, которые безопасным образом используют символы Google Closure Library без объявления флага. - -## Преобразование основных символов - -Следующие действия описаны в классе ProcessClosurePrimitives. - -### Замена константы COMPILED - -В Google Closure Library есть переменная: - -```js -/** - * @define {boolean} ... - */ -var COMPILED = false; -``` - -Проход ProcessClosurePrimitives переопределяет ее в true и использует это при оптимизациях, удаляя ветки кода, не предназначены для запуска на production. - -Такие функции существуют, например, в ядре Google Closure Library. К ним в первую очередь относятся вызовы, предназначенные для сборки и проверки зависимостей. Они содержат код, обрамленный проверкой COMPILED, например: - -```js -goog.require = function(rule) { - // ... - if (!COMPILED) { - // основное тело функции - } -} -``` - -Аналогично может поступить и любой скрипт, даже без использования Google Closure Library: - -```js -/** @define {boolean} */ -var COMPILED = false - -Framework = {} - -Framework.sayCompiled = function() { - if (!COMPILED) { - alert("Not compressed") - } else { - alert("Compressed") - } -} -``` - -Для того, чтобы сработало, нужно сжать в продвинутом режиме: - -```js -Framework = {}; -Framework.sayCompiled = Framework.a = function() { - alert( "Compressed" ); -}; -``` - -Компилятор переопределил COMPILED в true и произвел соответствующие оптимизации. - -### Автоподстановка локали - -В Google Closure Compiler есть внутренняя опция locale - -Эта опция переопределяет переменную goog.LOCALE на установленную при компиляции. - -Для использования опции locale, на момент написания статьи, ее нужно задать в Java коде компилятора, т.к. соответствующего флага нет. - -Как и COMPILED, константу goog.LOCALE можно и использовать в своем коде без библиотеки Google Closure Library. - -### Проверка зависимостей - -Директивы goog.provide, goog.require, goog.addDependency обрабатываются особым образом. - -Все зависимости проверяются, а сами директивы проверки -- удаляются из сжатого файла. - -### Экспорт символов - -Вызов goog.exportSymbol задаёт экспорт символа. - -Если подробнее, то код goog.exportSymbol('a',myVar) эквивалентен -`window['a'] = myVar`. - - -### Автозамена классов CSS - -Google Closure Library умеет преобразовывать классы CSS на более короткие по списку, который задаётся при помощи `goog.setCssNameMapping`. - -Например, следующая функция задает такой список. - -```js - goog.setCssNameMapping({ - "goog-menu": "a", - "goog-menu-disabled": "a-b", - "CSS_LOGO": "b", - "hidden": "c" - }); -``` - -Тогда следующий вызов преобразуется в "a a-b": - -```js -goog.getCssName('goog-menu') + ' ' + goog.getCssName('goog-menu', 'disabled') -``` - -Google Closure Compiler производит соответствующие преобразования в сжатом файле и удаляет вызов setCssNameMapping из кода. - -Чтобы это сжатие работало, в HTML/CSS классы тоже должны сжиматься. По всей видимости, в приложениях Google это и происходит, но соответствующие инструменты закрыты от публики. - -### Генерация списка экстернов - -При объявлении внутренней опции externExportsPath, содержащей путь к файлу, в этот файл будут записаны все экспорты, описанные через goog.exportSymbol/goog.exportProperty. - -В дальнейшем этот файл может быть использован как список экстернов для компиляции. - -Эта опция может быть полезна для создания внешних библиотек, распространяемых со списком экстернов. - -Для её использования нужна своя обёртка вокруг компилятора на Java. Соответствующий проход компилятора описан в классе ExternExportsPass. - -### Проверка типов - -В Google Closure Library есть ряд функций для проверки типов. Например: goog.isArray, goog.isString, goog.isNumber, goog.isDef и т.п. - -Компилятор использует их для проверки типов, более подробно см. [](/gcc-check-types) - -Эта логика описана в классе ClosureReverseAbstractInterpreter. Названия функций, определяющих типы, жестко прописаны в Java-коде, поменять их на свои без модификации исходников нельзя. - -### Автогенерация экспортов из аннотаций - -Для этого в Google Closure Compiler есть внутренняя опция generateExports. - -Эта недокументированная опция добавляет проход компилятора, описанный классом GenerateExports. - -Он читает аннотации @export и создает из них экспортирующие вызовы goog.exportSymbol/exportProperty. Название экспортирующих функций находится в классе соглашений кодирования, каким по умолчанию является GoogleCodingConvention. - -Например: - -```js -/** @export */ -function Widget() {} - /** @export */ -Widget.prototype.hide = function() { - this.elem.style.display = 'none' -} -``` - -После компиляции в продвинутом режиме: - -```js -function a() {} -goog.d("Widget", a); -a.prototype.a = function() { - this.b.style.display = "none" -}; -goog.c(a.prototype, "hide", a.prototype.a); -``` - -Свойства благополучно экспортированы. Удобно. - -### Резюме - -Google Closure Compiler содержит дополнительные фичи, облегчающие интеграцию с Google Closure Library. Некоторые из них весьма полезны, но требуют создания своего Java-файла, который ставит внутренние опции. - -При обработке символов компилятор не смотрит, подключена ли библиотека, он находит обрабатывает их просто по именам. Поэтому вы можете использовать свою реализацию соответствующих функций. - -Google Closure Compiler можно легко расширить, добавив свои опции и проходы оптимизатора, для интеграции с вашими инструментами. - diff --git a/6-optimize/8-memory-removechild-innerhtml/article.md b/6-optimize/8-memory-removechild-innerhtml/article.md deleted file mode 100644 index 5882124d..00000000 --- a/6-optimize/8-memory-removechild-innerhtml/article.md +++ /dev/null @@ -1,147 +0,0 @@ -# Очистка памяти при removeChild/innerHTML - -Управление памятью в случае с DOM работает по сути так же, как и с обычными JavaScript-объектами. Пока объект достижим -- он остаётся в памяти. - -Но есть и особенности, поскольку DOM весь переплетён ссылками. -[cut] -## Пример -Для примера рассмотрим следующий HTML: - -```html - - - -
      -
        -
      • Список
      • -
      - Сосед -
      - - - -``` - -Его DOM (показаны только основные ссылки): - - - -## Удаление removeChild - -Операция `removeChild` разрывает все связи удаляемым узлом и его родителем. - -Поэтому, если удалить `DIV` из `BODY`, то всё поддерево под `DIV` станет недостижимым и будет удалено. - -А что происходит, если на какой-то элемент внутри удаляемого поддерева есть ссылка? - -Например, `UL` сохранён в переменную `list`: - -```js -var list = document.getElementsByTagName('UL')[0]; -document.body.removeChild(document.body.children[0]); -``` - -В этом случае, так как из этого `UL` можно по ссылкам добраться до любого другого места DOM, то получается, что все объекты по-прежнему достижимы и должны остаться в памяти: - - - -То есть, DOM-объекты при использовании `removeChild` работают по той же логике, что и обычные объекты. - -## Удаление через innerHTML - -А вот удаление через очистку `elem.innerHTML="..."` браузеры интерпретируют по-разному. - -По идее, при присвоении `elem.innerHTML=html` из DOM должны удаляться предыдущие узлы и добавляться новые, из указанного `html`. Но стандарт ничего не говорит о том, что делать с узлами после удаления. И тут разные браузеры имеют разное мнение. - -Посмотрим, что произойдёт с DOM-структурой при очистке `BODY`, если на какой-либо элемент есть ссылка. - -```js -var list = document.getElementsByTagName('UL')[0]; -document.body.innerHTML = ""; -``` - -Обращаю внимание -- связь разрывается только между `DIV` и `BODY`, т.е. на верхнем уровне, а `list` -- это произвольный элемент. - -Чтобы увидеть, что останется в памяти, а что нет -- запустим код: - -```html - -
      -
        -
      • Список
      • -
      - Сосед -
      - - -``` - -Как ни странно, браузеры ведут себя по-разному: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      `parentNode``nextSibling``children.length`
      Chrome/Safari/Opera`null``null``1`
      Firefoxузел DOMузел DOM`1`
      IE 11-`null``null``0`
      - -Иными словами, браузеры ведут себя с различной степенью агрессивности по отношению к элементам. - -
      -
      Firefox
      -
      Главный пацифист. Оставляет всё, на что есть ссылки, т.е. элемент, его родителя, соседей и детей, в точности как при `removeChild`.
      -
      Chrome/Safari/Opera
      -
      Считают, что раз мы задали ссылку на `UL`, то нам нужно только это поддерево, а остальные узлы (соседей, родителей) можно удалить.
      -
      Internet Explorer
      -
      Как ни странно, самый агрессивный. Удаляет вообще всё, кроме узла, на который есть ссылка. Это поведение одинаково для всех версий IE.
      -
      - -На иллюстрации ниже показано, какую часть DOM оставит каждый из браузеров: - - -## Итого - -Если на какой-то DOM-узел есть ссылка, то: - -
        -
      • При использовании `removeChild` на родителе (или на этом узле, не важно) все узлы, достижимые из данного, остаются в памяти. - -То есть, фактически, в памяти может остаться большая часть дерева DOM. Это даёт наибольшую свободу в коде, но может привести к большим "утечкам памяти" из-за сохранения данных, которые реально не нужны.
      • -
      • При удалении через `innerHTML` браузеры ведут себя с различной степенью агрессивности. Кросс-браузерно гарантировано одно: сам узел, на который есть ссылка, останется в памяти. - -Поэтому обращаться к соседям и детям узла, предок которого удалён через присвоение `innerHTML`, нельзя.
      • -
      - - - - diff --git a/6-optimize/8-memory-removechild-innerhtml/html-innerhtml.png b/6-optimize/8-memory-removechild-innerhtml/html-innerhtml.png deleted file mode 100644 index e606be2ac029b41803c17c79cbedb8211b218f9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8262 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 diff --git a/6-optimize/8-memory-removechild-innerhtml/html.png b/6-optimize/8-memory-removechild-innerhtml/html.png deleted file mode 100644 index ca8da064980d5c4cb49b88f891d57a256c6217bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4400 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 dOZztd(nR+{j%_RT; diff --git a/7-frames-and-windows/6-clickjacking/article.md b/7-frames-and-windows/6-clickjacking/article.md index f6efd27c..18cb7cc5 100644 --- a/7-frames-and-windows/6-clickjacking/article.md +++ b/7-frames-and-windows/6-clickjacking/article.md @@ -63,12 +63,11 @@ iframe { /* iframe с сайта-жертвы */ [codetabs src="clickjacking" height=200] -Итак, все, что нужно для проведения атаки -- это правильно расположить iframe на вредоносной странице. В большинстве случаев это делается средствами HTML/CSS. +Итак, все, что нужно для проведения атаки -- это правильно расположить iframe на вредоносной странице, так чтобы кнопка с Facebook оказалась над "Жми тут!". В большинстве случаев это возможно и делается обычным CSS-позиционированием. [smart header="С клавиатурой так не сделаешь"] Атака назвается "Clickjacking", то есть "угон клика", так как события клавиатуры "угнать" гораздо труднее. - -Посетителя можно заставить сфокусироваться на `` с невидимого `