Cloning: what's the fastest alternative to JSON.parse(JSON.stringify(x))?

前端 未结 3 1991
青春惊慌失措
青春惊慌失措 2021-01-30 13:21

What\'s the fastest alternative to

JSON.parse(JSON.stringify(x))

There must be a nicer/built-in way to perform a deep clone on objects/arrays,

3条回答
  •  北恋
    北恋 (楼主)
    2021-01-30 13:55

    Cyclic references are not really an issue. I mean they are but that's just a matter of proper record keeping. Anyway quick answer for this one. Check this:

    https://github.com/greatfoundry/json-fu

    In my mad scientist lab of crazy javascript hackery I've been putting the basic implementation to use in serializing the entirety of the javascript context including the entire DOM from Chromium, sending it over a websocket to Node and reserializing it successfully. The only cyclic issue that is problematic is the retardo navigator.mimeTypes and navigator.plugins circle jerking one another to infinity, but easily solved.

    (function(mimeTypes, plugins){
        delete navigator.mimeTypes;
        delete navigator.plugins;
        var theENTIREwindowANDdom = jsonfu.serialize(window);
        WebsocketForStealingEverything.send(theENTIREwindowANDdom);
        navigator.mimeTypes = mimeTypes;
        navigator.plugins = plugins;
    })(navigator.mimeTypes, navigator.plugins);
    

    JSONFu uses the tactic of creating Sigils which represent more complex data types. Like a MoreSigil which say that the item is abbreviated and there's X levels deeper which can be requested. It's important to understand that if you're serializing EVERYTHING then it's obviously more complicated to revive it back to its original state. I've been experimenting with various things to see what's possible, what's reasonable, and ultimately what's ideal. For me the goal is a bit more auspicious than most needs in that I'm trying to get as close to merging two disparate and simultaneous javascript contexts into a reasonable approximation of a single context. Or to determine what the best compromise is in terms of exposing the desired capabilities while not causing performance issues. When you start looking to have revivers for functions then you cross the land from data serialization into remote procedure calling.

    A neat hacky function I cooked up along the way classifies all the properties on an object you pass to it into specific categories. The purpose for creating it was to be able to pass a window object in Chrome and have it spit out the properties organized by what's required to serialize and then revive them in a remote context. Also to accomplish this without any sort of preset cheatsheet lists, like a completely dumb checker that makes the determinations by prodding the passed value with a stick. This was only designed and ever checked in Chrome and is very much not production code, but it's a cool specimen.

    // categorizeEverything takes any object and will sort its properties into high level categories
    // based on it's profile in terms of what it can in JavaScript land. It accomplishes this task with a bafflingly
    // small amount of actual code by being extraordinarily uncareful, forcing errors, and generally just
    // throwing caution to the wind. But it does a really good job (in the one browser I made it for, Chrome,
    // and mostly works in webkit, and could work in Firefox with a modicum of effort)
    //
    // This will work on any object but its primarily useful for sorting the shitstorm that
    // is the webkit global context into something sane.
    function categorizeEverything(container){
        var types = {
            // DOMPrototypes are functions that get angry when you dare call them because IDL is dumb.
            // There's a few DOM protos that actually have useful constructors and there currently is no check.
            // They all end up under Class which isn't a bad place for them depending on your goals.
            // [Audio, Image, Option] are the only actual HTML DOM prototypes that sneak by.
            DOMPrototypes: {},
            // Plain object isn't callable, Object is its [[proto]]
            PlainObjects: {},
            // Classes have a constructor
            Classes: {},
            // Methods don't have a "prototype" property and  their [[proto]]  is named "Empty"
            Methods: {},
            // Natives also have "Empty" as their [[proto]]. This list has the big boys:
            // the various Error constructors, Object, Array, Function, Date, Number, String, etc.
            Natives: {},
            // Primitives are instances of String, Number, and Boolean plus bonus friends null, undefined, NaN, Infinity
            Primitives: {}
        };
    
        var str = ({}).toString;
        function __class__(obj){ return str.call(obj).slice(8,-1); }
    
        Object.getOwnPropertyNames(container).forEach(function(prop){
            var XX = container[prop],
                xClass = __class__(XX);
            // dumping the various references to window up front and also undefineds for laziness
            if(xClass == "Undefined" || xClass == "global") return;
    
            // Easy way to rustle out primitives right off the bat,
            // forcing errors for fun and profit.
            try {
                Object.keys(XX);
            } catch(e) {
                if(e.type == "obj_ctor_property_non_object")
                    return types.Primitives[prop] = XX;
            }
    
            // I'm making a LOT flagrant assumptions here but process of elimination is key.
            var isCtor = "prototype" in XX;
            var proto = Object.getPrototypeOf(XX);
    
            // All Natives also fit the Class category, but they have a special place in our heart.
            if(isCtor && proto.name == "Empty" ||
               XX.name == "ArrayBuffer" ||
               XX.name == "DataView"    ||
               "BYTES_PER_ELEMENT" in XX) {
                    return types.Natives[prop] = XX;
            }
    
            if(xClass == "Function"){
                try {
                    // Calling every single function in the global context without a care in the world?
                    // There's no way this can end badly.
                    // TODO: do this nonsense in an iframe or something
                    XX();
                } catch(e){
                    // Magical functions which you can never call. That's useful.
                    if(e.message == "Illegal constructor"){
                        return types.DOMPrototypes[prop] = XX;
                    }
                }
    
                // By process of elimination only regular functions can still be hanging out
                if(!isCtor) {
                    return types.Methods[prop] = XX;
                }
            }
    
            // Only left with full fledged objects now. Invokability (constructor) splits this group in half
            return (isCtor ? types.Classes : types.PlainObjects)[prop] = XX;
    
            // JSON, Math, document, and other stuff gets classified as plain objects
            // but they all seem correct going by what their actual profiles and functionality
        });
        return types;
    };
    

提交回复
热议问题