Coder Social home page Coder Social logo

jQuery源码学习笔记 about candywxt HOT 5 OPEN

candywxt avatar candywxt commented on May 20, 2024 1
jQuery源码学习笔记

from candywxt.

Comments (5)

candywxt avatar candywxt commented on May 20, 2024
关于jQuery.extend 和 jQuery.fn.extend
jQuery.extend = jQuery.fn.extend = function(){
    //对jQuery.fn的扩展
};
jQuery.extend({
    //对jQuery本身的扩展
})

jQuery.extend 和 jQuery.fn.extend的区别

啊,实际上没什么区别,你没看见那个 = 号码?
只是你在用的时候,jQuery.extend是对jQuery本身的扩展,而jQuery.fn.extend是对jQuery.fn的扩展。

jQuery.extend是怎么实现的?

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初始值为arguments[0],即深赋值还是浅赋值。如果传值,则修正target的值为arguments[1];
i 的初始值为1,表示期望源对象从第2个元素开始,当第一个参数为布尔值时,就期望源对象从第3个元素开始了!

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

如果目标对象不是对象,不是函数(也就是有可能传进字符串或者其他的基本类型,而基本类型上设置非原生属性是无效的。参考红宝书P118-119)则统统设置为{}

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

i表示源对象开始的下标,length表示arguments的个数,如果他们值相等,则说明源对象并没有传进来。则把jQuery或者jQuery.fn作为目标对象,并且把传进来的对象当做源对象。
出现这种情况当然是1、传进来一个参数:extend(object);2传进来两个参数:extend(deep,object) //deep为布尔值

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

第一个if语句中将赋值和判断都做了,jQuery果然精炼
遍历optons时,判断了源对象和目标对象是否相等,如果相等为了避免深度遍历时死循环,因此continue了。

                // 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 : {};
                    }

如果copy是数组,则将src也应该为数组[],如果copy是对象,则src也应该为对象{}

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

先把copy递归合并到clone中,然后覆盖目标对象的同名属性。

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

如果不是深度合并,并且copy不是undefined,则直接覆盖目标对象的同名属性

    // Return the modified object
    return target;
};

from candywxt.

candywxt avatar candywxt commented on May 20, 2024
jQuery.fn.init(selector, context, rootjQuery)

构造函数jQuery.fn.init负责解析参数selector和context类型,并执行相应的逻辑,最后返会jQuery.fn.init的实例。

init = jQuery.fn.init = function( selector, context){
    var match, elem;
    if(!selector) {
        return this;
    }
    if(typeof selector === 'string'){...}
    else if( selector.nodeType ) {...}
    else if( jQuery.isFunction( selector )) {...}
    if( selector.selector !== undefined ) {...}
    return jQuery.makeArray( selector,this )
}

这是init函数的大致结构,很明显,如果selector不存在,则直接返回this
接下来我们看简单的几种情况

selector是DOM元素时(即有nodeType时)
用法示例 $(document.body)

else if ( selector.nodeType ) {
            this.context = this[0] = selector;
            this.length = 1;
            return this;
        } 

手动设置第一个元素和属性context指向该DOM元素,返回包含了该DOM元素引用的jQuery对象

selector是String时

        // Handle HTML strings
        if ( typeof selector === "string" ) {
            if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
                // Assume that strings that start and end with <> are HTML and skip the regex check
                match = [ null, selector, null ];

            } else {
                match = rquickExpr.exec( selector );
            }

如果selector<div>样子的,则跳过rquickExpr检查,暂且将match设置为一个头尾为null的数组,否则将检查它
rquickExpr是这个样子的:

rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/

这个正则表达式匹配#id时返回的结果为["#id",undefined,"id"],所以如果匹配该正则,会用到document.getElementById(match[2]);
回到第一种情况,如果match为[null, selector, null]时,即match[1]存在,context存在与否无关

            // Match html or make sure no context is specified for #id
            if ( match && (match[1] || !context) ) {

                // HANDLE: $(html) -> $(array)
                if ( match[1] ) {
                    context = context instanceof jQuery ? context[0] : context;

                    // Option to run scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    jQuery.merge( this, jQuery.parseHTML(
                        match[1],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );
                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
                        for ( match in context ) {

注意:在这里match改变了!不再是数组,而是context里的key !而此时的this是$(match[1])

                            // Properties of context are called as methods if possible
                            if ( jQuery.isFunction( this[ match ] ) ) {
                                this[ match ]( context[ match ] );

如果$(match[1])[context[key]]是function,则执行他

                            // ...and otherwise set as attributes
                            } else {
                                this.attr( match, context[ match ] );
                            }
                        }
                    }

                    return this;

如果不是function,则认为是attr,则添加attr
jQuery.attr稍后再看!
如果match此时为 ["#id",undefined,"id"]

                // HANDLE: $(#id)
                } else {
                    elem = document.getElementById( match[2] );
                    // Support: Blackberry 4.6
                    // gEBID returns nodes no longer in the document (#6963)
                    if ( elem && elem.parentNode ) {
                        // Inject the element directly into the jQuery object
                        this.length = 1;
                        this[0] = elem;
                    }
                    this.context = document;
                    this.selector = selector;
                    return this;
                }

没错,此时调用了getElementById(match[2]);

selector是选择器表达式
用法示例:$('div p')

此时分支依然在字符串中,参数selector不是单独标签,复杂HTML代码,#id,而是选择器表达式。如果没有指定上下文,则执行rootjQuery.find(selector);如果指定了上下文,且上下文是jQuery对象,则执行context.find( selector ),如果指定了上下文但上下文不是jQuery对象,则执行this.constructor( context ).find( selector ),即先创建一个包含了context的jQuery对象,然后在该对象上调用方法.find()

            // HANDLE: $(expr, $(...))
            } else if ( !context || context.jquery ) {
                return ( context || rootjQuery ).find( selector );

            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
            } else {
                return this.constructor( context ).find( selector );
            }

selector 是function的情况下
用法示例:$(function(){...})

        } else if ( jQuery.isFunction( selector ) ) {
            return typeof rootjQuery.ready !== "undefined" ?
                rootjQuery.ready( selector ) :
                selector( jQuery );
        }

如果selector是函数,则认为是绑定了ready事件。也就是$(function)是$(document).ready(function)的简写~

selector 是jQuery对象

        if ( selector.selector !== undefined ) {
            this.selector = selector.selector;
            this.context = selector.context;
        }

如果参数selector含有属性selector则认为他是jQuery对象。

        return jQuery.makeArray( selector, this );
    };

最后返回jQuery对象

makeArray是做什么的

看函数名知道是将selector变成Array,这也就是为什么jQuery对象上返回的都是数组类型了。

from candywxt.

candywxt avatar candywxt commented on May 20, 2024
数组操作方法

不知道你还记不记得jQuery.merge()和jQuery.makeArray()先说这个吧!

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

很巧妙的将i设置为second.length,+是将其类型转换为number。

jQuery.makeArray

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

jQuery.indexOf

indexOf = function( list, elem ) {
        var i = 0,
            len = list.length;
        for ( ; i < len; i++ ) {
            if ( list[i] === elem ) {
                return i;
            }
        }
        return -1;
    }

jQuery.inarray

inArray: function( elem, arr, i ) {
        return arr == null ? -1 : indexOf.call( arr, elem, i );
    }

其他的数组方法我们等用到的时候再说~~
下面来看下Sizzle~

from candywxt.

candywxt avatar candywxt commented on May 20, 2024

Sizzle

Sizzle 恐怕是jQuery里面精华部分之一了。新手喜欢用jQuery也是因为它强大的选择器。所以现在就来解刨它。看看究竟长什么样。

总体结构
function Sizzle(selector, context, results, seed) {
    var match, elem, m, nodeType,
        i, groups, old, nid, newContext, newSelector;

    if( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
        setDocument( context );
    }

    context = context || document;
    results = results || [];
    nodeType = context.nodeType;

    if ( typeof selector !== 'string' || !selector || 
         nodeType !== 1 && nodeType !== 9 && nodeType !== 11) {
        return results;
    }

    if (!seed && documentIsHTML) {
        //something
    }

    return select( selector.replace( rtrim, '$1' ), context, results, seed );
}

大概看了一眼,如果上下文存在但是不是document,那么将它上下文变成document,如果selector不是string,不是element,document,fragment那么返回results
来看一眼select

select

select: {
            get: function( elem ) {
                var value, option,
                    options = elem.options,
                    index = elem.selectedIndex,
                    one = elem.type === "select-one" || index < 0,
                    values = one ? null : [],
                    max = one ? index + 1 : options.length,
                    i = index < 0 ?
                        max :
                        one ? index : 0;

                // Loop through all the selected options
                for ( ; i < max; i++ ) {
                    option = options[ i ];

                    // IE6-9 doesn't update selected after form reset (#2551)
                    if ( ( option.selected || i === index ) &&
                            // Don't return options that are disabled or in a disabled optgroup
                            ( support.optDisabled ? !option.disabled :
 option.getAttribute( "disabled" ) === null ) &&
                            ( !option.parentNode.disabled ||
 !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {

                        // Get the specific value for the option
                        value = jQuery( option ).val();

                        // We don't need an array for one selects
                        if ( one ) {
                            return value;
                        }

                        // Multi-Selects return an array
                        values.push( value );
                    }
                }

                return values;
            }

好像没什么卵用
我们还是看if( !seed && documentIsHTML)里面是什么吧

if (!seed && documentIsHTML) {
        if( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
            if( (m = match[1]) ){

            } else if(match[2]){

            } else if(m = match[3] && support.getElementsByClassName ) {

            }
        }
        if (support.qsa && (!rbuggyQSA || rbuggyQSA.test( selector )) ){

        }
    }

两个大分支,三个小分支。
给出rquickExpr

rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/

match[1] -> id match[2] -> tag match[3] -> class

看第一个分支的ID情况

if ( (m = match[1]) ) {
                if ( nodeType === 9 ) {
                    elem = context.getElementById( m );
                    // Check parentNode to catch when Blackberry 4.6 returns
                    // nodes that are no longer in the document (jQuery #6963)
                    if ( elem && elem.parentNode ) {
                        // Handle the case where IE, Opera, and Webkit return items
                        // by name instead of ID
                        if ( elem.id === m ) {
                            results.push( elem );
                            return results;
                        }
                    } else {
                        return results;
                    }
                } else {
                    // Context is not a document
                    if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
                        contains( context, elem ) && elem.id === m ) {
                        results.push( elem );
                        return results;
                    }
                }
else if ( match[2] ) {
                push.apply( results, context.getElementsByTagName( selector ) );
                return results;

            // Speed-up: Sizzle(".CLASS")
            }
else if ( (m = match[3]) && support.getElementsByClassName ) {
                push.apply( results, context.getElementsByClassName( m ) );
                return results;
            }

from candywxt.

candywxt avatar candywxt commented on May 20, 2024

异步队列Defferred Object

总体结构
jQuery.Callbacks = function( options ) {...}
jQuery.extend({
     Deferred: function( func ) {...},
     when: function( subordinate ) {...}
})

Callbcks( options ),返回一个链式工具对象,用于管理一组回调函数。回调函数列表支持添加,移除,触发,锁定和禁用回调函数。为jQuery.ajax(),jQuery.Deferred()和ready事件提供基础功能。

jQuery.Callbacks( options )
jQuery.Callbacks = function( options ) {
     options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );
    var // Last fire value (for non-forgettable lists)
        memory,
        // Flag to know if list was already fired
        fired,
        // Flag to know if list is currently firing
        firing,
        // First callback to fire (used internally by add and fireWith)
        firingStart,
        // End of the loop when firing
        firingLength,
        // Index of currently firing callback (modified by remove if needed)
        firingIndex,
        // Actual callback list
        list = [],
        // Stack of fire calls for repeatable lists
        stack = !options.once && [],
        // Fire callbacks
       fire = function (data) {...},
       self = {...};
       return self
}

self为Callbacks返回的对象。来看一下self是什么

self = {
    add:
    disable:
    disabled:
    empty:
    fire:
    fireWith:
    fired:
    has:
    lock:
    locked:
    remove:
}

self内部有一个add函数,用于向list中添加回调函数。工具函数fire()使用指定的上下文context,和参数args调用数组list中的回调函数。该函数通过闭包机制引用数组list。

fire函数

fire = function( data ) {
            memory = options.memory && data;
            fired = true;
            firingIndex = firingStart || 0;
            firingStart = 0;
            firingLength = list.length;
            firing = true;
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                    memory = false; // To prevent further calls using add
                    break;
                }
            }
            firing = false;
            if ( list ) {
                if ( stack ) {
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                } else if ( memory ) {
                    list = [];
                } else {
                    self.disable();
                }
            }
        }

参数data[0]用于指定回调函数执行时的上下文,即关键字this所引用的对象。data[1]用于指定调用回调函数时传入的参数。
变量memory的用法如下

可能值 回调函数列表状态 含义和场景
undefined 未被触发或已被禁用
[context, args] 已被触发 以下场景之一:1 memory + 非stopOnFalse模式 2:memory + stopOnFalse模式 + 回调函数的返回值不是false
true 已被触发 以下场景之一:1 非memory 模式 2 stopOnFalse模式 + 某个回调函数的返回值是false
jQuery.Deferred( func )
jQuery.when( deferreds )
异步队列在jQuery中的应用

from candywxt.

Related Issues (12)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.