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作出双向选择,这也是一个简单而巧妙的技巧。