2016-12-17 48 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

我确实考虑过这个选项,但后来开始想知道还有多少其他方法也需要重写。 –