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

2020-06-11  本文已影响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中的正则表达式我会在后面的文章中单独去讲解。感谢您的阅读,祝您身体健康,阖家幸福!

上一篇下一篇

猜你喜欢

热点阅读