Перехватывать изменение массива с помощью proxy

Есть таблица в которую выводятся объекты из массива. Пользователь может добавлять, удалять, изменять, сортировать объекты в массиве. Как можно отслеживать изменение массива(по типу push,slice и т.д.) с помощью proxy? Нашел несколько решений, но не могу разобраться с моим случаем.

<body>

        <div class="main">

            <header>
                <button class="btn" id="seeButton">Просмотр таблицы</button>
                <button class="btn" id="addButton">Добавить запись</button>
                <button class="btn" id="deleteButton">Удалить запись</button>
                <button class="btn" id="updateButton">Обновить запись</button>
                <button class="btn" id="searcheButton">Поиск записей</button>
                <button class="btn" id="logButton">Просмотреть лог</button>

            </header>

            <div>

                <table class="table" id="table_id">
                    <thead>
                        <tr>
                            <th>№</th>
                            <th>Фамилия</th>
                            <th>Должность</th>
                            <th>Год рождения</th>
                            <th>Оклад(грн)</th>
                        </tr>
                    </thead>
                    <tbody></tbody>
                </table>


            </div>


        </div>  

        <script>
            var seeButton = document.getElementById("seeButton");
            var addButton = document.getElementById("addButton");
            var deleteButton = document.getElementById("deleteButton");
            var updateButton = document.getElementById("updateButton");
            var searcheButton = document.getElementById("searcheButton");
            var logButton = document.getElementById("logButton");
            var tbody = document.querySelector("#table_id tbody");

            let arr = []

            addButton.addEventListener('click', () => {
                let user = add();
            });


            function add() {
                let fam = prompt("введите фамилию", "");
                let dol = prompt("введите должность", "");
                let date = prompt("введите год рождения", "");
                let salary = +prompt("введите оклад", "");

                function User(fam, dol, date, salary) {
                this.fam = fam;
                this.dol = dol;
                this.date = date;
                this.salary = salary;
                }

                var user = new User(fam, dol, date, salary)
                arr.push(user)
                
                return user;    
            }


            
            arr = new Proxy(arr, {
            set(arr){
                if (arr.push, arr.slice, arr.sort, arr.unshift){
                    function addObj(arr){  
                        for(i=0; i<arr.length; i++){
                             arr[i] = /// user.fam = this.fam???
                             var tr = `<tr>
                             <td>${arr.length}</td><td>${user.fam}</td><td>${user.dol}</td><td>${user.date}</td><td>${user.salary}</td>
                             </td>`;
                
                             tbody.insertAdjacentHTML('beforeend', tr);        
                     }}
                }
            }
        });


        </script>
    </body>

Ответы (1 шт):

Автор решения: Grundy

При обращении к свойствам proxy-объекта вызывается хук get, которому в параметры передаются целевой объект, в данном случае массив с данными, имя получаемого свойства, и непосредственно объект proxy.

get: function(target, propName, proxy) {

Таким образом, для перехвата push, splice и т.д. методов достаточно добавить условие, проверяющее имя требуемого поля. Если ожидаемое свойство - можно вернуть свою собственную функцию, в противном случае возвращать значение напрямую из целевого объекта:

if (propName == 'push' || propName == ...) {
    return function(...args){ ... }
}
return target[propName];

Возвращаемая функция должна

  1. принимать параметры
  2. вызывать исходную функцию с переданными параметрами
  3. вызывать метод отрисовки
  4. возвращать результат исходной функции

Например:

function (...args) { // собираем все принимаемые параметры в массив args
    const res = target[propName](...args); // вызываем метод у целевого объекта, с переданными параметрами, результат сохраняем в `res`
    render(target); // вызываем метод отрисовки, передавая в качестве параметра обновленный массив
    return res; // возвращаем результат вызова целевого метода
}

Метод отрисовки может быть простым проходом по всему массиву, собиранием одной большой html строки и присваиванием ее в свойство innerHTML элемента tbody.

например:

tbody.innerHTML = users.map(renderRow).join('');

где renderRow по элементу из объекта получает соответствующую html строку.

Пример в сборе:

var addButton = document.getElementById("addButton");
var tbody = document.querySelector("#table_id tbody");

function render(users) {
  tbody.innerHTML = users.map(renderRow).join('');
}

function renderRow(user, index) {
  return (
    `<tr>
      <td>${index}</td>
      <td>${user.fam}</td>
      <td>${user.dol}</td>
      <td>${user.date}</td>
      <td>${user.salary}</td>
    </tr>`
  );
}

let arr = new Proxy([], {
  get: function(target, prop) {
    if (prop == 'push') {
      return function(...args) {
        const res = target[prop](...args);

        render(target);
        return res;
      }
    }
    return target[prop];
  }
});

addButton.addEventListener('click', () => {
  let user = add();
  arr.push(user);
});


function add() {
  /*
    let fam = prompt("введите фамилию", "");
    let dol = prompt("введите должность", "");
    let date = prompt("введите год рождения", "");
    let salary = +prompt("введите оклад", "");
  */

  function User(fam, dol, date, salary) {
    this.fam = fam;
    this.dol = dol;
    this.date = date;
    this.salary = salary;
  }

  // var user = new User(fam, dol, date, salary)
  var user = new User(1, 2, 3, 4)
  return user;
}
<div class="main">
  <header>
    <button class="btn" id="addButton">Добавить запись</button>
  </header>
  <div>
    <table class="table" id="table_id">
      <thead>
        <tr>
          <th>№</th>
          <th>Фамилия</th>
          <th>Должность</th>
          <th>Год рождения</th>
          <th>Оклад(грн)</th>
        </tr>
      </thead>
      <tbody></tbody>
    </table>
  </div>
</div>

→ Ссылка