用JavaScript实现自己的DOM选择器

解释器模式(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));

相关推荐