目的:

根据传入的选择器类型选出第一个符合的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

 

JavaScript实现DOM对象选择器的更多相关文章

  1. javaScript操作DOM对象(看三遍,敲三遍,写三遍! 不会你找我)!!

    DOM是Document Object Model的缩写,即文档对象模型,是基于文档编程的一套API 使用javaScript操作DOM对象通常分为三类:1.DOM CORE        2.HTM ...

  2. 第四章 JavaScript操作DOM对象

    第四章   JavaScript操作DOM对象 一.DOM操作 DOM是Document Object Model的缩写,即文档对象模型,是基于文档编程的一套API接口,1988年,W3C发布了第一级 ...

  3. JavaScript之DOM对象的获取

    之前我们讲过JavaScript之DOM对象获取的两篇文章,本文是该系列文章之三,点击回顾上两篇文章能更好地理解本文.<JavaScript之DOM对象的获取(一)>: <JavaS ...

  4. JavaScript BOM DOM 对象

    title: JavaScript BOM DOM 对象 tags: JavaScript --- browser object model document onject model BOM对象 w ...

  5. JavaScript:DOM对象

    ylbtech-JavaScript:DOM对象 1. HTML DOM Document 对象返回顶部 1. HTML DOM Document 对象 HTML DOM 节点 在 HTML DOM ...

  6. JavaScript基础--DOM对象(十三):(windows对象:history\location\navigator\screen\event)

    DOM编程1.为什么要学习DOM(1) 通过dom编程,我们可以写出各种网页游戏(2)dom编程也是ajax的重要基础2.DOM编程介绍DOM = Document Object Model(文档对象 ...

  7. JavaScript操作DOM对象

    js的精华即是操作DOM对象 [1]先看代码 <!DOCTYPE html> <html> <head> <meta charset="UTF-8& ...

  8. JavaScript之DOM对象获取(1)

    我们在操作html中的节点的时候,第一步就需要获取到对应节点(元素),才能有后续的操作.获取节点的方式有很多 1.document.getElementById(‘id值’) 通过id精确的选中某一个 ...

  9. JavaScript 操作DOM对象

    1)JavaScript  操作DOM對象 1.DOM:是Document  Object  Model 的缩写,及文档对象模型 2.DOM通常分为三类:DOM Core(核心).HTML-DOM 和 ...

随机推荐

  1. SQL Server内存遭遇操作系统进程压榨案例

    场景: 最近一台DB服务器偶尔出现CPU报警,我的邮件报警阈(请读yù)值设置的是15%,开始时没当回事,以为是有什么统计类的查询,后来越来越频繁. 探索: 我决定来查一下,究竟是什么在作怪,我排查的 ...

  2. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用

    再次调整项目架构是因为和群友dezhou的一次聊天,我原来的想法是项目尽量做简单点别搞太复杂了,仅使用了DbContext的注入,其他的也没有写接口耦合度很高.和dezhou聊过之后我仔细考虑了一下, ...

  3. C++的内存泄漏检测

    C++大量的手动分配.回收内存是存在风险的,也许一个函数中一小块内存泄漏被重复放大之后,最后掏空内存. 这里介绍一种在debug模式下测试内存泄漏的方法. 首先在文件的开头以确定的顺序写下这段代码: ...

  4. PHP之用户验证和标签推荐的简单使用

    本篇主要是讲解一些最简单的验证知识 效果图 bookmark_fns.php <?php require_once('output_fns.php'); require_once('db_fns ...

  5. 玩转spring boot——结合AngularJs和JDBC

    参考官方例子:http://spring.io/guides/gs/relational-data-access/ 一.项目准备 在建立mysql数据库后新建表“t_order” ; -- ----- ...

  6. RMS:不能对生产服务器使用测试清单

    问题说明:在使用office软件RMS加密时报:不能对生产服务器使用测试清单,或者使用 rmsbulk.exe进行RMS加密时,报不能连接到RMS服务器. 解决办法: 请到https://suppor ...

  7. RunLoop 总结:RunLoop的应用场景(一)

    参考资料 好的书籍都是值得反复看的,那好的文章,好的资料也值得我们反复看.我们在不同的阶段来相同的文章或资料或书籍都能有不同的收获,那它就是好文章,好书籍,好资料.关于iOS 中的RunLoop资料非 ...

  8. 完美解决CodeSmith无法获取MySQL表及列Description说明注释的方案

    问题描述: CodeSmith是现在比较实用的代码生成器,但是我们发现一个问题: 使用CodeSmith编写MySQL模板的时候,会发现一个问题:MySQL数据表中的列说明获取不到,也就是column ...

  9. 记录在Windows上安装和使用Oracle数据库过程中的坑

    1.安装Oracle Oracle软件是免费的,可以去官网下载相应的安装包.但是如果用于商业用途需要购买License.官网上针对各种平台,32位和64位都有,如果在Windows一般会下载到两个文件 ...

  10. Spring MVC数据校验

    在web应用程序中,为了防止客户端传来的数据引发程序异常,常常需要对 数据进行验证.输入验证分为客户端验证与服务器端验证.客户端验证主要通过JavaScript脚本进行,而服务器端验证则主要通过Jav ...