研读了jQuery源码后,发现非常有趣的一个现象:一个前端单节点内的脚本语言编程框架与大数据领域内集群上的编译语言型编程框架从逻辑抽象层面上并没有差异,更通俗的说是与函数式编程的普遍思想没有差异--编程果然是殊途同归的。本篇博客参照Spark RDD的Transformations和Actions对jQuery静态工具方法做一个简单的总结。
一 jQuery.each
这里探究的的each是jQuery类型的静态方法,不是原型对象中的实例方法。
// args is for internal usage only
each: function( object, callback, args ) {
var name, i = 0,
length = object.length,
isObj = length === undefined || jQuery.isFunction( object );
if ( args ) {
if ( isObj ) {
for ( name in object ) {
if ( callback.apply( object[ name ], args ) === false ) {
break;
}
}
} else {
for ( ; i < length; ) {
if ( callback.apply( object[ i++ ], args ) === false ) {
break;
}
}
}
// A special, fast, case for the most common use of each
} else {
if ( isObj ) {
for ( name in object ) {
if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
break;
}
}
} else {
for ( ; i < length; ) {
if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
break;
}
}
}
}
return object;
},
该定义的实现说明jQuery.each实际上才是类似Spark Transformations中经典的map类型的操作(而不是另一个名为map的工具函数),用伪码描述就是op(map)。
该实现也约束了回调函数的参数类型和顺序(只看不使用第三个参数arg的用法):
如果处理的是数组类型的参数,callback.call( object[ i ], i, object[ i++ ] ):第一个参数是集合元素的index,第二个参数是该集合的元素。
如果处理的是对象类型的参数,callback.call( object[ name ], name, object[ name ] ):第一个参数是对象name,第二个参数是对应的对象value。
此处不是直接调用callback函数的--callback(i, object[ i++ ]),而是用了借用语法--call,所以为什么jQuery("xxx").each方法(本方法也一样)的回调函数中this关键字指向的是选中集合中原生的客户端对象就有了解释了:object是jQuery对象,但是object[index]是原生客户端对象--jQuery对象是Array-like Object。
最后一点是,该工具函数的返回值类型可能直接是jQuery类型的对象(如果第一个参数object是jQuery类型对象),也可能是Array类型(如果第一个参数object本身就是Array),当然也可能是其他任何Object对象--一切取决于传入的第一个参数的类型。
二 jQuery.map
这里探究的map同样是jQuery类型的静态方法,不是原型对象中的实例方法。
// arg is for internal usage only
map: function( elems, callback, arg ) {
var value, key, ret = [],
i = 0,
length = elems.length,
// jquery objects are treated as arrays
isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
// Go through the array, translating each of the items to their
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret[ ret.length ] = value;
}
}
// Go through every key on the object,
} else {
for ( key in elems ) {
value = callback( elems[ key ], key, arg );
if ( value != null ) {
ret[ ret.length ] = value;
}
}
}
// Flatten any nested arrays
return ret.concat.apply( [], ret );
},
该定义的实现说明jQuery.map实际上是类似Spark Transformations中flatMap操作加上Actions中collect操作,用伪码描述就是op(collect)(op(flatMap))。
当然如果灵活应用该方法也可以充当filter的功能,比如传递一个实现如下的函数:
jQuery.map(elems, function(elem){
return predicate(elem,slice.call(arguments, 1)) ? elem : null;
});
该实现也约束了回调函数的参数类型和顺序:
如果处理的是数组类型的参数,callback( elems[ i ], i, arg ):第一个参数是集合的元素,第二个参数是该元素的index,第三个参数是可选的。
如果处理的是对象类型的参数,callback( elems[ key ], key, arg ):第一个参数是对象value,第二个参数是对应的对象key,第三个参数是可选的。
最后一点是,该工具函数的返回值类型是Array,不是jQuery类型的对象,即op(filter)逻辑之后还有一个op(collect)逻辑(虽然内部是一次性实现的)。
jQuery.map(elems, function(elem){
return predicate(elem,slice.call(arguments, 1)) ? elem : null;
});
该实现也约束了回调函数的参数类型和顺序:
如果处理的是数组类型的参数,callback( elems[ i ], i, arg ):第一个参数是集合的元素,第二个参数是该元素的index,第三个参数是可选的。
如果处理的是对象类型的参数,callback( elems[ key ], key, arg ):第一个参数是对象value,第二个参数是对应的对象key,第三个参数是可选的。
最后一点是,该工具函数的返回值类型是Array,不是jQuery类型的对象,即op(filter)逻辑之后还有一个op(collect)逻辑(虽然内部是一次性实现的)。
三 jQuery.grep
这里探究的grep是jQuery类型的静态方法,不是原型对象中的实例方法。
grep: function( elems, callback, inv ) {
var ret = [], retVal;
inv = !!inv;
// Go through the array, only saving the items
// that pass the validator function
for ( var i = 0, length = elems.length; i < length; i++ ) {
retVal = !!callback( elems[ i ], i );
if ( inv !== retVal ) {
ret.push( elems[ i ] );
}
}
return ret;
},
该定义的实现说明jQuery.grep实际上是类似Spark Transformations中filter操作加上Actions中collect操作,用伪码描述就是op(collect)(op(filter))。
该实现也约束了回调函数的参数类型和顺序,callback( elems[ i ], i ):第一个参数是集合的元素,第二个参数是该元素的index。同时回调函数callback应该严格返回boolean类型值,即使不返回boolean类型值,grep方法也会显式转换其为boolean值。
还有一点扩展用法是第三个参数:inv,默认不传的话在函数体内值为undefined,!!inv运算之后为false,但是for循环内部还有一层取反判断,即通过回调函数callback审核的元素被接纳,不通过的过滤掉。若想实现外部API取反逻辑,第三个参数需要传入boolean类型为true的值。
最后一点是,该工具函数的返回值类型是Array,不是jQuery类型的对象,即op(filter)逻辑之后还有一个op(collect)逻辑(虽然内部是一次性实现的。并且这个返回数组内的元素一定是原始集合elems中的元素,不像map那样更灵活--这里的回调函数callback真的只是承担predicate的功能。
四 jQuery.merge(jQuery.makeArray方法类似)
这里探究的merge是jQuery类型的静态方法,不是原型对象中的实例方法。
merge: function( first, second ) {
var i = first.length,
j = 0;
if ( typeof second.length === "number" ) {
for ( var l = second.length; j < l; j++ ) {
first[ i++ ] = second[ j ];
}
} else {
while ( second[j] !== undefined ) {
first[ i++ ] = second[ j++ ];
}
}
first.length = i;
return first;
},
如果要类比的话,这个工具方法jQuery.merge非常类似Spark Transformations中的union操作,用伪码描述是op(union)。
这个方法的参数同样可以比较灵活,可以有四种组合类型:(jQuery,Array)/(jQuery, jQuery)/(Array, jQuery)/(Array, Array)
该方法并没有新建一个融合后的集合对象作为返回对象,而是把第一个参数表示的对象扩展后作为返回值的,即扩展的是第一个参数对象。
五 扩展
如果类比Spark Transformations的其他操作,我们其实还可以给jQuery静态工具方法设计sample/subtract/intersection等API。作为jQuery的静态工具方法,其操作或返回的主要对象并不局限于jQuery类型对象,其实这一套静态工具方法主要目的和功能是提供给jQuery.prototype使用的(大多对应一个同名的原型方法并被其调用)。如果把jQuery类型的实例对象比作RDD的话,jQuery.prototype的实例方法才是更类似于Spark Transformations的操作,下一篇总结jQuery.prototype实例方法中真正的仿Spark Transformations操作。