温馨提示
本文阅读对象: 对 JavaScript 有一定的了解,如果你没有学过或者忘记 JavaScript 某些操作,请看 阮一峰 JavaScript 教程 。
导语
DOM 有许多 API ,但是有些 API 太难用了。
比如,想获取某 element 的所有兄弟该怎么办?
再比如,我想给某 element 加多个 class ,你说有 node.classList.add( )啊。那我想加 100 个怎么办?总不能写 100 遍吧,so 自己封装个 API 。
向大佬学习
自己写一个毫无头绪啊,那jQuery是怎么做的,我们模仿一下嘛。
在jQuery这个库里,有许多函数,比如addClass( ),.css( ),.data( )等等。也就是说,jQuery是一个大仓库,仓库里有很多工具,我们需要什么的时候就用什么工具。
现在我们要做的就是,自己生产很多工具,然后建立自己的仓库,我们想用什么就从仓库里拿出来什么。
1. 生产工具
假设我们现在需要两个工具,分别是获取兄弟 element 和 添加多个 class 。
实现功能
1. 获取兄弟 element
操作步骤:
- 在 html 中有一个 ul 标签,在 ul 中有 5 个 li 。
<ul>
<li id="item1">item1</li>
<li id="item2">item2</li>
<li id="item3">item3</li>
<li id="item4">item4</li>
<li id="item5">item5</li>
</ul>
-
获取 id 为 item3 的兄弟元素。
首先定义一个 allChildren 变量来存储 item3 的父节点所有的子元素。
获取子元素 DOM 有两个 API ,node.parent.children 和 node.parent.childNodes 。应该选择哪个呢?parent.childNodes:获取节点,不同浏览器表现不同;
IE:只获取元素节点;
非IE:获取元素节点与文本节点;
parent.children:获取元素节点,浏览器表现相同。
因此建议使用children。
var allChildren = item3.parentNode.children;
上图是在某博客页面做的测试,可以看到使用 parent.childNodes 获取到了 text 节点。
- 定义一个空数组来存兄弟元素,此时数组长度为0。
var arr = {length:0};
- 遍历所有的孩子节点,如果不是 item3 ,那么就存到 arr 数组中。
for(var i = 0;i < allChildren.length;i++){
if(allChildren[i] !== item3){
arr[arr.length] = allChildren[i];
arr.length++;
}
}
小技巧:使用 arr[arr.length] = allChildren[i];
使得数组下标依次存储 item 元素。
完整代码:
<ul>
<li id="item1">item1</li>
<li id="item2">item2</li>
<li id="item3">item3</li>
<li id="item4">item4</li>
<li id="item5">item5</li>
</ul>
var allChildren = item3.parentNode.children;
var arr = {length:0};
for(var i = 0;i < allChildren.length;i++){
if(allChildren[i] !== item3){
arr[arr.length] = allChildren[i];
arr.length++;
}
}
console.log(arr);
运行结果:
注意:
这个 arr 数组是一个伪数组,它的原型链直接指向了 Object 并没有指向 Array.prototype ,只有原型链中指向 Array.prototype 的数组才是真正的数组。如果原型链中不包含 Array.prototype 是没有数组.push( )等方法的。
封装成函数
- 包装一下,加个 function ,同时起个名字,方便调用。
function getSiblings(){
var allChildren = item3.parentNode.children;
var arr = {length:0};
for(var i = 0;i < allChildren.length;i++){
if(allChildren[i] !== item3){
arr[arr.length] = allChildren[i];
arr.length++;
}
}
console.log(arr);
}
- 我们给这个函数一个返回值,返回值呢就是这个数组,把
console.log(arr)
改成return arr
。 - 此时我们发现,item3 这个 id 是函数外面的值,是在调用函数的时候传参才能获取到,所以给我们的函数加一个参数,同时把 item3 改成参数 node 。
function getSiblings(node){
var allChildren = node.parentNode.children;
var arr = {length:0};
for(var i = 0;i < allChildren.length;i++){
if(allChildren[i] !== node){
arr[arr.length] = allChildren[i];
arr.length++;
}
}
return arr;
}
console.log(getSiblings(item3));
2. 添加多个 class
操作流程同上,这里只给出代码和必要的解释。
实现功能:
<ul>
<li id="item1">item1</li>
<li id="item2">item2</li>
<li id="item3">item3</li>
<li id="item4">item4</li>
<li id="item5">item5</li>
</ul>
var classes = {'a':true,'b':false,'c':true}
for(var key in classes){
var value = classes[key];
if(value){
item3.classList.add(key);
}
else{
item3.classList.remove(key);
}
}
利用 hash 存储是否添加的 class ,如果为 true ,添加给 item3 ,如果为 false 移除该 class 。
封装成函数:
function addClasses(node,classes){
for(var key in classes){
var value = classes[key];
if(value){
node.classList.add(key);
}
else{
node.classList.remove(key);
}
}
}
addClasses(item3,{'a':true,'b':false,'c':true});
// 传入参数包括节点和要添加的 class
优化整理:
function addClasses(node,classes){
for(var key in classes){
var value = classes[key];
var methodName = value ? 'add':'remove';
node.classList[methodName](key);
}
}
addClasses(item3,{'a':true,'b':false,'c':true});
这里要说明一下,我们根据传来的 key 值为 true 或者 false 来决定是否添加这个 class。node.classList.add(key);
和 node.classList.remove(key);
是属于一类代码,区别就是调用的是 add 方法还是 remove 方法,那么存在优化的可能性。
定义一个 methodName 变量存储 value 值,也就是遍历过程中每次的 true 或者 false。让 node.classList[methodName](key)
每次直接调用 methodName 即可完成操作。
备注:如果你只了解对象 obj.add( )
这种调用,不理解关于obj[]( )
调用方法,情回顾 JavaScript 基础。
运行结果:
2. 建造仓库放工具
现在我们的工具都造好了,就要给工具造房子了。
jQuery 工具的房子叫 jQuery ,那我们也取一个属于自己的名字,比如我的叫 simpleTools 。
window.simpleTools = function(){
return{
getSiblings:function(){},
addClass:function(){}
};
};
window.simpleTools 是我们的大房子,这是一个函数大房子,房子里面现在住着 getSiblings 对象和 addClass 对象,这就好比是放工具的架子。接下来,我们把上面封装好的函数放在对应的架子上。
window.simpleTools = function(node){
return{
getSiblings:function(){
var allChildren = node.parentNode.children;
var arr = {length:0};
for(var i = 0;i < allChildren.length;i++){
if(allChildren[i] !== node){
arr[arr.length] = allChildren[i];
arr.length++;
}
}
return arr;
},
addClass:function(classes){
for(var key in classes){
var value = classes[key];
var methodName = value ? 'add':'remove';
node.classList[methodName](key);
}
}
};
};
传给 function 的 node 就好像是我们的仓库小管家,一旦他被通知要工作了(有参数传过来),那他就去告诉每一个工具,做好准备随时准备开工。
现在加几句测试语句,看看运行结果。
var nodeTest = simpleTools(item3);
console.log(nodeTest.getSiblings());
nodeTest.addClass({'a':true,'b':false,'c':true});
运行结果:
很好,和之前的运行结果相同,说明我们并没有因为放到仓库里而产生bug。
小结
到这为止,你已经学会了写一个自己的仓库。我们再来回顾一下流程吧。
生产工具:
- 实现功能
- 封装成函数
- 适当优化
建造仓库放工具:
- 建一个仓库
- 放入工具
快动手实现一个属于自己的仓库吧,
代码的后续优化请看 从封装函数到实现简易版自用jQuery (二)。