2016-12-17 2 views
1

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

Итак, у меня есть прокси-сервер, и для работы с базовым массивом он работает должным образом. Свойство 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);

+1

Массивы не имеют метода [[Вызов]]. Ваша ловушка 'apply' бесполезна. – Oriol

+0

Я думаю, вам будет лучше расширять прототип 'Array', переопределяя все методы этого нового прототипа, которые могут мутировать массив. – trincot

+0

@ trincot Это была моя первая идея, которая работала очень хорошо, пока не дошло до перегрузки [] стенографии. –

ответ

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'); 
    } 
    } 
}); 
+1

Что делать, если кто-то делает '[] .splice.call (fruitbowl, 0, 1)'? – trincot

+0

@trincot Тогда он не будет выкидывать ошибку. Я не думаю, что для этого есть хорошее решение, потому что нет способа определить, какой метод был вызван. –

+0

Я рассмотрел эту опцию, но затем начал задаваться вопросом, сколько других методов мне также придется переопределить. –

Смежные вопросы