你的位置:首页 > Java教程

[Java教程]框架设计—选择器模块


一个礼拜没动静了,实在是懒惰。。

好了,不扯淡了,进入正题:框架封装之选择器模块。

首先,我们为什么要封装框架?

浅显的文字不具有良好的说服性,来做几个题目吧:

  1. 求一个数组所有项之和

//求和 var sum=0;            for(var i=0; i<arr.length;i++){   sum+=arr[i];         }                 console.log(sum);       

 

  2. 求数组中最大值

//求最大值
var max=arr[0]; for(var i=0; i<arr.length;i++){ if(max<arr[i]){ max=arr[i]; } } console.log(max)

 3. 获取数组中指定值

//获取指定值         var index=0;       function get(v){       for(var i in arr){      if(arr[i]===v){        index=i;         break;        }           }           }             

ok,题目做到这就行了,我们可以看出每道题目中都用到了for循环,每次都写一遍是不是很麻烦,这里代码不多,可能感觉还好,如果工作量大,冗余度是很高的,所以我们就可以封装这个for循环:

// arr: 目标数组                             // fn: 自定义方法                               var each= function ( arr , fn ) {                     for(var i=0; i<arr.length;i++){                       // 使用call改变作用域,后面调用可直接使用this                       if( fn.call(arr[i],i,arr[i])===false) break;              //做搜索操作,break必须写到循环中,此处使用返回值检索是否执行break               }                                  }                                  

封装后重写求和方法

 //1.封装后求和          var sum=0;         each(arr,function(i,v){    sum+=v;         })             

好了,其他就不写了,比较简单,所以框架主要是为了降低程序之间的依赖性和耦合性,使重用性达到最高,下面说说我们的重点,如何封装选择器框架。

目标:实现常见选择器框架的封装,即id选择器,类选择器,标签选择器。

//传统做法function getId(id){    return document.getElementById(id);  }
function getTagName(name){    return document.getElementsByTagName(name);  }
//获取id为box的节点,然后操作
var dom = getId('box');'red';
dom.style.background = 'red';
//获取属性名为div 的节点,然后操作
var tags = getTagName('div');
for(var i=0; i<tags.length;i++){
  tags[i].style.background = 'red';
}

传统做法如上,可以通过该方法实现普通需求,但是要是继续想通过类名,标签名获得属性,然后分别加样式,岂不是很累,所以我们可以通过下面方式取代原始方法:

//id 选择器  function getId(id,node,arr){     arr.push(node.getElementById(id));    return arr;  }// 属性选择器  function getName(name,node,arr){    [].push.apply(arr,node.getElementsByTagName(name));    return arr;  }// 类选择器  function getClass(clas,node,arr){    [].push.apply(arr,node.getElementsByClassName(classname));    return arr;  }

 紧接着我们再实现一个接口, 根据输入,调用不同的选择器

//多选择器接口//  getType( tag , node , arr )//        类型  父节点 数组容器  function getType(tag,node,arr){    node= node || document;    arr= arr || [];
              rag(1) rag(2) rag(3) rag(4) var rag=/^(?:#([\w\-]+)|\.([\w\-]+)|([\w\-]+)|(\*))$/; var type=rag.exec(tag); if(type){ if(type[1]) arr=getId(type[1],node,arr); else if(type[2]) arr=getClass(type[2],node,arr); else arr=getName(type[3]||'*',node,arr); } return arr; }

上文中用到了正则表达式,简要解释下,/^(?:#([\w\-]+)|\.([\w\-]+)|([\w\-]+)|(\*))$/

^表示已xx开头,$表示已xx结尾; 

(?:)其中'( )'表示分组,此处使用只是提升中间部分优先级,使用 ?: 取消分组; 

匹配 id:#([\w\-]+)  可通过tag(1)获取除#外的id字符

匹配 类:\.([\w\-]+)    tag(2)

匹配 标签名:([\w\-]+)        tag(3)

通配符:(\*)        tag(4)

PS:
var arr1=[1,3,4];
var arr2=[3,4,5];

如果我们要把 arr2展开,然后一个一个追加到arr1中去,最后让arr1=[1,3,4,3,4,5]
arr1.push(arr2)显然是不行的。 因为这样做会得到[1,3,4,[3,4,5]]

但是我们可以用 Array.prototype.push.apply(arr1,arr2) 实现需求
因此我们这样做 [].push.apply(arr,node.getElementsByTagName(name));

但是,在IE8 及低于IE8以下的浏览器需要注意几个问题.

1、 apply 传参不接受类似 {0:'a',1:'b',length:2} 的对象,可以是 数组、arguments、  HTMLCollection 对象 和 Nodelist 对象等节点集合.

在这种情况下你也许想要把传参对象转换成数组.

2、节点集合无法调用数组的原型方法,但是 类似 {0:'a',1:'b',length:2} 的对象可以。

 看似我们实现了所有需求,但是别忘记另我们头疼的IE8浏览器,它不支持 getElementsByClassName 为此我们需要解决兼容问题。
您也许会想到以下方法:
 if(document.getElementsByClassName){    var results= document.getElementsByClassName('box');  }  else {    //自己实现的方法  }

 

但是存在两个问题 1.性能问题 2.安全问题
先解决性能问题 :我们都知道原型链吧,当函数或对象执行某方法或使用属性时,会从其自身开始搜寻,然后一直沿着原型链往上找,直至找到为止才停止寻找,每往上一层,都会增加搜寻时间,降低性能,getElementsByClassName是
document的属性方法,因此最好将该属性放在当前对象中,而且每次执行时都会判断是否兼容,也降低性能,因此我们可以用support对象缓存能力检测结果,如下
1.提升性能
 var support={};//!! 装换成boolean类型support.getElementsByClassName =!!document.getElementsByClassName;  if(support.getElementsByClassName){    var results= document.getElementsByClassName('box');  }  else {    //自己实现的方法  }

 

减少了性能问题,但是存在安全问题 ,看下面代码会输出什么?
document.getElementsByClassName=123; //注入代码攻击  var support={};  support.getElementsByClassName = !!document.getElementsByClassName;  if(support.getElementsByClassName){    console.log('支持ByClassName');  }  else {    console.log('不支持ByClassName');  }

 

无论在什么浏览器中都会输出: 支持ByClassName.
因为document.getElementsByClassName=123;这句代码,此时执行判断是true,具体传送门==>http://blog.csdn.net/writehappy/article/details/8970491
告诉你们一个大牛们使用注入代码攻击的做的事:在chrome上为自己的微博刷赞!!!
好了,转回整体,如何解决这个问题呢?
继续看操作:
2.提升安全性
//  在全局作用域(最终要变成沙箱模式),提供一个support对象,存储属性是否支持,不用每次检测    var support={};    support.getElementsByClassName = (function () {      istrue= !!document.getElementsByClassName;      if(istrue&& (typeof document.getElementsByClassName==="function")){        //不仅判断是否支持该方法,还要执行该方法检验        var div=document.createElement('div'),            testDiv=document.createElement('div');        testDiv.className='test';        div.appendChild(testDiv);        //检验        return div.getElementsByClassName('test')[0]===testDiv;      }      else return false;    })();

 

很棒吧,jquery就是这么实现的,以下
jquery实现
/** * Support testing using an element * @param {Function} fn Passed the created div and expects a boolean result */function assert( fn ) {  var div = document.createElement("div");  try {    return !!fn( div );  } catch (e) {    return false;  } finally {    // Remove from its parent by default    if ( div.parentNode ) {      div.parentNode.removeChild( div );    }    // release memory in IE    div = null;  }}  // Support: IE<8  // Verify that getAttribute really returns attributes and not properties  // (excepting IE8 booleans)  support.attributes = assert(function( div ) {    div.className = "i";    return !div.getAttribute("className");  });  /* getElement(s)By*  ---------------------------------------------------------------------- */  // Check if getElementsByTagName("*") returns only elements  support.getElementsByTagName = assert(function( div ) {    div.appendChild( doc.createComment("") );    return !div.getElementsByTagName("*").length;  });  // Support: IE<9  support.getElementsByClassName = rnative.test( doc.getElementsByClassName );

 

那么我们的类选择器可以重新写了
类选择器重写
 function getClass(clas,arr,node){    arr=arr||[];;    [].push.apply(arr,getclass(clas,node));    return arr;  }    //getClas兼容方法    function getclass(classname,node){      node=node || document;      if(support.getElementsByClassName){        return node.getElementsByClassName(classname);      }      else {//   2.自己实现的代码        var arr=document.getElementsByTagName('*');        var results=[];        each(arr, function (k,v) {          if(this.className===classname){            results.push(this);          }        });        return results;      }    }

 

 实现效果:


ok,这次就讲到这儿吧,啰啰嗦嗦,希望各位不要嫌弃我的这点拙见,还希望多多指点,共同进步,未完待续。
2016-04-10