JavaScript实现DOM对象选择器


Posted in Javascript onSeptember 24, 2016

目的: 

根据传入的选择器类型选出第一个符合的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是下面这样的时候,他会筛选出“第一个”,然而它并不是我们期待的。 

JavaScript实现DOM对象选择器

虽然实际应用中很少会这样给父元素和子元素定义相同的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

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

Javascript 相关文章推荐
JS小框架 fly javascript framework
Nov 26 Javascript
jQuery 类twitter的文本字数限制带提示效果插件
Apr 16 Javascript
jQuery获取注册信息并提示实现代码
Apr 21 Javascript
js控制当再次点击按钮时的间隔时间
Jun 03 Javascript
EasyUI实现二级页面的内容勾选的方法
Mar 01 Javascript
Bootstrap基本组件学习笔记之按钮组(8)
Dec 07 Javascript
详解Vue Elememt-UI构建管理后台
Feb 27 Javascript
JS实现的简单折叠展开动画效果示例
Apr 28 Javascript
浅入深出Vue之自动化路由
Aug 06 Javascript
微信小程序之 catalog 切换实现解析
Sep 12 Javascript
webpack是如何实现模块化加载的方法
Nov 06 Javascript
jQuery--遍历操作实例小结【后代、同胞及过滤】
May 22 jQuery
用瀑布流的方式在网页上插入图片的简单实现方法
Sep 23 #Javascript
浅谈Web页面向后台提交数据的方式和选择
Sep 23 #Javascript
Angular中使用ui router实现系统权限控制及开发遇到问题
Sep 23 #Javascript
打造自己的jQuery插件入门教程
Sep 23 #Javascript
jQuery.uploadify文件上传组件实例讲解
Sep 23 #Javascript
Bootstrap中点击按钮后变灰并显示加载中实例代码
Sep 23 #Javascript
jQuery实现边框动态效果的实例代码
Sep 23 #Javascript
You might like
PHP基础之运算符的使用方法
2013/04/28 PHP
phpcms配置列表页以及获得文章发布时间
2017/07/04 PHP
Laravel validate error处理,ajax,json示例
2019/10/25 PHP
使用PHP+Redis实现延迟任务,实现自动取消订单功能
2019/11/21 PHP
JS定时器实例
2013/04/17 Javascript
使用javascript实现ListBox左右全选,单选,多选,全请
2013/11/07 Javascript
JavaScript实现按照指定长度为数字前面补零输出的方法
2015/03/19 Javascript
javascript实现禁止复制网页内容汇总
2015/12/30 Javascript
JS 拦截全局ajax请求实例解析
2016/11/29 Javascript
jQuery实现火车票买票城市选择切换功能
2017/09/15 jQuery
基于jquery实现五星好评
2017/11/18 jQuery
10行原生JS实现文字无缝滚动(超简单)
2018/01/02 Javascript
对layui中表单元素的使用详解
2018/08/15 Javascript
深入浅出了解Node.js Streams
2019/05/27 Javascript
webpack实践之DLLPlugin 和 DLLReferencePlugin的使用教程
2019/06/10 Javascript
javascript中call,apply,callee,caller用法实例分析
2019/07/24 Javascript
python zip文件 压缩
2008/12/24 Python
在Python程序中实现分布式进程的教程
2015/04/28 Python
Django日志模块logging的配置详解
2017/02/14 Python
对Python Class之间函数的调用关系详解
2019/01/23 Python
python使用多线程查询数据库的实现示例
2020/08/17 Python
Python监听剪切板实现方法代码实例
2020/11/11 Python
Python爬虫爬取微博热搜保存为 Markdown 文件的源码
2021/02/22 Python
分享全球十款超强HTML5开发工具
2014/05/14 HTML / CSS
美国最受欢迎的童装品牌之一:The Children’s Place
2016/07/23 全球购物
倩碧英国官网:Clinique英国
2018/08/10 全球购物
欧舒丹俄罗斯官方网站:L’OCCITANE俄罗斯
2019/11/22 全球购物
Java编程面试题
2016/04/04 面试题
银行实习的自我鉴定
2013/12/10 职场文书
创业计划实施的7大步骤
2014/02/05 职场文书
革命先烈的英雄事迹材料
2014/02/15 职场文书
党员自我评价2015
2015/03/03 职场文书
工作调动申请报告
2015/05/18 职场文书
2015年物资管理工作总结
2015/05/20 职场文书
解决python3安装pandas出错的问题
2021/05/20 Python
Python 数据结构之十大经典排序算法一文通关
2021/10/16 Python