天天看点

jQuery方法原生实现---each遍历

最近有点浮躁,总想着快速前端进阶,成为高手。奈何现实残酷,故此用原生js实现下jQuery一些函数,用于练手。

each遍历类数组,数组挺好用的。网上也有很多教程,原理无非是利用call,apply改变this指针指向,指向谁呢?嗯,js数组!

原生数组有着很多方法能够读取数组元素,例如新增加every()方法遍历数组,IE9以上支持该方法

function each(object,callback){
		[].every.call(object, function(v, i){  
            return callback.call(v, i, v) === false ? false : true;  
        });  
	}

	each(ages,function(v,i){
		console.log(i);
	});
           

使用扩展方式,对js原生函数进行改造,也就是利用call改变this指针,让其指向传进来的object,后方的function则是参数。合起来就是带有两个参数的匿名function连同数组array同时指向object,此时object便具有原生数组的every特性和带有两个参数方法。

那为何function方法返回布尔值?原因就是every方法遍历返回的便是布尔值。

到此,简单的each遍历就实现了。不过问题来了,此时的each只能是数组和类数组,因为调用的是数组原生方法every,对象格式就不行了。

所以我们还要对象进行遍历,就得再写一个函数,返回对象键和值(key  value),我们需要for循环遍历

function each1(obj,fn){
    for(var i in obj){
        if(fn.call(obj,i,obj[i])===false){
            break;
        }
    }
}


var obj = {name:"张三",age:"15"}

each1(obj,function(i,v){
    console.log(i+"  "+v);
});
           

call能够改变this内部指针的指向,从而改变某个对象的执行上下文环境,也就是改变了fn回掉函数的指向,指向了obj对象,从而达到遍历对象目的。所以call也能够用来实现js式的继承,不过es6的出现,弥补好多js缺陷,有兴趣的自己去百度下es6继承。

类数组验证:

<p>1</p>
    <p>2</p>
    <p>3</p>
    <p>4</p>
           
var p = document.querySelectorAll('p');

each1(p,function(i,v){
    console.log(i+"  "+v.innerHTML);
})
           

结果会输出下标0123和值1234

回过头来看下,我们发现其实这个实现有缺陷,我们并没有对传入的第一个参数做类型判断。如果将对象类型和类数组类型传给第一个函数each,就会报错,它只能处理数组对象。

所以我们要给它添加类型判断,用于区分处理。这里我们要用到一个数组对象属性constructor,constructor 属性返回对创建此对象的数组函数的引用,我们用它来区分各个类型数据。

var test=new Array();

if (test.constructor==Array)
{
document.write("This is an Array");
}
if (test.constructor==Boolean)
{
document.write("This is a Boolean");
}
if (test.constructor==Date)
{
document.write("This is a Date");
}
if (test.constructor==String)
{
document.write("This is a String");
}
           

以上是引用w3c网站的例子,我们each遍历函数主要是数组,类数组,对象格式三种格式,所以我们在参数传进来时候做一个判断即可。

var type = (function(){
          switch (object.constructor){
            case Object:
                return 'Object';
                break;
            case Array:
                return 'Array';
                break;
            case NodeList:
                return 'NodeList';
                break;
            default:
                return 'null';
                break;
        }
    })();
           

到了这里我们就可以保证each函数不出错,数组和类数组调用each,对象格式调用each1,怎么?两个函数,自己开个if--else控制语句就可以合并了。到了这里基本可以算是完事了,但貌似every不支持ie9以下浏览器,那怎么办呢?你完全可以使用第二种方法嘛!数组也是对象啊!!!!!

each1([45,1,5,36],function(i,v){
    console.log(i+"  "+v);
});
           

那each和each1区别?个人猜测,毕竟是原生支持的属性,every在浏览器内部运行总归要快点吧。

那还有没有其它实现?有,你还可以使用arguments代替哈

function each2(){
    for(var i=0;i<arguments[0].length;i++){
        if(arguments[1].call(arguments[0],i,arguments[0][i])===false){
            break;
        }
    }
}
           

其实挺无聊的,就先这么着了,但是我们要实现jQuery的each函数,$('xx').each(obj,function(i,v){})这种形式才成,不然和jQuery就没关系了。

那么$('xx')怎么实现呢?简单的好实现,复杂的,自己研究jQuery源代码吧,网络上教程也不少。

<p id="aa">1</p>
    <p class="bb">2</p>
    <p class="bb">3</p>
    <p>4</p>
           
//$()的简单实现
var doc = document;
function $(dom){
    //dom元素简单区分
    //清除空格
    dom=dom.replace(/^(\s|\u00A0)+/,'').replace(/(\s|\u00A0)+$/,'');  
    //过滤#和.
    var str = /[^#|\.]\w*/.exec(dom);
    //获得#或.
    var match1 = /^#|\./.exec(dom);
    console.log(match1)
    return match1 == "#"?doc.getElementById(str):(match1 == "."?doc.getElementsByClassName(str):doc.getElementsByTagName(str));
}
console.log($(' #aa'));
console.log($('.bb'));
console.log($('p'));
           

以上是$的简单模拟,但是问题还没有解决,它并不能链式调用,怎么办呢?采用jQuery做法,内部返回原型,可是直接返回this没用,函数中this由调用该函数的环境决定。所以我们需要封装成对象,以下是模拟jQuery插件搞得一个简单js库,可以判断是ID,clas和标签,然后做一个链式调用。

+function(window){
    var aQuery = function(selector){
        return new aQuery.prototype.init(selector);
    }
    aQuery.prototype ={
        init : function(selector){
            //dom元素简单区分
            //清除空格
            var doc = document;
            selector=selector.replace(/^(\s|\u00A0)+/,'').replace(/(\s|\u00A0)+$/,'');  
            //过滤#和.
            var str = /[^#|\.]\w*/.exec(selector);
            //获得#或.
            var match1 = /^#|\./.exec(selector);
            this.selector = match1 == "#"?doc.getElementById(str):(match1 == "."?doc.getElementsByClassName(str):doc.getElementsByTagName(str));
            return this;
        },
        each : function(){
            console.log(this.selector)
            for(var i=0;i<this.selector.length;i++){
                if(arguments[0].call(this.selector,i,this.selector[i])===false){
                    break;
                }
            }
            return this;
        }
    }
    aQuery.prototype.init.prototype = aQuery.prototype;
    window.aQuery = window.$ = aQuery;
    
}(window);

$('.bb').each(function(i,v){
    console.log(i+" "+v.innerHTML);
})
           

简单说下原理,插件使用立即执行函数包裹,避免变量污染;使用无new构造方法,通过原型赋值实现无new方式;通过每个方法末端返回this,这里this指代aQuery对象,由于js天生原型链,所以返回的方法就可以被链式调用。

参考文章链接:http://www.cnblogs.com/aaronjs/p/3278578.html

    http://www.cnblogs.com/MonaSong/p/6424366.html

继续阅读