使对象对开发人员不可扩展,但可使用 ES5 在内部扩展

Make an object non-extensible to developer but extensible internally with ES5

提问人:Xotic750 提问时间:7/17/2013 最后编辑:Xotic750 更新时间:7/17/2013 访问量:1161

问:

我想使对象对开发人员/用户不可扩展,但仍然能够通过自己的方法向自身添加属性。我已经尝试了很多事情并做了相当多的阅读,但我似乎找不到任何解决方案,也许没有?new

这是我正在尝试/尝试做的一个例子。

/*jslint maxerr: 50, indent: 4, browser: true, white: true, devel: true */

(function () {
    "use strict";

    function isValid(property) {
        if (typeof property === "number") {
            property = property.toString();
        }

        return typeof property === "string" && (/^\d{1,10}$/).test(property) && property >= 0 && property <= 4294967294;
    }

    function Foo() {}

    Object.defineProperties(Foo.prototype, {
        put: {
            value: function (number, value) {
                if (isValid(number)) {
                    Object.defineProperty(this, number, {
                        configurable: true,
                        enumerable: true,
                        value: value
                    });
                }
            }
        },

        clear: {
            value: function () {
                var property;

                for (property in this) {
                    if (this.hasOwnProperty(property) && isValid(property)) {
                        delete this[property];
                    }
                }
            }
        }
    });

    function newFoo(object, name) {
        return Object.defineProperty(object, name, {
            configurable: true,
            value: new Foo()
        });
    }

    var bar = {};

    newFoo(bar, "fee");

    /* All of the following prevent the condition below, but there seems
     * no way to undo them once done
     */
    //Object.preventExtensions(bar.fee)
    //Object.seal(bar.fee);
    //Object.freeze(bar.fee)

    bar.fee.clear();
    bar.fee.put(0, true);
    bar.fee.put(10, true);
    bar.fee.put(100, true);
    bar.fee.put(1000, true);
    //bar.fee[1000] = true; // prevent this, OK
    bar.fee[10000] = true; // prevent this, not OK

    console.log({
        0: bar,
        1: Object.keys(bar.fee)
    });
}());

jsfiddle

更新:我希望添加的属性(索引)可枚举(就像数组一样的对象),以便您可以遍历它们。

进一步研究:因此,我采取了不暴露后备对象的想法:一个,(尽可能多地),并得到了以下结果。仍然不完全是我想要实现的目标,并且对那些标记的.Arraybad

/*jslint maxerr: 50, indent: 4, browser: true, white: true, devel: true */

(function (undef) {
    "use strict";

    var noop = function () {},
        bar,
        fum,
        neArray;

    function isValid(property) {
        if (typeof property === "number") {
            property = property.toString();
        }

        return typeof property === "string" && (/^\d{1,10}$/).test(property) && property >= 0 && property <= 4294967294;
    }

    function Foo() {
        Object.defineProperty(this, "data", {
            value: Object.preventExtensions([]) // tried seal and freeze
        });
    }

    Object.defineProperties(Foo.prototype, {
        length: {
            get: function () {
                return this.data.length;
            },

            set: noop
        },

        put: {
            value: function (number, value) {
                this.data[number] = value;
            }
        },

        item: {
            value: function (number) {
                return isValid(number) ? this.data[number] : undef;
            }
        },

        keys: {
            get: function () {
                return Object.keys(this.data);
            },

            set: noop
        },

        clear: {
            value: function () {
                this.data.length = 0;
            }
        }
    });

    ["forEach", "some", "every", "map", "filter", "reduce", "slice", "splice", "push", "pop", "shift", "unshift", "indexOf", "lastIndexOf", "valueOf", "toString", "hasOwnProperty"].forEach(function (element) {
        Object.defineProperty(Foo.prototype, element, {
            value: function () {
                return this.data[element].apply(this.data, arguments);
            }
        });
    });

    function newFoo() {
        return Object.preventExtensions(Object.defineProperty({}, "fee", {
            value: new Foo()
        }).fee);
    }

    bar = newFoo();
    fum = newFoo();

    bar.clear();
    bar.put(0, true);
    bar.put(10, true);
    //bar.put(10000, true); // bad
    bar.valueOf()[100] = false; // not so great
    bar.data[1000] = false; // not so great
    //bar.put("xxx", false); // prevent this, OK
    //bar.data["xxx"] = false; // prevent this, OK
    //bar[1000] = false; // prevent this, OK
    //bar[10000] = false; // prevent this, OK

    console.log({
        0: bar,
        1: Object.keys(bar.data), // not so great
        2: bar.keys,
        3: fum,
        4: bar.hasOwnProperty(0),
        5: bar.valueOf(),
        6: bar.toString(),
        7: bar instanceof Foo,
        8: bar.item("forEach") // prevent this, OK
    });

    bar.forEach(function (element, index, object) {
        console.log("loop", element, index, object);
    });

    neArray = Object.preventExtensions([]);

    //neArray[10000] = true; // bad
}());

On jsfiddle

And more: Here is the extent of my research using an as a backing store, phew, jumping through hoops to get something as reasonable as the and some things better, some worse.ObjectArray

/*jslint maxerr: 50, indent: 4, browser: true, white: true, devel: true */

(function (undef) {
    "use strict";

    var noop = function () {},
    bar,
    fum,
    neObject;

    function isValid(property) {
        if (typeof property === "number") {
            property = property.toString();
        }

        return typeof property === "string" && (/^\d{1,10}$/).test(property) && property >= 0 && property <= 4294967294;
    }

    function Foo() {
        var data = {
            length: 0
        };

        Object.defineProperty(data, "length", {
            enumerable: false
        });

        Object.defineProperty(this, "data", { // can't prevent extension on object
            value: data
        });
    }

    Object.defineProperties(Foo.prototype, {
        valueOf: {
            value: function () {
                return [].slice.call(this.data);  //OK, disable for large numbers
            }
        },

        toString: {
            value: function () {
                return this.valueOf().toString();  //OK, disable for large numbers
            }
        },

        length: {
            get: function () {
                return this.data.length;
            },

            set: noop
        },

        put: {
            value: function (number, value) {
                if (isValid(number)) {
                    this.data[number] = value;
                    Object.defineProperty(this.data, "length", {
                        writable: true
                    });

                    var newLength = number + 1;
                    if (newLength > this.data.length) {
                        this.data.length = number + 1;
                    }

                    Object.defineProperty(this.data, "length", {
                        writable: false
                    });
                }
            }
        },

        item: {
            value: function (number) {
                return isValid(number) ? this.data[number] : undef;
            }
        },

        keys: {
            get: function () {
                var length = this.data.length;

                return Object.keys(this.data).filter(function (property) {
                    return isValid(property) && property <= length;
                }).map(function (property) {
                    return +property;
                }); // not so good, hack to filter bad
            },

            set: noop
        },

        clear: {
            value: function () {
                var property;

                for (property in this.data) {
                    if (this.data.hasOwnProperty(property) && this.data.propertyIsEnumerable(property)) {
                        delete this.data[property];
                    }
                }

                Object.defineProperty(this.data, "length", {
                    writable: true
                });

                this.data.length = 0;
                Object.defineProperty(this.data, "length", {
                    writable: false
                });
            }
        }
    });

    ["forEach", "some", "every", "map", "filter", "reduce", "slice", "splice", "push", "pop", "shift", "unshift", "indexOf", "lastIndexOf", "hasOwnProperty"].forEach(function (element) {
        Object.defineProperty(Foo.prototype, element, {
            value: function () {
                return [][element].apply(this.data, arguments);
            }
        });
    });

    function newFoo() {
        return Object.preventExtensions(Object.defineProperty({}, "fee", {
            value: new Foo()
        }).fee);
    }

    bar = newFoo();
    fum = newFoo();

    bar.clear();
    bar.put(0, true);
    bar.put(10, true);
    //bar.put(4294967294, true); // OK, disabled because of processing
    bar.put(4294967295, true);
    //bar.valueOf()[100] = false; // prevent this, OK
    bar.data[1000] = false; // bad
    //bar.put("xxx", false); // prevent this, OK
    bar.data.xxx = false; // not so good
    Object.defineProperty(bar.data, "yyy", {
        value: false
    });

    //bar[1000] = false; // prevent this, OK
    //bar[10000] = false; // prevent this, OK
    //bar.clear(); // OKish, won't clear something set as innumerable through bad

    console.log({
        0: bar,
        1: Object.keys(bar.data), // not so good // disable for large numbers
        2: bar.keys, // OKish with hack
        3: fum,
        4: bar.hasOwnProperty(0),
        5: bar.valueOf(),
        6: bar.toString(),
        7: bar instanceof Foo,
        8: bar.item("forEach") // prevent this, OK
    });

    bar.forEach(function (element, index, object) {
        console.log("loop", element, index, object);
    });

    neObject = Object.preventExtensions({});

    //neObject[10000] = true; // bad
}());

jsfiddle

剩下唯一要做的就是将原型移动到它们各自的构造函数中,以便方法使用私有变量 for 作为后备对象,我相信这是@bfavaretto建议的,但随后我们失去了定义的品质:由于在 上创建方法,内存增加和构造时间增加dataprototypesnew

JavaScript的 对象 性能 不变性 可变

评论

1赞 bfavaretto 7/17/2013
我不认为有办法和朋友在一起。你可以做的是不向开发人员公开对象,只允许一些方法允许你想要允许的东西。Object.preventExtensions
0赞 dandavis 7/17/2013
您可以创建一个保持打开状态的阴影对象,同时提供具有一个隧道属性的公共冻结对象,该属性只能通过闭包(受保护)从构造函数内部访问。如果不使用 strict,也可以执行引用重载。
0赞 Xotic750 7/17/2013
谢谢,我希望有添加的属性(索引)(就像数组一样的对象),以便您可以遍历它们。我在问题中没有说清楚。不好意思。numerable
0赞 dandavis 7/17/2013
@Xotic750:expandos 是 ES5 中的一个蜜蜂,你必须对每个可能的索引进行 ODP,或者升级现有的原生对象,如 String 或 Array。您可以为内部数组提供受保护的 shell,因此内部方法仍可以使用索引,但外部接口仍需要 parens 进行交互。
0赞 Xotic750 7/17/2013
哎哟,ODPing 每个索引 (~4294967295 ) 似乎不可行。至少没有循环,除非有其他方法?Out of and ,由于它已经固有的不变性,它似乎才可能可行,但它是否真的有任何用处是我必须调查的事情。StringArrayString

答: 暂无答案