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的实例。
var eve = function(){
return new eve.prototype.init(arguments);
}
eve.prototype = {
init : function(obj){
this.setArray(this.makeArray(obj));
return this;
},
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;
}
}
try{
var e = new eve(1,2,3);
alert(e);
}catch(err){
alert(err)
}
运行代码
看到没有?报错找不到makeArray方法,换言之eve.prototype.init实例无法调用eve的原型方法。如果是jQuery,就是jQuery对象无法调用其jQuery命名空间对象的原型方法。解决方法很简单,直接把它们两个的原型重叠起来。为其命名空间对象的原型添加方法就是为其真身的原型添加方法。
eve.prototype.init.prototype = eve.prototype;
var eve = function(){
return new eve.prototype.init(arguments);
}
eve.prototype = {
init : function(obj){
this.setArray(this.makeArray(obj));
return this;
},
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;
}
}
eve.prototype.init.prototype = eve.prototype;
try{
var e = new eve(1,2,3);
alert(e);
}catch(err){
alert(err)
}
运行代码
我们再向它添加一些方法,看到底能仿真到什么程序。其实上面已经能用索引取值了,即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 ];
}
var eve = function(){
return new eve.prototype.init(arguments);
}
eve.prototype = {
init : function(obj){
this.setArray(this.makeArray(obj));
return this;
},
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;
},
toString : function(){//返回一个字符串
var array = Array.prototype.slice.call( this );
return array.toString();
},
valueOf:function(){return Array.prototype.slice.call( this );},
get: function( num ) {
return num === undefined ? Array.prototype.slice.call( this ) : this[ num ];
}
}
eve.prototype.init.prototype = eve.prototype;
try{
var e = new eve(1,2,3);
alert(e);
alert(e[1]);
alert(e.toString());
alert(e.valueOf());
}catch(err){
alert(err)
}
运行代码
很好,现在如果不用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}"
}
var eve = function(){
return new eve.prototype.init(arguments);
}
eve.prototype = {
init : function(obj){
this.setArray(this.makeArray(obj));
return this;
},
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}"
}
eve.prototype.init.prototype = eve.prototype;
// eve.prototype.init.prototype = new Array;
try{
var e = new eve(1,2,3);
alert(eve);
alert(Array)
alert(e);
alert(e[1]);
alert(e.toString());
alert(e.valueOf());
e.push("司徒正美");
alert(e);
e.shift();
alert(e)
}catch(err){
alert(err)
}
运行代码
在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}"
}