mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-05-01 11:44:46 +00:00
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.
This commit is contained in:
parent
f2106692b1
commit
6da355c3e1
6 changed files with 102 additions and 91 deletions
|
|
@ -598,16 +598,16 @@ function copy(source, destination){
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a shallow copy of an object
|
* Create a shallow copy of an object
|
||||||
* @param src
|
|
||||||
*/
|
*/
|
||||||
function shallowCopy(src) {
|
function shallowCopy(src, dst) {
|
||||||
var dst = {},
|
dst = dst || {};
|
||||||
key;
|
|
||||||
for(key in src) {
|
for(var key in src) {
|
||||||
if (src.hasOwnProperty(key)) {
|
if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
|
||||||
dst[key] = src[key];
|
dst[key] = src[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
return compile;
|
||||||
|
|
||||||
//================================
|
//================================
|
||||||
|
|
@ -278,13 +351,8 @@ function $CompileProvider($provide) {
|
||||||
directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;
|
directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;
|
||||||
|
|
||||||
for(var i = 0, ii = nodeList.length; i < ii; i++) {
|
for(var i = 0, ii = nodeList.length; i < ii; i++) {
|
||||||
attrs = {
|
attrs = new Attributes();
|
||||||
$attr: {},
|
|
||||||
$normalize: directiveNormalize,
|
|
||||||
$set: attrSetter,
|
|
||||||
$observe: interpolatedAttrObserve,
|
|
||||||
$observers: {}
|
|
||||||
};
|
|
||||||
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
|
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
|
||||||
directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
|
directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
|
||||||
|
|
||||||
|
|
@ -441,7 +509,7 @@ function $CompileProvider($provide) {
|
||||||
newIsolatedScopeDirective = null,
|
newIsolatedScopeDirective = null,
|
||||||
templateDirective = null,
|
templateDirective = null,
|
||||||
delayedLinkingFn = null,
|
delayedLinkingFn = null,
|
||||||
element = templateAttrs.$element = jqLite(templateNode),
|
element = templateAttrs.$$element = jqLite(templateNode),
|
||||||
directive,
|
directive,
|
||||||
directiveName,
|
directiveName,
|
||||||
template,
|
template,
|
||||||
|
|
@ -485,7 +553,7 @@ function $CompileProvider($provide) {
|
||||||
terminalPriority = directive.priority;
|
terminalPriority = directive.priority;
|
||||||
if (directiveValue == 'element') {
|
if (directiveValue == 'element') {
|
||||||
template = jqLite(templateNode);
|
template = jqLite(templateNode);
|
||||||
templateNode = (element = templateAttrs.$element = jqLite(
|
templateNode = (element = templateAttrs.$$element = jqLite(
|
||||||
'<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->'))[0];
|
'<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->'))[0];
|
||||||
replaceWith(rootElement, jqLite(template[0]), templateNode);
|
replaceWith(rootElement, jqLite(template[0]), templateNode);
|
||||||
childTranscludeFn = compile(template, transcludeFn, terminalPriority);
|
childTranscludeFn = compile(template, transcludeFn, terminalPriority);
|
||||||
|
|
@ -609,11 +677,9 @@ function $CompileProvider($provide) {
|
||||||
if (templateNode === linkNode) {
|
if (templateNode === linkNode) {
|
||||||
attrs = templateAttrs;
|
attrs = templateAttrs;
|
||||||
} else {
|
} else {
|
||||||
attrs = shallowCopy(templateAttrs);
|
attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
|
||||||
attrs.$element = jqLite(linkNode);
|
|
||||||
attrs.$observers = {};
|
|
||||||
}
|
}
|
||||||
element = attrs.$element;
|
element = attrs.$$element;
|
||||||
|
|
||||||
if (newScopeDirective && isObject(newScopeDirective.scope)) {
|
if (newScopeDirective && isObject(newScopeDirective.scope)) {
|
||||||
forEach(newScopeDirective.scope, function(mode, name) {
|
forEach(newScopeDirective.scope, function(mode, name) {
|
||||||
|
|
@ -720,7 +786,7 @@ function $CompileProvider($provide) {
|
||||||
function mergeTemplateAttributes(dst, src) {
|
function mergeTemplateAttributes(dst, src) {
|
||||||
var srcAttr = src.$attr,
|
var srcAttr = src.$attr,
|
||||||
dstAttr = dst.$attr,
|
dstAttr = dst.$attr,
|
||||||
element = dst.$element;
|
element = dst.$$element;
|
||||||
// reapply the old attributes to the new element
|
// reapply the old attributes to the new element
|
||||||
forEach(dst, function(value, key) {
|
forEach(dst, function(value, key) {
|
||||||
if (key.charAt(0) != '$') {
|
if (key.charAt(0) != '$') {
|
||||||
|
|
@ -873,7 +939,7 @@ function $CompileProvider($provide) {
|
||||||
|
|
||||||
// we define observers array only for interpolated attrs
|
// we define observers array only for interpolated attrs
|
||||||
// and ignore observers for non interpolated attrs to save some memory
|
// and ignore observers for non interpolated attrs to save some memory
|
||||||
attr.$observers[name] = [];
|
attr.$$observers[name] = [];
|
||||||
attr[name] = undefined;
|
attr[name] = undefined;
|
||||||
scope.$watch(interpolateFn, function(value) {
|
scope.$watch(interpolateFn, function(value) {
|
||||||
attr.$set(name, value);
|
attr.$set(name, value);
|
||||||
|
|
@ -910,70 +976,6 @@ function $CompileProvider($provide) {
|
||||||
}
|
}
|
||||||
element[0] = newNode;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -286,7 +286,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) {
|
||||||
priority: 100,
|
priority: 100,
|
||||||
compile: function(tpl, attr) {
|
compile: function(tpl, attr) {
|
||||||
return function(scope, element, attr) {
|
return function(scope, element, attr) {
|
||||||
attr.$observers[attrName] = [];
|
attr.$$observers[attrName] = [];
|
||||||
scope.$watch(attr[normalized], function(value) {
|
scope.$watch(attr[normalized], function(value) {
|
||||||
attr.$set(attrName, value);
|
attr.$set(attrName, value);
|
||||||
});
|
});
|
||||||
|
|
@ -305,7 +305,7 @@ forEach(['src', 'href'], function(attrName) {
|
||||||
priority: 100,
|
priority: 100,
|
||||||
compile: function(tpl, attr) {
|
compile: function(tpl, attr) {
|
||||||
return function(scope, element, attr) {
|
return function(scope, element, attr) {
|
||||||
attr.$observers[attrName] = [];
|
attr.$$observers[attrName] = [];
|
||||||
attr.$observe(normalized, function(value) {
|
attr.$observe(normalized, function(value) {
|
||||||
attr.$set(attrName, value);
|
attr.$set(attrName, value);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1181,7 +1181,7 @@ var ngValueDirective = [function() {
|
||||||
attr.$set('value', scope.$eval(attr.ngValue));
|
attr.$set('value', scope.$eval(attr.ngValue));
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
attr.$observers.value = [];
|
attr.$$observers.value = [];
|
||||||
|
|
||||||
return function(scope) {
|
return function(scope) {
|
||||||
scope.$watch(attr.ngValue, function(value) {
|
scope.$watch(attr.ngValue, function(value) {
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,15 @@ describe('angular', function() {
|
||||||
expect(copy).toEqual(original);
|
expect(copy).toEqual(original);
|
||||||
expect(copy.key).toBe(original.key);
|
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() {
|
describe('elementHTML', function() {
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ describe('$compile', function() {
|
||||||
compile: function(element, templateAttr) {
|
compile: function(element, templateAttr) {
|
||||||
expect(typeof templateAttr.$normalize).toBe('function');
|
expect(typeof templateAttr.$normalize).toBe('function');
|
||||||
expect(typeof templateAttr.$set).toBe('function');
|
expect(typeof templateAttr.$set).toBe('function');
|
||||||
expect(isElement(templateAttr.$element)).toBeTruthy();
|
expect(isElement(templateAttr.$$element)).toBeTruthy();
|
||||||
expect(element.text()).toEqual('unlinked');
|
expect(element.text()).toEqual('unlinked');
|
||||||
expect(templateAttr.exp).toEqual('abc');
|
expect(templateAttr.exp).toEqual('abc');
|
||||||
expect(templateAttr.aa).toEqual('A');
|
expect(templateAttr.aa).toEqual('A');
|
||||||
|
|
@ -344,7 +344,7 @@ describe('$compile', function() {
|
||||||
template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',
|
template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',
|
||||||
compile: function(element, attr) {
|
compile: function(element, attr) {
|
||||||
attr.$set('compiled', 'COMPILED');
|
attr.$set('compiled', 'COMPILED');
|
||||||
expect(element).toBe(attr.$element);
|
expect(element).toBe(attr.$$element);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
$compileProvider.directive('append', valueFn({
|
$compileProvider.directive('append', valueFn({
|
||||||
|
|
@ -352,7 +352,7 @@ describe('$compile', function() {
|
||||||
template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',
|
template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>',
|
||||||
compile: function(element, attr) {
|
compile: function(element, attr) {
|
||||||
attr.$set('compiled', 'COMPILED');
|
attr.$set('compiled', 'COMPILED');
|
||||||
expect(element).toBe(attr.$element);
|
expect(element).toBe(attr.$$element);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue