自己看的源代码,可能理解的不够透彻,但是自己看过一遍感觉还是比较不一样的。希望各路大神多多指教!

下面是自己写的其他源码理解文章:
jQuery源代码自我理解(二)

前面的if判断是否存在commonJs运行环境,若没有,直接执行else的内容。

    if ( typeof module === "object" && typeof module.exports === "object" ) {

        // For CommonJS and CommonJS-like environments where a proper `window`
        // is present, execute the factory and get jQuery.
        // For environments that do not have a `window` with a `document`
        // (such as Node.js), expose a factory as module.exports.
        // This accentuates the need for the creation of a real `window`.
        // e.g. var jQuery = require("jquery")(window);
        // See ticket #14549 for more info.
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }

jQuery选择页面元素的源码,之后学习再深入。

jQuery = function( selector, context ) {

        // The jQuery object is actually just the init constructor 'enhanced'
        // Need init if jQuery is called (just allow error to be thrown if not included)
        return new jQuery.fn.init( selector, context );
    }

pushStack()函数用来指定节点链表中该节点的上一个元素,方便元素的查找。

pushStack: function( elems ) {

        // Build a new jQuery matched element set
        var ret = jQuery.merge( this.constructor(), elems );

        // Add the old object onto the stack (as a reference)
        ret.prevObject = this;

        // Return the newly-formed element set
        return ret;
    }

其中涉及到的merge()函数是将第二个参数的值复制到第一个参数,然后返回第一个参数:

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;
    }

each()函数对数组或对象进行遍历,执行回调函数,如果有一个元素返回false则跳出循环,直到都返回true。

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;
    }

each()函数调用了isArrayLike()来判断一个对象是不是类数组类型,其中调用了jQuery.type()函数来判断对象类型。
函数中先取得obj的长度,如果是对象,则”length” in obj 返回的是false。
type()函数返回obj的类型。
之后

return type === "array" || length === 0 ||
        typeof length === "number" && length > 0 && ( length - 1 ) in obj;

如果obj的type为array或长度等于0或者(长度类型为number且长度大于0且长度减一属于obj)则返回true。

function isArrayLike( obj ) {

    // Support: real iOS 8.2 only (not reproducible in simulator)
    // `in` check used to prevent JIT error (gh-2145)
    // hasOwn isn't used here due to false negatives
    // regarding Nodelist length in IE
    var length = !!obj && "length" in obj && obj.length,
        type = jQuery.type( obj );

    if ( type === "function" || jQuery.isWindow( obj ) ) {
        return false;
    }

    return type === "array" || length === 0 ||
        typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}

上一个函数使用到的type方法返回对象的类型,其中调用class2type对象的值。
其中一个较长逻辑为判断obj是否为对象或者函数,是的话则调用class2type对象的属性值,否则直接返回obj的类型。
toString.call( obj )返回obj的类型字符串,包括:"Boolean Number String Function Array Date RegExp Object Error Symbol"等类型。
class2type[ toString.call( obj ) ]返回对象中的对应属性值。

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;
    }

定义class2type对象的属性值,值为一些常见的数据类型,例如布尔类型,数值类型,字符串类型等。
先用split()函数将字符串分割成数组,然后在jQuery.each遍历函数里将数组的值添加到class2type对象里。

jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );

Map()函数是对数组进行处理并且返回一个新数组。方法源码类似于each()函数,区别在于map()函数返回一个新数组。

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 );
    }

代码最后一行对数组进行扁平化处理,但是对于嵌套数组而言,map()并不会对其进行判断处理。比如:

var a=[2,[1,3],4,[3,[4,5,32],3],2];
var b=jQuery.map(a,function(v,i){
return v++;
});
console.log(b);

输出的为: [2, NaN, 4, NaN, 2]
此外,concat.apply([],ret);也只会处理一次数组,例如:

var a=[2,[1,3],4,[3,[4,5,32],3],2];
console.log(Array.prototype.concat.apply( [], a ));

输出的结果为: [2, 1, 3, 4, 3, Array[3], 3, 2]


slice()函数用来取数组的元素,代码中slice.apply( this, arguments )冒充js原生的slice()函数,然后保留链式关系。

slice: function() {
        return this.pushStack( slice.apply( this, arguments ) );
    }

eq()函数选择数组中的第i个元素。其中
j = +i + ( i < 0 ? len : 0 );表示若参数为负数,则返回(长度+i),否则返回i。
另外,代码j >= 0 && j < len ? [ this[ j ] ] : [] 表示在j>=0且j不超过最大长度时表达式等于数组中下标为j的元素,否则表达式等于空数组。
之后再调用pushStack()函数,确定链式关系。

eq: function( i ) {
        var len = this.length,
            j = +i + ( i < 0 ? len : 0 );
        return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
    }

end()函数返回元素的上一个节点,或返回该对象的构造函数。

end: function() {
        return this.prevObject || this.constructor();
    }

jQuery.extend()和jQuery.fn.extend()是实现对象拓展的函数,函数可以传递多个参数,但一般默认为jQuery.extend([deep], target, object1, [objectN])
其中deep为一个Boolean值,表示是否深度复制对象;
target表示函数调用后接收拓展内容的对象;
object1和object2表示要拓展到target的对象。
函数开始先对一些变量赋值,然后判断传进来的第一个参数是否为布尔值,若是则将变量deep赋值为true,表示深度拓展对象。然后将target变量迁移到下一个参数。

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[ 0 ] || {},
        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赋值为空对象。
如果i===length,则表示传递的参数只有一个,此时target则赋值为调用该方法的对象,然后将i自减一。

// Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
        target = {};
    }

    // Extend jQuery itself if only one argument is passed
    if ( i === length ) {
        target = this;
        i--;
    }

之后进行if代码片段(若只有一个参数,前面的代码段执行后i自减一,此时i

for ( ; i < length; i++ ) {

        // Only deal with non-null/undefined values
        if ( ( options = arguments[ i ] ) != null ) {

            // Extend the base object
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                // Prevent never-ending loop
                if ( target === copy ) {
                    continue;
                }

如果是深层克隆且copy属性存在且(copy为普通对象或copy为数组)则执行下面的代码,否则直接将copy赋值给target对象。

if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray( src ) ? src : [];
} 

copy为数组时执行这段代码,其中src && jQuery.isArray( src ) ? src : [];表示src若存在且是数组时,表达式返回src的值,否则重新定义一个新数组。

else {
clone = src && jQuery.isPlainObject( src ) ? src : {};
}

copy若是对象执行这段代码,其中src && jQuery.isPlainObject( src ) ? src : {};与上面类似,若src若存在且是对象时,表达式直接返回src的值,否则重新定义一个新对象。

// Recurse if we're merging plain objects or arrays
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {

                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray( src ) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject( src ) ? src : {};
                    }

                    // Never move original objects, clone them
                    target[ name ] = jQuery.extend( deep, clone, copy );

                // Don't bring in undefined values
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

    // Return the modified object
    return target;
};

该方法会覆盖原对象的一些属性和方法。


上面用到的isPlainObject()方法是判断对象是否为普通对象。

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()是判断对象是否为空,对于数组一样适用

isEmptyObject: function( obj ) {
        var name;
        for ( name in obj ) {
            return false;
        }
        return true;
    }
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐