Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Perf improvements #7759

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 20 additions & 32 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ var /** holds major version number for IE or NaN for real browsers */
angular = window.angular || (window.angular = {}),
angularModule,
nodeName_,
uid = ['0', '0', '0'];
uid = 0;

/**
* IE 11 changed the format of the UserAgent string.
Expand Down Expand Up @@ -226,8 +226,9 @@ function isArrayLike(obj) {
* @param {Object=} context Object to become context (`this`) for the iterator function.
* @returns {Object|Array} Reference to `obj`.
*/

function forEach(obj, iterator, context) {
var key;
var key, length;
if (obj) {
if (isFunction(obj)) {
for (key in obj) {
Expand All @@ -240,8 +241,9 @@ function forEach(obj, iterator, context) {
} else if (obj.forEach && obj.forEach !== forEach) {
obj.forEach(iterator, context);
} else if (isArrayLike(obj)) {
for (key = 0; key < obj.length; key++)
for (key = 0, length = obj.length; key < length; key++) {
iterator.call(context, obj[key], key);
}
} else {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
Expand Down Expand Up @@ -282,33 +284,17 @@ function reverseParams(iteratorFn) {
}

/**
* A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
* characters such as '012ABC'. The reason why we are not using simply a number counter is that
* the number string gets longer over time, and it can also overflow, where as the nextId
* will grow much slower, it is a string, and it will never overflow.
* A consistent way of creating unique IDs in angular.
*
* @returns {string} an unique alpha-numeric string
* Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
* we hit number precision issues in JavaScript.
*
* Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
*
* @returns {number} an unique alpha-numeric string
*/
function nextUid() {
var index = uid.length;
var digit;

while(index) {
index--;
digit = uid[index].charCodeAt(0);
if (digit == 57 /*'9'*/) {
uid[index] = 'A';
return uid.join('');
}
if (digit == 90 /*'Z'*/) {
uid[index] = '0';
} else {
uid[index] = String.fromCharCode(digit + 1);
return uid.join('');
}
}
uid.unshift('0');
return uid.join('');
return ++uid;
}


Expand Down Expand Up @@ -550,7 +536,7 @@ function isRegExp(value) {
* @returns {boolean} True if `obj` is a window obj.
*/
function isWindow(obj) {
return obj && obj.document && obj.location && obj.alert && obj.setInterval;
return obj && obj.window === obj;
}


Expand Down Expand Up @@ -804,10 +790,12 @@ function copy(source, destination) {
function shallowCopy(src, dst) {
dst = dst || {};

for(var key in src) {
// shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
// so we don't need to worry about using our custom hasOwnProperty here
if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
var keys = Object.keys(src);

for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];

if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
}
}
Expand Down
102 changes: 65 additions & 37 deletions src/jqLite.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@
* @returns {Object} jQuery object.
*/

JQLite.expando = 'ng';

var jqCache = JQLite.cache = {},
jqName = JQLite.expando = 'ng-' + new Date().getTime(),
jqId = 1,
addEventListenerFn = (window.document.addEventListener
? function(element, type, fn) {element.addEventListener(type, fn, false);}
Expand Down Expand Up @@ -239,8 +240,10 @@ function jqLiteClone(element) {

function jqLiteDealoc(element){
jqLiteRemoveData(element);
for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
jqLiteDealoc(children[i]);
var childElement;
for ( var i = 0, children = element.children, l = (children && children.length) || 0; i < l; i++) {
childElement = children[i];
jqLiteDealoc(childElement);
}
}

Expand Down Expand Up @@ -270,7 +273,7 @@ function jqLiteOff(element, type, fn, unsupported) {
}

function jqLiteRemoveData(element, name) {
var expandoId = element[jqName],
var expandoId = element.ng,
expandoStore = jqCache[expandoId];

if (expandoStore) {
Expand All @@ -284,17 +287,17 @@ function jqLiteRemoveData(element, name) {
jqLiteOff(element);
}
delete jqCache[expandoId];
element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
element.ng = undefined; // don't delete DOM expandos. IE and Chrome don't like it
}
}

function jqLiteExpandoStore(element, key, value) {
var expandoId = element[jqName],
var expandoId = element.ng,
expandoStore = jqCache[expandoId || -1];

if (isDefined(value)) {
if (!expandoStore) {
element[jqName] = expandoId = jqNextId();
element.ng = expandoId = jqNextId();
expandoStore = jqCache[expandoId] = {};
}
expandoStore[key] = value;
Expand All @@ -314,7 +317,10 @@ function jqLiteData(element, key, value) {
}

if (isSetter) {
data[key] = value;
// set data only on Elements and Documents
if (element.nodeType === 1 || element.nodeType === 9) {
data[key] = value;
}
} else {
if (keyDefined) {
if (isSimpleGetter) {
Expand Down Expand Up @@ -348,32 +354,60 @@ function jqLiteRemoveClass(element, cssClasses) {
}

function jqLiteAddClass(element, cssClasses) {
if (cssClasses && element.setAttribute) {
var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
.replace(/[\n\t]/g, " ");

forEach(cssClasses.split(' '), function(cssClass) {
cssClass = trim(cssClass);
if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
existingClasses += cssClass + ' ';
if (cssClasses) {

// if browser and element support classList api use it
if (element.classList) {
cssClasses = trim(cssClasses);
if (cssClasses) {
cssClasses = cssClasses.split(/\s+/);
if (cssClasses.length === 1) {
element.classList.add(cssClasses[0])
} else {
element.classList.add.apply(element.classList, cssClasses);
}
}
});
} else if (element.setAttribute) {
var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
.replace(/[\n\t]/g, " ");

forEach(cssClasses.split(' '), function (cssClass) {
cssClass = trim(cssClass);
if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
existingClasses += cssClass + ' ';
}
});

element.setAttribute('class', trim(existingClasses));
element.setAttribute('class', trim(existingClasses));
}
}
}


function jqLiteAddNodes(root, elements) {
// THIS CODE IS VERY HOT. Don't make changes without benchmarking.

if (elements) {
elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
? elements
: [ elements ];
for(var i=0; i < elements.length; i++) {
root.push(elements[i]);

// if a Node (the most common case)
if (elements.nodeType) {
root[root.length++] = elements;
} else {
var length = elements.length;

// if an Array or NodeList and not a Window
if (typeof length === 'number' && elements.window !== elements) {
if (length) {
push.apply(root, elements);
}
} else {
root[root.length++] = elements;
}
}
}
}


function jqLiteController(element, name) {
return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
}
Expand Down Expand Up @@ -560,23 +594,15 @@ forEach({
},

text: (function() {
var NODE_TYPE_TEXT_PROPERTY = [];
if (msie < 9) {
NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/
NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/
} else {
NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/
NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/
}
getText.$dv = '';
return getText;

function getText(element, value) {
var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType];
if (isUndefined(value)) {
return textProp ? element[textProp] : '';
var nodeType = element.nodeType;
return (nodeType === 1 || nodeType === 3) ? element.textContent : '';
}
element[textProp] = value;
element.textContent = value;
}
})(),

Expand Down Expand Up @@ -613,6 +639,7 @@ forEach({
*/
JQLite.prototype[name] = function(arg1, arg2) {
var i, key;
var nodeCount = this.length;

// jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
// in a way that survives minification.
Expand All @@ -622,7 +649,7 @@ forEach({
if (isObject(arg1)) {

// we are a write, but the object properties are the key/values
for (i = 0; i < this.length; i++) {
for (i = 0; i < nodeCount; i++) {
if (fn === jqLiteData) {
// data() takes the whole object in jQuery
fn(this[i], arg1);
Expand All @@ -636,9 +663,10 @@ forEach({
return this;
} else {
// we are a read, so read the first child.
// TODO: do we still need this?
var value = fn.$dv;
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
var jj = (value === undefined) ? Math.min(this.length, 1) : this.length;
var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount;
for (var j = 0; j < jj; j++) {
var nodeValue = fn(this[j], arg1, arg2);
value = value ? value + nodeValue : nodeValue;
Expand All @@ -647,7 +675,7 @@ forEach({
}
} else {
// we are a write, so apply to all children
for (i = 0; i < this.length; i++) {
for (i = 0; i < nodeCount; i++) {
fn(this[i], arg1, arg2);
}
// return self for chaining
Expand Down
30 changes: 18 additions & 12 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1811,18 +1811,24 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (interpolateFn) {
directives.push({
priority: 0,
compile: valueFn(function textInterpolateLinkFn(scope, node) {
var parent = node.parent(),
bindings = parent.data('$binding') || [];
// Need to interpolate again in case this is using one-time bindings in multiple clones
// of transcluded templates.
interpolateFn = $interpolate(text);
bindings.push(interpolateFn);
safeAddClass(parent.data('$binding', bindings), 'ng-binding');
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
node[0].nodeValue = value;
});
})
compile: function textInterpolateCompileFn(templateNode) {
safeAddClass(templateNode, 'ng-binding');

return function textInterpolateLinkFn(scope, node) {

var parent = node.parent(),
bindings = parent.data('$binding') || [];
// Need to interpolate again in case this is using one-time bindings in multiple clones
// of transcluded templates.
interpolateFn = $interpolate(text);
//bindings.push(interpolateFn);
parent.data('$binding', bindings);

scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
node[0].nodeValue = value;
});
}
}
});
}
}
Expand Down
21 changes: 13 additions & 8 deletions src/ng/directive/ngBind.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,19 @@
</file>
</example>
*/
var ngBindDirective = ngDirective(function(scope, element, attr) {
element.addClass('ng-binding').data('$binding', attr.ngBind);
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
// We are purposefully using == here rather than === because we want to
// catch when value is "null or undefined"
// jshint -W041
element.text(value == undefined ? '' : value);
});
var ngBindDirective = ngDirective({
compile: function(templateElement) {
templateElement.addClass('ng-binding');
return function (scope, element, attr) {
element.data('$binding', attr.ngBind);
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
// We are purposefully using == here rather than === because we want to
// catch when value is "null or undefined"
// jshint -W041
element.text(value == undefined ? '' : value);
});
};
}
});


Expand Down
2 changes: 1 addition & 1 deletion src/ng/directive/ngEventDirs.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ forEach(
return {
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function(scope, element, attr) {
return function ngEventHandler(scope, element) {
element.on(lowercase(name), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
Expand Down
2 changes: 1 addition & 1 deletion test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ describe('angular', function() {

while(count--) {
var current = nextUid();
expect(current.match(/[\d\w]+/)).toBeTruthy();
expect(typeof 2).toBe('number');
expect(seen[current]).toBeFalsy();
seen[current] = true;
}
Expand Down
2 changes: 1 addition & 1 deletion test/helpers/testabilityPatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ beforeEach(function() {
}

// This resets global id counter;
uid = ['0', '0', '0'];
uid = 0;

// reset to jQuery or default to us.
bindJQuery();
Expand Down
Loading