jQuery学习之init

上一篇主要看了core.js,提到了无 new的构造方式,这一篇就看看这个init里究竟干了什么吧

用法

jQuery()
Return a collection of matched elements either found in the DOM based on passed argument(s) or created by passing an HTML string.

  • jQuery( selector [, context ] )
    • jQuery( selector [, context ] )
    • jQuery( element )
    • jQuery( elementArray )
    • jQuery( object )
    • jQuery( selection )
    • jQuery()
  • jQuery( html [, ownerDocument ] )
    • jQuery( html [, ownerDocument ] )
    • jQuery( html, attributes )
  • jQuery( callback )

实现

jQuery的代码里,则是分成string, dom, callback来分别处理的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
if ( !selector ) {
return this;
}
// Method init() accepts an alternate rootjQuery
// so migrate can support jQuery.sub (gh-2101)
root = root || rootjQuery;
// Handle HTML strings
if ( typeof selector === "string" ) {
// ...
// HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
// ...
// HANDLE: $(function)
// Shortcut for document ready
} else if ( isFunction( selector ) ) {
// ...
}
return jQuery.makeArray( selector, this );
};
rootjQuery = jQuery( document );

对DOM的操作

对dom的操作,直接将dom在实例的0属性上,设置length,返回即可

1
2
3
4
5
if ( selector.nodeType ) {
this[ 0 ] = selector;
this.length = 1;
return this;
}

对callback的操作

为ready的简写

1
2
3
4
5
6
7
if ( isFunction( selector ) ) {
return root.ready !== undefined ?
root.ready( selector ) :
// Execute immediately if ready is not present
selector( jQuery );
}

对HTML string的处理

用正则表达式去提取字符串的信息

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

(?:pattern): 表示匹配但是不存储
\s*:前面的空格
(<[\w\W]+>): 匹配标签到结束<div></div>
[^>]: 匹配所有不是>的字符
#([\w-]*))$: id 表达式

正则表达式exec:

如果 exec() 找到了匹配的文本,则返回一个结果数组。否则,返回 null。此数组的第 0 个元素是与正则表达式相匹配的文本,第 i 个元素是与 RegExpObject 的第 i 个子表达式相匹配的文本(如果有的话)

用match来记录匹配的结果

1
2
3
4
5
6
7
8
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 );
}

然后来分析match的结果,我们知道match[1] 对应的是(<[\w\W]+>)(标签)这部分,match[2]对应的是([\w-]*)(id)部分

  1. 有match[1],那给的是html串,需要对html串,进行解析成一组dom ,与实例合并。如果第二个参数是属性数组,则给实例添加相应的属性或方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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 ) {
// Properties of context are called as methods if possible
if ( isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
}
  1. 没有match[1], 有match[2] 说明是id选择器,直接通过document.getElementById找到元素,然后像对待$(DOMElement)一样处理即可
1
2
3
4
5
6
7
8
9
elem = document.getElementById( match[ 2 ] );
if ( elem ) {
// Inject the element directly into the jQuery object
this[ 0 ] = elem;
this.length = 1;
}
return this;
  1. 没有match[1],没有match[2],那就是其他的选择器,在上下文对象上走find路线
1
2
3
4
5
6
7
8
if ( !context || context.jquery ) {
return ( context || root ).find( selector );
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}

parseHTML

作用是将字符串转成一组dom节点

用到了一个复杂的正则表达式去匹配单个标签元素的情况

1
2
/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;
} );

([a-z][^\/\0>:\x20\t\r\n\f]*): 匹配标签的名称,名称含有字母,不含空格,’>’, ‘:’, ‘/‘等
[\x20\t\r\n\f]*: 匹配空格、制表符、回车、换行、分页
\/?> 可以带/,考虑到<input/>这样的元素
(?:<\/\1>|): \1表示捕获的第一个分组,这部分匹配如</div> 或者为空

用正则去匹配字符串,match[1]就是标签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
jQuery.parseHTML = function( data, context, keepScripts ) {
if ( typeof data !== "string" ) {
return [];
}
if ( typeof context === "boolean" ) {
keepScripts = context;
context = false;
}
var base, parsed, scripts;
// 检测是否有上下文对象,没有就自己创建一个document
if ( !context ) {
// Stop scripts or inline event handlers from being executed immediately
// by using document.implementation
if ( support.createHTMLDocument ) {
context = document.implementation.createHTMLDocument( "" );
// Set the base href for the created document
// so any parsed elements with URLs
// are based on the document's URL (gh-2965)
base = context.createElement( "base" );
base.href = document.location.href;
context.head.appendChild( base );
} else {
context = document;
}
}
parsed = rsingleTag.exec( data );
scripts = !keepScripts && [];
// Single tag
// 单个标签,直接返回创建出的dom
if ( parsed ) {
return [ context.createElement( parsed[ 1 ] ) ];
}
// 非简单标签
parsed = buildFragment( [ data ], context, scripts );
if ( scripts && scripts.length ) {
jQuery( scripts ).remove();
}
return jQuery.merge( [], parsed.childNodes );
};

代码里对非单个标签的情况下,根据内容去创建文档碎片,然后返回这个碎片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function buildFragment( elems, context, scripts, selection, ignored ) {
var elem, tmp, tag, wrap, attached, j,
fragment = context.createDocumentFragment(),
nodes = [],
i = 0,
l = elems.length;
for ( ; i < l; i++ ) {
// 处理elems[i]
}
// Remove wrapper from fragment
fragment.textContent = "";
i = 0;
while ( ( elem = nodes[ i++ ] ) ) {
}
return fragment;
}

对碎片处理,分类处理。是dom节点,直接放到nodes里,是文本就创建文本节点,其他的就创建div,将字符串作为innerHTML插入,获取所有的节点,放在nodes里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
if ( elem || elem === 0 ) {
// Add nodes directly
if ( toType( elem ) === "object" ) {
// Support: Android <=4.0 only, PhantomJS 1 only
// push.apply(_, arraylike) throws on ancient WebKit
jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
// 文本节点
} else if ( !rhtml.test( elem ) ) {
nodes.push( context.createTextNode( elem ) );
// dom节点
} else {
tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
// 获取最外层的标签
tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
// 为标签补充外层,如option、thead等
wrap = wrapMap[ tag ] || wrapMap._default;
tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
// 找到包裹内容的上一层,如div, tbody
j = wrap[ 0 ];
while ( j-- ) {
tmp = tmp.lastChild;
}
// Support: Android <=4.0 only, PhantomJS 1 only
// push.apply(_, arraylike) throws on ancient WebKit
//
jQuery.merge( nodes, tmp.childNodes );
// Remember the top-level container
tmp = fragment.firstChild;
// Ensure the created nodes are orphaned (#12392)
tmp.textContent = "";
}
}

下面还有一段处理脚本的代码,还没看懂。。。
然后把所有的节点都在到碎片里,并对脚本进行处理,这一段没看懂。。。😅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
fragment.textContent = "";
i = 0;
while ( ( elem = nodes[ i++ ] ) ) {
// Skip elements already in the context collection (trac-4087)
if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
if ( ignored ) {
ignored.push( elem );
}
continue;
}
attached = isAttached( elem );
// Append to fragment
tmp = getAll( fragment.appendChild( elem ), "script" );
// Preserve script evaluation history
if ( attached ) {
setGlobalEval( tmp );
}
// Capture executables
if ( scripts ) {
j = 0;
while ( ( elem = tmp[ j++ ] ) ) {
if ( rscriptType.test( elem.type || "" ) ) {
scripts.push( elem );
}
}
}
}

到这似乎越来越接近sizzle了,但是看到这我都觉得有点费力了,之后可能不按顺序走了,挑点简单的看起。