一个礼拜没动静了,实在是懒惰。。
好了,不扯淡了,进入正题:框架封装之选择器模块。
首先,我们为什么要封装框架?
浅显的文字不具有良好的说服性,来做几个题目吧:
-
求一个数组所有项之和
//求和 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