简体   繁体   中英

JavaScript: Prevent Array.push()

I have a sealed object with an array member on which I want to prevent direct pushes.

var myModule = (function () {
    "use strict";
    var a = (function () {
        var _b = {},
            _c = _c = "",
            _d = [];
        Object.defineProperty(_b, "c", {
            get: function () { return _c; }
        });
        Object.defineProperty(_b, "d", {
            get { return _d; }
        });
        _b.addD = function (newD) {
            _d.push(newD);
        };
        Object.seal(_b);
        return _b;
    }());
    var _something = { B: _b };
    return {
        Something: _something,
        AddD: _b.addD
    };
}());

myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // pushed = sadness

How can I prevent the push?

UPDATE:

Thanks for all the thoughts. I eventually need the JSON to send to the server. It looks like I might need to use an object for the array then figure out a way to generate and return the JSON needed, or change _something to use .slice(). Will play and report.

you could override the push method:

var _d = [];
_d.__proto__.push = function() { return this.length; }

and when you need to use it in your module, call Array.prototype.push :

_b.addD = function (newD) {
    Array.prototype.push.call(_d, newD);
};

I haven't done any performance tests on this, but this certainly helps to protect your array.

(function(undefined) {
    var protectedArrays = [];
    protectArray = function protectArray(arr) {
        protectedArrays.push(arr);
        return getPrivateUpdater(arr);
    }
    var isProtected = function(arr) {
        return protectedArrays.indexOf(arr)>-1;
    }
    var getPrivateUpdater = function(arr) {
        var ret = {};
        Object.keys(funcBackups).forEach(function(funcName) {
            ret[funcName] = funcBackups[funcName].bind(arr);
        });
        return ret;
    }

    var returnsNewArray = ['Array.prototype.splice'];
    var returnsOriginalArray = ['Array.prototype.fill','Array.prototype.reverse','Array.prototype.copyWithin','Array.prototype.sort'];
    var returnsLength = ['Array.prototype.push','Array.prototype.unshift'];
    var returnsValue = ['Array.prototype.shift','Array.prototype.pop'];

    var funcBackups = {};
    overwriteFuncs(returnsNewArray, function() { return []; });
    overwriteFuncs(returnsOriginalArray, function() { return this; });
    overwriteFuncs(returnsLength, function() { return this.length; });
    overwriteFuncs(returnsValue, function() { return undefined; });

    function overwriteFuncs(funcs, ret) {
        for(var i=0,c=funcs.length;i<c;i++)
        {
            var func = funcs[i];
            var funcParts = func.split('.');
            var obj = window;
            for(var j=0,l=funcParts.length;j<l;j++)
            {
                (function() {
                    var part = funcParts[j];
                    if(j!=l-1) obj = obj[part];
                    else if(typeof obj[part] === "function")
                    {
                        var funcBk = obj[part];
                        funcBackups[funcBk.name] = funcBk;
                        obj[part] = renameFunction(funcBk.name, function() {
                            if(isProtected(this)) return ret.apply(this, arguments);
                            else return funcBk.apply(this,arguments);
                        });
                    }
                })();
            }
        }
    }
    function renameFunction(name, fn) {
        return (new Function("return function (call) { return function " + name +
            " () { return call(this, arguments) }; };")())(Function.apply.bind(fn));
    };
})();

You would use it like so:

var myArr = [];
var myArrInterface = protectArray(myArr);
myArr.push(5); //Doesn't work, but returns length as expected
myArrInterface.push(5); //Works as normal

This way, you can internally keep a copy of the interface that isn't made global to allow your helper funcs to modify the array as normal, but any attempt to use .push .splice etc will fail, either directly, or using the .bind(myArr,arg) method.

It's still not completely watertight, but a pretty good protector. You could potentially use the Object.defineProperty method to generate protected properties for the first 900 indexes, but I'm not sure of the implications of this. There is also the method Object.preventExtensions() but I'm unaware of a way to undo this effect when you need to change it yourself

Thank you, dandavis !

I used the slice method:

var myModule = (function () {
    "use strict";
    var a = (function () {
        var _b = {},
            _c = _c = "",
            _d = [];
        Object.defineProperty(_b, "c", {
            get: function () { return _c; }
        });
        Object.defineProperty(_b, "d", {
            get { return _d.slice(); } // UPDATED
        });
        _b.updateC = function (newValue) {
            _c = newValue;
        };
        _b.addD = function (newD) {
            _d.push(newD);
        };
        Object.seal(_b);
        return _b;
    }());
    var _something = { B: _b };
    return {
        Something: _something,
        AddD: _b.addD
    };
}());

myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // no more update = happiness

This allows me to protect from direct push calls enforcing some logic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM