AngularJS $parse getter (v4)

Revision 4 of this benchmark created by Igor Minar on


Setup

function cspSafeGetterFn(key0, key1, key2, key3, key4) {
    return function(scope, locals) {
      var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
          promise;
  
      if (!pathVal) return pathVal;
  
      pathVal = pathVal[key0];
      if (pathVal && pathVal.then) {
        if (!("$$v" in pathVal)) {
          promise = pathVal;
          promise.$$v = undefined;
          promise.then(function(val) { promise.$$v = val; });
        }
        pathVal = pathVal.$$v;
      }
      if (!key1 || !pathVal) return pathVal;
  
      pathVal = pathVal[key1];
      if (pathVal && pathVal.then) {
        if (!("$$v" in pathVal)) {
          promise = pathVal;
          promise.$$v = undefined;
          promise.then(function(val) { promise.$$v = val; });
        }
        pathVal = pathVal.$$v;
      }
      if (!key2 || !pathVal) return pathVal;
  
      pathVal = pathVal[key2];
      if (pathVal && pathVal.then) {
        if (!("$$v" in pathVal)) {
          promise = pathVal;
          promise.$$v = undefined;
          promise.then(function(val) { promise.$$v = val; });
        }
        pathVal = pathVal.$$v;
      }
      if (!key3 || !pathVal) return pathVal;
  
      pathVal = pathVal[key3];
      if (pathVal && pathVal.then) {
        if (!("$$v" in pathVal)) {
          promise = pathVal;
          promise.$$v = undefined;
          promise.then(function(val) { promise.$$v = val; });
        }
        pathVal = pathVal.$$v;
      }
      if (!key4 || !pathVal) return pathVal;
  
      pathVal = pathVal[key4];
      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('.'),
        pathKeysLength = pathKeys.length,
        fn;
  
    if (csp) {
      if (csp === 2) {
        fn = (pathKeysLength < 6)
          ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4])
          : function(scope, locals) {
            var i = 0, val;
            do {
              val = cspSafeGetterFn(
                      pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++]
                    )(scope, locals);
              locals = undefined; // clear after first iteration
            } while (i < pathKeysLength);
          };
       } 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;
  }
  
  
  evilGetter = getterFn('a.b.c.d.e');
  goodGetter = getterFn('a.b.c.d.e', 1);
  blackHoleGetter = getterFn('a.b.c.d.e', 2);
  completeObj = {a: { b: { c: { d: { e: { f: 23 } } } } } };
  locals = {a: { b: { c: { d: { e: { f: 23 } } } } } };
  incompleteObj = {a: { b: {} } };
  
  
  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
evilGetter(completeObj);
evilGetter(completeObj, locals);
evilGetter(incompleteObj);
ready
Good
goodGetter(completeObj);
goodGetter(completeObj, locals);
goodGetter(incompleteObj);
ready
Black Hole
blackHoleGetter(completeObj);
blackHoleGetter(completeObj, locals);
blackHoleGetter(incompleteObj);
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