框架设计—选择器模块

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

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

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

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

  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 || [];
              type(1) type(2) type(3) type(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-]+)  可通过  type(1)  获取除#外的id字符

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

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

通配符:(*)        type(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
原文地址:https://www.cnblogs.com/delicate/p/5376623.html