Arr.js: события для стандартного массива

    Arr.js — это «класс», унаследованный от стандартного Array. Отличительными особенностями являются: наличие события change для отслеживания любых изменений в массиве, и методы insert(), update(), remove(), set(), get() для упрощенной работы с массивом. Доступны все «родные» методы стандартного Array.

    var fruits = new Arr('apple', 'orange', 'pineapple');
    
    fruits.on('change', function(event) {
      alert('I changed fruits: ' + fruits.join(', '));
    });
    
    fruits.push('banana');
    


    Код: Примеры работы основных методов
    var fruits = new Arr('apple', 'orange', 'pineapple');
    
    fruits.get(0);
    // apple
    
    fruits.get(10, 'lime'); // trying to get undefined element - return defaultValue
    // lime
    
    fruits.get(20); // trying to get undefined element
    // null
    
    fruits.set(1, 'nut');
    // ['nut', 'orange', 'pineapple']
    
    fruits.insert(['lime', 'banana', 'kivi']);
    // ['nut', 'orange', 'pineapple', 'lime', 'banana', 'kivi']
    
    fruits.remove(function(item, index) {
      if (item.indexOf('apple') !== -1) { // remove only items where word "apple" is founded
        return true;
      }
    });
    // ['nut', 'orange', 'lime', 'banana', 'kivi']
    
    fruits.update(function(item, index) {
      if (item.indexOf('nut') !== -1) { // update "nut" to "apple"
        return 'apple';
      }
    });
    // ['apple', 'orange', 'lime', 'banana', 'kivi']
    



    Зачем событие change и как с ними работать


    Наличие события позволяет сделать:
    • подобие FRP: когда изменение одних данных должно повлечь за собой изменение других данных и так далее
    • отложенный рендеринг: что то изменилось в массиве — обновили HTML (ala angular)
    • автоматическое сохранение данных на сервер при любых изменениях

    Поддерживается одно событие — change.

    var fruits = new Arr('apple', 'orange', 'pineapple');
    
    fruits.on('change', function(event) { // handler
      console.log(event);
    });
    
    fruits.push('banana');
    // { "type": "insert", "items": ["banana"] }
    
    fruits.remove(function(item) { return item == 'banana'; });
    // { "type": "remove", "items": ['banana"] }
    

    Понять что произошло в массиве можно по передаваемому в handler объекту, event. Свойства объекта event: type может принимать значения: insert, update, remove. Свойство items позволяет узнать какие элементы массива были затронуты.

    Наглядный пример


    // Массив в котором планируем хранить данные о погоде
    var weatherList = new Arr;
    
    // При изменении в массиве - перересовываем список
    weatherList.on('change', function() {
      var el = $('#weather');
      var celsius, maxCelsius, minCelsius, weather;
    
      el.html('');
    
      weatherList.forEach(function(item) {
        celsius = Math.floor(item.main.temp - 273);
        maxCelsius = Math.floor(item.main.temp_max - 273);
        minCelsius = Math.floor(item.main.temp_min - 273);
        weather = item.weather.pop().main;
        el.append('<li><b>' + item.name + '</b> ' + ' ' + celsius + ' (max: ' + maxCelsius + ', min: ' + minCelsius + ') ' + weather + '</li>');
      });
    });
    
    // Загрузка погоды из сервиса, обновление массива weatherList
    function loadWeather(lat, lng) {
      $.get('http://api.openweathermap.org/data/2.5/find?lat=' + lat + '&lon=' + lng + '&cnt=10').done(function(data) {
        // clear weather list
        weatherList.remove(function() { return true; });
    
        // insert items
        weatherList.insert(data.list);
      });
    }
    
    // Погода в Киеве
    loadWeather(50.4666537, 30.5844519);
    

    Посмотреть рабочий пример на JSBin.

    В заключении


    Хочу добавить, что идея не нова, есть github://MatthewMueller/array. Но код мне показался слишком перегруженным, что собственно может вылиться в проблемы с производительностью. Поэтому было принято решение «расширить» стандартный Array.

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

    Расширять список методов пока не планируется, за исключением метода removeListener().

    Репозиторий Arr.js и документация (en).

    Комментарии по улучшению приветствуются!

    P.S.: В личных целях был разработан компонент https://github.com/jmas/list/blob/master/List.js который использует Arr.js. Компонент используется для создания списков, которые самостоятельно обновляют HTML при изменении массива данных. Компонент использует componentjs для разруливания зависимостей.
    Поделиться публикацией

    Похожие публикации

    Комментарии 25

      0
      Полезное расширение. Есть маленькое замечание, следующий код избыточен (если defaultValue undefined, то зачем его устанавливать в undefined). Это будет иметь смысл, только если новое значение не будет undefined. Сделать пул-реквест?

      defaultValue = typeof defaultValue === 'undefined' ? undefined: defaultValue;
        0
        Давайте. Просто делал хотфикс.
        +2
        Массивы слишком простой тип данных чтобы прикручивать к нему события.
          0
          И соглашусь, и нет. К примеру, коллекции в Backbone.Js решают подобные задачи, но код раздут.
            0
            Ок, вы можете использовать традиционные массивы и Arr совершенно параллельно, или создание «классов» с интерфейсом похожим на массив запрещено?
              0
              Хозяин, как говорится, барин.
              Пару моментов:
              1. events лучше сделать через объект, а не массив. Во-первых вы не будете бегать по всему массиву евентов при проброске события.
              Во вторых, намного проще будет делать отписку, которой сейчас нету.

              2. Сейчас у объекта есть лишь одно событие, и в каждом передается тип.
              Использование такой конструкции приведет к switch или if else, для разделения событий. Проще сразу бросать событие с нужным именем и обрабатывать его без проверок.

              Вот неплохая реализация работы с событиями github.com/Wolfy87/EventEmitter.
                0
                Отличные замечания. По 1-му: уже в планах. По 2-му посоветуйте как лучше, мне кажется можно выбрасывать change и тут же insert / remove / update.
                  0
                  Если вы таки будете улучшать обработку событий, то сделайте поддержку handleEvent, чтобы можно было избежать использования функции Function#bind
            0
            Работает по типу Object.observe?
            0
            Arr.js — это «класс», унаследованный от стандартного Array
            Только криво (тест), а надо было вот так.
              0
              Да, да. Конструктор инициализируется не совсем корректно. Исправим. Спасибо.
                0
                Ну и если редактировать массив при помощи `length` или менять значения по индексу, никаких событий не будет. Или например `splice` генерирует только `remove`, хотя сам метод не только удаляет.
                  0
                  Да. И при обычном a[0] = value событий так же не будет. Используйте set(), get(). Удаление splice(), remove().

                  Нужно доработать splice(). Спасибо.
                    0
                    splice() теперь умеет генерировать change, update, remove (https://github.com/jmas/arr/issues/5).
                    0
                    Учтите, что этот вариант не работает в IE меньше 11го, т.к. в этих IE не поддерживается __proto__
                      0
                      Кстати, целью было поддерживать библиотеку и в IE8+, поэтому для начала попробую корректно применить конструктор Array, в противном случае прийдется написать «похожее» поведение.
                        0
                        Решением для IE может быть копирование всех методов из прототипа в экземпляр в конструкторе CustomArray и в методах «concat», «reverse», «slice», «splice», «sort», «filter», «map». Из минусов можно выделить то, что не будет работать оператор instanceof.
                          0
                          instanceof критически важен. Спасибо за совет, нужно пробовать.
                    0
                      0
                      Конструктор теперь проходит тест. Правда логика конструктора != Array.prototype.constructor, что пока выглядит несколько странно. Буду иметь ввиду и подбирать более удачное решение.
                      +2
                      // trying to get undefined element
                      // null
                        –1
                        Уже поправлено. Традиционный undefined возвращает get().
                        –1
                        Считаю, что проще использовать такую конструкцию, где нужно:

                        var fruits = new Arr('apple', 'orange', 'pineapple');

                        function refresh(massive) {
                        // изменение других данных и так далее
                        // отложенный рендеринг
                        // автоматическое сохранение данных на сервер
                        }

                        fruits.push('banana');
                        refresh(fruits);

                        Т.к. в данном случае не нужна постоянно сидящая в памяти «прослушка».
                          0
                          Пример #1: компонент List для пере-рендеринга представления должен знать только о: данных, шаблоне, контейнере (куда вывыодить данные) и когда обновляться. Поэтому я посчитал логичным: инициализировать компонент этими минимально-необходимыми данными, дать интерфейс через который я смогу проще общаться с компонентом (только добавление / обновление / удаление) — проще говоря почти обычный массив. Интервейс работы с более чем одним компонентом теперь черезвычайно прост — добавилось-изменилось-удалилось: прошло обновление во всех компонентах-слушателях.

                          Пример #2: компонент ServerSync. Использует тот же интерфейс. Выполняет совершенно другие задачи.

                          В противном случае, необходимо знать какие методы (у каждого компонента) выведены наружу, например List.render(massive), ServerSync.update(massive) создают необходимость каждый раз подсматривать в реализацию (документацию).

                          Но такой подход — это всегда дело вкуса.

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

                        Самое читаемое