dojo/query源码解析
2015-11-29 21:23
501 查看
dojo/query模块是dojo为开发者提供的dom查询接口。该模块的输出对象是一个使用css选择符来查询dom元素并返回NodeList对象的函数。同时,dojo/query模块也是一个插件,开发者可以使用自定义的查询引擎,query模块会负责将引擎的查询结果包装成dojo自己的NodeList对象。
要理解这个模块就要搞清楚两个问题:
如何查询,查询的原理?
查询结果是什么,如何处理查询结果?
这两个问题涉及到本文的两个主题:选择器引擎和NodeList。
选择器引擎
前端的工作必然涉及到与DOM节点打交道,我们经常需要对一个DOM节点进行一系列的操作。但我们如何找到这个DOM节点呢,为此我们需要一种语言来告诉浏览器我们想要就是这个语言描述的dom节点,这种语言就是CSS选择器。比如我们想浏览器描述一个dom节点:div > p + .bodhi input[type="checkbox"],它的意思是在div元素下的直接子元素p的下一个class特性中含有bodhi的兄弟节点下的type属性是checkbox的input元素。
选择符种类
元素选择符:通配符*、类型选择符E、类选择符E.class、ID选择符E#id
关系选择符:包含(E F)、子选择符(E>F)、相邻选择符(E+F)、兄弟选择符(E~F)
属性选择符: E[att]、E[att="val"]、E[att~="val"]、E[att^="val"]、E[att$="val"]、E[att*="val"]
伪类选择符
伪对象选择符:E:first-letter、E:first-line、E:before、E:after、E::placehoser、E::selection
通过选择器来查询DOM节点,最简单的方式是依靠浏览器提供的几个原生接口:getElementById、getElementsByTagName、getElementsByName、getElementsByClassName、querySelector、querySelectorAll。但因为低版本浏览器不完全支持这些接口,而我们实际工作中有需要这些某些高级接口,所以才会有各种各样的选择器引擎。所以选择器引擎就是帮我们查询DOM节点的代码类库。
选择器引擎很简单,但是一个高校的选择器引擎会涉及到词法分析和预编译。不懂编译原理的我表示心有余而力不足。
但需要知道的一点是:解析css选择器的时候,都是按照从右到左的顺序来的,目的就是为了提高效率。比如“div p span.bodhi”;如果按照正向查询,我们首先要找到div元素集合,从集合中拿出一个元素,再找其中的p集合,p集合中拿出一个元素找class属性是bodhi的span元素,如果没找到重新回到开头的div元素,继续查找。这样的效率是极低的。相反,如果按照逆向查询,我们首先找出class为bodhi的span元素集合,在一直向上回溯看看祖先元素中有没有选择符内的元素即可,孩子找父亲很容易,但父亲找孩子是困难的。
选择器引擎为了优化效率,每一个选择器都可以被分割为好多部分,每一部分都会涉及到标签名(tag)、特性(attr)、css类(class)、伪节点(persudo)等,分割的方法与选择器引擎有关。比如选择器 div > p + .bodhi input[type="checkbox"]如果按照空格来分割,那它会被分割成以下几部分:
div
>
p
+
.bodhi
input[type="checkbox"]
对于每一部分选择器引擎都会使用一种数据结构来表达这些选择符,如dojo中acme使用的结构:
从这里可以看到有专门的结构来管理不同的类型的选择符。分割出来的每一部分在acme中都会生成一个part,part中有tag、伪元素、属性、元素关系等。。;所有的part都被放到queryParts数组中。然后从右到左每次便利一个part,低版本浏览器虽然不支持高级接口,但是一些低级接口还是支持的,比如:getElementsBy*;对于一个part,先匹配tag,然后判断class、attr、id等。这是一种解决方案,但这种方案有很严重的效率问题。(后面这句是猜想)试想一下:我们可不可以把一个part中有效的几项的判断函数来组装成一个函数,对于一个part只执行一次即可。没错,acme就是这样来处理的(这里涉及到预编译问题,看不明白的自动忽略即可。。。)
dojo/query模块的选择器引擎通过dojo/selector/loader来加载。如果没有在dojoConfig中配置selectorEngine属性,那么loader模块会自己判断使用acme和是lite引擎,原则是高版本浏览器尽量使用lite,而低版本尽量使用acme。
View Code
NodeList提供的很多操作,如:map、filter、concat等,都是借助原生的Array提供的相应方法,这些方法返回都是原生的array对象,所以需要对返回的array对象进行包装。有趣的是NodeList提供end()可以回到原始的NodeList中。整个结构如下:
我们来看一下包装函数:
这就是dojo中NodeList的设计!
query模块暴露的方法无非就是对选择器引擎的调用,下面就比较简单了。
如果您觉得这篇文章对您有帮助,请不吝点击推荐,您的鼓励是我分享的动力!!!
require(["dojo/query!sizzle"], function(query){ query("div")...
要理解这个模块就要搞清楚两个问题:
如何查询,查询的原理?
查询结果是什么,如何处理查询结果?
这两个问题涉及到本文的两个主题:选择器引擎和NodeList。
选择器引擎
前端的工作必然涉及到与DOM节点打交道,我们经常需要对一个DOM节点进行一系列的操作。但我们如何找到这个DOM节点呢,为此我们需要一种语言来告诉浏览器我们想要就是这个语言描述的dom节点,这种语言就是CSS选择器。比如我们想浏览器描述一个dom节点:div > p + .bodhi input[type="checkbox"],它的意思是在div元素下的直接子元素p的下一个class特性中含有bodhi的兄弟节点下的type属性是checkbox的input元素。
选择符种类
元素选择符:通配符*、类型选择符E、类选择符E.class、ID选择符E#id
关系选择符:包含(E F)、子选择符(E>F)、相邻选择符(E+F)、兄弟选择符(E~F)
属性选择符: E[att]、E[att="val"]、E[att~="val"]、E[att^="val"]、E[att$="val"]、E[att*="val"]
伪类选择符
伪对象选择符:E:first-letter、E:first-line、E:before、E:after、E::placehoser、E::selection
通过选择器来查询DOM节点,最简单的方式是依靠浏览器提供的几个原生接口:getElementById、getElementsByTagName、getElementsByName、getElementsByClassName、querySelector、querySelectorAll。但因为低版本浏览器不完全支持这些接口,而我们实际工作中有需要这些某些高级接口,所以才会有各种各样的选择器引擎。所以选择器引擎就是帮我们查询DOM节点的代码类库。
选择器引擎很简单,但是一个高校的选择器引擎会涉及到词法分析和预编译。不懂编译原理的我表示心有余而力不足。
但需要知道的一点是:解析css选择器的时候,都是按照从右到左的顺序来的,目的就是为了提高效率。比如“div p span.bodhi”;如果按照正向查询,我们首先要找到div元素集合,从集合中拿出一个元素,再找其中的p集合,p集合中拿出一个元素找class属性是bodhi的span元素,如果没找到重新回到开头的div元素,继续查找。这样的效率是极低的。相反,如果按照逆向查询,我们首先找出class为bodhi的span元素集合,在一直向上回溯看看祖先元素中有没有选择符内的元素即可,孩子找父亲很容易,但父亲找孩子是困难的。
选择器引擎为了优化效率,每一个选择器都可以被分割为好多部分,每一部分都会涉及到标签名(tag)、特性(attr)、css类(class)、伪节点(persudo)等,分割的方法与选择器引擎有关。比如选择器 div > p + .bodhi input[type="checkbox"]如果按照空格来分割,那它会被分割成以下几部分:
div
>
p
+
.bodhi
input[type="checkbox"]
对于每一部分选择器引擎都会使用一种数据结构来表达这些选择符,如dojo中acme使用的结构:
{ query: null, // the full text of the part's rule pseudos: [], // CSS supports multiple pseud-class matches in a single rule attrs: [], // CSS supports multi-attribute match, so we need an array classes: [], // class matches may be additive, e.g.: .thinger.blah.howdy tag: null, // only one tag... oper: null, // ...or operator per component. Note that these wind up being exclusive. id: null, // the id component of a rule getTag: function(){ return caseSensitive ? this.otag : this.tag; } }
从这里可以看到有专门的结构来管理不同的类型的选择符。分割出来的每一部分在acme中都会生成一个part,part中有tag、伪元素、属性、元素关系等。。;所有的part都被放到queryParts数组中。然后从右到左每次便利一个part,低版本浏览器虽然不支持高级接口,但是一些低级接口还是支持的,比如:getElementsBy*;对于一个part,先匹配tag,然后判断class、attr、id等。这是一种解决方案,但这种方案有很严重的效率问题。(后面这句是猜想)试想一下:我们可不可以把一个part中有效的几项的判断函数来组装成一个函数,对于一个part只执行一次即可。没错,acme就是这样来处理的(这里涉及到预编译问题,看不明白的自动忽略即可。。。)
dojo/query模块的选择器引擎通过dojo/selector/loader来加载。如果没有在dojoConfig中配置selectorEngine属性,那么loader模块会自己判断使用acme和是lite引擎,原则是高版本浏览器尽量使用lite,而低版本尽量使用acme。
// add array redirectors forEach(["slice", "splice"], function(name){ var f = ap[name]; //Use a copy of the this array via this.slice() to allow .end() to work right in the splice case. // CANNOT apply ._stash()/end() to splice since it currently modifies // the existing this array -- it would break backward compatibility if we copy the array before // the splice so that we can use .end(). So only doing the stash option to this._wrap for slice. //类似于:this._wrap(this.slice(parameter), this); nlp[name] = function(){ return this._wrap(f.apply(this, arguments), name == "slice" ? this : null); }; }); // concat should be here but some browsers with native NodeList have problems with it // add array.js redirectors forEach(["indexOf", "lastIndexOf", "every", "some"], function(name){ var f = array[name]; //类似于:dojo.indexOf(this, parameter) nlp[name] = function(){ return f.apply(dojo, [this].concat(aps.call(arguments, 0))); }; }); lang.extend(NodeList, {//将属性扩展至原型链 // copy the constructors constructor: nl, _NodeListCtor: nl, toString: function(){ // Array.prototype.toString can't be applied to objects, so we use join return this.join(","); }, _stash: function(parent){//保存parent,parent应当也是nl的一个实例 // summary: // private function to hold to a parent NodeList. end() to return the parent NodeList. // // example: // How to make a `dojo/NodeList` method that only returns the third node in // the dojo/NodeList but allows access to the original NodeList by using this._stash: // | require(["dojo/query", "dojo/_base/lang", "dojo/NodeList", "dojo/NodeList-dom" // | ], function(query, lang){ // | lang.extend(NodeList, { // | third: function(){ // | var newNodeList = NodeList(this[2]); // | return newNodeList._stash(this); // | } // | }); // | // then see how _stash applies a sub-list, to be .end()'ed out of // | query(".foo") // | .third() // | .addClass("thirdFoo") // | .end() // | // access to the orig .foo list // | .removeClass("foo") // | }); // this._parent = parent; return this; // dojo/NodeList }, on: function(eventName, listener){//绑定事件 // summary: // Listen for events on the nodes in the NodeList. Basic usage is: // // example: // | require(["dojo/query" // | ], function(query){ // | query(".my-class").on("click", listener); // This supports event delegation by using selectors as the first argument with the event names as // pseudo selectors. For example: // | query("#my-list").on("li:click", listener); // This will listen for click events within `<li>` elements that are inside the `#my-list` element. // Because on supports CSS selector syntax, we can use comma-delimited events as well: // | query("#my-list").on("li button:mouseover, li:click", listener); // | }); var handles = this.map(function(node){ return on(node, eventName, listener); // TODO: apply to the NodeList so the same selector engine is used for matches }); handles.remove = function(){ for(var i = 0; i < handles.length; i++){ handles[i].remove(); } }; return handles; }, end: function(){//由当前的nl返回父nl // summary: // Ends use of the current `NodeList` by returning the previous NodeList // that generated the current NodeList. // description: // Returns the `NodeList` that generated the current `NodeList`. If there // is no parent NodeList, an empty NodeList is returned. // example: // | require(["dojo/query", "dojo/NodeList-dom" // | ], function(query){ // | query("a") // | .filter(".disabled") // | // operate on the anchors that only have a disabled class // | .style("color", "grey") // | .end() // | // jump back to the list of anchors // | .style(...) // | }); // if(this._parent){ return this._parent; }else{ //Just return empty list. return new this._NodeListCtor(0); } }, concat: function(item){ // summary: // Returns a new NodeList comprised of items in this NodeList // as well as items passed in as parameters // description: // This method behaves exactly like the Array.concat method // with the caveat that it returns a `NodeList` and not a // raw Array. For more details, see the [Array.concat // docs](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/concat) // item: Object? // Any number of optional parameters may be passed in to be // spliced into the NodeList //return this._wrap(apc.apply(this, arguments)); // the line above won't work for the native NodeList, or for Dojo NodeLists either :-( // implementation notes: // Array.concat() doesn't recognize native NodeLists or Dojo NodeLists // as arrays, and so does not inline them into a unioned array, but // appends them as single entities. Both the original NodeList and the // items passed in as parameters must be converted to raw Arrays // and then the concatenation result may be re-_wrap()ed as a Dojo NodeList. var t = aps.call(this, 0), m = array.map(arguments, function(a){//array.concat方法不会将原始的NodeList和dojo的NodeList作为数组来处理,所以在这之前将他们转化成普通的数组 return aps.call(a, 0); }); return this._wrap(apc.apply(t, m), this); // dojo/NodeList }, map: function(/*Function*/ func, /*Function?*/ obj){ // summary: // see `dojo/_base/array.map()`. The primary difference is that the acted-on // array is implicitly this NodeList and the return is a // NodeList (a subclass of Array) return this._wrap(array.map(this, func, obj), this); // dojo/NodeList }, forEach: function(callback, thisObj){ // summary: // see `dojo/_base/array.forEach()`. The primary difference is that the acted-on // array is implicitly this NodeList. If you want the option to break out // of the forEach loop, use every() or some() instead. forEach(this, callback, thisObj); // non-standard return to allow easier chaining return this; // dojo/NodeList }, filter: function(/*String|Function*/ filter){ // summary: // "masks" the built-in javascript filter() method (supported // in Dojo via `dojo/_base/array.filter`) to support passing a simple // string filter in addition to supporting filtering function // objects. // filter: // If a string, a CSS rule like ".thinger" or "div > span". // example: // "regular" JS filter syntax as exposed in `dojo/_base/array.filter`: // | require(["dojo/query", "dojo/NodeList-dom" // | ], function(query){ // | query("*").filter(function(item){ // | // highlight every paragraph // | return (item.nodeName == "p"); // | }).style("backgroundColor", "yellow"); // | }); // example: // the same filtering using a CSS selector // | require(["dojo/query", "dojo/NodeList-dom" // | ], function(query){ // | query("*").filter("p").styles("backgroundColor", "yellow"); // | }); var a = arguments, items = this, start = 0; if(typeof filter == "string"){ // inline'd type check items = query._filterResult(this, a[0]);//如果filter是css选择器,调用query的filter方法从已有集合中选择合适的元素 if(a.length == 1){ // if we only got a string query, pass back the filtered results return items._stash(this); // dojo/NodeList } // if we got a callback, run it over the filtered items start = 1; } //如果filter是函数,那就调用array的filter先过滤在包装。 return this._wrap(array.filter(items, a[start], a[start + 1]), this); // dojo/NodeList }, instantiate: function(/*String|Object*/ declaredClass, /*Object?*/ properties){ // summary: // Create a new instance of a specified class, using the // specified properties and each node in the NodeList as a // srcNodeRef. // example: // Grabs all buttons in the page and converts them to dijit/form/Button's. // | var buttons = query("button").instantiate(Button, {showLabel: true}); //这个方法主要用于将原生dom元素实例化成dojo的dijit var c = lang.isFunction(declaredClass) ? declaredClass : lang.getObject(declaredClass); properties = properties || {}; return this.forEach(function(node){ new c(properties, node); }); // dojo/NodeList }, at: function(/*===== index =====*/){ // summary: // Returns a new NodeList comprised of items in this NodeList // at the given index or indices. // // index: Integer... // One or more 0-based indices of items in the current // NodeList. A negative index will start at the end of the // list and go backwards. // // example: // Shorten the list to the first, second, and third elements // | require(["dojo/query" // | ], function(query){ // | query("a").at(0, 1, 2).forEach(fn); // | }); // // example: // Retrieve the first and last elements of a unordered list: // | require(["dojo/query" // | ], function(query){ // | query("ul > li").at(0, -1).forEach(cb); // | }); // // example: // Do something for the first element only, but end() out back to // the original list and continue chaining: // | require(["dojo/query" // | ], function(query){ // | query("a").at(0).onclick(fn).end().forEach(function(n){ // | console.log(n); // all anchors on the page. // | }) // | }); //与array中的位置选择器类似 var t = new this._NodeListCtor(0); forEach(arguments, function(i){ if(i < 0){ i = this.length + i; } if(this[i]){ t.push(this[i]); } }, this); return t._stash(this); // dojo/NodeList } });
View Code
NodeList提供的很多操作,如:map、filter、concat等,都是借助原生的Array提供的相应方法,这些方法返回都是原生的array对象,所以需要对返回的array对象进行包装。有趣的是NodeList提供end()可以回到原始的NodeList中。整个结构如下:
我们来看一下包装函数:
nl._wrap = nlp._wrap = tnl; var tnl = function(/*Array*/ a, /*dojo/NodeList?*/ parent, /*Function?*/ NodeListCtor){ //将a包装成NodeList var nodeList = new (NodeListCtor || this._NodeListCtor || nl)(a); //设置nodeList._parent = parent;方便在end函数中返回原始nodeList return parent ? nodeList._stash(parent) : nodeList; }; end: function(){//由最近的nl返回父nl if(this._parent){ return this._parent; }else{ return new this._NodeListCtor(0); } },
这就是dojo中NodeList的设计!
query模块暴露的方法无非就是对选择器引擎的调用,下面就比较简单了。
function queryForEngine(engine, NodeList){ var query = function(/*String*/ query, /*String|DOMNode?*/ root){ if(typeof root == "string"){ root = dom.byId(root); if(!root){ return new NodeList([]); } } //使用选择器引擎来查询dom节点 var results = typeof query == "string" ? engine(query, root) : query ? (query.end && query.on) ? query : [query] : []; if(results.end && results.on){//有end和on方法则认为query已经是一个NodeList对象 // already wrapped return results; } return new NodeList(results); }; query.matches = engine.match || function(node, selector, root){ // summary: // Test to see if a node matches a selector return query.filter([node], selector, root).length > 0; }; // the engine provides a filtering function, use it to for matching query.filter = engine.filter || function(nodes, selector, root){ // summary: // Filters an array of nodes. Note that this does not guarantee to return a NodeList, just an array. return query(selector, root).filter(function(node){ return array.indexOf(nodes, node) > -1; }); }; if(typeof engine != "function"){ var search = engine.search; engine = function(selector, root){ // Slick does it backwards (or everyone else does it backwards, probably the latter) return search(root || document, selector); }; } return query; } var query = queryForEngine(defaultEngine, NodeList);
如果您觉得这篇文章对您有帮助,请不吝点击推荐,您的鼓励是我分享的动力!!!
相关文章推荐
- POJ1679 The Unique MST(次小生成树)
- UI第三MVC
- Codeforces Educational Codeforces Round 2 B. Queries about less or equal elements
- vidioc_querycap浅析
- 爬爬爬之路:UI(十二) 单例 UITabBarController 高级Block的应用
- Java基础---图形用户界面GUI(一)
- Educational Codeforces Round 2 B. Queries about less or equal elements 水题
- Android UICC 好文关键字备忘
- html/jsp 利用juicer模版添加页面标签内容
- 【Educational Codeforces Round 2B】【map or 二分查找】Queries about less or equal elements b[]中的每个数比a[]中多少数大
- 新手学EasyUI(八)----SubGrid三级嵌套
- UI控件的一些问题与解决方法
- UGUI摇杆
- Ubuntu10.04上编译Android源码(Build Android source in Ubuntu10.04 Platform)
- UI推荐
- UI_layoutSubView(判断横竖屏方法)
- SequenceInputStream
- UINavigationController
- UIWindow简单介绍
- 【iOS】UITableView 动态确定cell的高度