# JavaScript по БЭМ. Основные понятия

_Статья подготовлена по материалам выступления [Владимира Варанкина](https://github.com/narqo) [БЭМ и JavaScript: Зачем мы написали JS-фреймворк?](https://events.yandex.ru/events/yasubbotnik/msk-sep-2012/talks/323/) на Я.Субботнике в Москве 8 сентября 2012._

В стеке БЭМ-технологий есть [блок i-bem](https://ru.bem.info/technology/i-bem/current/i-bem-js/) библиотеки [bem-core](https://ru.bem.info/libs/bem-core), который также называют блоком-хелпером. Его JavaScript-реализация использует предметную область БЭМ и позволяет работать по всем БЭМ-принципам не только с внешним видом компонент, но и с их поведением.

## Зачем нужен еще один фреймворк?

Разработчики старой школы ещё помнят времена, когда не было даже jQuery и все приходилось делать самостоятельно. В каждом проекте был свой `common.js`-файл, который включал в себя набор вспомогательных функций. Его копировали из проекта в проект, а потом выносили в свою маленькую JavaScript библиотеку.

Так, эволюционно появлялись JavaScript-фреймворки.

Похожие этапы преодолел и БЭМ в создании своего фреймворка. Сначала понятие блоков (интерфейсных модулей), их элементов и модификаторов существовало только для CSS. Затем разработчики захотели работать с такой же структурой в JavaScript, и использовать ее ключевое понятие — `уровни переопределения` — чтобы дополнять и расширять поведение блоков от проекта к проекту.

Так появилась JavaScript реализация блока-хелпера [i-bem.js](https://ru.bem.info/technology/i-bem/current/i-bem-js/). Он и стал фреймворком (или блоком-ядром) для того, чтобы писать JavaScript в терминах БЭМ.

## Ассоциация с HTML-кодом

Как и любой JavaScript-компонент, код, написанный под `i-bem.js` должен быть проассоциирован с HTML-фрагментом, который он намерен превратить в функционирующую часть интерфейса. В БЭМ для этого достаточно добавить блоку CSS-класс `i-bem` и указать в атрибуте `data-bem` параметры блока.

```js
<div lass="myblock i-bem" data-bem="{ myblock: { }}">
    <span class="myblock__item"></span>
</div>
```

## Декларация поведения

В JavaScript-файле блока (`myblock.js`) описывается его поведение.

С точки зрения объектной модели все одинаковые блоки образуют класс. При этом каждое появление блока на странице рождает экземпляр этого класса.

Чтобы декларировать новый JS-блок с DOM-представлением (привязанный к HTML-элементу), необходимо доопределить [ymaps](https://ru.bem.info/tools/bem/modules/)-модуль `i-bem__dom`.

Для описания поведения используется метод `decl`, принимающий следующие параметры:

 1. Блок, о котором пойдёт речь.
 2. Собственные свойства экземпляра блока.
 3. Статические свойства класса, к которому принадлежит блок.

```js
modules.define('myblock', ['i-bem__dom'], function(provide, BEMDOM) {

  provide(BEMDOM.decl(this.name, {

        /\* собственные свойства экземпляра \*/

    }, {

        /\* статические свойства \*/

    }));

});
```

В JavaScript ссылку на экзепляр всегда можно получить по ключевому слову `this` и использовать его зарезервированные поля `__self` и `__base`.

 * `this.__self`<br/>
  Ссылается на статические методы класса, к которому принадлежит экземпляр.
 * `this.__base`<br/>
  Делает super call, то есть вызывает базовую реализацию метода.

Последнее позволяет использовать уровни переопределения. При расширении функциональности уже существующего блока, разработчик всегда имеет доступ к поведению, определённому предыдущим уровнем. То есть методы можно не только полностью перезаписывать, но и «обрамлять» дополнительным поведением.

```js
modules.define('myblock', ['i-bem__dom'], function(provide, BEMDOM) {

    provide(BEMDOM.decl('myblock', {

        method: function() {

            this.__base();
            this.doMore();

        }

    }));
});
```

Кроме наследования по уровням переопределения есть ещё возможность явно отнаследовать один блок от другого. Поэтому возможность наследования от уровня к уровню лучше воспринимать как соединение (merge) реализаций.

## Селекторы блоков

Для поиска других блоков можно воспользоваться одним из `find*` методов. Выбор метода зависит от того, где находится желаемый блок относительно текущего:

```js
// поиск внутри контекста
this.findBlockInside([elem], block)

// поиск снаружи контекста
this.findBlockOutside([elem], block)

// поиск на DOM-узле текущего блока
this.findBlockOn([elem], block)
```

Все эти методы возвращают JavaScript-объект, экземпляр найденного блока.

Похожим образом можно найти и коллекции блоков:

```js
// поиск внутри контекста
this.findBlocksInside([elem], block)

// поиск снаружи контекста
this.findBlocksOutside([elem], block)

// поиск на BEM-узле текущего блока
this.findBlocksOn([elem], block)
```

## Элементы

Аналогично, есть методы для доступа к элементам блока: `elem` и `findElem`. Метод `elem` кэширует свой результат при первом обращении. Это его основное отличие от метода `findElem`. То есть можно не сохранять вызов в переменную, чтобы сэкономить на поиске — всё уже сделано в реализации этого метода.

```js
//кэширующий селектор
this.elem(name,
    [modName], [modVal])

//некэширующий
this.findElem([ctx], name,
    [modName], [modVal])
```

## Модификаторы

Модификаторы в JavaScript служат для выражения состояния блока или элемента.

Методы работы с модификаторами одинаковы и для блоков, и для элементов. Но первый (опциональный) параметр показывает, о чём идёт речь.

```js
// значение модификатора блока
this.getMod(modName)

// значение модификатора элемента
this.getMod(elem, modName)

// проверка модификатора
this.hasMod([elem], modName, modVal)

// установка модификатора
this.setMod([elem], modName, modVal)

// удаление модификатора
this.delMod([elem], modName)

// переключение значений модификатора
this.toggleMod([elem], modName,
    modVal1, modVal2, [condition])
```

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

Для такого описания используется поле `onSetMod` из собственных свойств блока.

```js
modules.define('myblock', ['i-bem__dom'], function(provide, BEMDOM) {

    provide(BEMDOM.decl('myblock', {
        onSetMod : {
            'mod1' : {

                // установка модификатора mod1 в val1
                'val1' : function(mod, val, oldVal) {
            },
            // установка модификатора`mod2` в любое значение
            'mod2' : function(mod, val, oldVal) {
            }
        }
    }));
});
```

Похожая декларация есть и для модификаторов элементов:

```js
modules.define('myblock', ['i-bem__dom'], function(provide, BEMDOM) {

provide(BEMDOM.decl('myblock', {
    // …

    onElemSetMod : {

        // структура, аналогичная блоку
        'elem' : {
            'mod1' : {

                // дополнительный параметр `elem`
                'val1' : function(elem, mod, val, oldVal) {
                }

            }
        }
    }

}));

});
```

## События

События играют ключевую роль в JavaScript. Поэтому и при реализации блока `i-bem` их не обошли вниманием. Специальные методы позволяют работать с событиями как на DOM-узлах, соответствующих блокам, так и на BEM-объектах (JavaScript-объектах, представляющих экземпляры блоков).

```js
// DOM-события
this
    .bindTo([elem], event, fn)
    .unbindFrom([elem], event)

// BEM-события
this
    .on(event, [data], fn, [ctx])
    .un(event, [data], fn, [ctx])
    .trigger(event, [data])
```

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

BEM-события — это кастомные события, необходимые для возможности организовать API блоков.

## Инициализация

Работа блока начинается с его инициализации. В этот момент у блока появляется модификатор `js_inited`.

Аналогично другим модификаторам, на его установку можно реагировать исполнением задекларированного кода. То есть, существует возможность написать «конструктор».

```js
onSetMod : {

    'js' : {

        'inited' : function(){

            // «конструктор» блока

        }
    }
}
```

Кроме описанного, блок `i-bem` предоставляет для других блоков возможности отложенной (ленивой) инициализации, позволяет писать блоки без DOM-представления, использует идею делегирования событий и многое другое.

Об этом можно прочесть на [странице блока i-bem](https://ru.bem.info/technology/i-bem/current/i-bem-js/).
