AngularJS $parse getter (v2)

Revision 2 of this benchmark created by Igor Minar on


Setup

var cspSafeGetterFn = function(length, part1, part2, part3, part4, part5) {
      return function(scope, locals) {
        var pathVal = (locals && locals.hasOwnProperty(part1)) ? locals : scope,
            promise;
    
        if (!pathVal) return pathVal;
    
        pathVal = pathVal[part1];
        if (pathVal && pathVal.then) {
          if (!("$$v" in pathVal)) {
            promise = pathVal;
            promise.$$v = undefined;
            promise.then(function(val) { promise.$$v = val; });
          }
          pathVal = pathVal.$$v;
        }
        if (length === 1 || !pathVal) return pathVal;
    
        pathVal = pathVal[part2];
        if (pathVal && pathVal.then) {
          if (!("$$v" in pathVal)) {
            promise = pathVal;
            promise.$$v = undefined;
            promise.then(function(val) { promise.$$v = val; });
          }
          pathVal = pathVal.$$v;
        }
        if (length === 2 || !pathVal) return pathVal;
    
        pathVal = pathVal[part3];
        if (pathVal && pathVal.then) {
          if (!("$$v" in pathVal)) {
            promise = pathVal;
            promise.$$v = undefined;
            promise.then(function(val) { promise.$$v = val; });
          }
          pathVal = pathVal.$$v;
        }
        if (length === 3 || !pathVal) return pathVal;
    
        pathVal = pathVal[part4];
        if (pathVal && pathVal.then) {
          if (!("$$v" in pathVal)) {
            promise = pathVal;
            promise.$$v = undefined;
            promise.then(function(val) { promise.$$v = val; });
          }
          pathVal = pathVal.$$v;
        }
        if (length === 4 || !pathVal) return pathVal;
    
        pathVal = pathVal[part5];
        if (pathVal && pathVal.then) {
          if (!("$$v" in pathVal)) {
            promise = pathVal;
            promise.$$v = undefined;
            promise.then(function(val) { promise.$$v = val; });
          }
          pathVal = pathVal.$$v;
        }
        return pathVal;
      };
    };
    
    function getterFn(path, csp) {
      var pathKeys = path.split('.'),
          fn;
    
      if (csp) {
        if (csp === 2) {
        fn = (pathKeys.length < 6)
            ? cspSafeGetterFn.apply(undefined, [pathKeys.length].concat(pathKeys))
            : function(scope, locals) {
              var chunk, val;
              do {
                chunk = pathKeys.splice(0, 5);
                val = cspSafeGetterFn.apply(undefined, [chunk.length].concat(chunk))(scope, locals);
                locals = undefined; // clear after first iteration
              } while (pathKeys.length);
            }
         } else {
           fn = function(scope, locals) {
              var pathVal = (locals && locals.hasOwnProperty(pathKeys[0])) ? locals : scope,
                  promise, i, ii, key;
        
              for(i = 0, ii = pathKeys.length; i < ii; i++) {
                if (!pathVal) break;
                key = pathKeys[i];
                pathVal = pathVal[key];
        
                if (pathVal && pathVal.then) {
                  if (!("$$v" in pathVal)) {
                    promise = pathVal;
                    promise.$$v = undefined;
                    promise.then(function(val) { promise.$$v = val; });
                  }
                  pathVal = pathVal.$$v;
                }
              }
        
              return pathVal;
            }
         }
    
      } else {
        var code = 'var l, fn, p;\n';
        forEach(pathKeys, function(key, index) {
          code += 'if(!s) return s;\n' +
                  'l=s;\n' +
                  's='+ (index
                          // we simply dereference 's' on any .dot notation
                          ? 's'
                          // but if we are first then we check locals first, and if so read it first
                          : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
                  'if (s && s.then) {\n' +
                    ' if (!("$$v" in s)) {\n' +
                      ' p=s;\n' +
                      ' p.$$v = undefined;\n' +
                      ' p.then(function(v) {p.$$v=v;});\n' +
                      '}\n' +
                    ' s=s.$$v\n' +
                  '}\n';
        });
        code += 'return s;';
        fn = Function('s', 'k', code); // s=scope, k=locals
        fn.toString = function() { return code; };
      }
    
      return fn;
    }
    
    
    getterEvil = getterFn('a.b.c.d.e');
    getterGood = getterFn('a.b.c.d.e', 1);
    getterBetter = getterFn('a.b.c.d.e', 2);
    obj = {a: { b: { c: { d: { e: { f: 23 } } } } } };
    locals = {a: { b: { c: { d: { e: { f: 23 } } } } } };
    
    
    function forEach(array, iterator) {
      for(var i=0, ii=array.length; i < ii; i++) {
        iterator(array[i], i);
      }
    }

Test runner

Ready to run.

Testing in
TestOps/sec
Evil
getterEvil(obj);
getterEvil(obj, locals);
ready
Good
getterGood(obj);
getterGood(obj, locals);
ready
Better
getterBetter(obj);
getterBetter(obj, locals);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 1: published by Igor Minar on
  • Revision 2: published by Igor Minar on
  • Revision 3: published by Igor Minar on
  • Revision 4: published by Igor Minar on
  • Revision 6: published by Karl Seamon on