Skip to content

Commit 6da355c

Browse files
vojtajinaIgorMinar
authored andcommitted
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.
1 parent f210669 commit 6da355c

6 files changed

Lines changed: 102 additions & 91 deletions

File tree

src/Angular.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -598,16 +598,16 @@ function copy(source, destination){
598598

599599
/**
600600
* Create a shallow copy of an object
601-
* @param src
602601
*/
603-
function shallowCopy(src) {
604-
var dst = {},
605-
key;
606-
for(key in src) {
607-
if (src.hasOwnProperty(key)) {
602+
function shallowCopy(src, dst) {
603+
dst = dst || {};
604+
605+
for(var key in src) {
606+
if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
608607
dst[key] = src[key];
609608
}
610609
}
610+
611611
return dst;
612612
}
613613

src/ng/compiler.js

Lines changed: 81 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,79 @@ function $CompileProvider($provide) {
214214
}
215215
};
216216

217+
var Attributes = function(element, attr) {
218+
this.$$element = element;
219+
this.$$observers = {};
220+
this.$attr = attr || {};
221+
};
222+
223+
Attributes.prototype = {
224+
$normalize: directiveNormalize,
225+
226+
227+
/**
228+
* Set a normalized attribute on the element in a way such that all directives
229+
* can share the attribute. This function properly handles boolean attributes.
230+
* @param {string} key Normalized key. (ie ngAttribute)
231+
* @param {string|boolean} value The value to set. If `null` attribute will be deleted.
232+
* @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
233+
* Defaults to true.
234+
* @param {string=} attrName Optional none normalized name. Defaults to key.
235+
*/
236+
$set: function(key, value, writeAttr, attrName) {
237+
var booleanKey = isBooleanAttr(this.$$element[0], key.toLowerCase());
238+
239+
if (booleanKey) {
240+
this.$$element.prop(key, value);
241+
attrName = booleanKey;
242+
}
243+
244+
this[key] = value;
245+
246+
// translate normalized key to actual key
247+
if (attrName) {
248+
this.$attr[key] = attrName;
249+
} else {
250+
attrName = this.$attr[key];
251+
if (!attrName) {
252+
this.$attr[key] = attrName = snake_case(key, '-');
253+
}
254+
}
255+
256+
if (writeAttr !== false) {
257+
if (value === null || value === undefined) {
258+
this.$$element.removeAttr(attrName);
259+
} else {
260+
this.$$element.attr(attrName, value);
261+
}
262+
}
263+
264+
// fire observers
265+
forEach(this.$$observers[key], function(fn) {
266+
try {
267+
fn(value);
268+
} catch (e) {
269+
$exceptionHandler(e);
270+
}
271+
});
272+
},
273+
274+
275+
/**
276+
* Observe an interpolated attribute.
277+
* The observer will never be called, if given attribute is not interpolated.
278+
*
279+
* @param {string} key Normalized key. (ie ngAttribute) .
280+
* @param {function(*)} fn Function that will be called whenever the attribute value changes.
281+
*/
282+
$observe: function(key, fn) {
283+
// keep only observers for interpolated attrs
284+
if (this.$$observers[key]) {
285+
this.$$observers[key].push(fn);
286+
}
287+
}
288+
};
289+
217290
return compile;
218291

219292
//================================
@@ -278,13 +351,8 @@ function $CompileProvider($provide) {
278351
directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;
279352

280353
for(var i = 0, ii = nodeList.length; i < ii; i++) {
281-
attrs = {
282-
$attr: {},
283-
$normalize: directiveNormalize,
284-
$set: attrSetter,
285-
$observe: interpolatedAttrObserve,
286-
$observers: {}
287-
};
354+
attrs = new Attributes();
355+
288356
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
289357
directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
290358

@@ -441,7 +509,7 @@ function $CompileProvider($provide) {
441509
newIsolatedScopeDirective = null,
442510
templateDirective = null,
443511
delayedLinkingFn = null,
444-
element = templateAttrs.$element = jqLite(templateNode),
512+
element = templateAttrs.$$element = jqLite(templateNode),
445513
directive,
446514
directiveName,
447515
template,
@@ -485,7 +553,7 @@ function $CompileProvider($provide) {
485553
terminalPriority = directive.priority;
486554
if (directiveValue == 'element') {
487555
template = jqLite(templateNode);
488-
templateNode = (element = templateAttrs.$element = jqLite(
556+
templateNode = (element = templateAttrs.$$element = jqLite(
489557
'<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->'))[0];
490558
replaceWith(rootElement, jqLite(template[0]), templateNode);
491559
childTranscludeFn = compile(template, transcludeFn, terminalPriority);
@@ -609,11 +677,9 @@ function $CompileProvider($provide) {
609677
if (templateNode === linkNode) {
610678
attrs = templateAttrs;
611679
} else {
612-
attrs = shallowCopy(templateAttrs);
613-
attrs.$element = jqLite(linkNode);
614-
attrs.$observers = {};
680+
attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
615681
}
616-
element = attrs.$element;
682+
element = attrs.$$element;
617683

618684
if (newScopeDirective && isObject(newScopeDirective.scope)) {
619685
forEach(newScopeDirective.scope, function(mode, name) {
@@ -720,7 +786,7 @@ function $CompileProvider($provide) {
720786
function mergeTemplateAttributes(dst, src) {
721787
var srcAttr = src.$attr,
722788
dstAttr = dst.$attr,
723-
element = dst.$element;
789+
element = dst.$$element;
724790
// reapply the old attributes to the new element
725791
forEach(dst, function(value, key) {
726792
if (key.charAt(0) != '$') {
@@ -873,7 +939,7 @@ function $CompileProvider($provide) {
873939

874940
// we define observers array only for interpolated attrs
875941
// and ignore observers for non interpolated attrs to save some memory
876-
attr.$observers[name] = [];
942+
attr.$$observers[name] = [];
877943
attr[name] = undefined;
878944
scope.$watch(interpolateFn, function(value) {
879945
attr.$set(name, value);
@@ -910,70 +976,6 @@ function $CompileProvider($provide) {
910976
}
911977
element[0] = newNode;
912978
}
913-
914-
915-
/**
916-
* Set a normalized attribute on the element in a way such that all directives
917-
* can share the attribute. This function properly handles boolean attributes.
918-
* @param {string} key Normalized key. (ie ngAttribute)
919-
* @param {string|boolean} value The value to set. If `null` attribute will be deleted.
920-
* @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
921-
* Defaults to true.
922-
* @param {string=} attrName Optional none normalized name. Defaults to key.
923-
*/
924-
function attrSetter(key, value, writeAttr, attrName) {
925-
var booleanKey = isBooleanAttr(this.$element[0], key.toLowerCase());
926-
927-
if (booleanKey) {
928-
this.$element.prop(key, value);
929-
attrName = booleanKey;
930-
}
931-
932-
this[key] = value;
933-
934-
// translate normalized key to actual key
935-
if (attrName) {
936-
this.$attr[key] = attrName;
937-
} else {
938-
attrName = this.$attr[key];
939-
if (!attrName) {
940-
this.$attr[key] = attrName = snake_case(key, '-');
941-
}
942-
}
943-
944-
if (writeAttr !== false) {
945-
if (value === null || value === undefined) {
946-
this.$element.removeAttr(attrName);
947-
} else {
948-
this.$element.attr(attrName, value);
949-
}
950-
}
951-
952-
953-
// fire observers
954-
forEach(this.$observers[key], function(fn) {
955-
try {
956-
fn(value);
957-
} catch (e) {
958-
$exceptionHandler(e);
959-
}
960-
});
961-
}
962-
963-
964-
/**
965-
* Observe an interpolated attribute.
966-
* The observer will never be called, if given attribute is not interpolated.
967-
*
968-
* @param {string} key Normalized key. (ie ngAttribute) .
969-
* @param {function(*)} fn Function that will be called whenever the attribute value changes.
970-
*/
971-
function interpolatedAttrObserve(key, fn) {
972-
// keep only observers for interpolated attrs
973-
if (this.$observers[key]) {
974-
this.$observers[key].push(fn);
975-
}
976-
}
977979
}];
978980
}
979981

src/ng/directive/booleanAttrDirs.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) {
286286
priority: 100,
287287
compile: function(tpl, attr) {
288288
return function(scope, element, attr) {
289-
attr.$observers[attrName] = [];
289+
attr.$$observers[attrName] = [];
290290
scope.$watch(attr[normalized], function(value) {
291291
attr.$set(attrName, value);
292292
});
@@ -305,7 +305,7 @@ forEach(['src', 'href'], function(attrName) {
305305
priority: 100,
306306
compile: function(tpl, attr) {
307307
return function(scope, element, attr) {
308-
attr.$observers[attrName] = [];
308+
attr.$$observers[attrName] = [];
309309
attr.$observe(normalized, function(value) {
310310
attr.$set(attrName, value);
311311
});

src/ng/directive/input.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,7 @@ var ngValueDirective = [function() {
11811181
attr.$set('value', scope.$eval(attr.ngValue));
11821182
};
11831183
} else {
1184-
attr.$observers.value = [];
1184+
attr.$$observers.value = [];
11851185

11861186
return function(scope) {
11871187
scope.$watch(attr.ngValue, function(value) {

test/AngularSpec.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ describe('angular', function() {
8888
expect(copy).toEqual(original);
8989
expect(copy.key).toBe(original.key);
9090
});
91+
92+
it('should not copy $$ properties nor prototype properties', function() {
93+
var original = {$$some: true, $$: true};
94+
var clone = {};
95+
96+
expect(shallowCopy(original, clone)).toBe(clone);
97+
expect(clone.$$some).toBeUndefined();
98+
expect(clone.$$).toBeUndefined();
99+
});
91100
});
92101

93102
describe('elementHTML', function() {

test/ng/compilerSpec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ describe('$compile', function() {
166166
compile: function(element, templateAttr) {
167167
expect(typeof templateAttr.$normalize).toBe('function');
168168
expect(typeof templateAttr.$set).toBe('function');
169-
expect(isElement(templateAttr.$element)).toBeTruthy();
169+
expect(isElement(templateAttr.$$element)).toBeTruthy();
170170
expect(element.text()).toEqual('unlinked');
171171
expect(templateAttr.exp).toEqual('abc');
172172
expect(templateAttr.aa).toEqual('A');
@@ -344,15 +344,15 @@ describe('$compile', function() {
344344
template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',
345345
compile: function(element, attr) {
346346
attr.$set('compiled', 'COMPILED');
347-
expect(element).toBe(attr.$element);
347+
expect(element).toBe(attr.$$element);
348348
}
349349
}));
350350
$compileProvider.directive('append', valueFn({
351351
restrict: 'CAM',
352352
template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',
353353
compile: function(element, attr) {
354354
attr.$set('compiled', 'COMPILED');
355-
expect(element).toBe(attr.$element);
355+
expect(element).toBe(attr.$$element);
356356
}
357357
}));
358358
}));

0 commit comments

Comments
 (0)