Ruby's Louvre

每天学习一点点算法

导航

模拟jQuery实现类数组对象

jQuery最令人惊赞的东西就是那个类数组对象,亦即俗话中的jQuery对象。注意,在jQuery类库中,jQuery是作为命名空间而存在的函数。它拥有许多静态方法,由于函数也是对象,对象就有原型,而jQuery的原型方法是异常频繁地调用它的静态方法。它的第一个原型方法叫init,这是受Prototype类库影响的结果。init方法也可以作为构造函数使用,它new出来的实例就是jQuery对象。在jQuery命名空间的原型中,也有一个叫jquery的属性,起着其他类库版本号的作用,由于名字特殊,也用作识别是否为jQuery对象的作用。换言之,这里有三处可以集中添加方法与属性的地方,jQuery命名空间,用于添加静态方法,jQuery的原型,用于添加原型方法,只要依赖extend来添加,jQuery.prototype.init实例(jQuery对象),它的属性比较少,只有length,prevObject,selector与context。至于jQuery.prototype.init实例的原型,都是往jQuery.prototype里搬。接着下来,我们沿着jQuery的思路,实现一个类数组对象吧。

首先我们要定义一个类,我称之为eve,它其实是个工厂。

      var eve = function(){
        return new eve.prototype.init(arguments);
      }

如果是数组,传入一个数字,会返回与数字值相等长度的空数组,如果传入几个参数,这几个参数会成为返回数组的元素,如果什么都不会就返回空数组(这种方式一定要使用new操作符)。嘛,由于我们的类数组,用不着百分之一百模拟,就按其方式二生成类数组。

类数组要求拥有length属性与索引,length容易办,但索引我们要一个个赋值,如果在最前面添加新的元素时,就要重排索引,这很要命,效率很低。不过我们可以用数组的push方法实现。

        setArray : function(els) {
          this.length = 0;//设置length以及重排索引
          Array.prototype.push.apply(this, els);
          return this;
        },

为了确保用得了setArray方法,我们必须保证els为数组,因为里面用到 Array.prototype.push.apply(this, els),看到Array没有?至于push方法,简单,保证调用者拥有length属性就行了。 下面的makeArray,就是将非数组对象转换为数组对象,就算为空,也要用[]包裹起来。

        makeArray : function( arr ) {//把传入参数变成数组
          var ret = [];
          if( arr != null ){    var i = arr.length;
            //单个元素,但window, string、 function有 'length'的属性,加其它的判断
            if( i == null || arr.split || arr.setInterval || arr.call ){
              ret[0] = arr;
            }else{
              try{
                ret = Array.prototype.slice.call(arr)
              }catch(e){
                while( i ) ret[--i] = arr[i];//Clone数组
              }
            }
          }
          return ret;
        },

有了这两个方法,我们就可以写我们的构造方法了。

        init : function(obj){
          this.setArray(this.makeArray(obj));
          return this;
        },

但仅仅是这样无法new实例的,因为这里的this出了些状况。前两个this为eve的实例,第三个返回的this为eve.prototype.init的实例。

看到没有?报错找不到makeArray方法,换言之eve.prototype.init实例无法调用eve的原型方法。如果是jQuery,就是jQuery对象无法调用其jQuery命名空间对象的原型方法。解决方法很简单,直接把它们两个的原型重叠起来。为其命名空间对象的原型添加方法就是为其真身的原型添加方法。

  eve.prototype.init.prototype = eve.prototype;

我们再向它添加一些方法,看到底能仿真到什么程序。其实上面已经能用索引取值了,即e[1],会弹出2。但toString却是[object Object],而是不数字的序列。在jQuery中有个get()方法,如果不加参数,能取出里面的数组,我们把它的toString方法赋其eve.prototype就是。

        toString : function(){//返回一个字符串
          var array = Array.prototype.slice.call( this );
          return array.toString();
        },
        get: function( num ) {
          return num === undefined ? Array.prototype.slice.call( this ) : this[ num ];
        }

很好,现在如果不用instanceof与typeof判断,光凭感觉,是分辩不出它是数组与类数组了。我们再给添加一些数组方法。

        shift :[].shift,
        push: [].push,
        sort: [].sort,
        pop:  [].pop,
        splice: [].splice,
        concat: [].concat,
        slice: [].slice,
        constructor:eve,
//********************
     eve.toString = function(){
        return "function Array(){\n    [variant code]\n}"
      }

在jQuery中,它还实现eq方法与index方法来取得元素或元素的索引值,还实现javascript1.6的几个迭代器,each(forEach),map,filter。这样高度仿真的类数组对象让jQuery类库轻易实现了链式操作。但显然,它的filter与map方法比我们在网上找到的实现复杂多了,因为早在jQuery1.0.1中就搞了pushStack方法,用于保存执行push,pop等破坏性操作前的数组对象。

可能有人对init方法感觉非常不爽,因为它的两种this导致了需要用到两个原型。那是jQuery的init除了有生成jQuery对象的能力,还能充当domReady用。如果是单纯的类数组,我们完全可以去掉它,但生成类数组对象时,我们就一定要用new操作符了。

      var eve = function(){
        this.setArray(this.makeArray(arguments));
        return this;
      }
      eve.prototype = {
        isArray : function( obj ) {
          return Object.prototype.toString.call(obj) === "[object Array]";
        },
        setArray : function(elems) {
          this.length = 0;//设置length以及重排索引
          Array.prototype.push.apply(this, elems);
          return this;
        },
        makeArray : function( arr ) {//把传入参数变成数组
          var ret = [];
          if( arr != null ){    var i = arr.length;
            //单个元素,但window, string、 function有 'length'的属性,加其它的判断
            if( i == null || arr.split || arr.setInterval || arr.call ){
              ret[0] = arr;
            }else{
              try{
                ret = Array.prototype.slice.call(arr)
              }catch(e){
                while( i ) ret[--i] = arr[i];//Clone数组
              }
            }
          }
          return ret;
        },

        inArray : function(elem, array) {
          for (var i = 0, length = array.length;i < length; i++)
          // Use === because on IE, window == document
            if (array[i] === elem)
              return i;
          return -1;
        },
        index:function(el){return this.inArray(el,this)},
        toString : function(){//返回一个字符串
          var array = Array.prototype.slice.call( this );
          return array.toString();
        },
        valueOf:function(){return Array.prototype.slice.call( this );},
        shift :[].shift,
        push: [].push,
        sort: [].sort,
        pop:  [].pop,
        splice: [].splice,
        concat: [].concat,
        slice: [].slice,
        constructor:eve,
        get: function( num ) {
          return num === undefined ? Array.prototype.slice.call( this ) : this[ num ];
        }
      }
      eve.toString = function(){
        return "function Array(){\n    [variant code]\n}"
      }

posted on 2009-11-29 09:38  司徒正美  阅读(2898)  评论(5编辑  收藏  举报