diff options
| author | Vojta Jina | 2012-03-28 16:03:59 -0700 | 
|---|---|---|
| committer | Igor Minar | 2012-03-29 07:30:32 -0700 | 
| commit | 6da355c3e1571b9a104fca2796df77a4194a28a2 (patch) | |
| tree | 8599843420c713b0a835e02311065fa984dcb1ee | |
| parent | f2106692b1ebf00aa5f8b2accd75f014b6cd4faa (diff) | |
| download | angular.js-6da355c3e1571b9a104fca2796df77a4194a28a2.tar.bz2 | |
refactor($compile): move methods of attr object into prototype
We have many instances of this object and we clone them as well (e.g. ng-repeat).
This should save some memory and performance as well.
Double prefixed private properties of attr object:
attr.$element -> attr.$$element
attr.$observers -> attr.$$observers
Update shallowCopy to not copy $$ properties and allow passing optional destination object.
| -rw-r--r-- | src/Angular.js | 12 | ||||
| -rw-r--r-- | src/ng/compiler.js | 160 | ||||
| -rw-r--r-- | src/ng/directive/booleanAttrDirs.js | 4 | ||||
| -rw-r--r-- | src/ng/directive/input.js | 2 | ||||
| -rw-r--r-- | test/AngularSpec.js | 9 | ||||
| -rw-r--r-- | test/ng/compilerSpec.js | 6 | 
6 files changed, 102 insertions, 91 deletions
diff --git a/src/Angular.js b/src/Angular.js index 0f66d99c..c0da2174 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -598,16 +598,16 @@ function copy(source, destination){  /**   * Create a shallow copy of an object - * @param src   */ -function shallowCopy(src) { -  var dst = {}, -      key; -  for(key in src) { -    if (src.hasOwnProperty(key)) { +function shallowCopy(src, dst) { +  dst = dst || {}; + +  for(var key in src) { +    if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {        dst[key] = src[key];      }    } +    return dst;  } diff --git a/src/ng/compiler.js b/src/ng/compiler.js index 2e8de121..4a95eefb 100644 --- a/src/ng/compiler.js +++ b/src/ng/compiler.js @@ -214,6 +214,79 @@ function $CompileProvider($provide) {        }      }; +    var Attributes = function(element, attr) { +      this.$$element = element; +      this.$$observers = {}; +      this.$attr = attr || {}; +    }; + +    Attributes.prototype = { +      $normalize: directiveNormalize, + + +      /** +       * Set a normalized attribute on the element in a way such that all directives +       * can share the attribute. This function properly handles boolean attributes. +       * @param {string} key Normalized key. (ie ngAttribute) +       * @param {string|boolean} value The value to set. If `null` attribute will be deleted. +       * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. +       *     Defaults to true. +       * @param {string=} attrName Optional none normalized name. Defaults to key. +       */ +      $set: function(key, value, writeAttr, attrName) { +        var booleanKey = isBooleanAttr(this.$$element[0], key.toLowerCase()); + +        if (booleanKey) { +          this.$$element.prop(key, value); +          attrName = booleanKey; +        } + +        this[key] = value; + +        // translate normalized key to actual key +        if (attrName) { +          this.$attr[key] = attrName; +        } else { +          attrName = this.$attr[key]; +          if (!attrName) { +            this.$attr[key] = attrName = snake_case(key, '-'); +          } +        } + +        if (writeAttr !== false) { +          if (value === null || value === undefined) { +            this.$$element.removeAttr(attrName); +          } else { +            this.$$element.attr(attrName, value); +          } +        } + +        // fire observers +        forEach(this.$$observers[key], function(fn) { +          try { +            fn(value); +          } catch (e) { +            $exceptionHandler(e); +          } +        }); +      }, + + +      /** +       * Observe an interpolated attribute. +       * The observer will never be called, if given attribute is not interpolated. +       * +       * @param {string} key Normalized key. (ie ngAttribute) . +       * @param {function(*)} fn Function that will be called whenever the attribute value changes. +       */ +      $observe: function(key, fn) { +        // keep only observers for interpolated attrs +        if (this.$$observers[key]) { +          this.$$observers[key].push(fn); +        } +      } +    }; +      return compile;      //================================ @@ -278,13 +351,8 @@ function $CompileProvider($provide) {           directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;       for(var i = 0, ii = nodeList.length; i < ii; i++) { -       attrs = { -         $attr: {}, -         $normalize: directiveNormalize, -         $set: attrSetter, -         $observe: interpolatedAttrObserve, -         $observers: {} -       }; +       attrs = new Attributes(); +         // we must always refer to nodeList[i] since the nodes can be replaced underneath us.         directives = collectDirectives(nodeList[i], [], attrs, maxPriority); @@ -441,7 +509,7 @@ function $CompileProvider($provide) {            newIsolatedScopeDirective = null,            templateDirective = null,            delayedLinkingFn = null, -          element = templateAttrs.$element = jqLite(templateNode), +          element = templateAttrs.$$element = jqLite(templateNode),            directive,            directiveName,            template, @@ -485,7 +553,7 @@ function $CompileProvider($provide) {            terminalPriority = directive.priority;            if (directiveValue == 'element') {              template = jqLite(templateNode); -            templateNode = (element = templateAttrs.$element = jqLite( +            templateNode = (element = templateAttrs.$$element = jqLite(                  '<!-- ' + directiveName + ': ' + templateAttrs[directiveName]  + ' -->'))[0];              replaceWith(rootElement, jqLite(template[0]), templateNode);              childTranscludeFn = compile(template, transcludeFn, terminalPriority); @@ -609,11 +677,9 @@ function $CompileProvider($provide) {          if (templateNode === linkNode) {            attrs = templateAttrs;          } else { -          attrs = shallowCopy(templateAttrs); -          attrs.$element = jqLite(linkNode); -          attrs.$observers = {}; +          attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));          } -        element = attrs.$element; +        element = attrs.$$element;          if (newScopeDirective && isObject(newScopeDirective.scope)) {            forEach(newScopeDirective.scope, function(mode, name) { @@ -720,7 +786,7 @@ function $CompileProvider($provide) {      function mergeTemplateAttributes(dst, src) {        var srcAttr = src.$attr,            dstAttr = dst.$attr, -          element = dst.$element; +          element = dst.$$element;        // reapply the old attributes to the new element        forEach(dst, function(value, key) {          if (key.charAt(0) != '$') { @@ -873,7 +939,7 @@ function $CompileProvider($provide) {            // we define observers array only for interpolated attrs            // and ignore observers for non interpolated attrs to save some memory -          attr.$observers[name] = []; +          attr.$$observers[name] = [];            attr[name] = undefined;            scope.$watch(interpolateFn, function(value) {              attr.$set(name, value); @@ -910,70 +976,6 @@ function $CompileProvider($provide) {        }        element[0] = newNode;      } - - -    /** -     * Set a normalized attribute on the element in a way such that all directives -     * can share the attribute. This function properly handles boolean attributes. -     * @param {string} key Normalized key. (ie ngAttribute) -     * @param {string|boolean} value The value to set. If `null` attribute will be deleted. -     * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. -     *     Defaults to true. -     * @param {string=} attrName Optional none normalized name. Defaults to key. -     */ -    function attrSetter(key, value, writeAttr, attrName) { -      var booleanKey = isBooleanAttr(this.$element[0], key.toLowerCase()); - -      if (booleanKey) { -        this.$element.prop(key, value); -        attrName = booleanKey; -      } - -      this[key] = value; - -      // translate normalized key to actual key -      if (attrName) { -        this.$attr[key] = attrName; -      } else { -        attrName = this.$attr[key]; -        if (!attrName) { -          this.$attr[key] = attrName = snake_case(key, '-'); -        } -      } - -      if (writeAttr !== false) { -        if (value === null || value === undefined) { -          this.$element.removeAttr(attrName); -        } else { -          this.$element.attr(attrName, value); -        } -      } - - -      // fire observers -      forEach(this.$observers[key], function(fn) { -        try { -          fn(value); -        } catch (e) { -          $exceptionHandler(e); -        } -      }); -    } - - -    /** -     * Observe an interpolated attribute. -     * The observer will never be called, if given attribute is not interpolated. -     * -     * @param {string} key Normalized key. (ie ngAttribute) . -     * @param {function(*)} fn Function that will be called whenever the attribute value changes. -     */ -    function interpolatedAttrObserve(key, fn) { -      // keep only observers for interpolated attrs -      if (this.$observers[key]) { -        this.$observers[key].push(fn); -      } -    }    }];  } diff --git a/src/ng/directive/booleanAttrDirs.js b/src/ng/directive/booleanAttrDirs.js index f79c02ce..f0182610 100644 --- a/src/ng/directive/booleanAttrDirs.js +++ b/src/ng/directive/booleanAttrDirs.js @@ -286,7 +286,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) {        priority: 100,        compile: function(tpl, attr) {          return function(scope, element, attr) { -          attr.$observers[attrName] = []; +          attr.$$observers[attrName] = [];            scope.$watch(attr[normalized], function(value) {              attr.$set(attrName, value);            }); @@ -305,7 +305,7 @@ forEach(['src', 'href'], function(attrName) {        priority: 100,        compile: function(tpl, attr) {          return function(scope, element, attr) { -          attr.$observers[attrName] = []; +          attr.$$observers[attrName] = [];            attr.$observe(normalized, function(value) {              attr.$set(attrName, value);            }); diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 7343c358..2f7a6a64 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -1181,7 +1181,7 @@ var ngValueDirective = [function() {            attr.$set('value', scope.$eval(attr.ngValue));          };        } else { -        attr.$observers.value = []; +        attr.$$observers.value = [];          return function(scope) {            scope.$watch(attr.ngValue, function(value) { diff --git a/test/AngularSpec.js b/test/AngularSpec.js index da56449a..cf34305a 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -88,6 +88,15 @@ describe('angular', function() {        expect(copy).toEqual(original);        expect(copy.key).toBe(original.key);      }); + +    it('should not copy $$ properties nor prototype properties', function() { +      var original = {$$some: true, $$: true}; +      var clone = {}; + +      expect(shallowCopy(original, clone)).toBe(clone); +      expect(clone.$$some).toBeUndefined(); +      expect(clone.$$).toBeUndefined(); +    });    });    describe('elementHTML', function() { diff --git a/test/ng/compilerSpec.js b/test/ng/compilerSpec.js index fb5e99e6..55479396 100644 --- a/test/ng/compilerSpec.js +++ b/test/ng/compilerSpec.js @@ -166,7 +166,7 @@ describe('$compile', function() {                compile: function(element, templateAttr) {                  expect(typeof templateAttr.$normalize).toBe('function');                  expect(typeof templateAttr.$set).toBe('function'); -                expect(isElement(templateAttr.$element)).toBeTruthy(); +                expect(isElement(templateAttr.$$element)).toBeTruthy();                  expect(element.text()).toEqual('unlinked');                  expect(templateAttr.exp).toEqual('abc');                  expect(templateAttr.aa).toEqual('A'); @@ -344,7 +344,7 @@ describe('$compile', function() {              template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',              compile: function(element, attr) {                attr.$set('compiled', 'COMPILED'); -              expect(element).toBe(attr.$element); +              expect(element).toBe(attr.$$element);              }            }));            $compileProvider.directive('append', valueFn({ @@ -352,7 +352,7 @@ describe('$compile', function() {              template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',              compile: function(element, attr) {                attr.$set('compiled', 'COMPILED'); -              expect(element).toBe(attr.$element); +              expect(element).toBe(attr.$$element);              }            }));          }));  | 
