Отказ от ввода массива с помощью JS Proxy

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

Итак, у меня работает прокси, и для базовых функций массива он работает как положено. set свойство гарантирует, что только объекты, которые являются экземпляром Fruit, могут быть вставлены в Array, иначе выдается 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);

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');
    }
  }
});
Другие вопросы по тегам