AngularJS copy with hasOwnProperty.call

Benchmark created by gkalpak on


Description

Compares Angular's copy() with 2 alternative implementations using Object.prototype.hasOwnProperty and Object.keys. (Includes all necessary Angular helpers and modified versions of cope() and forEach() (internally called by the former).)

Setup

var factory = new ObjectFactory();
    function ObjectFactory() {
      this.new = function () {
        return {
          k1: {k2: 'v2', k3: 3},
          k4: [{k5: 5.0, k6: true}, {k7: 7.0, k8: false}],
          k9: 'v9',
          k10: 10,
          k11: new Date(),
          k12: new RegExp('^12$')
        };
      };
    }
    
    //////////////////////////////////////////////////
    
    var NODE_TYPE_ELEMENT = 1;
    var isArray = Array.isArray;
    var hasOwnProperty = Object.prototype.hasOwnProperty;
    var toString = Object.prototype.toString;
    
    function isArrayLike(obj) {
      if (obj == null || isWindow(obj)) {
        return false;
      }
    
      var length = obj.length;
    
      if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
        return true;
      }
    
      return isString(obj) || isArray(obj) || length === 0 ||
             typeof length === 'number' && length > 0 && (length - 1) in obj;
    }
    
    function isDate(value) {
      return toString.call(value) === '[object Date]';
    }
    
    function isFunction(value) {
      return typeof value === 'function';
    }
    
    function isObject(value) {
      return value !== null && typeof value === 'object';
    }
    
    function isRegExp(value) {
      return toString.call(value) === '[object RegExp]';
    }
    
    function isScope(obj) {
      return obj && obj.$evalAsync && obj.$watch;
    }
    
    function isString(value) {
      return typeof value === 'string';
    }
    
    function isWindow(obj) {
      return obj && obj.window === obj;
    }
    
    function setHashKey(obj, h) {
      if (h) {
        obj.$$hashKey = h;
      }
      else {
        delete obj.$$hashKey;
      }
    }
    
    //////////////////////////////////////////////////
    
    function copy(source, destination, stackSource, stackDest) {
      if (isWindow(source) || isScope(source)) {
        throw ngMinErr('cpws',
          "Can't copy! Making copies of Window or Scope instances is not supported.");
      }
    
      if (!destination) {
        destination = source;
        if (source) {
          if (isArray(source)) {
            destination = copy(source, [], stackSource, stackDest);
          } else if (isDate(source)) {
            destination = new Date(source.getTime());
          } else if (isRegExp(source)) {
            destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
            destination.lastIndex = source.lastIndex;
          } else if (isObject(source)) {
            var emptyObject = Object.create(Object.getPrototypeOf(source));
            destination = copy(source, emptyObject, stackSource, stackDest);
          }
        }
      } else {
        if (source === destination) throw ngMinErr('cpi',
          "Can't copy! Source and destination are identical.");
    
        stackSource = stackSource || [];
        stackDest = stackDest || [];
    
        if (isObject(source)) {
          var index = stackSource.indexOf(source);
          if (index !== -1) return stackDest[index];
    
          stackSource.push(source);
          stackDest.push(destination);
        }
    
        var result;
        if (isArray(source)) {
          destination.length = 0;
          for (var i = 0; i < source.length; i++) {
            result = copy(source[i], null, stackSource, stackDest);
            if (isObject(source[i])) {
              stackSource.push(source[i]);
              stackDest.push(result);
            }
            destination.push(result);
          }
        } else {
          var h = destination.$$hashKey;
          if (isArray(destination)) {
            destination.length = 0;
          } else {
            forEach(destination, function(value, key) {
              delete destination[key];
            });
          }
          for (var key in source) {
            if (source.hasOwnProperty(key)) {
              result = copy(source[key], null, stackSource, stackDest);
              if (isObject(source[key])) {
                stackSource.push(source[key]);
                stackDest.push(result);
              }
              destination[key] = result;
            }
          }
          setHashKey(destination,h);
        }
    
      }
      return destination;
    }
    
    function forEach(obj, iterator, context) {
      var key, length;
      if (obj) {
        if (isFunction(obj)) {
          for (key in obj) {
            // Need to check if hasOwnProperty exists,
            // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
            if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
              iterator.call(context, obj[key], key, obj);
            }
          }
        } else if (isArray(obj) || isArrayLike(obj)) {
          var isPrimitive = typeof obj !== 'object';
          for (key = 0, length = obj.length; key < length; key++) {
            if (isPrimitive || key in obj) {
              iterator.call(context, obj[key], key, obj);
            }
          }
        } else if (obj.forEach && obj.forEach !== forEach) {
            obj.forEach(iterator, context, obj);
        } else {
          for (key in obj) {
            if (obj.hasOwnProperty(key)) {
              iterator.call(context, obj[key], key, obj);
            }
          }
        }
      }
      return obj;
    }
    
    //////////////////////////////////////////////////
    
    function copy2(source, destination, stackSource, stackDest) {
      if (isWindow(source) || isScope(source)) {
        throw ngMinErr('cpws',
          "Can't copy! Making copies of Window or Scope instances is not supported.");
      }
    
      if (!destination) {
        destination = source;
        if (source) {
          if (isArray(source)) {
            destination = copy(source, [], stackSource, stackDest);
          } else if (isDate(source)) {
            destination = new Date(source.getTime());
          } else if (isRegExp(source)) {
            destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
            destination.lastIndex = source.lastIndex;
          } else if (isObject(source)) {
            var emptyObject = Object.create(Object.getPrototypeOf(source));
            destination = copy(source, emptyObject, stackSource, stackDest);
          }
        }
      } else {
        if (source === destination) throw ngMinErr('cpi',
          "Can't copy! Source and destination are identical.");
    
        stackSource = stackSource || [];
        stackDest = stackDest || [];
    
        if (isObject(source)) {
          var index = stackSource.indexOf(source);
          if (index !== -1) return stackDest[index];
    
          stackSource.push(source);
          stackDest.push(destination);
        }
    
        var result;
        if (isArray(source)) {
          destination.length = 0;
          for (var i = 0; i < source.length; i++) {
            result = copy(source[i], null, stackSource, stackDest);
            if (isObject(source[i])) {
              stackSource.push(source[i]);
              stackDest.push(result);
            }
            destination.push(result);
          }
        } else {
          var h = destination.$$hashKey;
          if (isArray(destination)) {
            destination.length = 0;
          } else {
            forEach2(destination, function(value, key) {
              delete destination[key];
            });
          }
          for (var key in source) {
            if (hasOwnProperty.call(source, key)) {
              result = copy(source[key], null, stackSource, stackDest);
              if (isObject(source[key])) {
                stackSource.push(source[key]);
                stackDest.push(result);
              }
              destination[key] = result;
            }
          }
          setHashKey(destination,h);
        }
    
      }
      return destination;
    }
    
    function forEach2(obj, iterator, context) {
      var key, length;
      if (obj) {
        if (isFunction(obj)) {
          for (key in obj) {
            // Need to check if hasOwnProperty exists,
            // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
            if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
              iterator.call(context, obj[key], key, obj);
            }
          }
        } else if (isArray(obj) || isArrayLike(obj)) {
          var isPrimitive = typeof obj !== 'object';
          for (key = 0, length = obj.length; key < length; key++) {
            if (isPrimitive || key in obj) {
              iterator.call(context, obj[key], key, obj);
            }
          }
        } else if (obj.forEach && obj.forEach !== forEach) {
            obj.forEach(iterator, context, obj);
        } else {
          for (key in obj) {
            if (hasOwnProperty.call(obj, key)) {
              iterator.call(context, obj[key], key, obj);
            }
          }
        }
      }
      return obj;
    }
    
    //////////////////////////////////////////////////
    
    function copy3(source, destination, stackSource, stackDest) {
      if (isWindow(source) || isScope(source)) {
        throw ngMinErr('cpws',
          "Can't copy! Making copies of Window or Scope instances is not supported.");
      }
    
      if (!destination) {
        destination = source;
        if (source) {
          if (isArray(source)) {
            destination = copy(source, [], stackSource, stackDest);
          } else if (isDate(source)) {
            destination = new Date(source.getTime());
          } else if (isRegExp(source)) {
            destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
            destination.lastIndex = source.lastIndex;
          } else if (isObject(source)) {
            var emptyObject = Object.create(Object.getPrototypeOf(source));
            destination = copy(source, emptyObject, stackSource, stackDest);
          }
        }
      } else {
        if (source === destination) throw ngMinErr('cpi',
          "Can't copy! Source and destination are identical.");
    
        stackSource = stackSource || [];
        stackDest = stackDest || [];
    
        if (isObject(source)) {
          var index = stackSource.indexOf(source);
          if (index !== -1) return stackDest[index];
    
          stackSource.push(source);
          stackDest.push(destination);
        }
    
        var result;
        if (isArray(source)) {
          destination.length = 0;
          for (var i = 0; i < source.length; i++) {
            result = copy(source[i], null, stackSource, stackDest);
            if (isObject(source[i])) {
              stackSource.push(source[i]);
              stackDest.push(result);
            }
            destination.push(result);
          }
        } else {
          var h = destination.$$hashKey;
          if (isArray(destination)) {
            destination.length = 0;
          } else {
            forEach3(destination, function(value, key) {
              delete destination[key];
            });
          }
          Object.keys(source).forEach(function (key) {
            result = copy(source[key], null, stackSource, stackDest);
            if (isObject(source[key])) {
              stackSource.push(source[key]);
              stackDest.push(result);
            }
            destination[key] = result;
          });
          setHashKey(destination,h);
        }
    
      }
      return destination;
    }
    
    function forEach3(obj, iterator, context) {
      var key, length;
      if (obj) {
        if (isFunction(obj)) {
          for (key in obj) {
            // Need to check if hasOwnProperty exists,
            // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
            if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
              iterator.call(context, obj[key], key, obj);
            }
          }
        } else if (isArray(obj) || isArrayLike(obj)) {
          var isPrimitive = typeof obj !== 'object';
          for (key = 0, length = obj.length; key < length; key++) {
            if (isPrimitive || key in obj) {
              iterator.call(context, obj[key], key, obj);
            }
          }
        } else if (obj.forEach && obj.forEach !== forEach) {
            obj.forEach(iterator, context, obj);
        } else {
          Object.keys(obj).forEach(function (key) {
            iterator.call(context, obj[key], key, obj);
          });
        }
      }
      return obj;
    }

Test runner

Ready to run.

Testing in
TestOps/sec
Using instance.hasOwnProperty
copy(factory.new(), {});
ready
Using hasOwnProperty.call
copy2(factory.new(), {});
ready
Using Object.keys
copy3(factory.new(), {});
ready

Revisions

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

  • Revision 1: published by gkalpak on