ko.viewmodel vs ko.mapping vs knockout.wrap

JavaScript performance comparison

Revision 6 of this test case created

Info

Performance comparison between the knockout mapping plugins ko.viewmodel and ko.mapping and knockout.wrap

Preparation code

<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.3.5/knockout.mapping.js"></script>
<script>
// Knockout Fast Mapping v0.1
// License: MIT (http://www.opensource.org/licenses/mit-license.php)

(function (factory) {
    // Module systems magic dance.

    if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
        // CommonJS or Node: hard-coded dependency on "knockout"
        factory(require("knockout"), exports);
    } else if (typeof define === "function" && define["amd"]) {
        // AMD anonymous module with hard-coded dependency on "knockout"
        define(["knockout", "exports"], factory);
    } else {
        // <script> tag: use the global 'ko' object, attaching a 'wrap' property
        factory(ko, ko.wrap = {});
    }
}(function (ko, exports) {

    // this function mimics ko.mapping
    exports.fromJS = function(jsObject, computedFunctions)
    {
        reset();
    return wrap(jsObject, computedFunctions);
    }

    // this function unwraps the outer for assigning the result to an observable
    // see https://github.com/SteveSanderson/knockout/issues/517
    exports.updateFromJS = function(observable, jsObject, computedFunctions)
    {
        reset();
    return observable(ko.utils.unwrapObservable(wrap(jsObject, computedFunctions)));
    }

    exports.fromJSON = function (jsonString, computedFunctions) {
    var parsed = ko.utils.parseJson(jsonString);
    arguments[0] = parsed;
    return exports.fromJS.apply(this, computedFunctions);
    };

    exports.toJS = function (observable) {
    return unwrap(observable);
    }

    exports.toJSON = function (observable) {
    var plainJavaScriptObject = exports.toJS(observable);
    return ko.utils.stringifyJson(plainJavaScriptObject);
    };

    function typeOf(value) {
    var s = typeof value;
    if (s === 'object') {
            if (value) {
                if (value.constructor == Date)
                    s = 'date';
        else if (Object.prototype.toString.call(value) == '[object Array]')
                    s = 'array';
            } else {
        s = 'null';
            }
    }
    return s;
    }

    // unwrapping
    function unwrapObject(o)
    {
    var t = {};

    for (var k in o)
    {
        var v = o[k];

        if (ko.isComputed(v))
        continue;

        t[k] = unwrap(v);
    }

    return t;
    }

    function unwrapArray(a)
    {
    var r = [];

    if (!a || a.length == 0)
        return r;

    for (var i = 0, l = a.length; i < l; ++i)
        r.push(unwrap(a[i]));

    return r;
    }

    function unwrap(v)
    {
    var isObservable = ko.isObservable(v);

    if (isObservable)
    {
        var val = v();

        if (typeOf(val) == "array")
        {
        return unwrapArray(val);
        }
        else
        {
        return val;
        }
    }
    else
    {
        if (typeOf(v) == "array")
        {
        return unwrapArray(v);
        }
        else if (typeOf(v) == "object")
        {
        return unwrapObject(v);
        }
        else
        {
        return v;
        }
    }
    }

    function reset()
    {
        parents = [{obj: null, wrapped: null, lvl: ""}];
    }

    // wrapping

    function wrapObject(o, computedFunctions)
    {
        // check for infinite recursion
        for (var i = 0; i < parents.length; ++i) {
            if (parents[i].obj === o) {
                return parents[i].wrapped;
            }
        }

    var t = {};

    for (var k in o)
    {
        var v = o[k];

            parents.push({obj: o, wrapped: t, lvl: currentLvl() + "/" + k});

        t[k] = wrap(v, computedFunctions);

            parents.pop();
    }

    if (computedFunctions && computedFunctions[currentLvl()])
        t = computedFunctions[currentLvl()](t);

    return t;
    }

    function wrapArray(a, computedFunctions)
    {
    var r = ko.observableArray();

    if (!a || a.length == 0)
        return r;

    for (var i = 0, l = a.length; i < l; ++i)
        r.push(wrap(a[i], computedFunctions));

    return r;
    }

    // a stack, used for two purposes:
    //  - circular reference checking
    //  - computed functions
    var parents;

    function currentLvl()
    {
    return parents[parents.length-1].lvl;
    }

    function wrap(v, computedFunctions)
    {
    if (typeOf(v) == "array")
    {
        return wrapArray(v, computedFunctions);
    }
    else if (typeOf(v) == "object")
    {
        return wrapObject(v, computedFunctions);
    }
    else
    {
        var t = ko.observable();
        t(v);
        return t;
    }
    }
}));
</script>
<script src="http://coderenaissance.github.com/knockout.viewmodel/knockout.viewmodel.min.js"></script>
<script>
Benchmark.prototype.setup = function() {
    var numberOfArrayRecords = 100,
    viewmodel = null,
    model = {
        items:[]
    };
   
    for(var x = 0; x < numberOfArrayRecords; x++){
        model.items.push({
            string:"Test",
            number:4,
            anotherObject:{
               items:[{id:4, name:"Test"},{id:7, name:"Test2"}]
            }      
        });  
           
    }
};
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
ko.mapping
viewmodel = ko.mapping.fromJS(model);
pending…
ko.viewmodel
viewmodel = ko.viewmodel.fromModel(model);
pending…
knockout.wrap
viewmodel = ko.wrap.fromJS(model);
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment