使一个对象不可扩展到开发人员,但可以在内部使用ES5进行扩展

时间:2013-07-16 17:32:34

标签: javascript object properties immutability mutable

我想这样做,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

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

进一步的研究:所以我采取了不暴露支持对象的想法:Array,(尽我所能),得到以下结果。仍然不完全是我想要达到的目标,并且对标有bad的那些人感到惊讶。

/*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
}());

jsfiddle

以及更多:以下是我使用Object作为支持商店的研究范围,phew,跳过篮球以获得与Array一样合理的内容有些事情更好,有些情况更糟。

/*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

唯一要做的就是将原型移动到各自的构造函数中,以便方法使用data的私有变量作为后备对象,这是我认为@bfavaretto所建议的,但是我们松散定义prototypes的品质:由于在new

上创建方法而增加了记忆并增加了施工时间

0 个答案:

没有答案