解釋器模式(Interpreter):定義一種語法格式,通過程序解釋執行它並完成相應的任務。在前端編程場景中可以應用解釋器模式來解釋CSS選擇符實現DOM元素的選擇。
開放封閉原則:面向對象中的開放封閉原則是類或模塊應該對擴展開放對修改封閉,在這個dom選擇器中實現id選擇器,元素選擇器,類選擇器,如果以后需要屬性選擇器的話定義一個屬性選擇器實現相應的方法,同時在簡單工廠中增加相應的創建屬性選擇器對象分支即可。
匹配原理:瀏覽器在匹配CSS選擇符時是按照從右到左匹配的,所以實現自己的DOM選擇器時匹配行為也應該和瀏覽原生匹配行為一致。
代碼:
(function (ns) { /* //tagName console.log(dom.get("p")); //#id console.log(dom.get("#div")); //.class console.log(dom.get(".span", document.body)); //tag.class console.log(dom.get("div.span")); //#id .class console.log(dom.get("#div .span")); //.class .class console.log(dom.get(".ul .li-test")); */ var doc = document; var simple = /^(?:#|\.)?([\w-_]+)/; function api(query, context) { context = context || doc; //調用原生選擇器 if(!simple.test(query) && context.querySelectorAll){ return context.querySelectorAll(query); }else { //調用自定義選擇器 return interpret(query, context); } } //解釋執行dom選擇符 function interpret(query, context){ var parts = query.replace(/\s+/, " ").split(" "); var part = parts.pop(); var selector = Factory.create(part); var ret = selector.find(context); return (parts[0] && ret[0]) ? filter(parts, ret) : ret; } //ID選擇器 function IDSelector(id) { this.id = id.substring(1); } IDSelector.prototype = { find: function (context) { return document.getElementById(this.id); }, match: function(element){ return element.id == this.id; } }; IDSelector.test = function (selector) { var regex = /^#([\w\-_]+)/; return regex.test(selector); }; //元素選擇器 function TagSelector(tagName) { this.tagName = tagName.toUpperCase(); } TagSelector.prototype = { find: function (context) { return context.getElementsByTagName(this.tagName); }, match: function(element){ return this.tagName == element.tagName.toUpperCase() || this.tagName === "*"; } }; TagSelector.test = function (selector) { var regex = /^([\w\*\-_]+)/; return regex.test(selector); }; //類選擇器 function ClassSelector(className) { var splits = className.split('.'); this.tagName = splits[0] || undefined ; this.className = splits[1]; } ClassSelector.prototype = { find: function (context) { var elements; var ret = []; var tagName = this.tagName; var className = this.className; var selector = new TagSelector((tagName || "*")); //支持原生getElementsByClassName if (context.getElementsByClassName) { elements = context.getElementsByClassName(className); if(!tagName){ return elements; } for(var i=0,n=elements.length; i<n; i++){ if( selector.match(elements[i]) ){ ret.push(elements[i]); } } } else { elements = selector.find(context); for(var i=0, n=elements.length; i<n; i++){ if( this.match(elements[i]) ) { ret.push(elements[i]); } } } return ret; }, match: function(element){ var className = this.className; var regex = new RegExp("^|\\s" + className + "$|\\s"); return regex.test(element.className); } }; ClassSelector.test = function (selector) { var regex = /^([\w\-_]+)?\.([\w\-_]+)/; return regex.test(selector); }; //TODO:屬性選擇器 function AttributeSelector(attr){ this.find = function(context){ }; this.match = function(element){ }; } AttributeSelector.test = function (selector){ var regex = /\[([\w\-_]+)(?:=([\w\-_]+))?\]/; return regex.test(selector); }; //根據父級元素過濾 function filter(parts, nodeList){ var part = parts.pop(); var selector = Factory.create(part); var ret = []; var parent; for(var i=0, n=nodeList.length; i<n; i++){ parent = nodeList[i].parentNode; while(parent && parent !== doc){ if(selector.match(parent)){ ret.push(nodeList[i]); break; } parent = parent.parentNode; } } return parts[0] && ret[0] ? filter(parts, ret) : ret; } //根據查詢選擇符創建相應選擇器對象 var Factory = { create: function (query) { if (IDSelector.test(query)) { return new IDSelector(query); } else if (ClassSelector.test(query)) { return new ClassSelector(query); } else { return new TagSelector(query); } } }; ns.dom || (ns.dom = {}); ns.dom.get = api; }(this));