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 相关文章推荐
Mootools 1.2教程 定时器和哈希简介
Sep 15 Javascript
Tinymce+jQuery.Validation使用产生的BUG
Mar 29 Javascript
修改或扩展jQuery原生方法的代码实例
Jan 13 Javascript
详解javascript传统方法实现异步校验
Jan 22 Javascript
Bootstrap Table使用方法详解
Aug 01 Javascript
Angularjs 自定义服务的三种方式(推荐)
Aug 02 Javascript
Node.js 数据加密传输浅析
Nov 16 Javascript
原生JS实现简单放大镜效果
Feb 08 Javascript
javascript修改浏览器title方法 JS动态修改浏览器标题
Nov 30 Javascript
JS基于递归实现网页版计算器的方法分析
Dec 20 Javascript
Vue-Quill-Editor富文本编辑器的使用教程
Sep 21 Javascript
Vue 中使用富文本编译器wangEditor3的方法
Sep 26 Javascript
用瀑布流的方式在网页上插入图片的简单实现方法
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
FCKeditor的安装(PHP)
2007/01/13 PHP
php中inlcude()性能对比详解
2012/09/16 PHP
destoon网站转移服务器后搜索汉字出现乱码的解决方法
2014/06/21 PHP
PHP中SimpleXML函数用法分析
2014/11/26 PHP
php数组添加元素方法小结
2014/12/20 PHP
使用纯php代码实现页面伪静态的方法
2015/07/25 PHP
Docker 如何布置PHP开发环境
2016/06/21 PHP
JQuery1.4+ Ajax IE8 内存泄漏问题
2010/10/15 Javascript
基于jQuery的获得各种控件Value的方法
2010/11/19 Javascript
jquery动态加载js三种方法实例
2013/08/03 Javascript
解析JavaScript中的不可见数据类型
2013/12/02 Javascript
javascript trim函数在IE下不能用的解决方法
2014/09/12 Javascript
AngularJS自定义控件实例详解
2016/12/13 Javascript
assert()函数用法总结(推荐)
2017/01/25 Javascript
JavaScript中双符号的运算详解
2017/03/12 Javascript
Postman的下载及安装教程详解
2018/10/16 Javascript
vue 解决无法对未定义的值,空值或基元值设置反应属性报错问题
2020/07/31 Javascript
深入源码解析Python中的对象与类型
2015/12/11 Python
Python切片知识解析
2016/03/06 Python
Python打印输出数组中全部元素
2018/03/13 Python
python Kmeans算法原理深入解析
2019/08/23 Python
如何使用python的ctypes调用医保中心的dll动态库下载医保中心的账单
2020/05/24 Python
完美解决Pycharm中matplotlib画图中文乱码问题
2021/01/11 Python
Python字符串的15个基本操作(小结)
2021/02/03 Python
苹果中国官方网站:Apple中国
2016/07/22 全球购物
德国箱包网上商店:koffer24.de
2016/07/27 全球购物
面向对象编程是如何提高软件开发水平的
2014/05/06 面试题
毕业生造价工程师求职信
2013/10/17 职场文书
学生党员思想汇报
2013/12/28 职场文书
护士思想汇报
2014/01/12 职场文书
期中考试反思800字
2014/05/01 职场文书
副乡长民主生活会个人对照检查材料思想汇报
2014/10/01 职场文书
2016年度继续教育学习心得体会
2016/01/19 职场文书
使用Spring处理x-www-form-urlencoded方式
2021/11/02 Java/Android
淡雅古典唯美少女娇媚宁静迷人写真
2022/03/21 杂记
MySQL 字符集 character
2022/05/04 MySQL