Skip to content

Commit d6eca01

Browse files
mgoljamesdaily
authored andcommitted
perf(jqLite): implement and use the empty method in place of html(‘’)
jQuery's elem.html('') is way slower than elem.empty(). As clearing element contents happens quite often in certain scenarios, switching to using .empty() provides a significant performance boost when using Angular with jQuery. Closes angular#4457
1 parent feeeda2 commit d6eca01

File tree

17 files changed

+64
-30
lines changed

17 files changed

+64
-30
lines changed

docs/component-spec/annotationsSpec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ describe('Docs Annotations', function() {
55
var body;
66
beforeEach(function() {
77
body = angular.element(document.body);
8-
body.html('');
8+
body.empty();
99
});
1010

1111
var normalizeHtml = function(html) {

docs/components/angular-bootstrap/bootstrap-prettify.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function escape(text) {
2828
function setHtmlIe8SafeWay(element, html) {
2929
var newElement = angular.element('<pre>' + html + '</pre>');
3030

31-
element.html('');
31+
element.empty();
3232
element.append(newElement.contents());
3333
return element;
3434
}

docs/content/guide/dev_guide.unit-testing.ngdoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ var pc = new PasswordCtrl();
222222
input.val('abc');
223223
pc.grade();
224224
expect(span.text()).toEqual('weak');
225-
$('body').html('');
225+
$('body').empty();
226226
</pre>
227227

228228
In angular the controllers are strictly separated from the DOM manipulation logic and this results in

src/Angular.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,7 @@ function startingTag(element) {
974974
try {
975975
// turns out IE does not let you set .html() on elements which
976976
// are not allowed to have children. So we just ignore it.
977-
element.html('');
977+
element.empty();
978978
} catch(e) {}
979979
// As Per DOM Standards
980980
var TEXT_NODE = 3;

src/jqLite.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
* - [`contents()`](http://api.jquery.com/contents/)
4747
* - [`css()`](http://api.jquery.com/css/)
4848
* - [`data()`](http://api.jquery.com/data/)
49+
* - [`empty()`](http://api.jquery.com/empty/)
4950
* - [`eq()`](http://api.jquery.com/eq/)
5051
* - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
5152
* - [`hasClass()`](http://api.jquery.com/hasClass/)
@@ -358,6 +359,15 @@ function jqLiteInheritedData(element, name, value) {
358359
}
359360
}
360361

362+
function jqLiteEmpty(element) {
363+
for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
364+
jqLiteDealoc(childNodes[i]);
365+
}
366+
while (element.firstChild) {
367+
element.removeChild(element.firstChild);
368+
}
369+
}
370+
361371
//////////////////////////////////////////
362372
// Functions which are declared directly.
363373
//////////////////////////////////////////
@@ -552,7 +562,9 @@ forEach({
552562
jqLiteDealoc(childNodes[i]);
553563
}
554564
element.innerHTML = value;
555-
}
565+
},
566+
567+
empty: jqLiteEmpty
556568
}, function(fn, name){
557569
/**
558570
* Properties: writes return selection, reads return first value
@@ -562,11 +574,13 @@ forEach({
562574

563575
// jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
564576
// in a way that survives minification.
565-
if (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined) {
577+
// jqLiteEmpty takes no arguments but is a setter.
578+
if (fn !== jqLiteEmpty &&
579+
(((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) {
566580
if (isObject(arg1)) {
567581

568582
// we are a write, but the object properties are the key/values
569-
for(i=0; i < this.length; i++) {
583+
for (i = 0; i < this.length; i++) {
570584
if (fn === jqLiteData) {
571585
// data() takes the whole object in jQuery
572586
fn(this[i], arg1);
@@ -591,7 +605,7 @@ forEach({
591605
}
592606
} else {
593607
// we are a write, so apply to all children
594-
for(i=0; i < this.length; i++) {
608+
for (i = 0; i < this.length; i++) {
595609
fn(this[i], arg1, arg2);
596610
}
597611
// return self for chaining

src/ng/compile.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,7 +1219,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
12191219
});
12201220
} else {
12211221
$template = jqLite(jqLiteClone(compileNode)).contents();
1222-
$compileNode.html(''); // clear contents
1222+
$compileNode.empty(); // clear contents
12231223
childTranscludeFn = compile($template, transcludeFn);
12241224
}
12251225
}
@@ -1651,7 +1651,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16511651
? origAsyncDirective.templateUrl($compileNode, tAttrs)
16521652
: origAsyncDirective.templateUrl;
16531653

1654-
$compileNode.html('');
1654+
$compileNode.empty();
16551655

16561656
$http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).
16571657
success(function(content) {

src/ng/directive/ngTransclude.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ var ngTranscludeDirective = ngDirective({
6969

7070
link: function($scope, $element, $attrs, controller) {
7171
controller.$transclude(function(clone) {
72-
$element.html('');
72+
$element.empty();
7373
$element.append(clone);
7474
});
7575
}

src/ng/directive/select.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,13 +333,13 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
333333
// becomes the compilation root
334334
nullOption.removeClass('ng-scope');
335335

336-
// we need to remove it before calling selectElement.html('') because otherwise IE will
336+
// we need to remove it before calling selectElement.empty() because otherwise IE will
337337
// remove the label from the element. wtf?
338338
nullOption.remove();
339339
}
340340

341341
// clear contents, we'll add what's needed based on the model
342-
selectElement.html('');
342+
selectElement.empty();
343343

344344
selectElement.on('change', function() {
345345
scope.$apply(function() {

src/ngAnimate/animate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,7 @@ angular.module('ngAnimate', ['ng'])
12371237
//make the element super hidden and override any CSS style values
12381238
clone.attr('style','position:absolute; top:-9999px; left:-9999px');
12391239
clone.removeAttr('id');
1240-
clone.html('');
1240+
clone.empty();
12411241

12421242
forEach(oldClasses.split(' '), function(klass) {
12431243
clone.removeClass(klass);

test/helpers/testabilityPatch.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ beforeEach(function() {
2929
bindJQuery();
3030
}
3131

32-
33-
angular.element(document.body).html('').removeData();
32+
angular.element(document.body).empty().removeData();
3433
});
3534

3635
afterEach(function() {

0 commit comments

Comments
 (0)