Skip to content

Instantly share code, notes, and snippets.

@c7x43t
Last active June 26, 2018 09:26
Show Gist options
  • Save c7x43t/b6cd499910bc5ea43473318282e5748a to your computer and use it in GitHub Desktop.
Save c7x43t/b6cd499910bc5ea43473318282e5748a to your computer and use it in GitHub Desktop.
(function(global) {
// Instead of looping trough all possibilities a object property lookup is used
// The object construction is in addition memoized, since it's relatively expensive
var hashFindHash = {};
function hashFind(str, hash, delimiter) {
delimiter = delimiter || ";";
if (hashFindHash.hasOwnProperty(hash)) {
return hashFindHash[hash].hasOwnProperty(str);
} else {
var obj = {};
map(hash.split(delimiter), function(e) {
obj[e] = true;
});
hashFindHash[hash] = obj;
return hashFind(str, hash);
}
}
// loops once trough the string parsing and interpreting it
var activeElements = [];
var activePosition = 0;
var pos = 0;
var sel;
function fastQuery(selector) {
sel = selector;
while (pos < sel.length) {
//console.log({pos:pos,len:sel.length});
//console.log({pos:pos[0],len:sel.length})
main();
//console.log({context:arguments.callee.name,activeElements:activeElements})
}
return activeElements;
}
function handleClass() {
// console.log({context:arguments.callee.name});
// find index of first character after the className
var className = indexOfAny(sel, "[;#;:;.;::;~;+;>; ;,", pos);
//console.log({className:className})
var tmp;
if (activeElements[activePosition] instanceof Element) {
tmp = document.getElementsByClassName(className);
//console.log({tmp:tmp})
map(tmp, function(e) {
//console.log(e);
activeElements.push(e);
}, activePosition);
} else {
if (sel[pos - 1] === " ") {
map(activeElements, function(e) {
tmp = e.getElementsByClassName(className);
map(tmp, function(e) {
activeElements.push(e);
});
}, activePosition);
} else {
map(activeElements, function(e) {
classList = e.classList;
map(classList, function(c) {
if (className === c) {
activeElements.push(e);
}
});
}, activePosition);
}
}
}
function map(arr, f, startIndex) {
var tmp = [];
for (var i = startIndex || 0, l = arr.length; i < l; i++) {
tmp[i] = f(arr[i], i, arr);
}
return tmp;
}
function handleID() {
var idName = indexOfAny(sel, "[;#;:;.;::;~;+;>; ;,", pos);
// console.log({context:arguments.callee.name,idName:idName});
activeElements.length = activePosition;
activeElements.push(document.getElementById(idName));
}
function handleTag() {
var tagName = indexOfAny(sel, "[;#;:;.;::;~;+;>; ;,", pos);
//console.log({context:arguments.callee.name,tagName:tagName});
var tmp;
if (activeElements[activePosition] instanceof Element) {
tmp = document.getElementsByTagName(tagName);
map(tmp, function(e) {
//console.log(e);
activeElements.push(e);
}, activePosition);
} else {
map(activeElements, function(e) {
tmp = e.getElementsByTagName(tagName);
map(tmp, function(e) {
activeElements.push(e);
});
}, activePosition);
}
}
function handleStar() {
activeElements = document.getElementsByTagName("*");
}
function indexOfAny(str, els) {
// console.log({context:arguments.callee.name});
var substr = "";
var i = pos || 0,
l = str.length;
for (; i < l; i++) {
if (hashFind(str[i], els)) {
pos = i;
return substr;
}
substr += str[i];
}
// console.log({settingPos:i})
pos = i;
return substr;
}
function main() {
pos++;
switch (sel[pos - 1]) {
case "*":
return handleStar();
case ".":
return handleClass();
case "#":
return handleID();
case "[":
return handleAttribute();
case ":":
// :
if (sel[pos + 1] !== ":") {
return handlePseudo();
// ::
} else {
pos++;
return handleCSS();
}
case ",":
return handleComma();
// new activeElements array or with pointer
case "~":
break;
// a.parentElement.children filter: e===b
case " ":
break;
// new query using active Elements as base
case ">":
break;
// either a.children filter: e===b or filter: b.parentElement===a
case "+":
break;
// a+b: filter: a.nextElementSibling === b
// TAGs
default:
//console.log({main:"TAG found",pos:pos});
pos--;
return handleTag();
}
}
global.fastQuery = fastQuery;
}(window));
// Note: don't use firstChild/lastChild because it caches empty Text Nodes (false positive)
// use .children instead.
// principle
// 2 active element stacks, one holds intermediate elements, the other the next filterstep
// swap back and forth by pointer
//.intro
//if no activeElements: .b
document.getElementsByClassName(className) map: activeElements.push(e)
// if activeElements: a .b
map(activeElements, ...: e.getElementsByClassName(className) map: activeElements2.push(f)
// if filter: a.b
map: a.classList filter: e === className
// #Lastname
activeElements = [] activeElements.push(document.getElementById(id))
//.intro, #Lastname
// push result before , into finalElements (by pointer)
// at the end return finalElements or else activeElements
//h1
//if no activeElements: b
document.getElementsByTagName(tagName) map: activeElements.push(e)
// if activeElements: a b
map(activeElements, ...: e.getElementsByTagName(tagName) map: activeElements2.push(f)
//div p
// buildin as activeElements filter
//div > p: a > b
a.children filter: e === b //O(a.children*b) <-reduce complexity
// with e.query b reduce: acc&= x === e
//ul + p: a + b
filter: a.nextElementSibling === b
//ul ~ table: a ~ b
a.parentElement.children filter: e === b // O(a.parentElement.children*b) <-reduce complexity
//* => immediatly return:
document.getElementsByTagName("*");
//[id]
//[id=my-Address]
//[id$=ess]
//[id|=my]
//[id^=L]
//[title~=beautiful]
//[id*=s]
// use getElementsByName if id===name else use querySelectorAll
// if no activeElements: [id]
// else if activeElements: a [id]: a.map: e.getElementsByName
//:checked
//:disabled
//:enabled
//:empty
//:focus
//p:first-child: a:first-child
function helper(e) {
return e.parentElement.firstChild === e
}; filter(activeElements, helper)
//p::first-letter
//p::first-line
//p:first-of-type === li:nth-of-type(1)
//h1:hover
//input:in-range
function helper(e) {
return e.min !== "" && e.max !== "" && e.value !== "" && +e.min <= +e.value && +e.value <= +e.max
}; filter(document.getElementsByTagName("input"), helper)
//input:out-of-range
function helper(e) {
return e.min !== "" && e.max !== "" && e.value !== "" && +e.min > +e.value && +e.value > +e.max
}; filter(document.getElementsByTagName("input"), helper)
//input:invalid
//input:valid
//p:lang(it)
//p:last-child: fix: multiple parent nodes
var p = "p";
function helper(e) {
return e.parentElement.lastChild === e
}; filter(document.getElementsByTagName(p), helper)
//p:last-of-type // works === li:nth-last-of-type(1)
var p = "p";
function helper(e) {
var candidates = e.parentElement.getElementsByTagName(p);
for (var i = candidates.length - 1; i >= 0; i--) {
if (e.parentElement === candidates[i].parentElement) {
return e === candidates[i];
}
}
return false;
}; filter(document.getElementsByTagName(p), helper)
//tr:nth-child(even) <- even/odd way too inefficient (75x)
var tr = "tr";
function helper(e, i) {
var children = e.parentElement.children;
for (var i = 0; i < (children.length - children.length % 2) / 2; i++) {
if (children[i] === e) return true;
}
return false;
}; filter(document.getElementsByTagName(tr), helper)
//tr:nth-child(odd)
//li:nth-child(n) //works
function helper(e, i) {
return e === e.parentElement.children[n - 1]
}; filter(document.getElementsByTagName(li), helper)
//li:nth-last-child(1)
function helper(e, i) {
var children = e.parentElement.children;
return e === children[children.length - n]
}; filter(document.getElementsByTagName(li), helper)
//li:nth-of-type(2) // works
var li = "li", n = 2;
function helper(e) {
var candidates = e.parentElement.getElementsByTagName(li);
for (var i = n - 1; i < candidates.length; i++) {
if (e.parentElement === candidates[i].parentElement) {
return e === candidates[i];
}
}
return false;
}; filter(document.getElementsByTagName(li), helper)
//li:nth-last-of-type(2)
var li = "li", n = 2;
function helper(e) {
var candidates = e.parentElement.getElementsByTagName(li);
for (var i = candidates.length - n; i >= 0; i--) {
if (e.parentElement === candidates[i].parentElement) {
return e === candidates[i];
}
}
return false;
}; filter(document.getElementsByTagName(li), helper)
//b:only-child // works
function helper(e) {
return e.parentElement.children.length === 1
}; filter(activeElements, helper)
//h3:only-of-type // -> too slow
var h3 = "h3";
function helper(e) {
var n = 0;
map(e.parentElement.getElementsByTagName(h3), function(candidate) {
if (e.parentElement === candidate.parentElement) n++;
});
return n === 1;
}; filter(document.getElementsByTagName(h3), helper)
//:root
var HTML = "html"; document.getElementsByTagName(HTML);
//Helper
function filter(arr, f, startIndex) {
var tmp = [];
for (var i = startIndex || 0, l = arr.length; i < l; i++) {
if (f(arr[i], i, arr)) tmp.push(arr[i]);
}
return tmp;
}
function map(arr, f, startIndex) {
var tmp = [];
for (var i = startIndex || 0, l = arr.length; i < l; i++) {
tmp[i] = f(arr[i], i, arr);
}
return tmp;
}
//Benchmark
function helper(e, i) {
return i % 2 === 0
};
var funcs = [
e => filter(document.getElementsByTagName("tr"), helper),
e => {
document.querySelectorAll("tr:nth-child(even)")
},
]; benchmark(funcs, 1e5, ["it"])
var selectorAtomizer = /(((.*?)(\[))?|(\.|::|:|#| |~|\+|>)?)(.*?)(\.|~=|\$=|\|=|\*=|\^=|=|::|:|\]|#|\(.*?\)| |~|\+|>|$)/g;
var sanitizeCombinators = / *(~=|\$=|\|=|\*=|\^=|=|>|~|\+|,|\[|\(|\)) */g;
var sanitizeSpaces = / +/g;
regExCommaSplit = / *, */;
var pointers = [3, 4, 6, 7];
function fastSelector(element, selector) {
element = element || document;
// clean up selector string
selector = selector.replace(sanitizeCombinators, '$1');
selector = selector.replace(sanitizeSpaces, ' ');
// splitting non related parts
var selectors = selector.split(regExCommaSplit);
// pointers to relevant regEx matching groups
var result = [];
selectors.forEach(function(selector) {
var activeElement = element;
var regex = new RegExp(selectorAtomizer);
do {
var tmp = regex.exec(selector);
// this is akin to looping over a sparse array
pointers.forEach(function(pointer) {
var current = tmp[pointer];
if (current && current !== "") {
// interpret chunk
result.push(current);
}
});
//result.push(tmp);
} while (tmp.index < selector.length);
});
return result;
}
//fastSelector("a, b c d + e ~ f > g [efe]")
var str = "div:nth-child( 2n ),div#scene1 div.dialog div,div[attribute=value]";
fastSelector(document, str);
// split at:
// : :: [ [space] ~ + > # .
// https://regex101.com/r/gy5IGh/1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment