声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢!
打开jQuery源码,一眼看去到处都充斥着正则表达式,jQuery框架的基础就是查询了,查询文档元素对象,所以狭隘的说呢,jQuery就是一个选择器,并这个基础上构建和运行查询过滤器!
工欲善其事,必先利其器,所以先从正则入手
我们来分解一个表达式
// 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-]*))$/,
作者的解释呢很简单,一个简单的检测HTML字符串的表达式
分解:
1. 通过选择|分割二义,匹配^开头或者$结尾
2. ^(?:\s*(<[\w\W]+>)[^>]*
3. #([\w-]*))$
4. 还要穿插一下exec方法
所以综合起来呢大概的意思就是:匹配HTML标记和ID表达式(<前面可以匹配任何空白字符,包括空格、制表符、换页符等等)
简单测试下:
var str = ' <div id=top></div>'; var match = rquickExpr.exec(str); console.log(match) //[" <div id=top></div>", "<div id=top></div>", undefined, index: 0, input: " <div id=top></div>"]
var str = '[?\f\n\r\t\v]<div id=top></div>';
var str = '#test'; var match = rquickExpr.exec(str); console.log(match) //["#test", undefined, "test", index: 0, input: "#test"]
jQuery选择器接口
API
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’))
jQuery这个选择器重构了几次后,现在逻辑结构相当的清晰了,一看大概就知道
不能不得说jQuery的反模式,非职责单一深受开发者喜欢,一个接口承载的职责越多内部处理就越复杂了
jQuery查询的的对象是dom元素,查询到目标元素后,如何存储?
本质上讲jQuery.fn.init构建的出来的对象,就是jQuery对象
init: function( selector, context, rootjQuery ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Handle HTML strings if ( typeof selector === "string" ) { // HANDLE: $(DOMElement) } else if ( selector.nodeType ) { // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); } if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this ); },
源码缩进后的结构:
匹配模式一:$("#id")
1. 进入字符串处理
if ( typeof selector === "string" ) {
2. 发现不是 "<"开始,">"结尾 $('<p id="test">My <em>new</em> text</p>')这种的情况
如果selector是html标签组成的话,直接match = [ null, selector, null ];
而不用正则检查
if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
3. 否则的话需要match = rquickExpr.exec( selector )
match = rquickExpr.exec( selector );
4. 匹配的html或确保没有上下文指定为# id
if ( match && (match[1] || !context) ) {
5. match[1]存在,处理$(html) -> $(array),,也就是处理的是html方式
if ( match[1] ) {
6. 处理ID
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;
至此本次检索完毕!
可以看到
this就是jQuery工厂化后返回的对象
匹配模式二:<htmltag>
重复的地方跳过直接看处理接口
if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) 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 ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes } else { this.attr( match, context[ match ] ); } } } return this; // HANDLE: $(#id) } else {
传入上下文:
context && context.nodeType ? context.ownerDocument || context : document
ownerDocument和 documentElement的区别
具体请看API手册
jQuery.merge( first, second ) 合并两个数组内容到第一个数组。
jQuery.parseHTML
使用原生的DOM元素的创建函数将字符串转换为一组DOM元素,然后,可以插入到文档中。
str = "hello, <b>my name is</b> jQuery.",
html = $.parseHTML( str ),
源码:
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>|)$/,
这样如果没有任何属性和子节点的字符串(比如'<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
当一个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 ];
$('<img />')
or $('<a></a>')
,jQuery将使用javasrcipt原生的 createElement()
函数创建这个元素。 .innerHTML
属性来解析传递的HTML并将其插入到当前文档中。在此过程中,一些浏览器过滤掉某些元素,如<html>
, <title>
, 或 <head>
的元素。其结果是,被插入元素可能不是传入的原始的字符串。 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
匹配模式三:$(.className)
如果第一个参数是一个.className,jQuery对象中拥有class名为className的标签元素,并增加一个属性值为参数字符串、document的selector、context属性
return jQuery(document).find(className);
匹配模式四:$(.className, context)
如果第一个参数是.className,第二个参数是一个上下文对象(可以是.className(等同于处理$(.className .className)),jQuery对象或dom节点),
jQuery对象包含第二个参数上下文对象中拥有class名为className的后代节点元素,并增加一个context和selector属性
return jQuery(context).find(className);
匹配模式五:$(jQuery对象)
如果第一个参数是jQuery对象,上面已经分析过如果在查询dom时,参数是一个#加元素id,返回的jQuery对象会增加一个属性值为参数字符串、document的selector、context属性
var jq = $('#container'); console.log(jq.selector); // #container console.log(jq.context); // document
那么当出现$($('#container'))该如何处理呢?同样的,返回的jQuery对象同情况
var jq2 = $($('#container')); console.log(jq2.selector); // #container console.log(jq2.context); // document
等等..................
jQuery 构造器
由此可见,从本质上来说,构建的jQuery对象,其实不仅仅只是dom,还有很多附加的元素,用数组的方式存储,当然各种组合有不一样,但是存储的方式是一样的
总的来说分2大类:
CSS选择器是通过jQuery.find(selector)函数完成的,通过它可以分析选择器字符串,并在DOM文档树中查找符合语法的元素集合
选择器这章有点乱,东西太多了,不能一一陈列 , 后期在慢慢整理