AngularJS $parse getter (v6)

Revision 6 of this benchmark created by Karl Seamon 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;
  }
  
  //////////////////////////
  // Updated code
  //////////////////////////
  function cspSafeGetterFn2(key0, key1, key2, key3, key4, fullExp, options) {
    ensureSafeMemberName(key0, fullExp);
    ensureSafeMemberName(key1, fullExp);
    ensureSafeMemberName(key2, fullExp);
    ensureSafeMemberName(key3, fullExp);
    ensureSafeMemberName(key4, fullExp);
  
    return !options.unwrapPromises
        ? function cspSafeGetter(scope, locals) {
            var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
  
            if (pathVal == null) return pathVal;
            pathVal = pathVal[key0];
  
            if (!key1 || pathVal == null) return pathVal;
            pathVal = pathVal[key1];
  
            if (!key2 || pathVal == null) return pathVal;
            pathVal = pathVal[key2];
  
            if (!key3 || pathVal == null) return pathVal;
            pathVal = pathVal[key3];
  
            if (!key4 || pathVal == null) return pathVal;
            pathVal = pathVal[key4];
  
            return pathVal;
          }
        : function cspSafePromiseEnabledGetter(scope, locals) {
            var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
                promise;
  
            if (pathVal == null) return pathVal;
  
            pathVal = pathVal[key0];
            if (pathVal && pathVal.then) {
              promiseWarning(fullExp);
              if (!("$$v" in pathVal)) {
                promise = pathVal;
                promise.$$v = undefined;
                promise.then(function(val) { promise.$$v = val; });
              }
              pathVal = pathVal.$$v;
            }
            if (!key1 || pathVal == null) return pathVal;
  
            pathVal = pathVal[key1];
            if (pathVal && pathVal.then) {
              promiseWarning(fullExp);
              if (!("$$v" in pathVal)) {
                promise = pathVal;
                promise.$$v = undefined;
                promise.then(function(val) { promise.$$v = val; });
              }
              pathVal = pathVal.$$v;
            }
            if (!key2 || pathVal == null) return pathVal;
  
            pathVal = pathVal[key2];
            if (pathVal && pathVal.then) {
              promiseWarning(fullExp);
              if (!("$$v" in pathVal)) {
                promise = pathVal;
                promise.$$v = undefined;
                promise.then(function(val) { promise.$$v = val; });
              }
              pathVal = pathVal.$$v;
            }
            if (!key3 || pathVal == null) return pathVal;
  
            pathVal = pathVal[key3];
            if (pathVal && pathVal.then) {
              promiseWarning(fullExp);
              if (!("$$v" in pathVal)) {
                promise = pathVal;
                promise.$$v = undefined;
                promise.then(function(val) { promise.$$v = val; });
              }
              pathVal = pathVal.$$v;
            }
            if (!key4 || pathVal == null) return pathVal;
  
            pathVal = pathVal[key4];
            if (pathVal && pathVal.then) {
              promiseWarning(fullExp);
              if (!("$$v" in pathVal)) {
                promise = pathVal;
                promise.$$v = undefined;
                promise.then(function(val) { promise.$$v = val; });
              }
              pathVal = pathVal.$$v;
            }
            return pathVal;
          };
  }
  
  function simpleGetterFn1(key0) {
  
    return function simpleGetterFn1(scope, locals) {
      var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
  
      return pathVal == null ? pathVal : pathVal[key0];
    };
  }
  
  function simpleGetterFn2(key0, key1) {
  
  
    return function simpleGetterFn2(scope, locals) {
      var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
  
      if (pathVal == null) return pathVal;
      pathVal = pathVal[key0];
  
      return pathVal == null ? pathVal : pathVal[key1];
    };
  }
  
  function simpleGetterFn3(key0, key1, key2) {
    return function simpleGetterFn3(scope, locals) {
      var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
  
      try {
        return pathVal[key0][key1][key2];
      } catch (e) {
        return null;
      }
    };
  }
  
  function simpleGetterFn4(key0, key1, key2, key3, fullExp) {
    ensureSafeMemberName(key0, fullExp);
    ensureSafeMemberName(key1, fullExp);
    ensureSafeMemberName(key2, fullExp);
    ensureSafeMemberName(key3, fullExp);
  
    return function simpleGetterFn4(scope, locals) {
      var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
  
      if (pathVal == null) return pathVal;
      pathVal = pathVal[key0];
  
      if (pathVal == null) return pathVal;
      pathVal = pathVal[key1];
  
      if (pathVal == null) return pathVal;
      pathVal = pathVal[key2];
  
      return pathVal == null ? pathVal : pathVal[key3];
    };
  }
  
  function getterFn2(path, options, fullExp) {
  
  
    var pathKeys = path.split('.'),
        pathKeysLength = pathKeys.length,
        fn;
  
        // For short 
  
    if (!options.noNew && pathKeysLength === 1) {
      fn = simpleGetterFn1(pathKeys[0], fullExp);
    } else if (!options.noNew && pathKeysLength === 2) {
      fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp);
    } else if (!options.noNew && pathKeysLength === 3) {
      fn = simpleGetterFn3(pathKeys[0], pathKeys[1], pathKeys[2], fullExp);
    } else if (!options.noNew && pathKeysLength === 4) {
      fn = simpleGetterFn4(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], fullExp);
    } else if (!options.noNew && pathKeysLength < 6) {
      fn = cspSafeGetterFn2(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp,
                           options);
    } else if (options.csp) {
      if (pathKeysLength < 6) {
        fn = cspSafeGetterFn2(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp,
                            options);
      } else {
        fn = function(scope, locals) {
          var i = 0, val;
          do {
            val = cspSafeGetterFn2(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++],
                                  pathKeys[i++], fullExp, options)(scope, locals);
  
            locals = undefined; // clear after first iteration
            scope = val;
          } while (i < pathKeysLength);
          return val;
        };
      }
    } else {
      var code = options.unwrapPromises ? 'var p;\n' : '';
          forEach(pathKeys, function(key, index) {
            ensureSafeMemberName(key, fullExp);
            code += 'if(s == null) return 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' +
                    (options.unwrapPromises
                      ? 'if (s && s.then) {\n' +
                        ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\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;';
          //var d = performance.now();
          /* jshint -W054 */
           var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning
          // window.t += performance.now() - d; window.c++;
          /* jshint +W054 */
           evaledFnGetter.toString = valueFn(code);
          //var evaledFnGetter = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, options);
          fn = options.unwrapPromises ? function(scope, locals) {
            return evaledFnGetter(scope, locals, promiseWarning);
          } : evaledFnGetter;
    }
  
    // Only cache the value if it's not going to mess up the cache object
    // This is more performant that using Object.prototype.hasOwnProperty.call
    
    return fn;
  }
  
  evilGetter = getterFn2('a.b.c.d.e', {noNew: 1});
  cspGetter = getterFn2('a.b.c.d.e', {noNew: 1, csp: 1});
  newHoleGetter = getterFn2('a.b.c.d.e', {});
  commonGetter1e = getterFn2('a', {noNew: 1});
  commonGetter1c = getterFn2('a', {noNew: 1, csp: 1});
  commonGetter1n = getterFn2('a', {});
  commonGetter2e = getterFn2('a.b', {noNew: 1});
  commonGetter2c = getterFn2('a.b', {noNew: 1, csp: 1});
  commonGetter2n = getterFn2('a.b', {});
  commonGetter3e = getterFn2('a.b.c', {noNew: 1});
  commonGetter3c = getterFn2('a.b.c', {noNew: 1, csp: 1});
  commonGetter3n = getterFn2('a.b.c', {});
  commonGetter4e = getterFn2('a.b.c.d', {noNew: 1});
  commonGetter4c = getterFn2('a.b.c.d', {noNew: 1, csp: 1});
  commonGetter4n = getterFn2('a.b.c.d', {});
  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);
    }
  }
  
  function ensureSafeMemberName() {}
  
  function valueFn(v) {return function() {return v;};}

Test runner

Ready to run.

Testing in
TestOps/sec
Evil
evilGetter(completeObj);
evilGetter(completeObj, locals);
evilGetter(incompleteObj);
ready
CSP
cspGetter(completeObj);
cspGetter(completeObj, locals);
cspGetter(incompleteObj);
ready
New
newHoleGetter(completeObj);
newHoleGetter(completeObj, locals);
newHoleGetter(incompleteObj);
ready
Evil 1 arg
commonGetter1e(completeObj);
commonGetter1e(completeObj, locals);
commonGetter1e(incompleteObj);
ready
CSP 1 arg
commonGetter1c(completeObj);
commonGetter1c(completeObj, locals);
commonGetter1c(incompleteObj);
ready
New 1 arg
commonGetter1n(completeObj);
commonGetter1n(completeObj, locals);
commonGetter1n(incompleteObj);
ready
Evil 2 arg
commonGetter2e(completeObj);
commonGetter2e(completeObj, locals);
commonGetter2e(incompleteObj);
ready
New 2 arg
commonGetter2n(completeObj);
commonGetter2n(completeObj, locals);
commonGetter2n(incompleteObj);
ready
Evil 3 arg
commonGetter3e(completeObj);
commonGetter3e(completeObj, locals);
commonGetter3e(incompleteObj);
ready
New 3 arg
commonGetter3n(completeObj);
commonGetter3n(completeObj, locals);
commonGetter3n(incompleteObj);
ready
Evil 4 arg
commonGetter4e(completeObj);
commonGetter4e(completeObj, locals);
commonGetter4e(incompleteObj);
ready
New 4 arg
commonGetter4n(completeObj);
commonGetter4n(completeObj, locals);
commonGetter4n(incompleteObj);
ready
Create evil
getterFn2('a.b', {noNew: 1});
ready
Create new
getterFn2('a.b', {});
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