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));