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 相关文章推荐
jquery validate.js表单验证的基本用法入门
May 13 Javascript
引用外部脚本时script标签关闭的写法
Jan 20 Javascript
js操作IE浏览器弹出浏览文件夹可以返回目录路径
Jul 14 Javascript
kindeditor修复会替换script内容的问题
Apr 03 Javascript
解析JavaScript面向对象概念中的Object类型与作用域
May 10 Javascript
基于JS代码实现导航条弹出式悬浮菜单
Jun 17 Javascript
详解vue2.0脚手架的webpack 配置文件分析
May 27 Javascript
JS实现unicode和UTF-8之间的互相转换互转
Jul 05 Javascript
vue中如何实现pdf文件预览的方法
Jul 12 Javascript
vue-cli项目配置多环境的详细操作过程
Oct 30 Javascript
JS表格的动态操作完整示例
Jan 13 Javascript
深入分析jQuery.one() 函数
Jun 03 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
模拟OICQ的实现思路和核心程序(三)
2006/10/09 PHP
php随机输出名人名言的代码
2012/10/07 PHP
解析smarty 截取字符串函数 truncate的用法介绍
2013/06/20 PHP
如何利用PHP执行.SQL文件
2013/07/05 PHP
PHP数据库万能引擎类adodb配置使用以及实例集锦
2014/06/12 PHP
qq登录,新浪微博登录接口申请过程中遇到的问题
2014/07/22 PHP
linux下为php添加iconv模块的方法
2016/02/28 PHP
TP5框架请求响应参数实例分析
2019/10/17 PHP
javascript 四则运算精度修正函数代码
2010/05/31 Javascript
Javascript实现重力弹跳拖拽运动效果示例
2013/06/28 Javascript
Javascript简单实现可拖动的div
2013/10/22 Javascript
js导航栏单击事件背景变换示例代码
2014/01/13 Javascript
JQuery表格拖动调整列宽效果(自己动手写的)
2014/09/01 Javascript
深入探究使JavaScript动画流畅的一些方法
2015/06/30 Javascript
写一个移动端惯性滑动&amp;回弹Vue导航栏组件 ly-tab
2018/03/06 Javascript
深入理解Node module模块
2018/03/26 Javascript
jQuery实现炫丽的3d旋转星空效果
2018/07/04 jQuery
微信小程序实现拍照画布指定区域生成图片
2019/07/18 Javascript
bootstrapValidator表单校验、更改状态、新增、移除校验字段的实例代码
2020/05/19 Javascript
Django框架中的对象列表视图使用示例
2015/07/21 Python
Python线程threading模块用法详解
2020/02/26 Python
python如何绘制疫情图
2020/09/16 Python
详解CSS3的图层阴影和文字阴影效果使用
2016/06/09 HTML / CSS
Canvas波浪花环的示例代码
2020/08/21 HTML / CSS
Amaze UI 文件选择域的示例代码
2020/08/26 HTML / CSS
Under Armour瑞典官方网站:美国高端运动科技品牌
2018/11/21 全球购物
恶意软件的定义
2014/11/12 面试题
初中英语教学反思
2014/01/25 职场文书
小学学习雷锋活动总结
2014/07/03 职场文书
大专应届毕业生求职信
2014/07/15 职场文书
大学生实习推荐信
2015/03/27 职场文书
社区义诊通知
2015/04/24 职场文书
销售人员管理制度
2015/08/06 职场文书
《烈火英雄》观后感:致敬和平时代的英雄
2019/11/11 职场文书
七年级作文之下雨天
2019/12/23 职场文书
JavaWeb Servlet开发注册页面实例
2022/04/11 Java/Android