jQuery源码之init函数的分析

这篇博客深入分析了jQuery库中的init函数。首先介绍了init在jQuery定义中的关键位置,解释了为何调用jQuery会返回init函数的实例,并展示了如何通过修改init原型实现对jQuery原型方法的访问。接着,通过一个测试例子说明了正确使用init的重要性。然后,作者揭示了jQuery原型对象赋值的细节,指出如果不重新声明constructor,将导致构造函数引用错误。最后,强调了init源码分析对于理解jQuery魅力的价值。
摘要由CSDN通过智能技术生成

jQuery源码之init函数的分析

精妙处之一

init在源码中最先出现的位置是jQuery定义的地方,如下所示

var 
    ...

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

调用jQuery之后应该返回的是一个init函数的一个实例,但是许多方法是存在于jQuery的,为了使得返回的这个对象实例可以调用jQuery的原型方法,就对init的原型进行了修改,如下

// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;

jQuery.fn就是jQuery的原型对象

jQuery.fn = jQuery.prototype = {
    jquery: version,
    constructor: jQuery,  
    selector: "",
    length: 0,
    toArray: function() {},
    get: function( num ) {},
    pushStack: function( elems ) {},
    each: function( callback, args ) {},
    map: function( callback ) {},
    slice: function() {},
    first: function() {},
    last: function() {},
    eq: function( i ) {},
    end: function() {},
    push: push,
    sort: arr.sort,
    splice: arr.splice
};

测试的例子

这里给出这样的例子,运行之后会出现Uncaught TypeError: instance.show is not a function的错误

        var myQuery = function() {
            return new myQuery.fn.init();
        };

        var myInit = function() {
            this.fun = 'myInit';
        };

        myQuery.fn = myQuery.prototype = {
            constructor: myQuery,
            show :  function() {
                console.log('this is show function');
            },
            addAttr: function(obj) {
                this.attr = obj;
            },
            init: myInit
        };

        //myInit.prototype = myQuery.fn;
        var instance = new myQuery();
        instance.show(); //Uncaught TypeError: instance.show is not a function

myInit.prototype = myQuery.fn;去掉注释之后,得到的结果就是正确的,结果为this is show function

精妙处二

从源码中我们看到下面的代码

jQuery.fn = jQuery.prototype = {
    jquery: version,
    constructor: jQuery, 
    ...
}

可以看到直接给jQuery的原型对象赋值为一个对象,如果是没有对constructor进行重新声明的话,那么此时的constructor就不是jQuery而是Object

init源码分析

// A central reference to the root jQuery(document)
var rootjQuery,

    // A simple way to check for HTML strings
    // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
    // Strict HTML recognition (#11290: must start with <)
    rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,    
    init = jQuery.fn.init = function( selector, context ) {
        var match, elem;

        // HANDLE: $(""), $(null), $(undefined), $(false)

         /*1.当selector是""、null、undefined、false时,返回的是this对象,可是这里的this对象是什么呢?这里要联想调用init的场景,new jQuery.fn.init( selector, context ),所以这个this指代的是一个实例对象,那么是谁的实例呢?你可是否还记得init的原型可是改为了jQuery.fn也就是jQuery.prototype,所以这个实例对象就是jQuery的实例,拥有着jQuery中的原型属性和方法*/

        if ( !selector ) {
            return this;
        }

        // Handle HTML strings 传入的是字符串的时候
        if ( typeof selector === "string" ) {
            if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
                /*2.这个if条件判断的就是类似这样的: $('<li>')   $('<li>1</li><li>2</li>')
                    得到的match如下:
                     match = [null, '<li>', null]
                     match = [null, '<li>1</li><li>2</li>', null];
                */              
                // Assume that strings that start and end with <> are HTML and skip the regex check
                match = [ null, selector, null ];

            } else {
                    /*3.类似这样的:$('#div')    $('<li>hello'),得到的match如下
                        match = ['#div1', null, 'div1']
                        match = ['<li>hello', 'li', null]
                        $('<li>hello').appendTo('ul')相当于加了个空标签'<li></li>'
                    */
                match = rquickExpr.exec( selector );
            }

            // Match html or make sure no context is specified for #id
             //这里符合if条件如:$('<li>') $('<li>1</li><li>2</li>') $('#div')  $('<li>hello')这样的,对于id的话,match[1]为null,context不存在,所以也是符合条件的
            if ( match && (match[1] || !context) ) {

                // HANDLE: $(html) -> $(array)
                 //创建标签的情况
                if ( match[1] ) {
                  /*4. $('<li>', document)第二个参数只能是document,如果是iframe,那么就是contentWindow.document。默认情况下就是document,下面的代码表示,如果context是document就直接返回document,如果context是$(document)那么就获取到原生的document*/
                    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
                      /*5. $('<li>1</li><li>2</li>')如何将字符串转换为this={0: 'li', 1:'li', length: 2},因为后面的是DOM节点数组,所以需要通过parseHTML来完成,parseHTML的默认的第三个参数是false,但是parseHTML也可以创建script标签(写script标签的时候需要转义,防止它和上面的script标签配对)true可以表示添加script标签,merge方法就是将DOM数组转换为JSON格式*/
                    jQuery.merge( this, jQuery.parseHTML(
                        match[1],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );

                    // HANDLE: $(html, props)
                      /*$('<li>', {title: '1', html:1})
                      var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); 这个正则是匹配单标签,如<li>或者<li></li>,isPlainObject用于判断是否为对象字面量*/
                    if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
                        for ( match in context ) {
                            // Properties of context are called as methods if possible
                              //如果match是个函数就调用jQuery中存在的函数,如html方法,this.html(1)
                            if ( jQuery.isFunction( this[ match ] ) ) {
                                this[ match ]( context[ match ] );

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

                    return this;

                // HANDLE: $(#id) 处理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;
                }

            // HANDLE: $(expr, $(...))
             //如果没有传入上下文或者传入的上下文是jQuery对象,那么调用上下文的find方法继续缩小朝着对象
             //如$('li', $('ul'))
            } else if ( !context || context.jquery ) {
                return ( context || rootjQuery ).find( selector );

            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
             //如果上下文不是jQuery对象,那么直接把上下文封装为jQuery对象继续调用find方法,this.constructor就是jQuery
            } else {
                return this.constructor( context ).find( selector );
            }

        // HANDLE: $(DOMElement)
         //如这些情况: $(this) $(document),之境界将它封装为jQuery对象,这里的selector不是string的情况
        } else if ( selector.nodeType ) {
            this.context = this[0] = selector;
            this.length = 1;
            return this;

        // HANDLE: $(function)
        // Shortcut for document ready
        //文档加载的方式:$(document).ready(function() {})  $(function() {}),  rootjQuery就是document
        } else if ( jQuery.isFunction( selector ) ) {  
            return typeof rootjQuery.ready !== "undefined" ?
                rootjQuery.ready( selector ) :
                // Execute immediately if ready is not present
                 //直接将jQuery对象传入这个参数进行调用
                selector( jQuery );
        }
        //如果调用的方式是:$($('#div'))那么要包装为$('#div'),jQuery对象是有selector属性的,而DOM对象是有nodeType属性的
        if ( selector.selector !== undefined ) {
            this.selector = selector.selector;
            this.context = selector.context;
        }
        //makeArray在内部调用的时候如果第二个参数传入的是JSON对象,那么最后返回的结果就是JSON
        return jQuery.makeArray( selector, this );
    };

源码还是很有魅力的,确实可以收获到一些东西

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值