美文网首页
三、(补充)jQuery源码中init方法详解

三、(补充)jQuery源码中init方法详解

作者: 雪燃归来 | 来源:发表于2020-06-11 15:19 被阅读0次

           在前面的某篇文章中,我对jQuery.fn中的init方法做了一些阐述,整体的思路相对也比较完整,但是总感觉有些不尽意。因为init方法是jQuery源码中一个非常重要的方法,几乎所有的dom创建dom选择等功能都是以它的实现为基础。所以本篇文章中,我们就来仔仔细细的探讨一下jQuery.fn.init.

            首先,我参照的jquery版本为v2.0.3,请您对照此版本来进行下面的阅读。由于这段代码比较长,所以这段代码我就不罗列在这里了。我将会按照具体的逻辑来逐条详解这段代码。

    一、回顾jQuery的使用

            在平常jquery的操作中,我们是通过$(selector)这样的方式的方式使用jQuery,通过传入selector值的不同,我们主要有下面的几种使用方式

    // 传入不合法的参数
    1、$(""), $(null), $(undefined), $(false)
    
    // 获取元素节点
    2、$('#div1')  $('.box')  $('div')  $('#div1 div.box')
    
    //创建元素节点
    3、$('<li>')  $('<li>1</li><li>2</li>')
    
    // 选取dom
    4、$(this)   $(document)
    
    // 传入参数为函数的情况
    5、$(function(){})
    
    // 传入其他的一些不常见的参数
    6、$([])  $({})
    

            也许您对上面的六种用法不完全了解,这很正常,因为有些方式确实在我们平常的工作中用的比较少。下面,我们就来一一讲解。

    二、源码逐行分解

    1、jQuery中的this对象

            jQuery中的this对象和原生js中的this对象有所不同,jQuery中的this是一个类数组对象,具体的结构如下图所示:


    jQuery中的this对象

            生成图片上的源代码代码如下所示:对比图和代码,我们就可以发现jQuery中的this除了选择到元素外,还添加了length、context、selector等属性。

    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
    <script>
        console.log($('li'))
    </script>
    

            至于这段代码的使用,我们需要看下面的css方法的代码:code1中代码执行的逻辑就是code2中的代码,这也就印证了this这个特殊对象的使用。
    code1

    $('li').css('backgroundColor','red')
    

    code2

    for(var i = 0; i < this.length; i++){
      this[i].style.background = 'red'
    }
    

    2、init方法传入方法的解释说明

    selector context rootjQuery
    jquery 上下文对象 $(document)

    3、当传入不合法的selector时,直接返回this对象

            不合法的this对象包括空字符串、null、undefined、false。源码如下:

    if ( !selector ) {
      return this;
    }
    

    4、传入的参数selector为字符串

            这种情况是init方法中最复杂的部分,也是我们经常使用到的。主要处理的情况有上面六种中的2、3两种情况,既:

    // 获取元素节点
    2、$('#div1')  $('.box')  $('div')  $('#div1 div.box')
    
    //创建元素节点
    3、$('<li>')  $('<li>1</li><li>2</li>')
    
    4.1 处理元素的创建

            下面的判断条件就是处理创建标签的情况。源码随后,最后我们得到了match = [ null, selector, null ];这个结果。

    $('<li>')  
    $('<li>1</li><li>2</li>')
    
    if ( selector.charAt(0) === "<" && selector.charAt( 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 ];
    }
    

    match的值

    selector match
    match = [ null, '<li>', null ] $('<li>')
    match = [ null, '<li>1</li><li>2</li>', null ] $('<li>1</li><li>2</li>')
    4.2 处理获取元素节点

            这部分就是处理下面的这种情况,也就是选择元素节点。通过这段代码之后,我们有会得出一组不同情况下的match值。

    2、$('#div1')  $('.box')  $('div')  $('#div1 div.box')
    
    else {
       match = rquickExpr.exec( selector );
    }
    

    match的值

    selector match
    match = null ('.box')('div') $('#div1 div.box')
    match = [ '#div1', null, 'div1' ] $('#div1')
    match = [ '<li>hello', '<li>', null ] $('<li>hello')

    5、处理创建标签和id选择

           经过上面的分析,我们已经能拿到match的相关值了。下面我们就通过match值的不同来继续梳理我们的逻辑。下面的判断条件就是处理创建标签和id选择的条件了。

    if ( match && (match[1] || !context) ) {
      ...
    }
    
    5.1、创建元素节点

           根据上面分析的match结果,match[1]存在的情况为下面三种情况。

    1、$('<li>')
    2、$('<li>1</li><li>2</li>')
    3、$('<li>hello')
    

           对照着上面的三种使用情况,我么来分析下面的这段源码。

    if ( match[1] ) {
        context = context instanceof jQuery ? context[0] : context;
        // scripts is true for back-compat
        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 ) {
                // Properties of context are called as methods if possible
                if ( jQuery.isFunction( this[ match ] ) ) {
                    his[ match ]( context[ match ] );
    
                  // ...and otherwise set as attributes
                } else {
                    this.attr( match, context[ match ] );
                }
            }
        }
        return this;
    }
    
    5.1.1 获取执行上下文
    context = context instanceof jQuery ? context[0] : context;
    

           这里对象执行上下文对象进行了修正,如果传入的context为jQuery对象(如$(document)),我们就需要使用context[0]的方式选择原生js的对象(如document)。

    5.1.2 jQuery.parseHTML解析元素字符串
    jQuery.parseHTML(
         match[1],
         context && context.nodeType ? context.ownerDocument || context : document, true
    ) 
    

           jQuery.parseHTML这个方法既可以在jQuery内部使用,也可以提供给外部使用。
    在外部使用:
           通过使用parseHTML方法可以将str转化为数组。

    var str = '<li>1</li><li>2</li><li>3</li>'
    var arr = jQuery.parseHTML(str)
    console.log(arr)         //[li, li, li]
    

    在内部使用:
           在内部使用的时候,我们给其多传递了两个参数。多传递的第二个参数是执行上下文对象,第三个参数的是一个boolean类型的值,其值默认为false,表示不会执行str中的javascript代码,可以将其设置为true,表示可以执行JavaScript代码。

    var str = '<li>1</li><li>2</li><li>3</li><script>alert("1")<\/script>'
    var arr = jQuery.parseHTML(str, document, true)
    $.each(arr, function(i){
      $('ul').append( arr[i] )
    })
    

           关于parseHTML具体的实现细节,我们在后续的文章中再介绍,这里我们只需要知道,我们的目的是将标签字符串str格式化为一个数组。

    5.1.3 jQuery.merge合并数组

           我们再来看一下这个合并数据和jQuery对象的方法吧。
    合并数组

    var arr = ['a', 'b']
    var arr1 = ['c', 'b']
    console.log($.merge(arr, arr1)) // ["a", "b", "c", "b"]
    

    合并jQuery对象
           除了上面的合并数组外,我们还可以合并jQuery对象,如下:

    var arr = {
         0: 'a',
         1: 'b',
         length: 2
    }
    var arr1 = ['c', 'b']
    console.log($.merge(arr, arr1)) 
    

    上面的代码运行的结果如下图。同样,jQuery.merge方法具体的细节,我们在后面的文章中会详细介绍。


    jQuery.merge方法合并jQuery对象
    5.1.4 对单标签的情况单独处理

           单标签的情况如下:

      $('<li>', {title: 'hi', html: 'abcd'}).appendTo('ul')
      $('<li></li>', {title: 'hi', html: 'abcd'}).appendTo('ul')
    

    注意,下面的方式是不行的。

      $('<li></li><li></li>',  {title: 'hi', html: 'abcd'}).appendTo(' ul ')
    

           那现在,我们就带着上面的分析,接着来看这段处理单标签的源码吧。

    if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
        for ( match in context ) {
        // Properties of context are called as methods if possible
            if ( jQuery.isFunction( this[ match ] ) ) {
                this[ match ]( context[ match ] );
    
                // ...and otherwise set as attributes
            } else {
                this.attr( match, context[ match ] );
            }
        }
    }
    

            假设这段源码中context为{title: 'hi', html: 'abcd'},我们对这个对象进行遍历,如果这个对象中的键为jQuery中的函数时if ( jQuery.isFunction( this[ match ] ) ) ,我们执行这个方法,并且为其赋值 this[ match ]( context[ match ] )。当然,如果是属性的话,我们就给他添加属性 this.attr( match, context[ match ] )。

    5.2、根据id获取元素
    else {
            elem = document.getElementById( match[2] );
    
            // Check parentNode to catch when Blackberry 4.6 returns
            // nodes that are 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;
    }
    

            通过id选择元素的方法就显得比较简单了,包括获取元素,拼装this对象然后返回。

    6、当不传入执行上下文context或者传递进去的是jQuery对象

    } else if ( !context || context.jquery ) {
        return ( context || rootjQuery ).find( selector );
    } else {
        return this.constructor( context ).find( selector );
    }
    

            这段代码的处理结果的逻辑如下。你可能对 context.jquery 比较奇怪,这样写为了判断context为$(document)时的情况,因为只有jQuery对象才会有jquery这个属性。

    $('ul', document).find('li');     else   jQuery(document).find()
    $('ul', $(document)).find('li')   if:    jQuery(document).find()
    

    7、当传入的参数为DOM节点

    else if ( selector.nodeType ) {
        this.context = this[0] = selector;
        this.length = 1;
        return this;
    
        // HANDLE: $(function)
        // Shortcut for document ready
    }
    

            这里的node比如:document,window以及原生的dom节点等。例如下面这段代码:

    <ul id="list"></ul>
    <script>
        var list = document.getElementById('list')
        console.log($(list))
    </script>
    

    获取到的结果如下图所示:


    传入原生对象获取到的结果

    8、当传入的参数为函数

    else if ( jQuery.isFunction( selector ) ) {
        return rootjQuery.ready( selector );
    }
    

            这种情况,我们应该很常见。比如,如果这个都不熟悉,那就得好好地反思一下了。

    $(document).ready(function(){})
    

    9、当传入的参数为jQuery对象

           根据只有jQuery对象才有selector属性的原则,我们可以通过这段代码处理下面的方法。

    if ( selector.selector !== undefined ) {
        this.selector = selector.selector;
        this.context = selector.context;
    }
    
    $($('#list'))  ==>> $('#list')
    

    10、合并this对象

    return jQuery.makeArray( selector, this );
    

           这个方法就比较简单了,就是将selector属性和之前的this对象合并在一起,当然这也要借助jQuery.makeArray()这个方法来实现。我们来简单看一下makeArray这个方法的用法。

    <ul id="list"></ul>
    <script>
        var list = document.getElementById('list')
        console.log( $.makeArray(list, {length : 0 }))
    </script>
    

    运行的结果如图所示:


    makeArray方法的使用

           至此,我们的文章结束了,当然里边还有没有写到点,比如正则表达式,关于jQuery中的正则表达式我会在后面的文章中单独去讲解。感谢您的阅读,祝您身体健康,阖家幸福!

    相关文章

      网友评论

          本文标题:三、(补充)jQuery源码中init方法详解

          本文链接:https://www.haomeiwen.com/subject/hlgvtktx.html