菜鸡看jquery源码(3)extend

来源:互联网 发布:威可多和雅戈尔 知乎 编辑:程序博客网 时间:2024/06/07 09:28

extend是jquery常用的一个方法,虽然原生用Object.assign实现了类似的功能。

jQuery.extend = jQuery.fn.extend = function() {    var options, name, src, copy, copyIsArray, clone,        target = arguments[ 0 ] || {}, //从arguments里拿第一个参数,没传就给默认空对象        i = 1,        length = arguments.length,//参数长度        deep = false;//默认不是深拷贝    // Handle a deep copy situation    //处理深拷贝的情况    if ( typeof target === "boolean" ) {        deep = target;        // Skip the boolean and the target        target = arguments[ i ] || {};        i++;    }    //判断传参情况,首先target取的是第一个参数,如果target是个布尔值,说明传入了是否深拷贝的布尔值,用argument[1]取到第二个参数给target    // Handle case when target is a string or something (possible in deep copy)    //处理目标值为字符串或者其他值情况    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {        target = {};    }    //简言之,不是对象就给target一个默认空对象    // Extend jQuery itself if only one argument is passed    //如果就传了一个参数就继承自身    if ( i === length ) {        target = this;        i--;    }    //这里的this指向jQuery    for ( ; i < length; i++ ) {        //第一层循环遍历传入的需要合并的参数对象        //为了方便观察,假设传入 extend(target,obj1,obj2,obj3)传入的第一个参数不是布尔值,所以i值为初始化的1;        // Only deal with non-null/undefined values        //只处理非空值        if ( ( options = arguments[ i ] ) != null ) {        //对传入的在target后的每一个对象进行遍历            // Extend the base object            for ( name in options ) {                src = target[ name ];                copy = options[ name ];                //target的属性值放进src里,options的属性值放进copy里                // Prevent never-ending loop                //阻止死循环                if ( target === copy ) {                    continue;                }                // Recurse if we're merging plain objects or arrays                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||                    ( copyIsArray = Array.isArray( copy ) ) ) ) {                    //判断拿到的copy是否为对象或数组并且传入的第一个参数为true则深拷贝,继续深入遍历                    if ( copyIsArray ) {                    //如果是数组,将copyIsArray重新变为false方便后面遍历的判断,并判断target对象对应的该属性是否为数组,是就赋值给clone,否则赋值空数组                        copyIsArray = false;                        clone = src && Array.isArray( src ) ? src : [];                    } else {                    //和上面处理数组一样处理这里面的对象                        clone = src && jQuery.isPlainObject( src ) ? src : {};                    }                    // Never move original objects, clone them                    //用前面拿到的clone和copy调用函数自身,实现一层一层进去复制,这里调用自身来做深拷贝很精妙                    target[ name ] = jQuery.extend( deep, clone, copy );                // Don't bring in undefined values                } else if ( copy !== undefined ) {                //如果不是深拷贝就好办了,直接把后面的对象对应的属性覆盖的target中对应的属性                    target[ name ] = copy;                }            }        }    }    // Return the modified object    //最后把这个修改过的对象传出去    return target;};

jquery的extend实现很巧妙,在其源码自身多处用到了这个方法,理解这个方法对后续的源码阅读有很大的帮助。
定义完extend后,直接使用extend方法在jquery自身上拓展一些工具类方法和属性,具体方法的实现以及意义暂时忽略不讲,先把源码整体思路理通顺,后续可能会将比较有学习意义的方法拿出来再看看。有意思的一点是在定义extend函数时用到的isFunction、isPlainObject等方法在jquery调用extend函数给自身添加方法时才加上,有点奇怪。

jQuery.extend( {    // Unique for each copy of jQuery on the page    expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),    // Assume jQuery is ready without the ready module    isReady: true,    error: function( msg ) {        throw new Error( msg );    },    noop: function() {},    isFunction: function( obj ) {        return jQuery.type( obj ) === "function";    },    isWindow: function( obj ) {        return obj != null && obj === obj.window;    },    isNumeric: function( obj ) {        // As of jQuery 3.0, isNumeric is limited to        // strings and numbers (primitives or objects)        // that can be coerced to finite numbers (gh-2662)        var type = jQuery.type( obj );        return ( type === "number" || type === "string" ) &&            // parseFloat NaNs numeric-cast false positives ("")            // ...but misinterprets leading-number strings, particularly hex literals ("0x...")            // subtraction forces infinities to NaN            !isNaN( obj - parseFloat( obj ) );    },    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;    },    isEmptyObject: function( obj ) {        /* eslint-disable no-unused-vars */        // See https://github.com/eslint/eslint/issues/6125        var name;        for ( name in obj ) {            return false;        }        return true;    },    type: function( obj ) {        if ( obj == null ) {            return obj + "";        }        // Support: Android <=2.3 only (functionish RegExp)        return typeof obj === "object" || typeof obj === "function" ?            class2type[ toString.call( obj ) ] || "object" :            typeof obj;    },    // Evaluates a script in a global context    globalEval: function( code ) {        DOMEval( code );    },    // Convert dashed to camelCase; used by the css and data modules    // Support: IE <=9 - 11, Edge 12 - 13    // Microsoft forgot to hump their vendor prefix (#9572)    camelCase: function( string ) {        return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );    },    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;    },    // Support: Android <=4.0 only    trim: function( text ) {        return text == null ?            "" :            ( text + "" ).replace( rtrim, "" );    },    // results is for internal usage only    makeArray: function( arr, results ) {        var ret = results || [];        if ( arr != null ) {            if ( isArrayLike( Object( arr ) ) ) {                jQuery.merge( ret,                    typeof arr === "string" ?                    [ arr ] : arr                );            } else {                push.call( ret, arr );            }        }        return ret;    },    inArray: function( elem, arr, i ) {        return arr == null ? -1 : indexOf.call( arr, elem, i );    },    // Support: Android <=4.0 only, PhantomJS 1 only    // push.apply(_, arraylike) throws on ancient WebKit    merge: function( first, second ) {        var len = +second.length,            j = 0,            i = first.length;        for ( ; j < len; j++ ) {            first[ i++ ] = second[ j ];        }        first.length = i;        return first;    },    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;    },    // arg is for internal usage only    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 );    },    // A global GUID counter for objects    guid: 1,    // Bind a function to a context, optionally partially applying any    // arguments.    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;    },    now: Date.now,    // jQuery.support is not used in Core but other projects attach their    // properties to it so it needs to exist.    support: support} );
原创粉丝点击