jquery源码笔记(三): jquery.prototype的一些方法 和属性 init

jquery.fn = jquery.prototype = {  添加实例属性和方法,

      jquery: 版本,

      constructor: 修正指向问题

      init(): 初始化 和 参数管理

      selector:存储选择字符串

      length: this 对象的长度

      toArray(): 转数组

      get(): 转原生集合

      pushStack(): jquery 对象入栈

      each()  :  便利集合

      ready():DOM加载的接口

      slice(): 集合的截取

      first(): 集合的第一项

      last(): 集合的最后一项

      eq(): 集合的指定项

      map(): 返回新集合

      end(): 返回集合的前一个状态

      push():  (内部使用)

      sort():  (内部使用)

      splice(): (内部使用)

}

jQuery选择器接口:

jquery.prototype  的 init:

jQuery.fn.init的功能是对传进来的selector参数进行分析,进行各种不同的处理,然后生成jQuery对象。

类型(selector)

处理方式

DOM元素

包装成jQuery对象,直接返回

body(优化)

从document.body读取

单独的HTML标签

document.createElement

HTML字符串

document.createDocumentFragment

#id

document.getElementById

选择器表达式

$(…).find

函数

注册到dom ready的回调函数


我们来分解一个表达式

// 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*(<[wW]+>)[^>]*|#([w-]*))$/,

作者的解释呢很简单,一个简单的检测HTML字符串的表达式

分解:

1. 通过选择|分割二义,匹配^开头或者$结尾

  • ^(?:s*(<[wW]+>)[^>]*
  • #([w-]*))$

2. ^(?:s*(<[wW]+>)[^>]*

  • (?:pattern) : 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用
  • s* : 匹配任何空白字符,包括空格、制表符、换页符等等 零次或多次 等价于{0,}
  • (pattern) : 匹配pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,使用 $0$9 属性
  • [wW]+ : 匹配于'[A-Za-z0-9_]'或[^A-Za-z0-9_]' 一次或多次, 等价{1,}
  • (<[wW]+>) :这个表示字符串里要包含用<>包含的字符,例如<p>,<div>等等都是符合要求的
  • [^>]* : 负值字符集合,字符串尾部是除了>的任意字符或者没有字符,零次或多次等价于{0,},

3. #([w-]*))$

  • 匹配结尾带上#号的任意字符,包括下划线与-

4. 还要穿插一下exec方法

  • 如果执行exec方法的正则表达式没有分组(没有括号括起来的内容),那么如果有匹配,他将返回一个只有一个元素的数组,这个数组唯一的元素就是该正则表达式匹配的第一个串;如果没有匹配则返回null。
  • exec如果找到了匹配,而且包含分组的话,返回的数组将包含多个元素,第一个元素是找到的匹配,之后的元素依次为该匹配中的第一、第二...个分组(反向引用)

所以综合起来呢大概的意思就是:匹配HTML标记和ID表达式(<前面可以匹配任何空白字符,包括空格、制表符、换页符等等)


一个简单的测试:

var s = /(userd)+([d-h]{2})./;
    var str = "cuser2ffffcdas32userc";
    alert(s.exec(str));        //["user2ff",'user2','ff']      user2ff  满足正则表达式,   user2 符合第一个括号里的   ff符合第二个括号的

    var s2 = /(userd).|([d-h]{2})./;
    var str2 = "cffc2user4c";
     alert(s2.exec(str2));    //['ffc',null,'ff']          ffc 满足整个表达式,  第一个括号没找到,所以是null  ,ff符合第二个括号

jQuery选择器接口

API

image

jQuery是总入口,选择器支持9种方式的处理

1.$(document)   
2.$(‘<div>’) 
3.$(‘div’) 
4.$(‘#test’) 
5.$(function(){}) 
6.$("input:radio", document.forms[0]); 
7.$(‘input’, $(‘div’)) 
8.$() 
9.$("<div>", { 
         "class": "test", 
         text: "Click me!", 
         click: function(){ $(this).toggleClass("test"); } 
      }).appendTo("body"); 
10$($(‘.test’))

init: function( selector, context, rootjQuery ) {
        var match, elem;
        // HANDLE: $(""), $(null), $(undefined), $(false)  如果参数是这4个,直接返回,
        if ( !selector ) {
            return this;
        }
        // Handle HTML strings  如果参数是 字符串 例如 :   $("#id") $(".ccc") $("div") $('<li>')   $('<div>')  $('<li>2</li><li>33</li>') $('<li>hello')
        if ( typeof selector === "string" ) { 
            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 如果参数 以'<' 开头,  以'>' 结尾,即标签,如: $('<li>') $('<div>') $('<li>1</li><li>2</li>')
                match = [ null, selector, null ];     //match = [null,'<li>',null]    [null,'<div>',null]  [null,'<li>1</li><li>2</li>',null]
            } else {
                match = rquickExpr.exec( selector );    
            }
            //$('#id') 对应的 match = ['#id',null,'id'] $('<li>hello') 对应的 match = ['<li>hello','<li>',null];
             $('.ccc') $('div') 因为匹配不到,所以对应的 match = null;
    if ( match && (match[1] || !context) ) {  //满足 match是真, 说明是 插入标签 或者 id

处理 插入标签:

// HANDLE: $(html) -> $(array)
                if ( match[1] ) {        //如果match[1] 是真 ,说明插入的是标签,(标签的match第二个不是null)
                    context = context instanceof jQuery ? context[0] : context;  //如果context 是jquery对象,转化为js对象,

                    parseHTML(string, 根节点, boolean)方法:把字符串转成一个节点数组,第二个参数是根节点,第三个表示是否对script也进行转换,
                    true 表示 进行转换,false 不进行
                     praseHTML("<li>1</li><li>2</li><li>3</li>",document,true)  ---->  ['li','li','li']

                     merge()方法:将数组进行合并,这里可以将数组合并到json中:var s = {0:'a',1:'b',length:2}, 
                                 进行 merge(s,['c','d'])后,{0:'a',1:'b',2:'c',3:'d',length:4};

                    // scripts is true for back-compat
                    jQuery.merge( this, jQuery.parseHTML(
                        match[1],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );


                    这里是 插入标签 并添加属性的方式:只针对但标签,<li></li><li></li> 这个就不行,不是单标签,有2个li
                         例如:$("<li>",{title:'hi',html:"这是个li"})
                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
                        //满足单标签 并且 第二个参数 是一个面向字面量对象,即json格式的
                        for ( match in context ) {
                            // Properties of context are called as methods if possible
                            if ( jQuery.isFunction( this[ match ] ) ) {    //判断是不是函数,如果是函数,例如: html
                                this[ match ]( context[ match ] );        执行 html("这是个li");

                            // ...and otherwise set as attributes
                            } else {        
                                this.attr( match, context[ match ] );        添加个属性:attr('title','hi');
                            }
                        }
                    }

                    return this;

传入上下文:

context && context.nodeType ? context.ownerDocument || context : document

ownerDocument和 documentElement的区别

  • ownerDocument是Node对象的一个属性,返回的是某个元素的根节点文档对象:即document对象
  • documentElement是Document对象的属性,返回的是文档根节点
  • 对于HTML文档来说,documentElement是<html>标签对应的Element对象,ownerDocument是document对象

具体请看API手册

jQuery.merge( first, second ) 合并两个数组内容到第一个数组。

jQuery.parseHTML

使用原生的DOM元素的创建函数将字符串转换为一组DOM元素,然后,可以插入到文档中。

str = "hello, <b>my name is</b> jQuery.",
html = $.parseHTML( str ),

image

源码:

复制代码
parseHTML: function( data, context, keepScripts ) {
    if ( !data || typeof data !== "string" ) {
        return null;
    }
    if ( typeof context === "boolean" ) {
        keepScripts = context;
        context = false;
    }
    context = context || document;
    var parsed = rsingleTag.exec( data ),
        scripts = !keepScripts && [];
    // Single tag
    if ( parsed ) {
        return [ context.createElement( parsed[1] ) ];
    }
    parsed = jQuery.buildFragment( [ data ], context, scripts );
    if ( scripts ) {
        jQuery( scripts ).remove();
    }
    return jQuery.merge( [], parsed.childNodes );
},
复制代码

匹配一个独立的标签

rsingleTag = /^<(w+)s*/?>(?:</1>|)$/,
  • ^<(w+)s*/?>  : 以<开头,至少跟着一个字符和任意个空白字符,之后出现0或1次/>
  • (?:</1>|)$        : 可以匹配<、一个/或者空白并以之为结尾

      这样如果没有任何属性和子节点的字符串(比如'<html></html>'或者'<div></div>'这样)会通过正则的匹配,当通过正则的匹配后则会通过传入的上下文直接创建一个节点:

只是单一的标签:

if ( parsed ) {
        return [ context.createElement( parsed[1] ) ];
 }

而未通过节点的字符串,则通过创建一个div节点,将字符串置入div的innerHTML:

parsed = jQuery.buildFragment( [ data ], context, scripts );

它会把传入的复杂的html转为文档碎片并且存储在jQuery.fragments这个对象里。这里要提一下,document.createDocumentFragment()是相当好用的,可以减少对dom的操作.

创建一个文档碎片DocumentFragment

  • 如果要插入多个DOM元素,可以先将这些DOM元素插入一个文档碎片,然后将文档碎片插入文档中,这时插入的不是文档碎片,而是它的子孙节点;相比于挨个插入DOM元素,使用文档碎片可以获得2-3倍的性能提升;
  • 如果将重复的HTML代码转换为DOM元素,可以将转换后的DOM元素缓存起来,下次(实际是第3次)转换同样的HTML代码时,可以直接缓存的DOM元素克隆返

当一个HTML比一个没有属性的简单标签复杂的时候,实际上,创建元素的处理是利用了浏览器的innerHTML 机制。

复制代码
1 tmp = tmp || fragment.appendChild( context.createElement("div") );
2 
3 // Deserialize a standard representation
4 tag = ( rtagName.exec( elem ) || ["", ""] )[ 1 ].toLowerCase();
5 wrap = wrapMap[ tag ] || wrapMap._default;
6 tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ];
复制代码
  • 特别说明,jQuery创建一个新的<div>元素,并且设置innerHTML属性为传入的HTML代码片段。当参数是一个单标签,就像 $('<img />') or $('<a></a>'),jQuery将使用javasrcipt原生的 createElement()函数创建这个元素。
  • 当传入一个复杂的html,一些浏览器可能不会产生一个完全复制HTML源代码所提供的DOM。正如前面提到的,jQuery使用的浏览器.innerHTML属性来解析传递的HTML并将其插入到当前文档中。在此过程中,一些浏览器过滤掉某些元素,如<html><title>, 或 <head>的元素。其结果是,被插入元素可能不是传入的原始的字符串。
  • 不过,这些被过滤掉的标签有限的。有些浏览器可能不完全复制所提供的HTML源代码生成DOM。例如,Internet Explorer的版本8之前转换所有链接的href属性为绝对URL路径,和Internet Explorer第9版之前,不增加一个单独的兼容层的情况下,将无法正确处理HTML5元素。
  • 为了确保跨平台的兼容性,代码片段必须是良好的。标签可以包含其他元素,但需要搭配的结束标记

 如果第一个参数(HTML字符串)为一个空的单标签,且第二个参数context为一个非空纯对象

var jqHTML = $('<div></div>', { class: 'css-class', data-name: 'data-val' });
 
console.log(jqHTML.attr['class']); //css-class
console.log(jqHTML.attr['data-name']); //data-val

处理id:

// HANDLE: $(#id)
                } else {
                    elem = document.getElementById( match[2] );  //通过js的原生方法,获取id元素

                    // Check parentNode to catch when Blackberry 4.6 returns
                    // nodes that are no longer in the document #6963
                    if ( elem && elem.parentNode ) {    //黑莓有个bug,elem不存在,但是能找到,所以为了准确,再判断elem的父节点是否存在
                        // Inject the element directly into the jQuery object
                        this.length = 1;
                        this[0] = elem;
                    }

                    this.context = document;
                    this.selector = selector;
                    return this;
                }

匹配模式三:$(.className)    $(.className, context)

如果第一个参数是一个.className,jQuery对象中拥有class名为className的标签元素,

    // HANDLE: $(expr, $(...))
            } else if ( !context || context.jquery ) {    //当执行上下文不存在(第二个参数不存在)或者上下文是jquery对象
                return ( context || rootjQuery ).find( selector );

            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
            } else {                                    //当上下文不是jquery对象,this.constructor 就是jquery, 将他转化为jquery对象
                return this.constructor( context ).find( selector );
            }
                                                            最终都是$(document).find(...);

匹配 节点模式:  $(this)  $(document)

} else if ( selector.nodeType ) {    //不管是什么节点,都有nodeType 类型,如果不是节点,就不可能有nodeType 
            this.context = this[0] = selector;
            this.length = 1;
            return this;

$(jQuery对象)   $(函数function)

} else if ( jQuery.isFunction( selector ) ) {  //判断是不是函数,$(function(){ })
            return rootjQuery.ready( selector );
        }
                    简写 方式 最终 都是  document.ready(function(){ });

            例如: $( $("#id") )  和 $("#id") 是一样的
        if ( selector.selector !== undefined ) {    //判断是不是jquery对象,如果是jquery对象,那么他的selector就不是undefined
            this.selector = selector.selector;
            this.context = selector.context;
        }

转成json 内部调用的时候:和merge()差不多

return jQuery.makeArray( selector, this );

jQuery 构造器

     由此可见,从本质上来说,构建的jQuery对象,其实不仅仅只是dom,还有很多附加的元素,用数组的方式存储,当然各种组合有不一样,但是存储的方式是一样的

总的来说分2大类:

  • 单个DOM元素,如$(ID),直接把DOM元素作数组传递给this对象
  • 多个DOM元素,集合形式,可以通过CSS选择器匹配是有的DOM元素,过滤操作,构建数据结构

CSS选择器是通过jQuery.find(selector)函数完成的,通过它可以分析选择器字符串,并在DOM文档树中查找符合语法的元素集合


    toArray: function() {
        return core_slice.call( this );
    },

    get: function( num ) {
        return num == null ?

            // Return a 'clean' array
            this.toArray() :

            // Return just the object
            ( num < 0 ? this[ this.length + num ] : this[ num ] );
    },

    pushStack: function( elems ) {      //     $('div').pushStack($('span')).css('background','red');  div和span放在栈中,div先进,span后进,但是先进后出
                                        // 所以后面的sapn背景是红色
                                        //end()方法,就是找到栈的上一层
        // Build a new jQuery matched element setting
        var ret = jQuery.merge( this.constructor(), elems );

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

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

    each: function( callback, args ) {
        return jQuery.each( this, callback, args );
    },

    ready: function( fn ) {
        // Add the callback
        jQuery.ready.promise().done( fn );

        return this;
    },

    slice: function() {    //返回新的包装集
        return this.pushStack( core_slice.apply( this, arguments ) );
    },

    first: function() {
        return this.eq( 0 );
    },

    last: function() {
        return this.eq( -1 );
    },

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

    map: function( callback ) {
        return this.pushStack( jQuery.map(this, function( elem, i ) {
            return callback.call( elem, i, elem );
        }));
    },

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

    // For internal use only.
    // Behaves like an Array's method, not like a jQuery method.
    push: core_push,
    sort: [].sort,
    splice: [].splice
 $("li","ul")   //选择ul中的li
    $(".cc","#test")    //选择id是test中的class是cc   后面是前面的父元素
原文地址:https://www.cnblogs.com/a-lonely-wolf/p/5701327.html