I'm reading Sizzle source code. I saw the definition below
function Sizzle(selector, context, results, seed)
My question is what's the meaning about the parameter seed? I can't find it in API document
Thanks
addendum
The seed parameter is used in jQuery's event handler source (from 2.1.4):
jQuery.find = Sizzle;
// [...]
jQuery.event = {
// [..]
handlers: function( event, handlers ) {
// [..]
// Find delegate handlers
if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
for ( ; cur !== this; cur = cur.parentNode || this ) {
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
if ( cur.disabled !== true || event.type !== "click" ) {
matches = [];
for ( i = 0; i < delegateCount; i++ ) {
handleObj = handlers[ i ];
// Don't conflict with Object.prototype properties (#13203)
sel = handleObj.selector + " ";
if ( matches[ sel ] === undefined ) {
matches[ sel ] = handleObj.needsContext ?
jQuery( sel, this ).index( cur ) >= 0 :
//
// Right here to find if cur matches the
// delegated event handler's selector.
//
jQuery.find( sel, this, null, [ cur ] ).length;
// There: -----------------------^
}
if ( matches[ sel ] ) {
matches.push( handleObj );
}
}
if ( matches.length ) {
handlerQueue.push({ elem: cur, handlers: matches });
}
}
}
}
},
You can use the seed parameter to limit the selection to a list of candidates. Just pass in an array of DOM elements.
For example let's say we have the following DOM:
<div id="id1"></div>
<div id="id2"></div>
Then, perform the following selections:
Sizzle("#id1", null, null, null);
// [<div id="id1"></div>]
And:
var candidates = [
document.getElementById("id1"),
document.getElementById("id2")
];
Sizzle("#id1", null, null, candidates);
// [<div id="id1"></div>]
But:
var candidates = [
document.getElementById("id2")
];
Sizzle("#id1", null, null, candidates);
// []
Note: This functionality doesn't seem to be part of the public API.
A seed is usually used to determine specific sequences of pseudo-random numbers. If you want the same repeated order of numbers on every run you use the same seed. Random number generators can use time stamps to make sure seeds vary, but for testing it it extremely useful to be able to set such seeds.
I assume the seed in this case will have a similar meaning, it will mean the outcome of Sizzle will be identical on every run if the seed is the same, if it is different the outcomes will be different.
Related
Like the title says. If it's not then what would be the same as .innerHTML = "" ?
It's nearly the same. If you look at the source for the method, you'll see that it's:
empty: function() {
var elem,
i = 0;
for ( ; ( elem = this[ i ] ) != null; i++ ) {
if ( elem.nodeType === 1 ) {
// Prevent memory leaks
jQuery.cleanData( getAll( elem, false ) );
// Remove any remaining nodes
elem.textContent = "";
}
}
return this;
},
And assigning the empty string to the .textContent of an element is the same as assigning the empty string to the .innerHTML of an element.
The only difference is that .empty calls .cleanData, which removes a number of jQuery-specific data/events associated with the element, if there happen to be any.
I am checking if the selector has a certain class, id, or both. If there is a match on id, class, or both, eitherClassIdOrBoth() pushes the selector into the elements array. It works perfectly fine, but I was wondering if I could achieve this in ES6 using the spread operator.
This is where it returns a boolean:
var idAndClassMatch = function(matchId, matchClass, matchBoth) {
return (
(matchBoth && matchId && matchClass) || (!matchBoth && (matchId || matchClass))); }
This is the function in which I want to use the spread operator:
var elements = []
function eitherClassIdOrBoth(
selectId, selectClass, array, matchBoth, elements
)
{
for (let i = 0; i < array.length; i++) {
var classSection = array[i].className.split(" ");
var matchId = selectId !== undefined && array[i].id === selectId;
var matchClass = classSection !== undefined &&
classSection.indexOf(selectClass) !== -1;
if (idAndClassMatch(matchId, matchClass, matchBoth)) {
elements.push(array[i]);
}
}
}
I am passing these values from an if statement:
if (arr.length === 2) {
computedFunction.eitherClassIdOrBoth(
selectId, selectClass, tags, false, Allelements
);
}
Any help would be extremely helpful!
There are better ways to write this logic, but the main point probably isn't ES6-specific.
The biggest thing that jumps out at me is that the main query of this whole function could be replaced with calls to Element#matches which checks an element based on a CSS selector. It isn't available consistently on older browsers, but it is trivial to load a polyfill to make sure it is available. Then your whole
computedFunction.eitherClassIdOrBoth(
selectId, selectClass, tags, false, Allelements
);
call would just be
Allelements.push(
...tags.filter(tag => tag.matches(`.${selectClass}, [id="${selectId}"]`)
);
e.g. Use .filter and .matches to make a new array with just items with the given tags array, and then use an ES6 spread to easily push all the items into the Allelements array.
You seem to have a few cases where selectClass or selectId could be undefined, so you could also build that query up, e.g.
const selector = [
selectClass ? `.${selectClass}` : "",
selectId ? `[id="${selectId}"]` : "",
].filter(Boolean).join(",");
Allelements.push(
...tags.filter(tag => selector ? tag.matches(selector) : false
);
and for cases where matchBoth is true, you just join the selector with "" instead of ",". So you'd end up with
function eitherClassIdOrBoth(
selectId, selectClass, array, matchBoth, elements
) {
const selector = [
selectClass ? `.${selectClass}` : "",
selectId ? `[id="${selectId}"]` : "",
].filter(Boolean).join(matchBoth ? "" : ",");
if (selector) {
elements.push(...array.filter(tag => tag.matches(selector));
}
}
if you wanted a general utility.
context
I created an array docket to keep track of coordinates as the user clicks on a canvas space. During the main program loop the array is to be scanned by a draw function so that selected pixels can be seen. Originally, inside of my event listener, I was using the push( ) method but then I realized I wanted a way to sort of toggle the pixels.
code description
So I added a method poke( ) to Array.prototype, as seen below, which allows me to push the whole docket array into a local array param.array and assign the trigger coordinate to a local variable param.entry. entry is then pushed into array and array is processed by the main poke( ) loop to ensure there are no duplicate values. If a match is found, both elements are annihilated and param.array is returned to the top, ultimately shrinking docket by 1; If no matches are found then no elements are annihilated and param.array is returned to the top, ultimately expanding docket by 1.
main issue: example 1
Anyway, as the method is currently written, it must be called thusly:
docket.poke( docket, e.key ); Note: for simplicity I have used keyboard key values.
Array.prototype.poke = function( a, b ) {
var bool = { }, i = { }, param = { };
param.array = a; param.entry = b;
//
param.array.push( param.entry );
i.len = param.array.length;
i.end = i.len - 1;
//
for ( i.cur = 0; i.cur < i.len; i.cur++ ) {
bool.match = param.array[ i.cur ] == param.array[ i.end ];
bool.nSelf = !( i.cur == i.end );
//
if ( bool.match && bool.nSelf ) {
param.array.splice( i.end, 1 );
param.array.splice( i.cur, 1 );
//
i.end -= 2;
i.len -= 2;
}
}
//
return param.array;
}
This seems a little redundant, but it offers two critical advantages. First to readability and aesthetic. Being able to visibly pass off the contents of docket to a local array for processing and then visibly return the results to the top I think is very helpful to comprehension. Second, both this example and the next use a sort of confusing truth test to filter out false positives on duplicate value detection. This example doesn't have too though. It could easily be rewritten to compare each element in param.array to param.entry using a tight, no nonsense for loop.
main issue: example 2
docket.poke( e.key ); is the less redundant and more desired approach. This is my code.
Array.prototype.poke = function( a ) {
var bool = { }, entry = a, i = { };
//
this.push( entry );
i.len = this.length;
i.end = i.len - 1;
//
for ( i.cur = 0; i.cur < i.len; i.cur++ ) {
bool.match = this[ i.cur ] == this[ i.end ];
bool.nSelf = !( i.cur == i.end );
//
if ( bool.match && bool.nSelf ) {
this.splice( i.end, 1 );
this.splice( i.cur, 1 );
//
i.end -= 2;
i.len -= 2;
}
}
}
As you can see, this eliminates the the redundancy in the call, but it sacrifices some readability of the method and more importantly the opportunity to really slim up the code using the simple comparison I mentioned above.
So now I'm wondering if there is some less than obvious way that I've missed which will allow me to pass the full contents of my array to a local variable without having to first pass them in as a parameter of its own method.
Any ideas?
There is no reason to define the method on the prototype if you are going to pass the array as an argument. A plain function would be just fine for that.
The second version of your code has indeed the advantage that you can apply the method to a given array instead of passing the array to a function.
The code could however be simplified if:
You would only add the element after you have determined it does not yet occur in the array
You would use indexOf:
Array.prototype.toggle = function(value) {
var index = this.indexOf(value);
if (index > -1) {
this.splice(index, 1);
} else {
this.push(value);
}
}
var a = [4,2,5,8];
a.toggle(2);
console.log(a.join());
a.toggle(2);
console.log(a.join());
NB: I personally find the name toggle more telling than poke.
Consider also the power of a Set: it will find an existing member in constant time (while an array implementation needs linear time), and will also be able to remove it in constant time. So if you are open to using something else than an array for this, go for a Set.
Set.prototype.toggle = function(value) {
if (!this.delete(value)) this.add(value);
}
var a = new Set([4,2,5,8]);
a.toggle(2);
console.log([...a].join());
a.toggle(2);
console.log([...a].join());
jQuery's .append() function can take multiple arguments, either flat or in an array. I have some code where I need to append 3 items, one of which might not exist, like:
whatever.append(always).append(maybe).append(alwaysToo);
/* or */
whatever.append(always, maybe, alwaysToo);
/* or */
var arrayOfThoseThree = [ always, maybe, alwaysToo ];
whatever.append(arrayOfThoseThree);
I can not make out from the jQuery docs what, if anything, the value of maybe should be to say "just ignore this one":
maybe = '';
maybe = null;
maybe = undefined;
maybe = ???
as in:
maybe = needMaybe ? $('<blah...>') : ignoreThisValue;
I could, of course, do something like:
whatever.append(always);
if (maybe) whatever.append(maybe);
whatever.append(alwaysToo);
but that's ugly (especially as this is part of a larger chain).
And I could experiment with different values until I find one that "works", but I was hoping there was an "official" documented way that won't fail to work some future day because I was using an "undocumented feature".
Point me in the right direction?
[EDIT]
I was wondering in general, but the concrete example in front of me is:
var titl = this.dataset.title; /* optional */
var ifr = $('<iframe>');
var bas = $('<base href="' + document.baseURI + '">');
var ttl = titl ? $('<title>' + titl + '</title>') : null; /* HERE */
var lnk = $('<link rel="stylesheet" href="/css/print.css">');
/* ... */
ifr.contents().find('head').append(bas, ttl, lnk);
How about
whatever.append([always, maybe, alwaysToo].filter(item => !!item));
Here's what happens in the jQuery code (the version I'm using anyway).
Note that this defines what "works" today, not what is documented to work and continue working in the future.
The .append() function is written similarly to many others in that domManip() does much of the work:
append: function() {
return this.domManip( arguments, function( elem ) {
if ( this.nodeType === 1 ||
this.nodeType === 11 ||
this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.appendChild( elem );
}
});
},
and the first thing domManip() does is:
domManip: function( args, callback ) {
// Flatten any nested arrays
args = concat.apply( [], args );
then it calls buildFragment():
fragment = jQuery.buildFragment( args, ... );
which does:
buildFragment: function( elems, context, scripts, selection ) {
var /* ..., */ i = 0;
for ( ; i < l; i++ ) {
elem = elems[ i ];
if ( elem || elem === 0 ) {
/* ... process this argument ... */
}
}
So empty arrays get squashed by Array.prototype.concat() and then anything that fails the test ( elem || elem === 0 ) gets ignored.
So, in fact, when ttl could be null, all of these (currently) do "the right thing":
whatever.append( bas, ttl, lnk);
whatever.append([bas, ttl, lnk]);
whatever.append([bas],[ttl], [lnk]);
whatever.append( bas, [ttl], lnk);
whatever.append(bas).append( ttl ).append(lnk);
whatever.append(bas).append([ttl]).append(lnk);
But, as near as I can find, the documentation makes no statements about a value or values which you can use which will safely be ignored (now and forever).
Thus the safest course of action (at least where => is supported) is the Answer from Assan:
whatever.append( [bas, ttl, lnk].filter( e => !!e ) );
I am writing a UserScript that will remove elements from a page that contain a certain string.
If I understand jQuery's contains() function correctly, it seems like the correct tool for the job.
Unfortunately, since the page I'll be running the UserScript on does not use jQuery, I can't use :contains(). Any of you lovely people know what the native way to do this is?
http://codepen.io/coulbourne/pen/olerh
This should do in modern browsers:
function contains(selector, text) {
var elements = document.querySelectorAll(selector);
return [].filter.call(elements, function(element){
return RegExp(text).test(element.textContent);
});
}
Then use it like so:
contains('p', 'world'); // find "p" that contain "world"
contains('p', /^world/); // find "p" that start with "world"
contains('p', /world$/i); // find "p" that end with "world", case-insensitive
...
Super modern one-line approach with optional chaining operator
[...document.querySelectorAll('*')].filter(element => element.childNodes?.[0]?.nodeValue?.match('❤'));
And better way is to search in all child nodes
[...document.querySelectorAll("*")].filter(e => e.childNodes && [...e.childNodes].find(n => n.nodeValue?.match("❤")))
If you want to implement contains method exaclty as jQuery does, this is what you need to have
function contains(elem, text) {
return (elem.textContent || elem.innerText || getText(elem)).indexOf(text) > -1;
}
function getText(elem) {
var node,
ret = "",
i = 0,
nodeType = elem.nodeType;
if ( !nodeType ) {
// If no nodeType, this is expected to be an array
for ( ; (node = elem[i]); i++ ) {
// Do not traverse comment nodes
ret += getText( node );
}
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// Use textContent for elements
// innerText usage removed for consistency of new lines (see #11153)
if ( typeof elem.textContent === "string" ) {
return elem.textContent;
} else {
// Traverse its children
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
ret += getText( elem );
}
}
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue;
}
// Do not include comment or processing instruction nodes
return ret;
};
SOURCE: Sizzle.js
The original question is from 2013
Here is an even older solution, and the fastest solution because the main workload is done by the Browser Engine NOT the JavaScript Engine
The TreeWalker API has been around for ages, IE9 was the last browser to implement it... in 2011
All those 'modern' and 'super-modern' querySelectorAll("*") need to process all nodes and do string comparisons on every node.
The TreeWalker API gives you only the #text Nodes, and then you do what you want with them.
You could also use the NodeIterator API, but TreeWalker is faster
function textNodesContaining(txt, root = document.body) {
let nodes = [],
node,
tree = document.createTreeWalker(
root,
4, // NodeFilter.SHOW_TEXT
{
node: node => RegExp(txt).test(node.data)
});
while (node = tree.nextNode()) { // only return accepted nodes
nodes.push(node);
}
return nodes;
}
Usage
textNodesContaining(/Overflow/);
textNodesContaining("Overflow").map(x=>console.log(x.parentNode.nodeName,x));
// get "Overflow" IN A parent
textNodesContaining("Overflow")
.filter(x=>x.parentNode.nodeName == 'A')
.map(x=>console.log(x));
// get "Overflow" IN A ancestor
textNodesContaining("Overflow")
.filter(x=>x.parentNode.closest('A'))
.map(x=>console.log(x.parentNode.closest('A')));
This is the modern approach
function get_nodes_containing_text(selector, text) {
const elements = [...document.querySelectorAll(selector)];
return elements.filter(
(element) =>
element.childNodes[0]
&& element.childNodes[0].nodeValue
&& RegExp(text, "u").test(element.childNodes[0].nodeValue.trim())
);
}
Well, jQuery comes equipped with a DOM traversing engine that operates a lot better than the one i'm about to show you, but it will do the trick.
var items = document.getElementsByTagName("*");
for (var i = 0; i < items.length; i++) {
if (items[i].innerHTML.indexOf("word") != -1) {
// Do your magic
}
}
Wrap it in a function if you will, but i would strongly recommend to use jQuery's implementation.