JavaScript实现DOM对象选择器

目的:

根据传入的选择器类型选出第一个符合的DOM对象。 
①可以通过id获取DOM对象,例如 $("#adom"); 
②可以通过tagName获取DOM对象,例如 $("a"); 
③可以通过样式名称获取DOM对象,例如 $(".classa"); 
④可以通过attribute匹配获取DOM对象,例如 $("[data-log]"),$("[data-time=2015]"); 
⑤可以通过层叠组合获取DOM对象,例如 $("#adom .classa");

思路:

需要区分复合选择还是单项选择,单项选择的话分别用各自的方法进行获取,复合选择的话就要进行筛选。

所以第一步,区分是单项还是组合。

实现方法是将传入选择器的字符串转换成数组,如果数组长度大于1的话,就是复合选择。如果不是的话,再判断是哪一种单项选择器。

if(trim(selector).split(" ").length > 1){ //trim()方法用于去除字符串开头和结尾的空白
//复合选择器代码
}
//判断是哪一种单项选择器

第二步,判断是哪一种单项选择器,然后进行筛选返回第一个元素。

①判断,有两种方法:

•方法一:用正则表达式。

if(/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
  //ID选择器
}
if(/^((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
  //Tag选择器
}
if(/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
  //class选择器
}
if(/^\[[A-Za-z0-9_-\S]+\]$/.test(selector)){
 //属性选择器
}

•方法二:检查传入选择器的第一个字符

var type=trim(selector).charAt(0);
switch(type){
 case ".":
  //class选择器
 case "#":
  //id选择器
 case "[":
  //属性选择器
 default:
  //tag选择器
}

②根据选择器进行筛选。

•id和tag直接用DOM方法就可以了。
 •class的document.getElementsByClassName有兼容问题,需要为IE定义方法。
 •属性选择器需要遍历所有的DOM节点对象,选择出符合条件的。

//ID选择器
return document.getElementById(selector.slice(1,selector.length));
//tag选择器
return document.getElementsByTagName(selector)[0];
//类选择器
if(document.getElementsByClassName){
 return document.getElementsByClassName(selector.slice(1,selector.length))[0];
}else{
 var nodes = document.all ? document.all : document.getElementsByTagName('*');
 for(var i=0;i<nodes.length;i++){
  var classes=nodes[i].className.split(/\s+/);
   if(classes.indexOf(selector.slice(1))!=-1){ //indexOf不兼容,需要在原型上扩展
    return nodes[i];
    break;
   }
  }
 }
}
//属性选择器
if(/^\[[A-Za-z0-9_-\S]+\]$/.test(selector)){
 selector = selector.slice(1,selector.length-1);
 var eles = document.getElementsByTagName("*");
 selector = selector.split("=");
 var att = selector[0];
 var value = selector[1];
 if (value) {
  for (var i = 0; i < eles.length; i++) {
   if(eles[i].getAttribute(att)==value){
    return eles[i];
   }
  }
 }else{
  for (var i = 0; i < eles.length; i++) {
   if(eles[i].getAttribute(att)){
    return eles[i];
   }
  }
 }
}

第三步,实现复杂选择器。

•思路一:

最终筛选出的DOM对象一定是满足最后一个选择器的DOM对象集合之一,所以可以先选出这些对象,然后逐个检查他的祖先元素,是否符合上一层选择器,不符合的话就删掉。一直迭代到最外一层选择器,剩下的DOM对象集合中的第一个就是我们要找的DOM对象。

那么,如果有n个选择器,就需要进行n-1轮筛选。

这里需要做两件事情,①检查元素的祖先元素是否是选择器对象集合之一。②检查对象集合中的每个元素,删掉不符合条件的DOM对象。

定义两个函数来做这两件事:

//递归检查ele的祖先对象是否符合选择器
function isParent(ele,str){
 if (!isArray(str)) {  //如果不是数组
  str = toArray(str); //转换成数组
 }
 if (ele.parentNode) {
  if (str.indexOf(ele.parentNode)>-1) {
   return true;
  }else{
   return isParent(ele.parentNode,str);
  }
 }else{
  return false;
 }
}
//从eles中删掉祖先对象不符合选择器的对象
function fliterEles(eles,str){
 if(!isArray(eles)){
   eles = toArray(eles);
 }
 for (var i = 0,len=eles.length;i<len; i++) {
  if (!isParent(eles[i],str)) {
   eles.splice(i,1);
   i = i - 1;
  }
 }
 return eles;
}

这个实现会有一个BUG,就是当HTML是下面这样的时候,他会筛选出“第一个”,然而它并不是我们期待的。

虽然实际应用中很少会这样给父元素和子元素定义相同的class名,但我们不能忽略这个BUG的存在。

这个实现的性能也是很差的,因为当他检查对象集合中的一个对象的祖先元素是否符合一个选择器时,他先检查他的父元素,不满足的话再检查他父元素的父元素,一直到没有父元素为止。然后他还需要检查是否符合下一个选择器,这样他又遍历了一遍他的父元素。这里有重复访问的地方。

思路一的所有代码:

//需要一个可以选择所有元素的方法
function getElements(selector){
 //类选择器,返回全部项
 if(/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
  if(document.getElementsByClassName){
   return document.getElementsByClassName(selector.slice(1,selector.length));
  }
  var nodes = document.all ? document.all : document.getElementsByTagName('*');
  var arr=[]; //用来保存符合的className;
  for(var i=0;i<nodes.length;i++){
   if(hasClass(nodes[i],selector.slice(1,selector.length))){
    arr.push(nodes[i]);
   }
  }
  return arr;
 }

 //ID选择器
 if(/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
  return document.getElementById(selector.slice(1,selector.length));
 }

 //tag选择器
 if(/^((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
  return document.getElementsByTagName(selector);
 }

 //属性选择器
 if(/^\[[A-Za-z0-9_-\S]+\]$/.test(selector)){
  selector = selector.slice(1,selector.length-1);
  var eles = document.getElementsByTagName("*");
  selector = selector.split("=");
  var att = selector[0];
  var value = selector[1];
  var arr = [];
  if (value) {
   for (var i = 0; i < eles.length; i++) {
    if(eles[i].getAttribute(att)==value){
     arr.push(eles[i]);
    }
   }
  }else{
   for (var i = 0; i < eles.length; i++) {
    if(eles[i].getAttribute(att)){
     arr.push(eles[i]);
    }
   }
  }
  return arr;
 }
}

//检查ele的祖先对象是否符合选择器
function isParent(ele,str){
 if (!isArray(str)) {
  str = toArray(str);
 }
 if (ele.parentNode) {
  if (str.indexOf(ele.parentNode)>-1) {
   return true;
  }else{
   return isParent(ele.parentNode,str);
  }
 }else{
  return false;
 }
}

//从eles中删掉祖先对象不符合选择器的对象
function fliterEles(eles,str){
 if(!isArray(eles)){
   eles = toArray(eles);
 }
 for (var i = 0; i < eles.length; i++) {
  if (!isParent(eles[i],str)) {
   eles.splice(i,1);
   i = i - 1;
  }
 }
 return eles;
}

//DOM元素选择器
function $(selector){
 if(!typeof selector === "string"){
  return false;
 }

 //复合选择器
 if(trim(selector).split(" ").length > 1){
  var all = trim(selector).split(" ");
  var eles = getElements(all[all.length-1]);
  for(var i = 2 ; i < all.length+2 && all.length-i >=0; i++){
   eles = fliterEles(eles,getElements(all[all.length-i]));
  }
  return eles[0];
 }

 //ID选择器
 if(/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
  return document.getElementById(selector.slice(1,selector.length));
 }

 //tag选择器,只返回第一个
 if(/^((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
  return document.getElementsByTagName(selector)[0];
 }

 //类选择器
 if(/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
  if(document.getElementsByClassName){
   return document.getElementsByClassName(selector.slice(1,selector.length))[0];
  }
  var nodes = document.all ? document.all : document.getElementsByTagName('*');
  for(var i=0;i<nodes.length;i++){
   if(hasClass(nodes[i],selector.slice(1,selector.length))){
    return nodes[i];
   }
  }
 }

 //属性选择器
 if(/^\[[A-Za-z0-9_-\S]+\]$/.test(selector)){
  selector = selector.slice(1,selector.length-1);
  var eles = document.getElementsByTagName("*");
  selector = selector.split("=");
  var att = selector[0];
  var value = selector[1];
  if (value) {
   for (var i = 0; i < eles.length; i++) {
    if(eles[i].getAttribute(att)==value){
     return eles[i];
    }
   }
  }else{
   for (var i = 0; i < eles.length; i++) {
    if(eles[i].getAttribute(att)){
     return eles[i];
    }
   }
  }
 }
}

•思路二:

从最外层向里面筛选。

先从document选出符合最外层选择器的对象集,目标对象一定是这个对象集的一个对象的子孙元素。

所以,遍历这个对象集中的每个元素,从中选出符合第二个选择器的对象集,然后再遍历新的对象集。

直到筛选完最后一个选择器,剩下的对象集中的第一个就是目标对象。

这个方法不需要区分符合选择器和单项选择器,也不需要重新定义获得所有元素的方法。

function $(selector){
 var all=selector.split(/\s+/);
 var result = [],rooot=[document];
 for (var i = 0; i < all.length; i++) {
  var type=all[i][0];
  switch(type){
  //ID
  case "#" :
   for (var j = 0; j < rooot.length; j++) {
    var ele=rooot[j].getElementById(all[i].slice(1));
    if (ele) {
     result.push(ele);
    }
   }
   break;

  //class
  case ".":
   for (var j = 0; j < rooot.length; j++) {
    if (document.getElementsByClassName) {
     var eles=rooot[j].getElementsByClassName(all[i].slice(1));
     if (eles) {
      result=result.concat(Array.prototype.slice.call(eles));
     }
    }else{
     var arr = rooot[j].getElementsByTagName("*");
     for (var i = 0; i < arr.length; i++) {
      if (hasClass(arr[i], className)) {
       result.push(arr[i]);
      }
     }
    }
   }
   break;
  //属性
  case "[":
   var att = all[i].slice(1,all[i].length-1).split("=");
   var key = att[0],value=att[1];
   for (var j = 0; j < rooot.length; j++) {
    var eles=rooot[j].getElementsByTagName("*");
    for (var i = 0; i < eles.length; i++) {
     if (value) {
      for (var i = 0; i < eles.length; i++) {
       if(eles[i].getAttribute(key)==value){
        result.push(eles[i]);
       }
      }
     }else{
      for (var i = 0; i < eles.length; i++) {
       if(eles[i].getAttribute(key)){
        result.push(eles[i]);
       }
      }
     }
    }
   }
   break;
  //tag
  default:
   for (var j = 0; j < rooot.length; j++) {
    eles=rooot[j].getElementsByTagName(all[i]);
    if (eles) {
     result=result.concat(Array.prototype.slice.call(eles));
    }
   }
  }//switch
  rooot=result;
  result=[];
 }//for
 return rooot[0];
}

用到的公共方法:

//IE9-不支持数组的indexOf()
if (!Array.prototype.indexOf) {
 Array.prototype.indexOf=function(value){
  for (var i = 0,len=this.length;i<len; i++) {
   if(this[i]==value){
    return i;
   }
  }
  return -1;
 };
}

//检查ele是否有className
function hasClass(ele,className){
 if (ele&&ele.className) {
  var classes=ele.className.split(/\s+/);//这里必须要切成数组之后再判断
  if(classes.indexOf(className)!=-1){
   return true;
  }
 }
 return false;
}

// 判断arr是否为一个数组,返回一个bool值
function isArray(arr){
 return Array.isArray(arr)||Object.prototype.toString.call(arr) === "[object Array]";
}

// 对字符串头尾进行空格字符的去除、包括全角半角空格、Tab等,返回一个字符串
function trim(str){
 return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")
}

//把一个类数组转换成数组
function toArray(obj){
 if (obj.nodeType == 1 ) {
  return [obj];
 }
 var arr = [];
 for( var i = 0 ; i < obj.length ; i++){
  arr.push(obj[i]);
 }
 return arr;
}

参考:

https://github.com/baidu-ife/ife/blob/master/2015_spring/task/task0002/review/demo/js/util_demo.js 
https://github.com/starkwang/ife/blob/master/task/task0002/work/starkwang/js/util.js

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • js对象浅拷贝和深拷贝详解

    本文为大家分享了JavaScript对象的浅拷贝和深拷贝代码,供大家参考,具体内容如下 1.浅拷贝 拷贝就是把父对像的属性,全部拷贝给子对象. 下面这个函数,就是在做拷贝: var Chinese = { nation:'中国' } var Doctor = { career:'医生' } function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i]; } c.uber = p; return c; } 使用的时候,这样写

  • js面向对象实现canvas制作彩虹球喷枪效果

    前段时间在研究canvas,感觉还挺好玩的,就写了一个小demo,效果如下: 第一次尝试用js面向对象的方式来写,经验不足,还请大家多多包涵. 下面开始简单介绍代码: canvas画布: 复制代码 代码如下: <canvas id='canvas' width='1050' height='500' style='background:#333;overflow: hidden;'></canvas> 彩虹球的随机颜色是通过下面两个方法来实现的,在<js常用方法和一些封装(2

  • JavaScript中Number对象的toFixed() 方法详解

    定义和用法 toFixed() 方法可把 Number 四舍五入为指定小数位数的数字. 语法 NumberObject.toFixed(num) 参数 描述 num 必需.规定小数的位数,是 0 ~ 20 之间的值,包括 0 和 20,有些实现可以支持更大的数值范围.如果省略了该参数,将用 0 代替. 返回值 返回 NumberObject 的字符串表示,不采用指数计数法,小数点后有固定的 num 位数字.如果必要,该数字会被舍入,也可以用 0 补足,以便它达到指定的长度.如果 num 大于 l

  • javascript宿主对象之window.navigator详解

    window.navigator用来反映浏览器及其功能信息的对象. // 检测浏览器版本信息 function getBrowserInfo(){ var Sys = {}; var ua = window.navigator.userAgent.toLowerCase(); var re =/(msie|firefox|chrome|opera|version).*?([\d.]+)/; var m = ua.match(re); Sys.browser = m[1].replace(/ver

  • JavaScript 对象详细整理总结

    Javascript 对象总结: JavaScript对象可以看作是属性的无序集合,每个属性就是一个键值对,可增可删. JavaScript中的所有事物都是对象:字符串.数字.数组.日期,等等. JavaScript对象除了可以保持自有的属性外,还可以从一个称为原型的对象继承属性.对象的方法通常是继承的属性.这种"原型式集成"是JavaScript的的核心特征. 1.创建对象 第一种:对象直接量表示法创建对象. 这是最简单的对象创建方式,对象直接量由若干key:value键值对属性组成

  • JavaScript每天必学之数组和对象部分

    对象部分 Object类型  Object 是一个无序的集合,可以存放任意类型对象,所有其他对象都继承自这个对象. 创建Object类型有两种,一种是使用new运算符,一种是字面量表示法. 1.使用new运算符创建Object  var obj = new Object();//注意大写,也可以直接写成Object() 注意,通过new Object() 的写法生成新对象,与字面量的写法 obj = {} 是等价的. 2. 使用字面量方式创建: var obj = { name : 'trigk

  • JavaScript实现DOM对象选择器

    目的: 根据传入的选择器类型选出第一个符合的DOM对象.  ①可以通过id获取DOM对象,例如 $("#adom");  ②可以通过tagName获取DOM对象,例如 $("a");  ③可以通过样式名称获取DOM对象,例如 $(".classa");  ④可以通过attribute匹配获取DOM对象,例如 $("[data-log]"),$("[data-time=2015]");  ⑤可以通过层叠组合获

  • Javascript操作dom对象之select全面解析

    html代码: <select id="university"> <option value="北京大学">北京大学</option> <option value="清华大学">清华大学</option> <option value="北京电影学院">北京电影学院</option> </select> js原生操作 1.获取sele

  • JavaScript操作DOM对象详解

    一.DOM基础 DOM(Document Object Model)即文档对象模型,针对HTML和XML文档的API(应用程序接口).DOM描绘了一个层次化的节点树,运行开发人员添加.移除和修改页面的某一部分.DOM中的三个字母,D(文档)可以理解为整个Web加载的网页文档:O(对象)可以理解为类似Window对象之类的东西,可以调用属性和方法,这里我们说的是document对象:M(模型)可以理解为网页文档的树型结构. 1.节点 加载HTML页面时,Web浏览器生成一个树型结构,用来表示页面内

  • JavaScript变量Dom对象的所有属性

    DOM对象的HTML: <button>Disable State</buttom> 1,遍历DOM对象所具有的属性(全部,不管是否在HTML tag中是否设置) <!DOCTYPE html> <html> <body> <button id="btnToggleState" onclick="toggleStateManagement()"> Disable State </butto

  • javascript 删除dom对象的事件函数代码

    JS添加/删除事件在IE和支持dom浏览器分别为:attachEvent(ie中的添加事件),detachEvent(ie中的删除事件),addEventListener(支持dom浏览器中的添加事件),removeEventListener(支持dom浏览器中的删除事件). 例如第一次点击黑色区域的时候弹出警告,并移除click事件,也就是第二次再点击的时候就没反应了,整合代码如下: 添加删除事件 var EventUtil=new Object; //oTarget:目标:sEventTyp

  • JavaScript简单遍历DOM对象所有属性的实现方法

    本文实例讲述了JavaScript遍历DOM对象所有属性的实现方法.分享给大家供大家参考,具体如下: DOM对象的HTML: 复制代码 代码如下: <button id="btnToggleState" onclick="toggleStateManagement()">Disable State Cookie</button> 1.遍历DOM对象所有具备的属性(全属性.不管在HTML tag中是否设置都会遍历) var obj=docume

  • jquery入门—访问DOM对象方法

    1.JavaScript访问DOM对象方法: var oTxtValue=document.getElementById("Text1").value; 2.JQuery访问DOM对象方法: var oTxtValue=$("#Text1").val(); 通过JQuery库中的方法访问或控制页面中的元素,比使用JavaScript代码要简洁得多,而且兼容各浏览器. 示例代码: 复制代码 代码如下: <!DOCTYPE HTML PUBLIC "-/

  • JS实现访问DOM对象指定节点的方法示例

    本文实例讲述了JS实现访问DOM对象指定节点的方法.分享给大家供大家参考,具体如下: 一 介绍 使用getElementById()方法来访问指定id的节点,并用nodeName属性.nodeType属性和nodeValue属性来显示出该节点名称.节点类型和节点值. 1.nodeName属性 该属性用来获取某一个节点的名称. [sName=]obj.nodeName sName:字符串变量用来存储节点的名称. 2.nodeType属性 该属性用来获取某一个节点的类型. [sType=]obj.n

  • javascript DOM对象的学习实例代码

    DOM对象的学习 function searchNode(){ /*文本节点*/ /*元素节点*/ /*属性节点*/ var nodes=document.getElementsByTagName("li"); alert("节点数"+nodes.length+"第一个节点元素的名称"+nodes[0].tagName+";第四个节点的第一个节点文本"+nodes[3].childNodes[0].nodeValue); va

  • javascript学习基础笔记之DOM对象操作

    DOM文档对象模型是HTML和XML的应用程序接口(API),DOM将整个页面规划成由节点层次构成的文档.DOM对象给予开发者对HTML的访问权限,并且使开发者能将HTML作为XML文档来处理和查看.DOM对象是与语言无关的API,意味着它的实现并不是与javascript绑定,这在于初学者来说可能会理解错误.DOM是针对XML的基于树的API,它关注的不仅仅是解析XML代码,而是用一系列相互关联的对象来表示这些代码,而这些对象可以被修改,而且无需重新解析代码就能直接访问它们.由于DOM的使用上

随机推荐