我们都知道jqery中的each方法可以帮助我们遍历数组和对象
那么each中如此强大的遍历功能是是怎么实现的呢?
首先看一个简单的案例,在我们的页面中创建一些div标签、p标签和span标签,要实现对这些标签的选择并循环设置样式
<body>
<div>div</div>
<p>p</p>
<div>div</div>
<span>span</span>
<p>p</p>
<div>div</div>
</body>
<script>
//将qsa方法封装起来,降低代码的错误率
function select( selector ) {
return document.querySelectorAll( selector ); // 得到的是一个伪数组
}
//封装了一个简单的each函数,目前只能遍历数组
function each( arr, callback ) {
for ( var i = 0; i < arr.length; i++ ) {
callback( arr[ i ], i );
}
}
var nodes = select( 'div, p' );
each( nodes, function ( v ) {
v.style.border = v.nodeName == 'DIV' ? //nodeName是一个标签名的大写形式
'1px solid red'
: '1px solid blue';
});
</script>
得到以下样式
但是在jq当中,有一个链式编程的概念,显然我们这里还没有实现,别急,我们一步一步来
要实现链式编程,最重要的是返回的是一个对象,对象中有对应的方法 比如
function function(){
return {
each:function(){}
}
}
func().each();
而我们要做的就是让select方法返回一个对象,也就是把伪数组变成一个对象
这样我们就实现了链式编程 如下:function select(selector){ var obj = document.querySelectorAll(selector); obj.each = function(callback) {//使用callback处理伪数组obj中的每一个元素 each(this,callback);//由于each是obj调用,所以this指向obj,也就是遍历obj } return obj; }
到这里是不是觉得已经和jq中的each方法很接近了呢 然而我们上面的代码并不是很完美 因为我们的each还不能遍历对象 还不能跳出循环,不能用this指向当前遍历元素。。。select('div,p').each(function(v){ v.style.border = '1px solid blue'; })
下面我们将each方法继续完善一下
function each(arr,callback){ for(var i=0;i<arr.length;i++) { // callback(arr[i],i);//此时this默认指向window 而我们想让this指向当前遍历的元素 也就是arr[i] 显然我们不能用这个
callback.call(arr[i],arr[i],i);//我们采用方法借用将callback中的this指向arr[i]
//jq的each方法中,采用return false跳出循环 那我们使用什么方法呢?这里就有一点绕了,我们跳出循环要用break,那问题是我们
//要在哪里break呢 答案就是下面这句 当callback的返回值为false时,就跳出循环
if(callback.call(arr[i],arr[i],i)===false)break; //在这里还需注意要===而不是==,因为jq中明确告诉我们使用return false跳出循环
//而如果我们写==的话,就证明return 0也可以跳出,我们要严格按照jq的方法去做
} return arr; }
解决了跳出循环和this指向的问题,还有一个棘手的问题,那就是如何判断对象是数组或伪数组1>如何判断数组?
推荐使用 Object.prototype.toString.call(arr)==[object Array] ,这里不推荐使用instanceof,因为会在html嵌套(iframe)的页面中出问题2>如何判断伪数组?
a.必须有length属性 var length = 'length' in arr && arr.length
b.判断他是不是数字return typeof length==='number&&'length>=0
c.长度需要非负
下面我们封装完整的each方法
function isArrayLike( obj ) { if ( Object.prototype.toString.call( obj ) == '[object Array]' ) { return true; } var length = 'length' in obj && obj.length; return typeof length === 'number' && length >= 0; } function each( arr, callback ) { if ( isArrayLike ( arr ) ) { for ( var i = 0; i < arr.length; i++ ) { if ( callback.call( arr[ i ], arr[ i ], i ) === false ) break; } } else { for ( var k in arr ) { if ( callback.call( arr[ k ], arr[ k ], k ) === false ) break; } } return arr; }
至此,我们的each方法就封装完了,用于实现对数组、伪数组以及对象的遍历。