- 2.3、jquery对象的数组特性
- 从上面一小节可以看出jquery构建函数完成了查找或转换或其它的功能,其结果就是找到元素,查找,转找不过是方式而已。找到元素就得找个地方去存储起来。这个地方是就是这一节要分析的。
- 存储有序数据的地方(集合)在JS中最好的当然是数组。那么又如何在jQuery内面实现数组呢?可以采用如下的方式:
- jQuery.fn.prototype=new Array();
- 在上一节中的this.setArray(arr)函数中加上
- Array.apply(this,arr);
- 如果还要完美一点,就加上:
- jQuery.fn.prototype.constructor=jQuery。
- 这样我们继承了数组的所有特性,又可以在Jquery对象进行数组的功能扩展。但是jQuery并没有这样采用继承Array而实现这个内部的集合。它采用了Array-Like的对象的实现(见JavaScript: The Definitive Guide, 5th Edition7.8节)。
- 类数组的对象还是对象,只不过像数组。数组与对象其实是没有什么好大的区别的,有序和无序的集合是它们区别。这个区别反应在数组有的length属性。当添加元素它会自动加上相对的个数,当删除元素,它会自动减去相对的个数。
- 看一下jQuery是怎么实现的:
- // 第一种情况 Handle $(DOMElement)单个Dom 元素,忽略上下文
- if (selector.nodeType) { ②
- this[0] = selector;
- this.length = 1;
- return this;
- }
- 这是它的第一种实现方法,通过this[0]来直接设定第一个位置的Dom元素,同时设定length=1。这里我们可以看出对象与数组一样都是采用key/Value对的形式存在对象中。上面的Json形式为{0:aDom,length=1}。这里细细分析一下数组,数组是继承于对象。其[]的解释分析最终结果可以看作{}构的对象,对[]或数组的构建时会进行把index(如0.1,….)做为对象属性的key。把数组中的值做为其对应的value。同时改变length的值。这也就是说为什么本质上对象与数组没有多大的区别。在很多的源码中,如YUI中都采用对象的形式来构建多维数组。
- this.setArray(jQuery.makeArray(selector));
- 是其第二种实现的方法,上面实现是单个的元素,这个实现的多个元素的集合。它首先调用了jQuery.makeArray(selector)这个静态方法把集合(类数组)转换成数组。
- 上面已经分析了数组和对象都可以采用obj.[attr]的形式来取得其key对应的value。对于集合或类数组,必须要求其实现length属性,有了length的长度,那么就从0~length-1的key属性中取得对应的value就可以了:
- //把类数组的集合转换成数组,如果是单个元素就生成单个元素的数组。
- makeArray: function( array ) {
- var ret = [];
- if( array != null ){ var i = array.length;
- //单个元素,但window, string、 function有 'length'的属性,加其它的判断
- if( i == null || array.split || array.setInterval || array.call )
- ret[0] = array;
- else//类数组的集合
- while( i ) ret[--i] = array[i];//Clone数组
- }
- return ret;
- },
- 生成了一个标准的数组,那么接下来setArray来干什么呢?
- // 把array-like对象的元素全部push当前jquery对象。
- setArray : function(elems) {
- this.length = 0;//初始化长度,因为push会在原始的length++
- Array.prototype.push.apply(this, elems);
- return this;
- },
- 这个调用了Array.prototype.push来自动修改length的属性值(当然是加入了元素)。由此可以推想到Array中众多的方法(如shift)都可以看作改变length的值在对象的key/value对中完成无序到有序或重新排序的工作。实际上Array等是采用C或C++来实现的。但是它构出的JS特性让我们可以这样去思考采用JavaScript的实现方式。
- 上面的setArray(elems)函数只是会改变当前jQuery对象的集合,它会清除这个对象集合中以前的元素。但是有的时候我们想保存原来的集合中元素,同时也能就新传入的元素进行jquery对象的操作。它提供了pushStack函数来新建一个jQuery对象同时保存原来对象的引用。这样就可能在需要时用到自己所要的对象:
- pushStack : function(elems) {// 采用jQuery构建新对象,同时引用老对象。
- var ret = jQuery(elems);// 构建新的jquery对象
- ret.prevObject = this;// 保存老的对象的引用
- return ret;
- },
- 返回的是新构建成的对象,有着jQuery对象的全部功能,同时还可以通过prevObject来访问原来的老对象。
- 构建了类数组,那么还得提供一些方法来操作这个集合,对于集合的操作无非就是定位元素,查找元素,复制(slice)和删除的操作(splice)等。jQuery还提供each,map的扩展。
- 这些方法只和集合相关,与集合的元素的无关。jQuery提供了众多的和其中元素(DOM元素)相关的方法。
- 它提供了两个get元素的方法,get(index)和eq(index),不同是get取得是集合中的元素,而eq则是返回该元素的Clone。不会修改数组。其实可以直接通过[i]来代替get(i)。如果get没有参数则是获得全部元素。
- // 取到本jquery对象的第几个Dom元素,无参数,代表全部的Dom元素
- get : function(num) {
- return num == undefined ? jQuery.makeArray(this) : this[num];
- },
- // 获取第N个元素 。这个元素的位置是从0算起。
- eq : function(i) {
- return this.slice(i, +i + 1);
- },
- 这两个函数就不用分析了。接下来看看如何实现在集合中定位元素:
- / 找到elem在本jquery对象的位置(index)
- index : function(elem) {
- var ret = -1;
- return jQuery.inArray( // 如是jQuery对象就取第一个元素
- elem && elem.jquery ? elem[0] : elem, this);
- },
- // 判断elem元素在array中的位置(index) 静态方法
- 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;
- },
- inArray是jQuery的静态方法,而index是通过调用inArray来实现其定位的功能。Index的函数支持的参数可以是jQuery对象或Dom元素,而inArray则是实用方法,支持任何的元素。
- Jquery提供了如数组中slice复制的功能的方法,还提供类似concat的静态方法merge。Slice是通过Array中slice来实现的:
- / 代理数组的slice,同样的操作。
- slice : function() {
- return this.pushStack(Array.prototype.slice.apply(this, arguments));
- },
- 它返回生成新的jQuery对象。这个对象的集合就是要复制后的集合。对于merge,它是静态方法,实现把第二个元素追加到第一个参数的数组中。
- // 把second 元素追加到first的数组中。
- merge : function(first, second) {
- // We have to loop this way because IE & Opera overwrite the length
- // expando of getElementsByTagName
- var i = 0, elem, pos = first.length;
- // Also, we need to make sure that the correct elements are being
- // returned (IE returns comment nodes in a '*' query)
- if (jQuery.browser.msie) {
- while (elem = second[i++])
- if (elem.nodeType != 8)
- first[pos++] = elem;
- } else
- while (elem = second[i++])
- first[pos++] = elem;
- return first;
- },
- Jquery的each是对集合中每个元素都执行回调函数。
- // 当前jquery对象中每个元素都执行callback(index,elem)函数
- each : function(callback, args) {// 返回this
- // 其调用了jQuery的静态方法。prototype中的mothodize是解决这类问题的好方法
- return jQuery.each(this, callback, args);
- },
- 它通过调用jQuery.each这个静态方法来完成功能的:
- // 对object中的每个对象都执行callback函数进行处理。args仅仅内部用
- each : function(object, callback, args) {
- var name, i = 0, length = object.length;
- // 和else的处理差不多,args的传参代替object的属性值
- if (args) {
- if (length == undefined) {
- for (name in object)
- if (callback.apply(object[name], args) === false)
- break;
- } else
- for (;i < length;)
- if (callback.apply(object[i++], args) === false)
- break;
- } else {
- // 不是array-like的object,对每个属性进行callback函数的调用
- if (length == undefined) {
- for (name in object)
- if (callback.call(object[name], name, object[name]) === false)
- break;
- } else
- // array-like object,采用数组的形式来处理
- for (var value = object[0];i < length && callback.call(value, i, value) !== false; value = object[++i]) {}
- }
- return object;
- },
- 该静态方法支持第一个参数的类数组(数组)或对象。是数组就对每个元素进行callback的操作。如果是对象,就是对每个属性值进行callback的操作。这个callback回调函数的格式如下:callback:function(index,value)。Index是索引号,value是数组的index对应的元素或对象的第index个处理的属性。如果使用args参数,那callback回调函数的格式如下:callback:function(args)。Args是给回调函数设定参数。再看一下jQuery对象的each。它的第二个参数args就是采用传入的args直接进行给callback设定参数,而不是默认提集合中index和对应的元素,但执行的次数还是集合的length次。
- Jquery的map是将一组元素转换成其他数组(通过回调函数返回值组成的) // 将一组元素转换成其他数组 然后根据这个数组构建新的jquery对象。。
- map : function(callback) {
- return this.pushStack(jQuery.map(this, function(elem, i) {
- return callback.call(elem, i, elem);
- }));
- },
- 这个函数首先通过jQuery.map(this, function(elem, i){}来把this的jQuery对象集合的每个元素当作回调函数的elem的参数传到回调函数中。而这个回调函数又执行实例方法的map : function(callback)中callback函数,也就是jQuery.map中的回调仅仅是传参代理的功能。jQuery.map通过代理的回调来取得转换而成的元素集合。
- 接下来就是采用pushStack把这集合的元素构建成新的jQuery对象并返回,同时保存原jQuery对象的引用。
- 看下Map的静态方法:
- // 返回对elems每个元素都进行操作的callback函数的返回值的集合。
- map : function(elems, callback) {
- var ret = [];
- for (var i = 0, length = elems.length;i < length; i++) {
- var value = callback(elems[i], i);
- if (value != null)//说明转换的集合的个数可能少于原来的集合
- ret[ret.length] = value;
- }
- return ret.concat.apply([], ret);//采用array的concat实现
- }
- Map的静态方法返回每个callback返回的元素组成的数组。