[转]JS底层接口函数
离上一篇文章《纯CSS3透明水晶盒》已经3个月之久了,之间住院离职搬家回家,博客长草了已经,过深圳后发现身体还是处于垃圾状态,捆了电脑跟几本在职期间略翻过几遍的书回家过冬,对于预约了面试以及回家后陆续来电的公司我深感抱歉,希望等我恢复状态了之后还有合作的机会。
以前做外包单子的时候都是链个jQuery然后使用封装好的接口,很方便,但有时候动不动就用几十K的jQuery写一个普通得不能再普通的效果,大材小用不说,严重影响了页面的加载速度。然而用习惯了之后一撇开它用最原始的JS来实现,就难免会有兼容性问题,目前已经回家10来天,JS练着也感觉有一些水准了,写了些底层接口,分享出来:
1、通过类名获取元素:
function getElementsByClassName(className,root){
var list=new Array();
var temClass;
if(!root)root=document.body;
var array=root.getElementsByTagName_r("*");
for(var i=0;i<array.length;i++){
if(document.all) temClass=array[i].getAttribute("className");
else temClass=array[i].getAttribute("class");
if(temClass==null)
continue;
var temList=temClass.split(" ");
for(var j=0;j<temList.length;j++){
if(temList[j]==className){
list.push(array[i]);
}
}
}
return list;
}
以root为根节点获取页面中类名为“className”的元素集合,如果root为undefined,则未传入root参数,将取document.body为默认根节点来获取页面中类名为“className”的元素集合并返回。获取所有标签并判断类名属性是否与className相符。
2、添加类名:
function addClass(object,className){
var classString;
if(document.all) classString=object.getAttribute("className");
else classString=object.getAttribute("class");
if(classString==null){
if(document.all) object.setAttribute("className",className);
else object.setAttribute("class",className);
}
else{
classString+=" "+className;
if(document.all) object.setAttribute("className",classString);
else object.setAttribute("class",classString);
}
}
为object添加className类,如果object已经定义了类则后续再添加,如为<div class="main">添加"box"类,添加后结果为<div class="main box">,而不是<div class="box">。
3、删除类名:
function removeClass(object,className){
var classString;
if(document.all) classString=object.getAttribute("className");
else classString=object.getAttribute("class");
if(classString==null) return false;
var classArray=classString.split(" ");
for(var i=0;i<classArray.length;i++){
if(classArray[i]!=className) continue;
else{
classArray.splice(i,1);
}
}
classString=classArray.join(" ");
if(document.all)object.setAttribute("className",classString);
else object.setAttribute("class",classString);
}
为object删去className类,如为<div class="main box">删去"box"类,添加后结果为<div class="main">,而不是<div class="">。
先以字符串的形式获取object的现有类名,再以split空格分割为数组,逐个与className比较,匹配了就删了它然后join成字符串赋给object。
4、获取下一个节点:
function getNextNode(object){
if(document.all){
if(object.nextSibling) return object.nextSibling;
}
else{
var nodeText=object.nextSibling.nodeValue.replace(/\s/g,"");
if(object.nextSibling.nodeType==3&&nodeText==""){
if(object.nextSibling.nextSibling) return object.nextSibling.nextSibling;
}
else if(object.nextSibling.nodeType==3&&nodeText.length>0) return object.nextSibling;
}
}
JS原生有提供一个nextSibling属性,不过需要对其进行FF兼容处理,所以如果IE的话,如果元素存在,就直接用nextSibling返回元素;FF的话用替换掉多余的换行符后进行判断如果下一个节点是文本节点并且为空,则返回object的下下个元素,如果不为空就返回下一个元素。这个我解释下为什么要这样处理,因为我看到一些例子直接就返回下下个元素,为的是避开FF元素之间的换行文本,但是如果object的下一个是文本元素呢,这种情况如果直接返回下下个元素,就错了,因为用户可能需要的是文本元素。如:
<div class="main">
<div class="box"></div>
龙
<span></span>
</div>
IE下:main里面有3个子节点,2个元素1个文本
FF下:main里面有5个子节点,2个元素3个文本(两个换行跟文本合并在一起成为一个文本),大致如下:
#text
<div class="box"></div>
#text龙#text
<span></span>
#text
#text为换行符,所以替换掉元素上面的换行符后,如果文本元素不为空,如“#text龙#text”,替换后为“龙”,则可知当前存在文本节点,应当返回该节点。
5、获取子节点:
function getChildNodes(object){
if(document.all) return object.childNodes;
else{
var childArray=object.childNodes;
var temArray=new Array();
for(var i=0;i<childArray.length;i++){
if(childArray[i].nodeType==3&&childArray[i].nodeValue.replace(/\s/g,"")=="") continue;
temArray.push(childArray[i]);
}
return temArray;
}
}
传入根元素,获取其子节点,兼容处理跟getNextNode一致,不解释。
6、获取元素:
function repeatCheck(checkList){
for(var i=0;i<checkList.length;i++)
for(var j=i+1;j<checkList.length;j++)
if(checkList[i]===checkList[j]) checkList.splice(j,1);
return checkList;
}
function getElement(string,rootArray){
if(!rootArray){
rootArray=new Array();
rootArray[0]=document.body;
}
var temArray=string.split(" ");
if(temArray.length==1){
var returnList=new Array();
string=temArray[0];
while(rootArray.length){
if(string.match(/^\#{1}/)){
var temId=string.replace(/^\#{1}/,"");
returnList.push(document.getElementByIdx_x(temId));
}
else if(string.match(/^\.{1}/)){
var temClass=string.replace(/^\.{1}/,"");
var classList=getElementsByClassName(temClass,rootArray[0]);
for(var i=0;i<classList.length;i++){
returnList.push(classList[i]);
}
}
else{
var obj=rootArray[0].getElementsByTagName_r(string);
if(obj) for(var i=0;i<obj.length;i++) returnList.push(obj[i]);
}
rootArray.shift();
}
return repeatCheck(returnList);
}
else{
var childArray=new Array();
for(var i=0;i<rootArray.length;i++){
var arr=new Array(rootArray[i]);
childArray=childArray.concat(getElement(temArray[0],arr));
}
if(temArray.length>1){
temArray.shift();
string=temArray.join(" ");
return getElement(string,childArray);
}
}
}
感谢你看完整篇文章,作为报答献上本文章的压轴函数,个人目前最精华的了哈,可以这样getElement(".main .box a")获取到main里面类属性为.box子元素中的a元素,这个是模仿jQuery里面的写法的(采用分治递归的思想,大学睡过去的一门算法课,只可惜周公不会算法,所以我现在算法半桶水,汗...),具体实现思路:
获取一段类名字符串以及根节点数组,如果根节点数组不为空,则以数组元素为根节点获取对应类名元素,为空则以document.body为根;
把类名字符串分割,分两种情况:
如果分割得到数组temArray长度为1,则为最普通的情况,没有子选择器,再细分为三种:由ID获得、由类名获得、由标签名获得。其中只有document.getElementByIdx_x_x,没有root.getElementById的函数。
如果得到数组长度大于1,那么就取temArray的第一个元素循环获取以当前rootArray里面元素为根节点的元素集合,合并到rootArray里面,循环一次,就删除头部一个元素,一套循环下来,刚好把原来的rootArray元素删光并且获得新的所有与string中的第一个元素匹配的元素,放在childArray里面;如果temArray长度大于1,那么删了第一个,因为已经获取到了,把剩下合并成字符串进行递归返回即可。
如string:".main .box a"那么一套循环下来就获得了.main的所有元素,然后把字符串string变成".box a",把前一轮获取到的.main的元素变成下一轮的根节点进行递归,下一轮string就变成了"a",当前获得的元素则是满足".main .box",再走一轮就能得到需求的元素集合。
我有个习惯,看了一段时间东西之后会进行下整理总结,做一个综合性的例子,之前的盒子就是一个总结的实例,不过目前就写这几个接口函数而已,在写两个小游戏,虽然基本写出来了,不过还是等整理后再发出来,敬请期待。