results.push(callback.call(this,this[i],i));
}
returnresults;
};
当然,map函数传入单个参数,一个回调函数。
我们遍历数组中的每一项,收集回调函数返回的所有内容放到results数组中。
注意我们如何调用回调函数:
?
1
callback.call(this,this[i],i));
这样函数就会在我们的Dome实例的上下文中被调用,它接受两个参数:
当前元素,以及索引号。
我们也想要一个forEach函数。
它确实非常简单:
?
1
2
3
4
Dome.prototype.forEach(callback){
this.map(callback);
returnthis;
};
map和forEach间的唯一区别是map需要返回一些东西,因此我们也可以只传入我们的回调函数给this.map并忽略返回的数组,我们将返回this来使得我们的库支持链式操作。
我们将经常使用forEach。
所以,注意当返回我们的this.forEach对函数的调用时,我们事实上是返回了this。
例如,下面的方法实际上返回相同的东西:
?
1
2
3
4
5
6
7
Dome.prototype.someMethod1=function(callback){
this.forEach(callback);
returnthis;
};
Dome.prototype.someMethod2=function(callback){
returnthis.forEach(callback);
};
另外:
mapOne。
很容易看出这个函数是干什么的,但是问题是为什么我们需要它?
它需要一些你可以叫做“库哲学”的东西来解释。
一个简单的“哲学的”迂回
如果创建一个库只是写代码,那就不是什么难的工作了。
但是我正在做这个项目,我发现困难的部分是决定一些方法应该如何工作。
很快,我们将建一个text方法,它返回我们选择元素的文本。
如果我们的Dome对象封装几个DOM节点(如dome.get("li")),它会返回什么呢?
如果你在jQuery做类似的事情($("li").text()),你将会得到一个所有元素的文本拼起来的字符串。
它有用吗?
我认为没用,但是我不知道更好的返回是什么。
在这个项目中,我将以数组形式返回多个元素的文本,除非数组中只有一个元素,那我们就返回一个文本字符串,而不是只有一个元素的数组。
我想你最常用的是获取单个元素的文本,所以我们对这个情况进行优化。
然而,如果你获取多个元素的文本,我们也会返回一些你能操作的东西。
回到代码
所以,mapOne方法只是简单的运行map,然后要么返回数组,要么返回单元素数组中的元素。
如果你还是不确定这有什么用,等一会你会发现的!
?
1
2
3
4
Dome.prototype.mapOne=function(callback){
varm=this.map(callback);
returnm.length>1?
m:
m[0];
};
步骤5:
处理文本和HTML
接下来,让我们添加text方法。
就像jQuery一样,我们可以给它传入一个字符串并设置元素的文本,或不传参数来获取元素的文本。
?
1
2
3
4
5
6
7
8
9
10
11
Dome.prototype.text=function(text){
if(typeoftext!
=="undefined"){
returnthis.forEach(function(el){
el.innerText=text;
});
}else{
returnthis.mapOne(function(el){
returnel.innerText;
});
}
};
?
1
2
3
4
5
6
7
8
9
10
11
Dome.prototype.text=function(text){
if(typeoftext!
=="undefined"){
returnthis.forEach(function(el){
el.innerText=text;
});
}else{
returnthis.mapOne(function(el){
returnel.innerText;
});
}
};
你可能也想到了,我们需要检查text的值来看它是要设置还是要获取。
注意如果只是用if(text)会有问题,因为空字符串会被判断为false。
如果我们在设置值,我们将对元素调用forEach并且设置它们的innerText属性为text。
如果我们要获取,我们将返回元素的innerText属性。
注意我们使用mapOne方法:
如果我们在处理多个元素,它将返回一个数组,否则它将就是一个字符串。
html方法几乎与text一样,除了它使用innerHTML属性而不是innerText。
?
1
2
3
4
5
6
7
8
9
10
11
12
Dome.prototype.html=function(html){
if(typeofhtml!
=="undefined"){
this.forEach(function(el){
el.innerHTML=html;
});
returnthis;
}else{
returnthis.mapOne(function(el){
returnel.innerHTML;
});
}
};
就像我说的:
几乎完全一样。
步骤6:
调整样式
再接下来,我们希望能添加和删除样式,因此让我们来写一个addClass和removeClass方法。
我们的addClass方法将接收一个字符串或是样式名称的数组。
为了做到这点,我们需要检查参数的类型。
如果是数组,我们将遍历它并创建一个样式名的字符串。
否则,我们就简单的在样式名前加一个空格,这样它就不会和元素已有的样式混在一些。
然后我们遍历元素并且将新的样式附加到className属性后面。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
Dome.prototype.addClass=function(classes){
varclassName="";
if(typeofclasses!
=="string"){
for(vari=0;i className+=""+classes[i];
}
}else{
className=""+classes;
}
returnthis.forEach(function(el){
el.className+=className;
});
};
很直接,对吗?
那如何删除样式呢?
为了保持简单,我们只允许一次删除一个样式。
?
1
2
3
4
5
6
7
8
9
Dome.prototype.removeClass=function(clazz){
returnthis.forEach(function(el){
varcs=el.className.split(""),i;
while((i=cs.indexOf(clazz))>-1){
cs=cs.slice(0,i).concat(cs.slice(++i));
}
el.className=cs.join("");
});
};
对每个元素,我们将el.className分隔成一个数组。
然后,我们使用一个while循环来剔除我们传入的样式,直到cs.indexOf(clazz)返回-1。
我们这样做是为了处理同样的样式在一个元素中出现的不止一次的特殊情况:
我们必须保证它真的被删除了。
一旦我们确保删除每个样式的实例,我们用空格连接数组的每一项并把它设置到el.className。
步骤7:
修正一个IE的Bug
我们正在处理的最糟糕的浏览器是IE8。
在我们的小小的库中,只有一个IEbug需要我们处理,很幸运它很简单。
IE8不支持Array的indexOf方法;我们在removeClass中使用到它,所以让我们修复它:
?
1
2
3
4
5
6
7
8
9
10
if(typeofArray.prototype.indexOf!
=="function"){
Array.prototype.indexOf=function(item){
for(vari=0;i if(this[i]===item){
returni;
}
}
return-1;
};
}
它非常简单,并且这不是一个完全的实现(不支持第二个参数),但是能达到我们的目的。
步骤8:
调节属性
现在,我们想要一个attr函数。
这很容易,因为它与我们的text或html方法非常类似。
像那些方法一样,我们能够获取或设置属性值:
我们可以传入元素名和值来设置,也可以只传入属性名来获取。
?
1
2
3
4
5
6
7
8
9
10
11
Dome.prototype.attr=function(attr,val){
if(typeofval!
=="undefined"){
returnthis.forEach(function(el){
el.setAttribute(attr,val);
});
}else{
returnthis.mapOne(function(el){
returnel.getAttribute(attr);
});
}
};
如果val有一个值,我们将遍历这些元素并且将选择的属性设置为这个值,使用元素的setAttribute方法。
否则,我们使用mapOne通过getAttribute方法来返回属性值。
步骤9:
创建元素
像很多好的库一样,我们应该能够创建新的元素。
当然它作为一个Dome实例的一个方法不是很好,所以让我们直接把它挂到dome对象上去。
?
1
2
3
4
5
vardome={
//getmethodhere
create:
function(tagName,attrs){
}
};
你已经看到,我们使用两个参数:
元素的名字,和属性值对象。
大部分属性能过attr方法赋值,但是两种方法可以做特殊处理。
我们使用addClass方法操作className属性,以及text方法操作text属性。
当然,我们首先需要创建元素和Dome对象。
下面是整个操作的代码:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
create:
function(tagName,attrs){
varel=newDome([document.createElement(tagName)]);
if(attrs){
if(attrs.className){
el.addClass(attrs.className);
deleteattrs.className;
}
if(attrs.text){
el.text(attrs.text);
deleteattrs.text;
}
for(varkeyinattrs){
if(attrs.hasOwnProperty(key)){
el.attr(key,attrs[key]);
}
}
}
returnel;
}
我们创建元素并将它传给一个新的Dome对象。
然后中我们处理属性。
注意在操作完它们后我们必须删除className和text属性。
这样可以避免当我们在attrs中遍历剩下的key值时被应用为属性。
当然我们最后要返回这个新建的Dome对象。
但是现在只是创建了新的元素,我们希望把它插入到DOM中对吗?
步骤10:
附加元素
下一步,我们将写append和prepend方法。
这些确实是有点难搞的函数,主要是因为有很多种使用情况。
以下是我们希望能做到的:
?
1
2
dome1.append(dome2);
dome1.prepend(dome2);
使用情况如下:
我们可能想要append或prepend
∙一个新的元素到一个或多个已存在的元素
∙多个新元素到一个或多个已存在的元素
∙一个已存在的元素到一个或多个已存在的元素
∙多个已存在的元素到一个或多个已存在的元素
注意:
我使用“新”来表示元素还没有在DOM中;已存在的元素是已经在DOM中有的。
让我们一步一步来:
?
1
2
3
4
5
6
Dome.prototype.append=function(els){
this.forEach(function(parEl,i){
els.forEach(function(childEl){
});
});
};
我们期望els参数是一个Dome对象。
一个完整的DOM库可以接受一个节点或nodelist作为参数,但是我们暂时不这样做。
我们必须遍历我们每一个元素,并且在它