Отклонение входных данных массива с помощью JS Proxy

Я хочу создать массив, который принимает для хранения только определенный тип экземпляра. Кажется, лучшим решением было использовать прокси, вдохновленный этим сутью и SO thread.

Итак, у меня работает прокси, и для основных функций массива он работает, как и ожидалось. Свойство set гарантирует, что только объекты, являющиеся экземпляром Fruit, могут быть вставлены в массив, иначе выдается ошибка TypeError. единственное другое свойство, которое можно установить прямо сейчас, — это length.

Проблема связана с расширенным вводом-выводом, таким как splice(). Регистрация функции set показывает, что элементы массива смещаются, чтобы освободить место для нового элемента, который будет вставлен в [0], но когда новый элемент отклоняется, он оставляет массив в беспорядке.

Поскольку set вызывается итеративно, я не вижу четкого способа предотвратить запуск соединения или восстановить массив до его былой славы (предпочтительно прежний вариант) в прокси. Кто-нибудь еще знает, как реализовать любую из этих идей, или есть другое предложение?

"use strict";

class Fruit {
  constructor(name) {
    this._name = name;
  }

  set name(name) {
    this._name = name;
  }

  get name() {
    return this._name;
  }
}

class Vegetable {
  constructor(name) {
    this.name(name);
  }

  set name(name) {
    this._name = name;
  }

  get name() {
    return this._name;
  }
}

// a proxy for our array
var fruitbowl = new Proxy([], {
  apply: function(target, thisArg, argumentsList) {
    return thisArg[target].apply(this, argumentList);
  },
  deleteProperty: function(target, property) {
    console.log("Deleted %s", property);
    return true;
  },
  set: function(target, property, value, receiver) {
    // UNCOMMENT HERE for useful output:
    // console.log("Setting " + property + " to ", value);
    if (property == "length") {
      target.length = value;
      return true;
    } else {
      if (value instanceof Fruit) {
        target[property] = value;
        return true;
      } else {
        return false;
        // throw TypeError("Expected Fruit, got " + typeof(value) + " (" + value + ")");
      }
    }
  }
});

console.log("\n\n=== Putting fruit into the bowl... ===\n\n");

try {
  fruitbowl.push(new Vegetable("potato"));
} catch (e) {
  console.log("Shoudln't allow vegetables: PASSED");
}

fruitbowl.push(new Fruit("apple"));
console.log("Should allow fruit: " + (fruitbowl.length == 1 ? "PASSED" : "FAILED"));

fruitbowl[0] = new Fruit("orange");
console.log("Should replace item specified as long as it's a Fruit: " + (fruitbowl.length == 1 && fruitbowl[0].name == "orange" ? "PASSED" : "FAILED"));

try {
  fruitbowl[0] = "Bananas!!1one";
} catch (e) {

}
console.log("Should not replace with a string: " + (fruitbowl.length == 1 && fruitbowl[0].name == "orange" ? "PASSED" : "FAILED"));

fruitbowl.push(new Fruit("banana"), new Fruit("pear"));
console.log("Should have 3 items [orange, banana, pear]: " + (fruitbowl.length == 3 ? "PASSED" : "FAILED"), fruitbowl);

console.log("\n\n === Cropping the bowl... ===\n\n");

fruitbowl.length = 2;
console.log("Should have 2 items [orange,banana]: " + (fruitbowl.length == 2 ? "PASSED" : "FAILED"));
console.log("Should error at item 2: " + (!fruitbowl[2] ? "PASSED" : "FAILED"), fruitbowl);

console.log("\n\n === Splicing the bowl... ===\n\n");

console.log(fruitbowl.length);

try {
  console.log(fruitbowl.length);
  fruitbowl.splice(0, 0, "pineapples!!1one");
  console.log(fruitbowl.length);
} catch (e) {
  console.log("Shouldn't have inserted string: PASSED");
}
console.log("Should still only have 2 fruit: " + (fruitbowl.length == 2 ? "PASSED" : "FAILED (" + fruitbowl.length + ")"));
console.log(fruitbowl);


person Hussein Duvigneau    schedule 17.12.2016    source источник
comment
У массивов нет метода [[Call]]. Ваша apply ловушка бесполезна.   -  person Oriol    schedule 17.12.2016
comment
Я думаю, вам будет лучше, если вы расширите прототип Array, переопределив все методы этого нового прототипа, которые могут видоизменять массив.   -  person trincot    schedule 17.12.2016
comment
@trincot Это была моя первая идея, которая работала очень хорошо, пока дело не дошло до перегрузки стенографии [].   -  person Hussein Duvigneau    schedule 17.12.2016
comment
Я не думаю, что вы захотите изменить поведение [], так как ваш код (или сторонний код) может привести к неожиданным результатам, когда вы захотите использовать другие, обычные массивы. Ваш новый прототип может определить свой собственный метод '.from()`, и MyArray.from([]) вернет пустой массив вашего прототипа.   -  person trincot    schedule 17.12.2016


Ответы (1)


Насколько мне известно, единственный способ добиться этого — переопределить функцию splice(). Вы должны проверить, все ли элементы являются Fruit объектами, и если нет, выдать ошибку. Если все они являются Fruit объектами, вы должны вызвать исходную функцию.

Reflect.defineProperty(fruitbowl, 'splice', {
  configurable: true,
  enumerable: false,
  value: function(start, deleteCount, ...items) {
    if (items.every(item => item instanceof Fruit)) {
      return Reflect.apply(Array.prototype.splice, this, [start, deleteCount, ...items]);
    } else {
      throw new Error('All elements must be Fruit objects');
    }
  }
});
person Michał Perłakowski    schedule 17.12.2016
comment
Что, если кто-то сделает [].splice.call(fruitbowl, 0, 1)? - person trincot; 17.12.2016
comment
@trincot Тогда это не выдаст ошибку. Я не думаю, что для этого есть какое-то хорошее решение, потому что нет способа определить, какой метод был вызван. - person Michał Perłakowski; 17.12.2016
comment
Я рассматривал этот вариант, но потом начал задаваться вопросом, сколько других методов мне также придется переопределить. - person Hussein Duvigneau; 17.12.2016