Right taking a plunge in the deepend. Maybe a big ask this.

I'm having a go at writing a selector engine. It's for personal use with my own sites, so doesn't really need to be idiot proof.

It will be a fallback for querySelectorAll.

I started out just wanting something simple i.e. find 'ul li.myClass', but that wasn't enough.

This selector is intended to be a module along with an events module, which will bolt onto a framework. The framework constructor will be along the lines of jquery with a dummy constructor, prototype.init etc.

My real question is about the design or design pattern of this particular module. It's all getting a little bit messy and inconsistent.

There's a line I keep reading which is 'favour composition over inheritence'. I'm still none the wiser regarding that, but do get the impression that sticking rigidly to inheritence is problematic. Just not sure how or where to go about that.

So in conclusion pretty clueless

Rather than waffling on here's the code. If extra comments or details are needed let me know. Also I'm not looking for exhaustive answers or re-writes, but maybe just a nudge in the right direction.

Code JavaScript:
// written by Russell Gooday (c)2011
(function(doc, global){
 
  var Qselect, utils, splitToParts, find, filter, mapFind, child, relative,
      attribute, groupChildren, propDelete,
 
      hasOwn = Object.prototype.hasOwnProperty,
 
      push = Array.prototype.push,
 
      slice = Array.prototype.slice,
 
      rxSplitParts = new RegExp(
      '(\\w+:[-\\w]+(?:\\([^)]+\\))?)|'
      + '((?:\\w+)?\\[[^\\]]+])|'
      + '(>)|'
      + '(#[-\\w]+)|'
      + '((?:\\w+)?\\.[-\\w]+)|'
      + '(\\w+)', 'g'),
 
      partMatches = ['selector', 'pseudo', 'attribute', 'relative', 'id', 'className', 'tag'];
 
// -------------- Generic utils ----------------------------------
 
  utils = {
    // make Array
    slice : (function(doc){
 
      try {
        slice.call(doc.createElement('div'));
        return function( obj ) { return slice.call( obj ); };
      } catch(e) {
        return function( obj ) {
          var ret = [], len = obj.length;
 
          if (len) { while (len--) { ret[len] = obj[len]; } }
          return ret;
        };
      }
    }(doc)),
    // forEach
    forEach : function( arr, fn, context ) {
 
      var that = context || null,
          len = arr.length, i = 0;
 
      for (; i < len; i++) { fn.call(that, arr[i], i, arr); }
 
    },
    // shallow copy
    extend: function(from) {
 
      var to = arguments[1] || this, prop;
 
      for (prop in from) {
        if ( hasOwn.call(from, prop) ) { to[prop] = from[prop]; }
      }
    }
  };
 
// ---------------- End Generic utils ----------------------------
 
// ---------------- Split to Parts -------------------------------
 
  // returns an array of tokens. for example
  // 'ul li:first-child' becomes
  // [0] {find: 'tag', selector: 'ul'}
  // [1] {find: 'pseudo', selector: 'li:first-child'}
  // These are processed by search.
  splitToParts = function(selector) {
 
      // Match Types
      // ['selector',
      // 1:'pseudo', 2:'attribute', 3:'relative', 4:'id', 5:'className', 6:'tag']
 
      var regEx = rxSplitParts,
          matches = partMatches,
          len = matches.length,
          parts = [],
          match, i = 1;
 
      // reset regEx counter for exec	
      regEx.lastIndex = 0;
 
      while (match = regEx.exec(selector)) {
 
        // loop through match types
        for (i = 1; i < len; i++) {
          if (match[i]) {
            parts.push({find: matches[i], selector: match[0]}); break;
          }
        }
      }
    return parts;
  };
 
// ----------------------------------- End Split to Parts --------------------------------------------
 
// ----------------------------------- Qselect -------------------------------------------------------
 
  Qselect = function( selector, root ) {
 
    if ( !(this instanceof Qselect) ) { return new Qselect( selector, root ); }
 
    var parts,   
        root = root || doc;
        selector = selector.replace(/^[ ]|[ ]$/g, '');
 
    if( Qselect.cache[selector] ) { return Qselect.cache[selector]; }
 
    // Need to also handle where root maybe an html collection
    if (root instanceof Qselect) { 
      parts = splitToParts(root.selector.match(/([^\s]+)$/)[1] + ' ' + selector);
      root = root.els;
    } else {
      parts = splitToParts(selector);
    }
 
    this.els = Qselect.search(parts, root);
    this.selector = selector; //Todo this is flawed. Needs to be root + selector
    Qselect.cache[selector] = this;
  };
 
  // Add utils as static properties of Qselect
  utils.extend(utils, Qselect);
 
  Qselect.extend({
 
    rxClasses : {
 
      "CLASS": /(\w+)?\.([-\w]+)/,
 
      "PSEUDO": /(\w+):([-\w]+)(?:\(([^)]+)\))?/,
 
      "ATTRIBUTE": /(\w+)?\[/ // Todo Finish this expression
 
    },
 
    cache : {}, // needs work.
 
    // The main search method.
    search : function search( selector, root ){
 
      var query = selector[0],
          rest = selector.slice(1),
          results = [],
          els = (root.length) ? root : false, 
          len, i = 0;
 
      if (!els) {
        if (query.find === 'relative') {
          els = mapFind.relative(root, query.selector, rest[0]);
          rest = rest.slice(1);
        } else {
          els = mapFind[query.find](root, query.selector);
        }
      }
 
      for (len = els.length; i < len; i++) {
 
        if (rest[0]) { push.apply( results, search(rest, els[i]) ); }
 
        else { results.push (els[i]); }
      }
      return results;
    }
  });
 
  Qselect.prototype.get = function(){ return this.els; };
 
  filter = {
 
    attr : function (elems, attr, name) {
 
      var results = [], len = elems.length, i = 0;
 
      for (; i < len; i++) { 
        if (elems[i][attr] === name) { results.push(elems[i]); }
      }  
      return results;  
    }
  };
 
  mapFind = {
 
    id : function(root, selector){ return find.id(root, selector.slice(1)); },
 
    tag : function(root, selector){ return find.tag(root, selector); },
 
    className : function(root, selector){
 
      var match = selector.match(Qselect.rxClasses.CLASS),
          className = match[2],
          tag = match[1];
 
      return (!tag)
        ? find.className( root, className )
        : filter.attr( find.className(root, className), 'nodeName', tag.toUpperCase() );
 
    },
 
    pseudo : function(root, selector){
 
      var match = selector.match(Qselect.rxClasses.PSEUDO),
          tag = match[1], pseudo = match[2], expr = match[3];
 
      return find.pseudo( root, tag, pseudo, expr );
 
    },
 
    attribute : function(root, selector){
     // Todo
    },
 
    relative : function(root, selector, query){
      return find.relative(root, selector, query);
    }
  };
 
  find = {
    // ID
    id : function (root, id) { return [root.getElementById(id)]; },	
    // TAG
    tag : function (root, tag) { return Qselect.slice(root.getElementsByTagName(tag)); },
    // CLASSNAME
    className : (function() {
 
      if (doc.getElementsByClassName) {
 
        return function (root, className) {   
          return Qselect.slice(root.getElementsByClassName(className));    
        };
 
      }
 
      return function (root, className) {
 
        var toMatch = new RegExp("\\b" + className + "\\b"),
            elems = root.getElementsByTagName('*'),
            len = elems.length,
            results = [],
            elem = elems[0],
            i = 0, j = 0;
 
        for (; i < len; elem = elems[++i]) {   
          if (toMatch.test(elem.className)) { results[j++] = elem; }
        }
        return results;	
      };
    }(doc)),	
    // PSEUDO
    pseudo : function( root, tag, pseudo, expr ) {
 
      var els = root.getElementsByTagName(tag),
          filtered = [],
          len, el, i = 0, j = 0;
 
      if (/(first|last)-child/.test(pseudo)) {
 
        len = els.length; el = els[0];
 
        for (; i < len; el = els[++i]) { if (child[pseudo](el)) { filtered[j++] = el; } }
 
        return filtered;	
      }	
      // Note: May re-write pseudo to take a node and return a boolean instead.
      // That way can eliminate this extra conditional branching.
      if (/nth-child/.test(pseudo)) {
        Qselect.forEach(groupChildren(els), function(els){
          push.apply(filtered, child[pseudo](els, expr));
        });
      }	
    return filtered;
    },
 
    // only takes '>' at the moment.
    relative: function(root, selector, query) {
 
      var els = mapFind[query.find](root, query.selector),
          len = els.length,
          filtered = [],
          i = 0, j = 0;
 
      for (; i < len; i++) {
        if(relative[selector]( root, els[i] )) { filtered[j++] = els[i]; }
      }     
      return filtered;   
    }
  };
 
  child = {
 
    // split nth-child expression in to matches
    rxExpr : /(-(?=\d*n\+\d+))?(\d*)(?:(n)(?:([-+])(\d+))?)?/,
 
    exprCache : {odd: 0, even: 1},
 
    "first-child" : function(el) {
 
      var currElem = el;
 
      while (currElem = currElem.previousSibling) {
        if (currElem.nodeType === 1) { return false; }
      }
      return true;
    },
 
    "last-child" : function(el) {
 
      var currElem = el;
 
      while (currElem = currElem.nextSibling) {
        if (currElem.nodeType === 1) { return false; }
      }
      return true;
    },
 
    "nth-child" : function(els, expr) {
 
      var filtered = [],
          len = els.length,
          e = {}, n = 0, i = 0, j = 0;
 
      // if just a quick index value e.g. (5). return that element
      if (/^\d+$/.test(expr)){ return [els[expr]]; }
 
      if (/^odd|even$/.test(expr)) { n = 2; i = child.exprCache[expr]; }
 
      else {
 
        // if the matched/split expression isn't cached.
        if (!child.exprCache[expr]) {
 
          child.rxExpr.test(expr);
          // For clarity have labeled the matches
          child.exprCache[expr] = {
            oper1 : RegExp.$1, // -
            num : RegExp.$2,   // 3
            n : RegExp.$3,     // n
            oper2 : RegExp.$4, // +
            offset : RegExp.$5 // 9
          };
        }
        // n is the step i.e. 2n is a step of 2
        // i is the first element.
        e = child.exprCache[expr];
        n = parseInt(e.num || 1, 10);
        i = parseInt((e.oper2)
          ? (e.oper2 === '+')
            ? e.offset : (e.num || e.offset) - e.offset
          : (e.num || 1), 10)-1;
      }
 
      if (!e.oper1) { for ( ; i < len; i += n ) { filtered[j++] = els[i]; } }
 
      else { for (; i > -1; i -= n ) { filtered.unshift(els[i]); } }
 
      return filtered;	
    }	
  };
 
  attribute = {
 
  };
 
  relative = {
 
    ">" : function( root, node ){
 
      if (node.parentNode === root) { return true; }
      return false;
 
    }
  };
 
  // groups elements in parent->children groups
  // need this for nth-child
  groupChildren = function(els){
 
    var len = els.length,
        groups = [], parents = [],
        pId = 0, // unique id for parentNodes
        el, pNode, i = 0;
 
    for (; i < len; i++) {
      el = els[i];
      pNode = el.parentNode;
      if ( pNode._pId === undefined ) {
        pNode._pId = pId;
        groups[pId] = []; // add new parent group
        parents[pId++] = pNode; // store parent node for clean up later
      }
      groups[pNode._pId].push(el);
    }
    // cleanup parents and remove unique ids
    while(parents.length) { propDelete(parents.pop(), '_pId'); }
 
    return groups;
  };
 
  // delete property from element
  propDelete = (function(el, prop){
    try {
      var body = doc.body, tmpId = '_tmpId:' + new Date();	
      body[tmpId] = true;
      delete body[tmpId]; // try this
      return function(el, prop){ delete el[prop]; };
    } catch(e){
      body.removeAttribute(tmpId);
      return function(el,prop){ el.removeAttribute(prop); };
    }
  }());
 
  // Todo: Add to framework, rather than window.
  global.Qselect = Qselect;
 
}(window.document, window));

This is a simple test I'm running on the code. I've just added the ability to use the returned object as the root for subsequent selections. As pointed out by Raffles. Still needs work though.

Code HTML4Strict:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>qSelect test</title>
<style type='text/css'></style>
</head>
<body id = 'main'>
<div class = 'testLists'>
  <h2>List 1</h2>
  <ul class = 'uList1'>
    <li>Item 1</li><li>Item 2</li><li>Item 3</li>
    <li>Item 4
      <ul class = 'uList2'>
        <li>Item 1</li><li>Item 2</li><li>Item 3</li>
        <li>Item 4</li><li>Item 5</li><li>Item 6</li>
      </ul>
    </li>
    <li>Item 5</li><li>Item 6</li>
    <li>Item 7</li>
  </ul>
  <h2>List 2</h2>
  <ul class = 'uList3'>
    <li>Item 1</li><li>Item 2</li><li>Item 3</li>
    <li>Item 4</li><li>Item 5</li><li>Item 6</li>
  </ul>
</div>
<!--qSelect is intended to be a selector module, which will be added to a simple framework-->
<script type = 'text/javascript' src = 'qSelect.js'></script>
<script type='text/javascript'>
 
// Just a quick function to test Qselect
function testQselect(selector, root, style){
  var elems = Qselect(selector, root).get(), //get Elements here
      style = style.match(/\s?([^:]+):\s?(.+)/);   
  Qselect.forEach(elems, function(el){
	el.style[style[1]] = style[2];
  });
}
 
// A test to see if root is functioning properly
var list1 = Qselect('div.testLists ul.uList1'),
    list3 = Qselect('div.testLists ul.uList3');
 
testQselect('li:nth-child(odd)', list1,  'background: #ccf');
testQselect('li:nth-child(2n)', list3,  'background: #cfc');
testQselect('> li', list1, 'listStyle: none');
</script>
</body>
</html>

Thank you very much

RLM