jQuery分析(3) - jQuery.fn.init

jQuery分析(3) - jQuery.fn.init

 

1.前言

上一篇jQuery分析(2)中了解了jQuery库的骨架实现原理,这就比如摇滚音乐,摇滚音乐不是某种音乐他就像一个音乐盒子,里面包含了各类不一样的摇滚风格(山地、朋克、乡村、流行、硬摇、金属、迷幻等)。那么上一篇只是大体了解了jQuery的基本形状,从这篇文章开始会深刻jQuery库的各类函数,深刻详细的去了解他,那将值得慢慢探索,发现新的神奇好玩的东西。javascript

2.辅助函数

在jQuery.fn.init方法里面使用到了一些jQuery的静态函数,在这里提早统一的介绍html

  • jQuery.merge 合并两个数组,将第二个参数数组合并到第一个参数数组中。
  • jQuery.parseHTML 解析html字符串,第一个参数html字符串,第二个参数是产生fragment的context,第三个参数是否忽略scripts默认忽略
  • isPlainObject 判断一个参数是否为javascript对象即{}
  • jQuery.isFunction 判断一个参数是否为函数
  • jQuery.makeArray 合并数组(内部使用)

3.jQuery.fn.init 函数归纳👻

下面图是jQeury的构造函数参数即$()调用的参数种类集合图
jQuery.fn.init构造参数图java

下面的代码是可能处理各类参数的方式,多余的代码我已删除掉,下面代码清晰看到selector无非就是三种类型:一、字符串 二、DOMElement 三、函数。下面将会就这三种类型进行详细深刻的分析他们的实现原理,这将须要一步一步来理解,首先脑子里要清晰每一步作了什么为何这样作,这样一步一步下来才能更好的去理解jQuery的写法。node

// 匹配html标签写法和id选择器 // 第一个分组是 <div> 中的div,第二个分组是#id中的id rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, jQuery.fn.init = function(selector, context, root) { var match, elem; // 没有selector将会返回当前this,以便建立空的jQuery对象在后面会用到。 if (!selector) { return this; } // 得到初始化文档的jQuery对象 root = root || rootjQuery; // 处理参数为字符串参数 if (typeof selector === "string") { // 处理参数为DOM节点 } else if (selector.nodeType) { // 处理参数为function } else if (jQuery.isFunction(selector)) { } // 处理参数为NodeLists return jQuery.makeArray(selector, this); }; // init函数继承jQuery init.prototype = jQuery.fn; // 初始化document为jQuery对象 rootjQuery = jQuery( document );

4.参数为字符串类型分解🐢

由于这个jQuery.fn.init函数代码不少因此单独的参数类型会把他的代码单独提出来分解,下面看提出来的参数为字符串的代码。jquery

对于字符串参数的几种调用方法参见上面脑图ios

// 匹配html标签写法和id选择器 // 第一个分组是 <div> 中的div,第二个分组是#id中的id rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, // 参数为字符串处理方式 if (typeof selector === "string") { if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { // 字符串是单标签的DOM格式直接建立正则匹配的格式以跳过正则匹配以节约性能 match = [null, selector, null]; } else { // 匹配到DOM字符串 或者ID选择器 match = rquickExpr.exec(selector); } // 若是selector是一个html字符串或者是一个ID选择器 if (match && (match[1] || !context)) { // html字符串解析 if (match[1]) { context = context instanceof jQuery ? context[0] : context; // 解析html(单标签或多标签) jQuery.merge(this, jQuery.parseHTML( match[1], context && context.nodeType ? context.ownerDocument || context : document, true )); // 构建html元素时传递了第二个参数为一个对象,那么会对对象的key和value进行解析 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; // 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) { // Handle the case where IE and Opera return items // by name instead of ID if (elem.id !== match[2]) { return rootjQuery.find(selector); } // Otherwise, we inject the element directly into the jQuery object this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; } // 其余选择器(class、element、attr)等 } else if (!context || context.jquery) { return (context || root).find(selector); // 其余选择器(class、element、attr)等且有context(一个DOMElement) } else { return this.constructor(context).find(selector); } }
  • 4-1.字符串处理
    • 4-1-1.selector为单标签的html会跳过正则表达式匹配,直接构造出一个正则表达结果结果放置match变量中。代码
    • 4-1-2.匹配selector是否为为多标签或者ID选择器结果放置match变量中代码
    • 4-1-3.selector是一个html字符串会对他们进行解析(单标签/多标签)代码,若是还传递了额外的props属性也会在解析和建立完DOM节点后附加到这个DOM上代码
    • 4-1-4.selector为ID选择器则直接调用document.getElementById进行ID选择元素,把选择到的元素放入this[0]中,随后修正length、context、selector便可代码
    • 4-1-5.selector为其余选择器(class、element、attr等)而且没有给定第二个参数(context)或者第二个参数(context)是一个jquery对象那么会调用(context || root).find(selector)进行查找元素代码
    • 4-1-6.selector为其余选择器(class、element、attr等)而且第二个参数(context)为一个DOMElement会先构建context为一个jQuery对象再利用这个对象进行.find(selector)查找代码

5.字符串解析流程中涉及的函数分析

4-1把全部的字符串解析流程罗列了出来,在这个流程中所涉及了一些关键的函数在这里给拆分解析一下。git

  • 5-1.jQuery.parseHTML解析html字符串为DOM节点
// data: string of html // context (optional): If specified, the fragment will be created in this context, // defaults to document // keepScripts (optional): If true, will include scripts passed in the html string jQuery.parseHTML = function( data, context, keepScripts ) { if ( !data || typeof data !== "string" ) { return null; } // 修正参数,只有2个参数状况下忽略context if ( typeof context === "boolean" ) { keepScripts = context; context = false; } // 修正context默认为document context = context || document; var parsed = rsingleTag.exec( data ), scripts = !keepScripts && []; // Single tag if ( parsed ) { return [ context.createElement( parsed[ 1 ] ) ]; } parsed = buildFragment( [ data ], context, scripts ); // 移除已经执行过的脚本 if ( scripts && scripts.length ) { jQuery( scripts ).remove(); } // 合并并返回标dom集合的数组 return jQuery.merge( [], parsed.childNodes ); };

这个函数一共有三个参数,参数一是一个html字符串,参数二是建立fragment的context,参数三是表示是否保留scripts脚本默认为falsegithub

// 单标签 var parsed = rsingleTag.exec( data ), scripts = !keepScripts && []; // Single tag if ( parsed ) { return [ context.createElement( parsed[ 1 ] ) ]; }

若是data参数为一个单标签html字符串("正则表达式

","
")将直接调用context.createElement建立DOM元素并返回

 

// 多标签 parsed = buildFragment( [ data ], context, scripts );

若是data参数为一个多标签html字符串("数组

abc
")会调用buildFragment函数进行解析建立。

 

最后返回一个数组元素,里面是全部解析好的DOM元素

其实在整个selector为字符串参数的代码处理中,buildFragment应该仍是算代码比较多的了,其余那些class、element、attr等都是Sizzle选择器引擎搞定了,因此buildFragment仍是一个比较有看头的函数,其中文档碎片技术和必需要外包裹标签的建立方法其实咱们平时编码时也会常常使用,能够借鉴一二。

/* buildFragment 重要的参数是前面3个 elems 一个待转换的html字符串 context 转换上下文 scripts 是否忽略script标签 */ function buildFragment(elems, context, scripts, selection, ignored) { var j, elem, contains, tmp, tag, tbody, wrap, l = elems.length, // Ensure a safe fragment // 建立文档碎片 safe = createSafeFragment(context), nodes = [], i = 0; for (; i < l; i++) { elem = elems[i]; if (elem || elem === 0) { // Add nodes directly // 若是在elems中的某个数组元素是对象直接添加到nodes if (jQuery.type(elem) === "object") { jQuery.merge(nodes, elem.nodeType ? [elem] : elem); // Convert non-html into a text node // 转换非html的字符串为文本节点 } else if (!rhtml.test(elem)) { nodes.push(context.createTextNode(elem)); // Convert html into DOM nodes } else { //给文档碎片建立一个元素,用来装接下来咱们须要的html字符串 tmp = tmp || safe.appendChild(context.createElement("div")); // Deserialize a standard representation // 取得标签名称,并转为小写 tag = (rtagName.exec(elem) || ["", ""])[1].toLowerCase(); // 须要其余元素包裹的标签 wrap = wrapMap[tag] || wrapMap._default; //把咱们的html字符串放入刚刚建立文档碎片的div中造成dom元素 // 若是须要包裹元素则把html字符串进行包裹 <td>123</td> => <table><tbody><tr><td>abc</td></tr></tbody></table> tmp.innerHTML = wrap[1] + jQuery.htmlPrefilter(elem) + wrap[2]; // Descend through wrappers to the right content // 取得刚刚建立的元素 j = wrap[0]; while (j--) { tmp = tmp.lastChild; } // Manually add leading whitespace removed by IE if (!support.leadingWhitespace && rleadingWhitespace.test(elem)) { nodes.push(context.createTextNode(rleadingWhitespace.exec(elem)[0])); } // Remove IE's autoinserted <tbody> from table fragments if (!support.tbody) { // String was a <table>, *may* have spurious <tbody> elem = tag === "table" && !rtbody.test(elem) ? tmp.firstChild : // String was a bare <thead> or <tfoot> wrap[1] === "<table>" && !rtbody.test(elem) ? tmp : 0; j = elem && elem.childNodes.length; while (j--) { if (jQuery.nodeName((tbody = elem.childNodes[j]), "tbody") && !tbody.childNodes.length) { elem.removeChild(tbody); } } } // 把建立好的dom节点也就是tmp的子节点合并到nodes中 jQuery.merge(nodes, tmp.childNodes); // Fix #12392 for WebKit and IE > 9 tmp.textContent = ""; // Fix #12392 for oldIE while (tmp.firstChild) { tmp.removeChild(tmp.firstChild); } // Remember the top-level container for proper cleanup tmp = safe.lastChild; } } } // Fix #11356: Clear elements from fragment // 清除文档碎片中的元素 if (tmp) { safe.removeChild(tmp); } // Reset defaultChecked for any radios and checkboxes // about to be appended to the DOM in IE 6/7 (#8060) if (!support.appendChecked) { jQuery.grep(getAll(nodes, "input"), fixDefaultChecked); } 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; } // 元素是否已经包含在document中 contains = jQuery.contains(elem.ownerDocument, elem); // Append to fragment // 将建立好的元素再次添加到文档碎片中,并取得scirpt标签 tmp = getAll(safe.appendChild(elem), "script"); // Preserve script evaluation history if (contains) { setGlobalEval(tmp); } // Capture executables // 收集要执行的脚本 if (scripts) { j = 0; while ((elem = tmp[j++])) { if (rscriptType.test(elem.type || "")) { scripts.push(elem); } } } } tmp = null; // 返回建立好的文档碎片 return safe; }

关于buildFragment函数里面还有一些兼容性的解决方案还没分析到,后面再作吧。至此关于jQuery.fn.init函数的构造流程也就分析完毕。

若有疏忽、遗漏、错误请狠狠批评谢谢。

 

转载:http://www.cnblogs.com/monsterooo/p/5524985.html

相关文章
相关标签/搜索