jQuery 源码分析6: jQuery 基本静态方法(二)

  1 jQuery.extend({
  2 
  3 // 遍历obj的所有值
  4 // args 这参数只能内部调用的会用到
  5 // 注意到,如果回调函数调用失败会直接跳出并中止遍历
  6 // 当有args数组时,使用apply调用,否则使用call调用
  7 each: function( obj, callback, args ) {
  8      var value,
  9           i = 0,
 10           length = obj.length,
 11           isArray = isArraylike( obj );
 12       if ( args ) {     // 内部调用时才会有args
 13           if ( isArray ) {     // obj是Array
 14 
 15                for ( ; i < length; i++ ) {
 16                     value = callback.apply( obj[ i ], args );
 17                     if ( value === false ) {
 18                         break;
 19                     }
 20                }
 21           } else {             // obj是Object
 22 
 23 
 24           for ( i in obj ) {
 25 
 26               value = callback.apply( obj[ i ], args );
 27 
 28               if ( value === false ) {
 29 
 30                    break;
 31 
 32               }
 33 
 34           }
 35 
 36           }
 37     // 最常用的each使用方式
 38     } else {
 39          if ( isArray ) {     // obj是Array
 40               for ( ; i < length; i++ ) {
 41                    value = callback.call( obj[ i ], i, obj[ i ] );   // 回调函数会获取i 和 对应的属性
 42                    if ( value === false ) {
 43                         break;
 44                    }
 45               }
 46          } else {             // obj是Object
 47               for ( i in obj ) {
 48                    value = callback.call( obj[ i ], i, obj[ i ] );
 49                    if ( value === false ) {
 50                         break;
 51                    }
 52               }
 53           }
 54      }
 55      return obj;
 56 },
 57 
 58 // 支持: Android<4.1, IE<9
 59 // rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g
 60 // 删除 BOM and NBSP 
 61 trim: function( text ) {
 62      return text == null ?
 63             "" :
 64             ( text + "" ).replace( rtrim, "" );
 65 },
 66 
 67 // results参数仅限内部调用
 68 // 将arr变成一个Array
 69 // 如果有results,arr会合并到results后面
 70 // 如果arr是'string',则会转换为[arr]然后合并到Array上
 71 makeArray: function( arr, results ) {
 72      var ret = results || [];
 73      if ( arr != null ) {
 74           if ( isArraylike( Object(arr) ) ) {
 75                jQuery.merge( ret,
 76                           typeof arr === "string" ?
 77                          [ arr ] : arr
 78                );
 79           } else {
 80                push.call( ret, arr );
 81           }
 82      }
 83      return ret;
 84 },
 85 
 86 // 判断elem是否在arr这个数组上
 87 // i是检索的起始index
 88 inArray: function( elem, arr, i ) {
 89      var len;
 90      if ( arr ) {
 91           if ( indexOf ) {
 92                return indexOf.call( arr, elem, i );     // 调用Array.indexOf
 93           }
 94           len = arr.length;
 95           i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;     // 如果i<0,则设i为len+i
 96           for ( ; i < len; i++ ) {
 97           // 跳过对稀疏数组的访问?
 98           // Skip accessing in sparse arrays
 99                if ( i in arr && arr[ i ] === elem ) {
100                     return i;
101                }
102           }
103      }
104      return -1;
105 },
106 
107  // 把second合并到first上
108 merge: function( first, second ) {
109     var len = +second.length,
110         j = 0,
111         i = first.length;
112     while ( j < len ) {
113         first[ i++ ] = second[ j++ ];
114     }
115     // 支持: IE<9
116     // 如果类数组对象没有length,因此.length不是一个数字,例如NodeLists
117     if ( len !== len ) {
118          while ( second[j] !== undefined ) {
119              first[ i++ ] = second[ j++ ];
120          }
121     }
122     first.length = i;
123     return first;
124 },
125 
126 // 筛选遍历数组
127 // callback用于判断是否符合
128 // invert表示与callback的结果相反,即希望保留callback返回值为false的元素
129 grep: function( elems, callback, invert ) {
130      var callbackInverse,
131          matches = [],
132          i = 0,
133          length = elems.length,
134          callbackExpect = !invert;   // invert == true, 表示希望callback返回false
135      // 遍历数组,只保留通过筛选的元素
136      for ( ; i < length; i++ ) {
137          callbackInverse = !callback( elems[ i ], i );
138          if ( callbackInverse !== callbackExpect ) {
139              matches.push( elems[ i ] );
140          }
141      }
142      return matches;
143 },
144 
145 // arg参数只在内部调用时使用
146 map: function( elems, callback, arg ) {
147     var value,
148     i = 0,
149     length = elems.length,
150     isArray = isArraylike( elems ),
151     ret = [];
152      // 遍历数组,将其转换成新的值并放到ret数组中
153     if ( isArray ) {
154         for ( ; i < length; i++ ) {
155             value = callback( elems[ i ], i, arg );
156             if ( value != null ) {
157                 ret.push( value );
158             }
159         }
160     // 遍历对象的属性,将其转换成新的值并放到ret数组中
161     } else {
162         for ( i in elems ) {
163             value = callback( elems[ i ], i, arg );
164             if ( value != null ) {
165                ret.push( value );
166             }
167         }
168     }
169      // 如果存在嵌套的数组,将其展开
170     return concat.apply( [], ret );
171 },
172 
173  // 对象的全局GUID计数器
174 guid: 1,
175 
176  // Bind a function to a context, optionally partially applying any arguments.
177 // 为一个function绑定一个上下文环境,
178 proxy: function( fn, context ) {
179     var args, proxy, tmp;
180     // 处理: $.proxy(context, name)
181     if ( typeof context === "string" ) {
182         tmp = fn[ context ];      // 从参数context提取一个function
183         context = fn;             // 将上下文设置成参数context
184         fn = tmp;                 // 将绑定function对象设置为conext[nam]
185     }
186     
187     // 确保fn是一个可调用对象
188     // 如果不可调用,返回undefined
189     if ( !jQuery.isFunction( fn ) ) {
190         return undefined;
191     }
192     // 模拟上下文绑定
193     args = slice.call( arguments, 2 );     // 截取fn所需要的参数
194     proxy = function() {
195         return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
196     };
197     // 为唯一的句柄设置guid,这个guid应该与原理fn的guid一样,使得它可以被移除
198     proxy.guid = fn.guid = fn.guid || jQuery.guid++;
199     return proxy;
200 },
201 
202  // 新建一个时间对象并返回
203 now: function() {
204     return +( new Date() );
205 },
206 
207 // jQuery不会在内核中使用,但其他项目会将一些属性设置到support中
208 support: support
209 
210  
211 
212 });

 

 
 
总结:
  • jQuery.extend在进行扩展的时候,是使用到了深拷贝,这是教科书式的用法,对Object和Array对象进行递归合并,将其中所有的属性都作拷贝。这样做的原因是在JavaScript里,对右值为Object和Array的赋值操作是执行引用而非拷贝,因此必须遍历Object和Array的属性以实现深拷贝;
  • 方法重载的技巧再次出现,如jQuery.proxy。重载的实现实际上要对参数类型、参数数量进行判断,以进行不同的处理;
  • 为了绑定函数方法的上下文环境,我们可以使用jQuery.proxy,而.proxy的实现使用到了function.apply(conext, args);
  • jQuery.grep的实现也是比较巧妙,添加一个形参invert来进行反向选择,即可以利用一个callback作出双向选择,这也是一个简单而巧妙的技巧。
posted @ 2015-07-24 12:11  elcarim  阅读(...)  评论(... 编辑 收藏