ko.viewmodel vs ko.mapping vs knockout.wrap

JavaScript performance comparison

Revision 6 of this test case created by

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

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

Test runner

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

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
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.

0 Comments