Home  >  Article  >  Web Front-end  >  Prototype Selector object learning_prototype

Prototype Selector object learning_prototype

WBOY
WBOYOriginal
2016-05-16 18:49:141087browse
Copy code The code is as follows:

function $$() {
return Selector.findChildElements(document , $A(arguments));
}

This class can be divided into three parts: The first part is to determine what DOM operation method to use according to different browsers. Among them, operating IE is to use the ordinary getElementBy* series of methods; FF is document.evaluate; Opera and Safari are selectorsAPI. The second part is the basic functions provided externally, such as findElements, match, etc. Many methods in the Element object directly call the methods in this object. The third part is XPath and other matching criteria for querying DOM, such as what string represents the search for first-child, and what string represents the search for nth-child.

Since there are many methods in this object, I will not give all the source code. In fact, I only understand the code of some methods. Here we use a simple example to walk through the process of DOM selection according to different browsers. In this process, the required source code is given and explained.

The specific example is as follows:
Copy code The code is as follows:

< ;div id="parent2">






The following uses FF as an example. The process is as follows:
Copy code The code is as follows:

/*First find the $$ method, which has been given above, in this method The findChildElements method of Selector will be called, and the first parameter is document, and the remaining parameters are arrays of DOM query strings*/

findChildElements: function(element, expressions) {
//Call here first split processes the string array, determines whether it is legal, and deletes spaces
expressions = Selector.split(expressions.join(','));
//handlers contains some processing of DOM nodes Methods, like concat, unique, etc.
var results = [], h = Selector.handlers;
//Process query expressions one by one
for (var i = 0, l = expressions.length, selector; i < l; i ) {
//New Selector
selector = new Selector(expressions[i].strip());
//Connect the queried node to results
h.concat(results, selector.findElements(element));
}
//If the number of nodes found is greater than one, filter out duplicate nodes
return (l > 1) ? h.unique (results) : results;
}

//================================ ===================

//Selector.split method:
split: function(expression) {
var expressions = [] ;
expression.scan(/(([w#:.~> ()s-] |*|[.*?]) )s*(,|$)/, function(m) {
           //alert(m[1]);
expressions.push(m[1].strip());
});
return expressions;
}

//================================================ ===

//Selector.handlers object
handlers: {
concat: function(a, b) {
for (var i = 0, node; node = b[i ]; i )
a.push(node);
return a;
},
//...Omit some methods
unique: function(nodes) {
if (nodes.length == 0) return nodes;
var results = [], n;
for (var i = 0, l = nodes.length; i < l; i )
if ( typeof (n = nodes[i])._countedByPrototype == 'undefined') {
n._countedByPrototype = Prototype.emptyFunction;
results.push(Element.extend(n));
}
return Selector.handlers.unmark(results);
},

//Now turn to the process of creating a Selector object! !

Copy code The code is as follows:

//Look at the initialization part of Selector first
//It can be seen that the initialization part is to determine what method to use to operate the DOM. Let’s take a look at these methods
var Selector = Class.create( {
initialize: function(expression) {
this.expression = expression.strip();

if (this.shouldUseSelectorsAPI()) {
this.mode = 'selectorsAPI';
} else if (this.shouldUseXPath()) {
this.mode = 'xpath';
this.compileXPathMatcher();
} else {
this.mode = "normal" ;
this.compileMatcher();
}

}

//===================== ==============================

//XPath, FF supports this method
shouldUseXPath: (function() {

//Let’s check if there is a BUG in the browser. I haven’t found the specific cause of this BUG on the Internet. It probably means checking whether a certain node can be found correctly. Number
var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
var isBuggy = false;
if (document.evaluate && window.XPathResult) {
var el = document.createElement('div');
el.innerHTML = '
< /div>';
//The local-name() here means to remove the namespace and search
var xpath = ".//*[local-name()='ul' or local-name ()='UL']"
"//*[local-name()='li' or local-name()='LI']";
//document.evaluate is the core DOM Query method, specific usage can be searched online
var result = document.evaluate(xpath, el, null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

isBuggy = (result.snapshotLength != = 2);
el = null;
}
return isBuggy;
})();

return function() {
//Judge in the returned method Whether this kind of DOM operation is supported.
if (!Prototype.BrowserFeatures.XPath) return false;

var e = this.expression;
//You can see here that Safari does not support -of-type expressions and empty expressions Operation
if (Prototype.Browser.WebKit &&
(e.include("-of-type") || e.include(":empty")))
return false;

if ((/([[w-]*?:|:checked)/).test(e))
return false;

if (IS_DESCENDANT_SELECTOR_BUGGY) return false;

return true;
}

})(),

//====================== ==============================

//Sarafi and opera support this method
shouldUseSelectorsAPI: function() {
if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
//This determines whether case-sensitive search is supported
if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;

if (!Selector._div) Selector._div = new Element('div');
//Check whether an exception will be thrown when querying in an empty div
try {
Selector._div.querySelector (this.expression);
} catch(e) {
return false;
}

//================== ==================================

//Selector.CASE_INSENSITIVE_CLASS_NAMES property
/ *document.compatMode is used to determine the rendering mode used by the current browser.
When document.compatMode is equal to BackCompat, the browser client area width is document.body.clientWidth;
When document.compatMode is equal to CSS1Compat, the browser client area width is document.documentElement.clientWidth. */

if (Prototype.BrowserFeatures.SelectorsAPI &&
document.compatMode === 'BackCompat') {
Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
var div = document. createElement('div'),
span = document.createElement('span');

div.id = "prototype_test_id";
span.className = 'Test';
div .appendChild(span);
var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
div = span = null;
return isIgnored;
}) ();
}

return true;
},

//====================== ==============================

//If neither of these two, use document.getElement( s)By* series of methods for processing. It seems that IE8 has begun to support SelectorAPI. Other versions of IE can only use ordinary methods to query DOM

//The following turns to the shouldUseXPath method supported by FF!!!

Copy code The code is as follows:

//When it is judged that XPath is to be used for query, the compileXPathMatcher method is called

compileXPathMatcher: function() {
//Patterns and xpath are given below
var e = this.expression, ps = Selector.patterns,
x = Selector.xpath, le, m, len = ps.length, name;

//Determine whether the query string e is cached
if (Selector._cache[e]) {
this.xpath = Selector._cache[e]; return;
}
// './/*' means querying under the current node If you don’t know how to represent all nodes, you can go online and look at the XPath representation method
this.matcher = ['.//*'];
//The le here prevents infinite loop searches, and the regular expression matches except a single space All characters except characters
while (e && le != e && (/S/).test(e)) {
le = e;
//Find pattern one by one
for ( var i = 0; i//The name here is the name attribute of the object in pattern
name = ps[i].name;
//Check whether the expression matches here The regular expression of this pattern      
if (m = e.match(ps[i].re)) {
/*
Note here, some of the xpaths below are methods and some are strings, so here You need to judge. If it is a string, you need to call the evaluate method of Template and replace the #{...} string inside; if it is a method, then pass in the correct parameters to call the method
*/
this. matcher.push(Object.isFunction(x[name]) ? x[name](m) :
new Template(x[name]).evaluate(m));
//Remove the matching part , continue the following string matching
e = e.replace(m[0], '');

break;
}
}

}
//Connect all matching xpath expressions to form the final xpath query string
this.xpath = this.matcher.join('');
//Put it in the cache
Selector._cache[this.expression] = this.xpath;
},
//============================ ===================

//These patterns are used to determine what the query string is looking for, based on the corresponding entire expression, for example The string '#navbar' matches according to patterns, then it is id
patterns: [
{ name: 'laterSibling', re: /^s*~s*/ },
{ name: 'child' , re: /^s*>s*/ },
{ name: 'adjacent', re: /^s* s*/ },
{ name: 'descendant', re: /^s / },
{ name: 'tagName', re: /^s*(*|[w-] )(b|$)?/ },
{ name: 'id', re: /^ #([w-*] )(b|$)/ },
{ name: 'className', re: /^.([w-*] )(b|$)/ },
{ name: 'pseudo', re:
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|d
is )abled|not)(((.*?)))?(b|$|(?=s|[: ~>]))/ },
{ name: 'attrPresence', re: /^ [((?:[w-] :)?[w-] )]/ },
{ name: 'attr', re:
/[((?:[w-]*:)? [w-] )s*(?:([!^$*~|]?=)s*((['"])([^4]*?)4|([^'"][^
]]*?)))?]/ }
],

//======================== ======================

/*After finding the pattern, use the corresponding name to find the xpath representation of the corresponding query string. . For example, the above id corresponds to the id string. In compileXPathMatcher, it will be judged whether the xpath is a string or a method. If it is a method, the corresponding parameters will be passed in for the call*/
xpath: {
descendant: "/ /*",
child: "/*",
adjacent: "/following-sibling::*[1]",
laterSibling: '/following-sibling::*',
tagName: function(m) {
if (m[1] == '*') return '';
return "[local-name()='" m[1].toLowerCase()
"' or local-name()='" m[1].toUpperCase() "']";
},
className: "[contains(concat(' ', @class, ' ') , ' #{1} ')]",
id: "[@id='#{1}']",
//...Omit some methods

//= =============================================

//Enter the findElements method of Selector below! !

Copy code The code is as follows:

findElements: function(root) {
//Determine whether root is null. If it is null, set it to document
root = root || document;
var e = this.expression, results ;
//Determine which mode is used to operate the DOM, under FF it is xpath
switch (this.mode) {
case 'selectorsAPI':

if (root !== document) {
var oldId = root.id, id = $(root).identify();
id = id.replace(/[.:]/g, "\$0");
e = "#" id " " e;

}
results = $A(root.querySelectorAll(e)).map(Element.extend);
root.id = oldId;

return results;
case 'xpath':
//Let’s take a look at the _getElementsByXPath method
return document._getElementsByXPath(this.xpath, root);
default:
return this.matcher(root);
}
},

//========================== ==================

//This method actually puts the found nodes into results and returns them. Document.evaluate is used here. The URL for a detailed explanation of this method is given below
if (Prototype.BrowserFeatures.XPath) {
document._getElementsByXPath = function(expression, parentElement) {
var results = [];
var query = document.evaluate(expression, $(parentElement) || document,
null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0, length = query.snapshotLength; i < length; i )
results.push(Element.extend(query.snapshotItem(i)));
return results;
};
}

/*
The following URL is Explanation of the method of document.evaluate: https://developer.mozilla.org/cn/DOM/document.evaluate
*/

Let’s use the examples given to explain it in succession:

First, call the findChildElements method in $$, and expressions are set to ['#navbar a','#siderbar a']

The following call: selector = new Selector(expressions[i].strip ()); Create a new Selector object, call the initialize method, that is, determine what DOM API to use. Since it is FF, it is this.shouldUseXPath(), then call compileXPathMatcher()

and then compileXPathMatcher() var e = this.expression, set e to '#navbar a', then enter the while loop, traverse patterns, check the matching pattern of the query string, here based on the regular expression of pattern, find { name: 'id', re : /^#([w-*] )(b|$)/ },, so name is id. When m = e.match(ps[i].re) matches, m is set to an array, where m[0] is the entire matched string '#navbar', m[1] is the first matched group string 'navbar'

Next, determine Object.isFunction(x[name]), Since id corresponds to a string, new Template(x[name]).evaluate(m)) is executed. String: id: "[@id='#{1}']", #{1} in is replaced by m[1], that is, 'navbar', and finally the result is put into this.matcher

Then by deleting the first matched string, e becomes 'a', here is A space! Next, continue to match

This time the match is: { name: 'descendant', re: /^s/ }, and then find the corresponding descendant item in xpath: descendant: "//*", Then put this string into this.matcher, remove the space e and only the character 'a' is left. Continue to match the word

and the matched word is: { name: 'tagName', re: /^ s*(*|[w-] )(b|$)?/ }, and then find the xpath item corresponding to tagName,

tagName: function(m) {
if (m[1] = = '*') return '';
return "[local-name()='" m[1].toLowerCase()
"' or local-name()='" m[1]. toUpperCase() "']";
}

is a method, so it will call x[name](m), and m[1]='a', return the following string of characters, and then After putting it in this.matcher, this time e is an empty string. The first condition of while is not satisfied. Exit the loop and connect the this.matcher array into an xpath string: .//*[@id='navbar' ]//*[local-name()='a' or local-name()='A']

After initializing the Selector, execute the instance method findElements of the Selector. Call it directly here: document. _getElementsByXPath(this.xpath, root);

Execute the real DOM query method document.evaluate in the _getElementsByXPath method, and finally return the result

The above is the entire process of querying DOM under FF!

The process under IE is the same as under Opera and Safari, but the specific execution method is slightly different. If you are interested, you can study it yourself. I will not give examples of complex DOM selection operations. The process constructed here is very worth learning, including generating xpath through pattern matching and proposing those patterns, xpath, etc.

It can be seen that it is not easy to write a framework that is compatible with all browsers! Learn, learn!
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn