2016-12-17 54 views
1

我想創建一個只接受要存儲的特定實例類型的數組。看起來最好的解決方案是使用Proxies,受此啓發gistSO thread使用JS代理拒絕數組輸入

所以我有一個代理工作,併爲基本的數組功能,它按預期工作。 set屬性確保只有對象是水果的實例才能插入數組,否則會引發TypeError。唯一可以立即設置的其他財產是length

問題在於高級I/O,如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

數組沒有[[Call]]方法。你的'應用'陷阱是沒用的。 – 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

我確實考慮過這個選項,但後來開始想知道還有多少其他方法也需要重寫。 –