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

Commit e62fafb

Browse files
committed
perf($compile): only use document fragments and copy data when necessary
1 parent b6fd184 commit e62fafb

File tree

5 files changed

+52
-45
lines changed

5 files changed

+52
-45
lines changed

src/.jshintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"isElement": false,
5959
"makeMap": false,
6060
"includes": false,
61+
"indexOf": false,
6162
"arrayRemove": false,
6263
"copy": false,
6364
"shallowCopy": false,

src/Angular.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
isElement: true,
5454
makeMap: true,
5555
includes: true,
56+
indexOf: true,
5657
arrayRemove: true,
5758
copy: true,
5859
shallowCopy: true,
@@ -628,8 +629,10 @@ function nodeName_(element) {
628629
return lowercase(element.nodeName || element[0].nodeName);
629630
}
630631

632+
var indexOf = Array.prototype.indexOf;
633+
631634
function includes(array, obj) {
632-
return Array.prototype.indexOf.call(array, obj) != -1;
635+
return indexOf.call(array, obj) != -1;
633636
}
634637

635638
function arrayRemove(array, value) {

src/jqLite.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,13 @@ function jqLiteAcceptsData(node) {
169169
return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
170170
}
171171

172+
function jqLiteHasData(node) {
173+
for (var key in jqCache[node.ng339]) {
174+
return true;
175+
}
176+
return false;
177+
}
178+
172179
function jqLiteBuildFragment(html, context) {
173180
var tmp, tag, wrap,
174181
fragment = context.createDocumentFragment(),
@@ -543,7 +550,8 @@ function getAliasedAttrName(element, name) {
543550

544551
forEach({
545552
data: jqLiteData,
546-
removeData: jqLiteRemoveData
553+
removeData: jqLiteRemoveData,
554+
hasData: jqLiteHasData
547555
}, function(fn, name) {
548556
JQLite[name] = fn;
549557
});

src/ng/compile.js

Lines changed: 31 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,7 +2207,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
22072207
// it was cloned therefore we have to clone as well.
22082208
linkNode = jqLiteClone(compileNode);
22092209
}
2210-
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
2210+
replaceWith(linkRootElement, [beforeTemplateLinkNode], linkNode);
22112211

22122212
// Copy in CSS classes from original node
22132213
safeAddClass(jqLite(linkNode), oldClasses);
@@ -2389,60 +2389,56 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
23892389
*
23902390
* @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
23912391
* in the root of the tree.
2392-
* @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
2393-
* the shell, but replace its DOM node reference.
2392+
* @param {JqLite|Array} elementsToRemove The elements which we are going to replace. We keep
2393+
* the shell, but replace its DOM node reference.
23942394
* @param {Node} newNode The new DOM node.
23952395
*/
23962396
function replaceWith($rootElement, elementsToRemove, newNode) {
23972397
var firstElementToRemove = elementsToRemove[0],
23982398
removeCount = elementsToRemove.length,
23992399
parent = firstElementToRemove.parentNode,
2400-
i, ii;
2401-
2402-
if ($rootElement) {
2403-
for (i = 0, ii = $rootElement.length; i < ii; i++) {
2404-
if ($rootElement[i] == firstElementToRemove) {
2405-
$rootElement[i++] = newNode;
2406-
for (var j = i, j2 = j + removeCount - 1,
2407-
jj = $rootElement.length;
2408-
j < jj; j++, j2++) {
2409-
if (j2 < jj) {
2410-
$rootElement[j] = $rootElement[j2];
2411-
} else {
2412-
delete $rootElement[j];
2413-
}
2414-
}
2415-
$rootElement.length -= removeCount - 1;
2400+
i;
24162401

2417-
// If the replaced element is also the jQuery .context then replace it
2418-
// .context is a deprecated jQuery api, so we should set it only when jQuery set it
2419-
// http://api.jquery.com/context/
2420-
if ($rootElement.context === firstElementToRemove) {
2421-
$rootElement.context = newNode;
2422-
}
2423-
break;
2424-
}
2402+
if ($rootElement && (i = indexOf.call($rootElement, firstElementToRemove)) !== -1) {
2403+
$rootElement.splice(i, removeCount, newNode);
2404+
2405+
// If the replaced element is also the jQuery .context then replace it
2406+
// .context is a deprecated jQuery api, so we should set it only when jQuery set it
2407+
// http://api.jquery.com/context/
2408+
if ($rootElement.context === firstElementToRemove) {
2409+
$rootElement.context = newNode;
24252410
}
24262411
}
24272412

2413+
// Add new node to the parent in place of the firstElementToRemove
24282414
if (parent) {
24292415
parent.replaceChild(newNode, firstElementToRemove);
24302416
}
24312417

2432-
// TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
2433-
var fragment = document.createDocumentFragment();
2434-
fragment.appendChild(firstElementToRemove);
2418+
// If multiple elements are being replaced they will be placed into a temporary fragment
2419+
// so they can still be traversed via .nextSibling or .parent.children while detached.
2420+
// This also detaches the elementsToRemove if they have a parent.
2421+
if (removeCount > 1) {
2422+
var fragment = document.createDocumentFragment();
2423+
for (i = 0; i < removeCount; i++) {
2424+
fragment.appendChild(elementsToRemove[i]);
2425+
}
2426+
}
24352427

24362428
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
24372429
// data here because there's no public interface in jQuery to do that and copying over
24382430
// event listeners (which is the main use of private data) wouldn't work anyway.
2439-
jqLite(newNode).data(jqLite(firstElementToRemove).data());
2431+
if (jqLite.hasData(firstElementToRemove)) {
2432+
jqLite.data(newNode, jqLite.data(firstElementToRemove));
2433+
}
24402434

24412435
// Remove data of the replaced element. We cannot just call .remove()
24422436
// on the element it since that would deallocate scope that is needed
24432437
// for the new node. Instead, remove the data "manually".
24442438
if (!jQuery) {
2445-
delete jqLite.cache[firstElementToRemove[jqLite.expando]];
2439+
for (i = 0; i < removeCount; i++) {
2440+
delete jqLite.cache[elementsToRemove[i][jqLite.expando]];
2441+
}
24462442
} else {
24472443
// jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
24482444
// the replaced element. The cleanData version monkey-patched by Angular would cause
@@ -2452,18 +2448,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
24522448
// example is jQuery UI). Instead, set a flag indicating scope destroying should be
24532449
// skipped this one time.
24542450
skipDestroyOnNextJQueryCleanData = true;
2455-
jQuery.cleanData([firstElementToRemove]);
2456-
}
2457-
2458-
for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
2459-
var element = elementsToRemove[k];
2460-
jqLite(element).remove(); // must do this way to clean up expando
2461-
fragment.appendChild(element);
2462-
delete elementsToRemove[k];
2451+
jQuery.cleanData(elementsToRemove);
24632452
}
24642453

2465-
elementsToRemove[0] = newNode;
2466-
elementsToRemove.length = 1;
2454+
//Modify elementsToRemove jqLite/Array replacing all content with the newnode
2455+
elementsToRemove.splice(0, removeCount, newNode);
24672456
}
24682457

24692458

test/jqLiteSpec.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,13 +402,16 @@ describe('jqLite', function() {
402402
});
403403

404404

405-
it('should provide the non-wrapped data calls', function() {
405+
it('should provide the non-wrapped get/remove/has data calls', function() {
406406
var node = document.createElement('div');
407407

408+
expect(jqLite.hasData(node)).toBe(false);
408409
expect(jqLite.data(node, "foo")).toBeUndefined();
410+
expect(jqLite.hasData(node)).toBe(false);
409411

410412
jqLite.data(node, "foo", "bar");
411413

414+
expect(jqLite.hasData(node)).toBe(true);
412415
expect(jqLite.data(node, "foo")).toBe("bar");
413416
expect(jqLite(node).data("foo")).toBe("bar");
414417

@@ -421,6 +424,9 @@ describe('jqLite', function() {
421424
jqLite.removeData(node);
422425
jqLite.removeData(node);
423426
expect(jqLite.data(node, "bar")).toBeUndefined();
427+
428+
jqLite(node).remove();
429+
expect(jqLite.hasData(node)).toBe(false);
424430
});
425431

426432
it('should emit $destroy event if element removed via remove()', function() {

0 commit comments

Comments
 (0)