【jQuery源码浅析】(四)--jQuery工具--$.fn

来源:互联网 发布:c语言主函数参数 编辑:程序博客网 时间:2024/06/06 21:13

前言

jQuery的工具,顾名思义,就是直接可以以jQuery.do()的方式去执行jQuery中的方法,其实就是jQuery的静态方法。

由于jQuery的静态方法比较多,我就抽出几个重要的工具方法来进行源码解读一下。主要有:$.isPlainObject、$.each、$.map、$.grep、$.proxy,下面我就来一一解读一下这几个工具的源码。

源码分析

isPlainObject

该方法的用法请看$.isPlainObject

版本对比:

$.isPlainObject-v1.10.2源码请从这里下载查看

        /********** v3.1.1 **********/62      var class2type = {};        var getProto = Object.getPrototypeOf;        var hasOwn = {}.hasOwnProperty;        var fnToString = hasOwn.toString;        var ObjectFunctionString = fnToString.call( Object );302     isPlainObject: function( obj ) {            var proto, Ctor;            // Detect obvious negatives            // Use toString instead of jQuery.type to catch host objects            if ( !obj || toString.call( obj ) !== "[object Object]" ) {                return false;            }            proto = getProto( obj );            // Objects with no prototype (e.g., `Object.create( null )`) are plain            if ( !proto ) {                return true;            }            // Objects with prototype are plain iff they were constructed by a global Object function            Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;            return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;321     }
对比小结:
  1. 整体实现都是一样的,旧版本做了一些兼容,最终判断方式是判断是否有非继承属性core_hasOwn.call(obj, key),实现过程看这里$.isPlainObject实现原理
  2. 新版本的判断方式更为严谨一点,主要有两个判断,一是对象是否由new Object()或者{}而来,任何不是Object构造器实例都不是plainObject,比如new Date()window之类的;;;二是该对象的原型构造器一定是function,如果是var obj = {}; Object.setPrototypeOf(obj, { constructor: 'aa'} ),那么这个obj也不是plainObject
  3. 判断是否为Object的实例,由fnToString.call( Ctor ) === ObjectFunctionString判断;判断是否对象原型的构造器没有被重写,由typeof Ctor === "function"判断
  4. Object.create(null)也是plainObject

each

该方法的用法请看$.each

版本对比

$.each-v1.10.2源码请从这里下载查看

        /********** v3.1.1 **********/362     each: function( obj, callback ) {            var length, i = 0;            if ( isArrayLike( obj ) ) {                length = obj.length;                for ( ; i < length; i++ ) {                    if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {                        break;                    }                }            } else {                for ( i in obj ) {                    if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {                        break;                    }                }            }            return obj;381     }
对比小结
  1. 实现的功能都一样,遍历类数组对象的属性,然后用callback来处理遍历到的属性。如果callback返回的是false,则终止遍历。不过1.x版本可以传入第三个参数args,作为callback的参数,但是代码写得有点赘余。
  2. 新版本不用传递args参数,已经确定好callback的只有两个参数,分别为indexelement,得源于callback.call( obj[ i ], i, obj[ i ] )
  3. callback里面的this是obj的属性值,因此可以用this.prop = xxx,这样来改变obj的属性值。
  4. 对象的遍历会遍历到父类的属性上面,得源于for ( i in obj )
  5. 最后返回的是传入的对象本身return obj

—map

该方法的用法请看$.map

与$.each的比较
        /********** v3.1.1 **********/56      var concat = [].concat;        // arg is for internal usage only448     map: function( elems, callback, arg ) {            var length, value,                i = 0,                ret = [];            // Go through the array, translating each of the items to their new values            if ( isArrayLike( elems ) ) {                length = elems.length;                for ( ; i < length; i++ ) {                    value = callback( elems[ i ], i, arg );                    if ( value != null ) {                        ret.push( value );                    }                }            // Go through every key on the object,            } else {                for ( i in elems ) {                    value = callback( elems[ i ], i, arg );                    if ( value != null ) {                        ret.push( value );                    }                }            }            // Flatten any nested arrays            return concat.apply( [], ret );477     }
对比小结
  1. map不会终止遍历
  2. callback三个参数,分别为element、index、arguments,得源于value = callback( elems[ i ], i, arg )
  3. 如果callback返回的不是null,则将返回的value放入到最后返回的对象或数组的其中一段空间里面,ret.push( value )。最后返回的结果和call完全不一样,call是改造了原来的element,而map是重新创建一个element,我把这个element叫做dist,然后把每个element的每个属性值改造完后通过callback返回一个最终的属性值,把它加入dist里面,concat.apply( [], ret )也就是说,最终返回的肯定是数组,数组里面的值由callback的返回值决定。

—grep

该方法的用法请看$.grep

版本对比

$.grep-v1.10.2源码请从这里下载查看

        /********** v3.1.1 **********/428     grep: function( elems, callback, invert ) {            var callbackInverse,                matches = [],                i = 0,                length = elems.length,                callbackExpect = !invert;            // Go through the array, only saving the items            // that pass the validator function            for ( ; i < length; i++ ) {                callbackInverse = !callback( elems[ i ], i );                if ( callbackInverse !== callbackExpect ) {                    matches.push( elems[ i ] );                }            }            return matches;445     }
对比小结
  1. 首先我认为这个方法应该叫filter,这样就可以更直观地知道这个方法是用于过滤数组的。Array里面就有一个Array.filter的方法。
  2. 版本升级后有一点点改动,不过实现过程是几乎一样的,只不过旧版本多了一个判断if (inv !== retVal)而已。
  3. 原理很简单,就是根据invert的值来判断callback里面的值是否要被筛选出来。
  4. 当中有一个小技巧,`就是先把invertcallback转成boolean类型再比较,非常严谨。

—proxy

该方法的用法请看$.proxy

源码
484     proxy: function( fn, context ) {            var tmp, args, proxy;            if ( typeof context === "string" ) {                tmp = fn[ context ];                context = fn;                fn = tmp;            }            // Quick check to determine if target is callable, in the spec            // this throws a TypeError, but we will just return undefined.            if ( !jQuery.isFunction( fn ) ) {                return undefined;            }            // Simulated bind            args = slice.call( arguments, 2 );            proxy = function() {                return fn.apply( context || this, args.concat( slice.call( arguments ) ) );            };            // Set the guid of unique handler to the same of original handler, so it can be removed            proxy.guid = fn.guid = fn.guid || jQuery.guid++;            return proxy;509     }
源码分析
  1. 这个方法和function.prototype.bind()很像,都是指定function中的this所指的最终对象。但是proxy可以接收第二种参数列表的方式,即proxy(context,fnName)的方式,context也就是所需要绑定的obj,if ( typeof context === "string" ) { tmp = fn[ context ]; context = fn;fn = tmp;}
  2. 可接收除了这两个重要参数的其他参数args = slice.call( arguments, 2 )
  3. 采用对象冒充的方式强制将fn的this指向所想要绑定的context身上fn.apply( context || this, args.concat( slice.call( arguments ) ) ),如果context不存在的话有可能是本身也有可能是window,取决于上下文环境。接收多个用于fn的参数args.concat( slice.call( arguments ) )
  4. 最终返回的类型方法,即代理后的方法。

相关资料

  • jQuery技术内幕:深入解析jQuery架构设计与实现原理
  • this.method.apply(this,arguments)
    用法解析
  • jQuery3.1.1.js源码下载
原创粉丝点击