Skip to content

Commit 53df365

Browse files
authored
Merge pull request #21 from dbezborodovrp/feature/autoperf
Simplify performance improvements.
2 parents 764f068 + fa800ad commit 53df365

3 files changed

Lines changed: 94 additions & 55 deletions

File tree

jquery.initialize.js

Lines changed: 78 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,58 @@
55
* Copyright (c) 2015-2016 Adam Pietrasiak
66
* Released under the MIT license
77
* https://github.com/timpler/jquery.initialize/blob/master/LICENSE
8+
*
9+
* This is based on MutationObserver
10+
* https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
811
*/
912
;(function ($) {
1013

1114
"use strict";
1215

16+
var combinators = [' ', '>', '+', '~']; // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors#Combinators
17+
var fraternisers = ['+', '~']; // These combinators involve siblings.
18+
var complexTypes = ['ATTR', 'PSEUDO', 'ID', 'CLASS']; // These selectors are based upon attributes.
19+
20+
// Understand what kind of selector the initializer is based upon.
21+
function grok(msobserver) {
22+
if (!$.find.tokenize) {
23+
// This is an old version of jQuery, so cannot parse the selector.
24+
// Therefore we must assume the worst case scenario. That is, that
25+
// this is a complicated selector. This feature was available in:
26+
// https://github.com/jquery/sizzle/issues/242
27+
msobserver.isCombinatorial = true;
28+
msobserver.isFraternal = true;
29+
msobserver.isComplex = true;
30+
return;
31+
}
32+
33+
// Parse the selector.
34+
msobserver.isCombinatorial = false;
35+
msobserver.isFraternal = false;
36+
msobserver.isComplex = false;
37+
var token = $.find.tokenize(msobserver.selector);
38+
for (var i = 0; i < token.length; i++) {
39+
for (var j = 0; j < token[i].length; j++) {
40+
if (combinators.indexOf(token[i][j].type) != -1)
41+
msobserver.isCombinatorial = true; // This selector uses combinators.
42+
43+
if (fraternisers.indexOf(token[i][j].type) != -1)
44+
msobserver.isFraternal = true; // This selector uses sibling combinators.
45+
46+
if (complexTypes.indexOf(token[i][j].type) != -1)
47+
msobserver.isComplex = true; // This selector is based on attributes.
48+
}
49+
}
50+
}
51+
1352
// MutationSelectorObserver represents a selector and it's associated initialization callback.
1453
var MutationSelectorObserver = function (selector, callback, options) {
15-
this.selector = selector;
54+
this.selector = selector.trim();
1655
this.callback = callback;
1756
this.options = options;
18-
};
1957

20-
// List of mutation types that are observable.
21-
var mtypes = ['childList', 'attributes'];
58+
grok(this);
59+
};
2260

2361
// List of MutationSelectorObservers.
2462
var msobservers = [];
@@ -43,70 +81,56 @@
4381

4482
// The MutationObserver watches for when new elements are added to the DOM.
4583
var observer = new MutationObserver(function (mutations) {
46-
4784
var matches = [];
48-
function add(match) {
85+
function push(match) {
4986
matches.push(match);
5087
}
5188

5289
// For each mutation.
5390
for (var m = 0; m < mutations.length; m++) {
5491

55-
// Do we observe this mutation type?
56-
if ($.inArray(mutations[m].type, mtypes) == -1) continue;
57-
58-
if (msobserver.options.scanMode == 'target') {
59-
60-
// Search within the observed node for elements matching the selector.
61-
// This can take longer, but we are more likely to find a match with
62-
// complex selectors.
63-
msobserver.options.target.querySelectorAll(msobserver.selector).forEach(add);
64-
} else if (msobserver.options.scanMode == 'descendants') {
65-
66-
// If this is an attributes mutation, then the target is the node upon which the mutation occurred.
67-
if (mutations[m].type == 'attributes') {
68-
mutations[m].target.querySelectorAll(msobserver.selector).forEach(add);
69-
if (mutations[m].target.matches(msobserver.selector)) {
70-
matches.push(mutations[m].target);
71-
}
72-
} else if (mutations[m].type == 'childList') {
73-
74-
// Otherwise, search for added nodes.
75-
// Search added nodes only for matching selectors.
76-
for (var n = 0; n < mutations[m].addedNodes.length; n++) {
77-
if (!(mutations[m].addedNodes[n] instanceof Element)) continue;
78-
79-
mutations[m].addedNodes[n].querySelectorAll(msobserver.selector).forEach(add);
80-
if (mutations[m].addedNodes[n].matches(msobserver.selector)) {
81-
matches.push(mutations[m].addedNodes[n]);
82-
}
83-
}
84-
}
85-
} else if (msobserver.options.scanMode == 'exact') {
86-
87-
// Similar to descendant scan mode, except it will not search within child nodes.
88-
// This offers the most performance.
89-
if (mutations[m].type == 'attributes') {
90-
if (mutations[m].target.matches(msobserver.selector))
91-
matches.push(mutations[m].target);
92-
} else if (mutations[m].type == 'childList') {
93-
for (var n = 0; n < mutations[m].addedNodes.length; n++) {
94-
if (!(mutations[m].addedNodes[n] instanceof Element)) continue;
95-
if (mutations[m].addedNodes[n].matches(msobserver.selector))
96-
matches.push(mutations[m].addedNodes[n]);
97-
}
92+
// If this is an attributes mutation, then the target is the node upon which the mutation occurred.
93+
if (mutations[m].type == 'attributes') {
94+
// Check if the mutated node matchs.
95+
if (mutations[m].target.matches(msobserver.selector))
96+
matches.push(mutations[m].target);
97+
98+
// If the selector is fraternal, query siblings of the mutated node for matches.
99+
if (msobserver.isFraternal)
100+
mutations[m].target.parentElement.querySelectorAll(msobserver.selector).forEach(push);
101+
else
102+
mutations[m].target.querySelectorAll(msobserver.selector).forEach(push);
103+
}
104+
105+
// If this is an childList mutation, then inspect added nodes.
106+
if (mutations[m].type == 'childList') {
107+
108+
// Search added nodes for matching selectors.
109+
for (var n = 0; n < mutations[m].addedNodes.length; n++) {
110+
if (!(mutations[m].addedNodes[n] instanceof Element)) continue;
111+
112+
// Check if the added node matches the selector
113+
if (mutations[m].addedNodes[n].matches(msobserver.selector))
114+
matches.push(mutations[m].addedNodes[n]);
115+
116+
// If the selector is fraternal, query siblings for matches.
117+
if (msobserver.isFraternal)
118+
mutations[m].addedNodes[n].parentElement.querySelectorAll(msobserver.selector).forEach(push);
119+
else
120+
mutations[m].addedNodes[n].querySelectorAll(msobserver.selector).forEach(push);
98121
}
99-
100122
}
101123
}
102124

125+
// For each match, call the callback using jQuery.each() to initialize the element (once only.)
103126
matches.forEach(function(match) {
104127
$(match).each(msobserver.callback);
105128
});
106129
});
107130

108131
// Observe the target element.
109-
observer.observe(options.target, {childList: true, subtree: true, attributes: true});
132+
var defaultObeserverOpts = { childList: true, subtree: true, attributes: msobserver.isComplex };
133+
observer.observe(options.target, options.observer || defaultObeserverOpts );
110134
};
111135

112136
// Deprecated API (does not work with jQuery >= 3.1.1):
@@ -119,9 +143,10 @@
119143
msobservers.initialize(selector, callback, $.extend({}, $.initialize.defaults, options));
120144
};
121145

146+
// Options
122147
$.initialize.defaults = {
123-
scanMode: 'target', // Can be either: 'target', 'descendants', or 'exact'
124-
target: document.documentElement // Defaults observe the entire document.
148+
target: document.documentElement, // Defaults to observe the entire document.
149+
observer: null // MutationObserverInit: Defaults to internal configuration if not provided.
125150
}
126151

127152
})(jQuery);

jquery.initialize.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test2.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ <h2>We want every .initialize-me item to have color changed to blue by js - no m
3333
</div>
3434
</div>
3535

36+
<div id="sibling-test" style="border: 1px dashed blue; margin: 15px;">
37+
<p>This should be bold, if there is a div in front of it.</p>
38+
<button id="add-div">Add div</button>
39+
</div>
40+
3641
<div class="initialize-me">
3742
This item wont be initialised because it is outside the target element that is being observed.
3843
</div>
@@ -41,7 +46,6 @@ <h2>We want every .initialize-me item to have color changed to blue by js - no m
4146

4247
$(function() {
4348

44-
$.initialize.defaults.scanMode = 'descendants';
4549
$.initialize.defaults.target = document.getElementById('target-element');
4650

4751
$.initialize('.initialize-me', function() {
@@ -53,6 +57,10 @@ <h2>We want every .initialize-me item to have color changed to blue by js - no m
5357
$(this).css('color', 'white');
5458
});
5559

60+
$.initialize('div + p', function() {
61+
$(this).css('font-weight', 'bold');
62+
}, { target: document.getElementById('sibling-test') });
63+
5664
$('#add-new').click(function(){
5765
var $div = $('<div>')
5866
.addClass('initialize-me')
@@ -72,6 +80,12 @@ <h2>We want every .initialize-me item to have color changed to blue by js - no m
7280
$('.wrong-class').addClass('initialize-me');
7381
});
7482

83+
$('#add-div').click(function(){
84+
var $div = $('<div>')
85+
.text('Hello, brother. I am a div that is sibling to the paragraph!')
86+
.prependTo('#sibling-test');
87+
});
88+
7589
});
7690

7791
</script>

0 commit comments

Comments
 (0)