init
This commit is contained in:
parent
06f61d8ce8
commit
f301cb744d
2271 changed files with 103162 additions and 0 deletions
304
01-js/05-functions-closures/04-closures-module/article.md
Normal file
304
01-js/05-functions-closures/04-closures-module/article.md
Normal file
|
@ -0,0 +1,304 @@
|
|||
# Модули через замыкания
|
||||
|
||||
Приём программирования "модуль" имеет громадное количество вариаций.
|
||||
|
||||
Его цель -- скрыть внутренние детали реализации скрипта. В том числе: временные переменные, константы, вспомогательные мини-функции и т.п.
|
||||
|
||||
## Зачем нужен модуль?
|
||||
|
||||
Допустим, мы хотим разработать скрипт, который делает что-то полезное.
|
||||
|
||||
В браузере скрипты могут делать много чего -- если бы мы умели работать со страницей, то могли бы сделать так, чтобы все блоки кода красиво расцвечивались, так сделано на этом сайте.
|
||||
|
||||
Но, так как пока мы со страницей работать не умеем (скоро научимся), то пусть скрипт просто выводит сообщение:
|
||||
|
||||
Файл `highlight.js`
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// глобальная переменная нашего скрипта
|
||||
var message = "Привет";
|
||||
|
||||
// функция для вывода этой переменной
|
||||
function showMessage() {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
// выводим сообщение
|
||||
showMessage();
|
||||
```
|
||||
|
||||
**У этого скрипта есть свои внутренние переменные и функции.**
|
||||
|
||||
В данном случае это `message` и `showMessage`.
|
||||
|
||||
**Если подключить подобный скрипт к странице "как есть", то возможен конфликт с переменными, которые она использует.**
|
||||
|
||||
То есть, при подключении к такой странице он её "сломает":
|
||||
|
||||
```html
|
||||
<script>
|
||||
var message = "Пожалуйста, нажмите на кнопку";
|
||||
</script>
|
||||
<script src="highlight.js"></script>
|
||||
|
||||
<button>Кнопка</button>
|
||||
<script>
|
||||
alert(message);
|
||||
</script>
|
||||
```
|
||||
|
||||
Будет выведено два раза слово "Привет".
|
||||
|
||||
[edit src="highlight-conflict"/]
|
||||
|
||||
Если же убрать скрипт `highlight.js`, то страница будет выводить правильное сообщение.
|
||||
|
||||
**Проблема возникла потому, что переменная `message` из скрипта `highlight.js` перезаписала объявленную на странице.**
|
||||
|
||||
## Приём проектирования "Модуль"
|
||||
|
||||
Чтобы проблемы не было, нам всего-то нужно, чтобы у скрипта была *своя собственная область видимости*, чтобы его переменные не попали на страницу.
|
||||
|
||||
Для этого мы завернём всё его содержимое в функцию, которую тут же запустим.
|
||||
|
||||
Файл `highlight.js`, оформленный как модуль:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
(function() {
|
||||
|
||||
// глобальная переменная нашего скрипта
|
||||
var message = "Привет";
|
||||
|
||||
// функция для вывода этой переменной
|
||||
function showMessage() {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
// выводим сообщение
|
||||
showMessage();
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
Этот скрипт при подключении к той же странице будет работать корректно.
|
||||
|
||||
Будет выводиться "Привет", а затем "Пожалуйста, нажмите на кнопку".
|
||||
|
||||
[edit src="highlight-module"/]
|
||||
|
||||
### Зачем скобки вокруг функции?
|
||||
|
||||
В примере выше объявление модуля выглядит так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
(function() {
|
||||
|
||||
alert("объявляем локальные переменные, функции, работаем");
|
||||
// ...
|
||||
|
||||
}());
|
||||
```
|
||||
|
||||
**В начале и в конце стоят скобки, так как иначе была бы ошибка.**
|
||||
|
||||
Вот неверный вариант:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function() {
|
||||
// будет ошибка
|
||||
}();
|
||||
```
|
||||
|
||||
Ошибка при его запуске произойдет потому, что браузер, видя ключевое слово `function` в основном потоке кода, попытается прочитать `Function Declaration`, а здесь имени нет.
|
||||
|
||||
Впрочем, даже если имя поставить, то работать тоже не будет:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function work() {
|
||||
// ...
|
||||
}(); // syntax error
|
||||
```
|
||||
|
||||
**Дело в том, что "на месте" разрешено вызывать *только* `Function Expression`.**
|
||||
|
||||
Общее правило таково:
|
||||
|
||||
<ul>
|
||||
<li>**Если браузер видит `function` в основном потоке кода -- он считает, что это `Function Declaration`.**</li>
|
||||
<li>**Если же `function` идёт в составе более сложного выражения, то он считает, что это `Function Expression`.**</li>
|
||||
</ul>
|
||||
|
||||
Для этого и нужны скобки -- показать, что у нас `Function Expression`, который по правилам JavaScript можно вызвать "на месте".
|
||||
|
||||
Можно показать это другим способом, например поставив перед функцией оператор:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
+function() {
|
||||
alert('Вызов на месте');
|
||||
}();
|
||||
|
||||
!function() {
|
||||
alert('Так тоже будет работать');
|
||||
}();
|
||||
```
|
||||
|
||||
## Библиотека
|
||||
|
||||
**Приём "модуль" используется почти во всех современных библиотеках.**
|
||||
|
||||
Ведь что такое библиотека? Это полезные функции, ради которых её подключают, плюс временные переменные и вспомогательные функции, которые библиотека использует внутри себя.
|
||||
|
||||
Посмотрим, к примеру, на библиотеку [Lodash](http://lodash.com/), хотя могли бы и [jQuery](http://jquery.com/), там почти то же самое.
|
||||
|
||||
Если её подключить, то появится функция `lodash` (она же `_`), в которую можно обернуть любой объект, так что `lodash(obj)` -- это обёртка, добавляющая к объекту функциональность.
|
||||
|
||||
Кроме того, `lodash` имеет ряд полезных свойств-функций, например [lodash.defaults(object, source)](http://lodash.com/docs#defaults) для удобного добавления в объект `object` значений свойств "по умолчанию", описанных в `source`.
|
||||
|
||||
Выдержка из файла [lodash.js](https://github.com/lodash/lodash/blob/master/dist/lodash.js) для демонстрации того, как организована библиотека:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
;(function() {
|
||||
|
||||
*!*
|
||||
// порядок не важен, но сначала объявим то, что нужно только внутри библиотеки
|
||||
// version, objectTypes, assignDefaults
|
||||
*/!*
|
||||
var version = '2.4.1';
|
||||
|
||||
var objectTypes = {
|
||||
'function': true,
|
||||
'object': true
|
||||
};
|
||||
|
||||
function assignDefaults(objectValue, sourceValue) {
|
||||
return typeof objectValue == 'undefined' ? sourceValue : objectValue;
|
||||
}
|
||||
|
||||
*!*
|
||||
// а это функция, которая станет lodash.defaults
|
||||
*/!*
|
||||
function defaults(object) {
|
||||
if (!object || arguments.length < 2) {
|
||||
return object;
|
||||
}
|
||||
var args = slice(arguments);
|
||||
args.push(assignDefaults);
|
||||
return assign.apply(null, args);
|
||||
}
|
||||
|
||||
*!*
|
||||
// lodash - основная функция для библиотеки, единственное, что пойдёт наружу
|
||||
*/!*
|
||||
function lodash(value) {
|
||||
// ...
|
||||
}
|
||||
|
||||
*!*
|
||||
// присвоим ей defaults и другие функции, которые нужно вынести из модуля
|
||||
*/!*
|
||||
lodash.defaults = defaults;
|
||||
// lodash... = ...
|
||||
|
||||
*!*
|
||||
// root - это window в браузере
|
||||
// в других окружениях, где window нет, root = this
|
||||
*/!*
|
||||
var root = (objectTypes[typeof window] && window) || this;
|
||||
|
||||
root.lodash = lodash; // в браузере будет window.lodash = lodash
|
||||
|
||||
}.call(this)); // this = window в браузере, в Node.JS - по-другому
|
||||
```
|
||||
|
||||
**Внутри внешней функции:**
|
||||
<ol>
|
||||
<li>**Происходит что угодно, объявляются свои локальные переменные, функции.**</li>
|
||||
<li>**В `window` выносится то, что нужно снаружи.**</li>
|
||||
</ol>
|
||||
|
||||
Технически, мы могли бы вынести в `window` не только `lodash`, но и вообще все объекты и функции. На практике, обычно модуль -- это один объект, глобальную область во избежание конфликтов хранят максимально чистой.
|
||||
|
||||
[smart header="Зачем точка с запятой в начале?"]
|
||||
Если получится, что несколько JS-файлы объединены в один (и, скорее всего, сжаты минификатором, но это не важно), то без точки с запятой будет ошибка:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// a.js, в конце забыта точка с запятой!
|
||||
*!*
|
||||
var a = 5
|
||||
*/!*
|
||||
|
||||
// lib.js, библиотека
|
||||
(function() {
|
||||
// ...
|
||||
})();
|
||||
```
|
||||
|
||||
Ошибка при запуске будет потому, что JavaScript интерпретирует код как `var a = 5(function ...)`, то есть пытается вызвать число `5` как функцию.
|
||||
|
||||
Таковы правила языка, и поэтому рекомендуется явно ставить точку с запятой. В данном случае автор Lodash ставит `;` перед функцией, чтобы предупредить эту ошибку.
|
||||
[/smart]
|
||||
|
||||
Использование:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<p>Подключим библиотеку</p>
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js"></script>
|
||||
|
||||
<p>Используем <code>_.defaults()</code>.</p>
|
||||
<script>
|
||||
var user = { name: 'Вася' };
|
||||
|
||||
*!*
|
||||
_.defaults(user, { name: 'Не указано', employer: 'Не указан' });
|
||||
*/!*
|
||||
|
||||
alert(user.name); // Вася
|
||||
alert(user.employer); // Не указан
|
||||
</script>
|
||||
```
|
||||
|
||||
## Экспортирование через return
|
||||
|
||||
Можно оформить модуль и чуть по-другому, например передать значение через `return`:
|
||||
|
||||
```js
|
||||
var lodash = (function() {
|
||||
|
||||
var version;
|
||||
function assignDefaults() { ... }
|
||||
|
||||
return {
|
||||
defaults: function() { }
|
||||
}
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
Здесь, кстати, скобки вокруг внешней `function() { ... }` не обязательны, ведь функция и так объявлена внутри выражения присваивания, а значит -- является Function Expression.
|
||||
|
||||
Тем не менее, лучше их ставить, для улучшения читаемости кода, чтобы было сразу видно, что это не простое присвоение функции.
|
||||
|
||||
## Итого
|
||||
|
||||
Модуль при помощи замыканий -- это оборачивание пакета функционала в единую внешнюю функцию, которая тут же выполняется.
|
||||
|
||||
**Все функции модуля будут иметь доступ к другим переменным и внутренним функциям этого же модуля через замыкание.**
|
||||
|
||||
Например, `defaults` из примера выше имеет доступ к `assignDefaults`.
|
||||
|
||||
**Но снаружи программист, использующий модуль, может обращаться напрямую только к тем, которые экспортированы.**
|
||||
|
||||
Благодаря этому будут скрыты внутренние аспекты реализации, которые нужны только разработчику модуля.
|
||||
|
||||
Можно придумать и много других вариаций такого подхода. В конце концов, "модуль" -- это всего лишь функция-обёртка для скрытия переменных.
|
||||
|
||||
|
1
01-js/05-functions-closures/04-closures-module/highlight-conflict/.plnkr
Executable file
1
01-js/05-functions-closures/04-closures-module/highlight-conflict/.plnkr
Executable file
|
@ -0,0 +1 @@
|
|||
{"name":"highlight-conflict","plunk":"rAQGmfsmod5PhfFSgPuO"}
|
|
@ -0,0 +1,10 @@
|
|||
// глобальная переменная нашего скрипта
|
||||
var message = "Привет";
|
||||
|
||||
// функция для вывода этой переменной
|
||||
function showMessage() {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
// выводим сообщение
|
||||
showMessage();
|
20
01-js/05-functions-closures/04-closures-module/highlight-conflict/index.html
Executable file
20
01-js/05-functions-closures/04-closures-module/highlight-conflict/index.html
Executable file
|
@ -0,0 +1,20 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script>
|
||||
var message = 'Пожалуйста, нажмите на кнопку';
|
||||
</script>
|
||||
<script src="highlight.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<button>Кнопка</button>
|
||||
|
||||
<script>
|
||||
alert(message);
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
1
01-js/05-functions-closures/04-closures-module/highlight-module/.plnkr
Executable file
1
01-js/05-functions-closures/04-closures-module/highlight-module/.plnkr
Executable file
|
@ -0,0 +1 @@
|
|||
{"name":"highlight-module","plunk":"tvUQjnxPqbajGDr0Ft9y"}
|
14
01-js/05-functions-closures/04-closures-module/highlight-module/highlight.js
Executable file
14
01-js/05-functions-closures/04-closures-module/highlight-module/highlight.js
Executable file
|
@ -0,0 +1,14 @@
|
|||
(function() {
|
||||
|
||||
// глобальная переменная нашего скрипта
|
||||
var message = "Привет";
|
||||
|
||||
// функция для вывода этой переменной
|
||||
function showMessage() {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
// выводим сообщение
|
||||
showMessage();
|
||||
|
||||
})();
|
20
01-js/05-functions-closures/04-closures-module/highlight-module/index.html
Executable file
20
01-js/05-functions-closures/04-closures-module/highlight-module/index.html
Executable file
|
@ -0,0 +1,20 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script>
|
||||
var message = 'Пожалуйста, нажмите на кнопку';
|
||||
</script>
|
||||
<script src="highlight.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<button>Кнопка</button>
|
||||
|
||||
<script>
|
||||
alert(message);
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue