jQuery源码阅读(七)--init()遗留部分buildFragment()函数

本文主要是介绍jQuery源码阅读(七)--init()遗留部分buildFragment()函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在 jQuery源码阅读(五)—init函数中,已经分析了init函数逻辑的大头,即参数selector为字符串的形式,但这里边仍然有两个为深入看的方法,一个是当selector是复杂标签的形式时,调用的bildFragment()方法,另一个是当selector为各种选择器时,调Sizzle模块的find()方法。

这一篇先来看buildFragment()函数的源码,分析该函数在处理参数为复杂标签时是如何做的?至于Sizzle模块的find()方法,打算后面阅读Sizzle选择器部分的源码时再来分析整理。

buildFragment()用法

要分析一个函数怎么实现的,首先得知道这个函数是干嘛的。所以,我们先来看看buildFragment函数是用来干什么的?

//调jQuery的静态方法
jQuery.buildFragment( ['<ul><li>苹果</li><li>柠檬</li></ul>',], [document] )

先来看看函数返回什么?
这里写图片描述
返回了一个对象,有两个属性,一个是为布尔值的cacheable,另一个是为文碎片的fragment。

{cacheable: true,fragment: ducoment-fragment
}

再打开fragment看看
这里写图片描述
可以看到,fragment里面childNodes有一个元素ul, 然后在ul 里面childNodes又有两个li元素,看到这,我们大概就知道buildFragment这个函数的作用了,他就是将我们传入的包含有标签的字符串,转换成对应的文档碎片,并记录下来。

那么到底在源码里面是如何实现的?下来我们一步步分析。

buildFragment()源码

就用我们上面说的jQuery.buildFragment( ['<ul><li>苹果</li><li>柠檬</li></ul>',], [document] )这个例子对照着源码走一遍。

jQuery.buildFragment = function( args, nodes, scripts ) {//这几个变量,先不管,后面用到再分析var fragment, cacheable, cacheresults, doc,//第一个参数(数组)的第一个元素(这里我们例子中是'<ul><li>苹果</li><li>柠檬</li></ul>)first = args[ 0 ];// nodes may contain either an explicit document object, a jQuery collection or context object.// If nodes[0] contains a valid object to assign to doc//这里类似于我们之前的context,一般都是documentif ( nodes && nodes[0] ) {doc = nodes[0].ownerDocument || nodes[0];}// Ensure that an attr object doesn't incorrectly stand in as a document object// Chrome and Firefox seem to allow this to occur and will throw exception// Fixes #8950if ( !doc.createDocumentFragment ) {doc = document;}// 判断当前标签是否可缓存,可缓存就将之前的变量cacheable设为true,这是一个比较重要的标志,后面详细分析如何判断标签是否是可缓存的if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&first.charAt(0) === "<" && !rnocache.test( first ) &&(jQuery.support.checkClone || !rchecked.test( first )) &&(jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {cacheable = true;//读取缓存中的该标签cacheresults = jQuery.fragments[ first ];if ( cacheresults && cacheresults !== 1 ) {fragment = cacheresults;}}if ( !fragment ) {  //fragment为空,有两种情况,一种是标签不可缓存,没有进入上一步的判读中;另一种是标签可缓存,但是还没有缓存,即第一次碰到这个标签//这种情况下,先创建一个文档片段,为了存放后面创建的DOM元素,接着去调jQuery.clean函数,这个后面再单独分析fragment = doc.createDocumentFragment();jQuery.clean( args, doc, fragment, scripts );}if ( cacheable ) {//如果可以缓存,存起来jQuery.fragments[ first ] = cacheresults ? fragment : 1;}//最后返回含有两个元素的对象,分别是代码片段和可否缓存标志return { fragment: fragment, cacheable: cacheable };
};

看完上面代码,可以看到主要的操作都是在jQuery.clean函数中进行的,下来我们看看jQuery.clean函数是如何做的?
还是先来看clean函数是干什么的?

jQuery.clean(['<ul><li>苹果</li><li>柠檬</li></ul>', '<div></div>'], [document])     //返回一个数组

这里写图片描述

可以看到,jQuery.clean函数将输入的含有标签的字符串转换成了DOM元素,并存在数组里面。

下来看看它的源码框架:

clean: function( elems, context, fragment, scripts ){var checkScriptType, script, j,ret = [];context = context || document;if ( typeof context.createElement === "undefined" ) {//这里处理了context为DOM元素,jQuery对象和其他情况context = context.ownerDocument || context[0] && context[0].ownerDocument || document;}//对每一个字符串进行处理for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {//元素为数字if ( typeof elem === "number" ) {elem += "";}//元素为undefined或者null或者''if ( !elem ) {continue;}//元素为字符串if ( typeof elem === "string" ) {}//在IE6/7中修正复选框单选按钮的选中状态var len;if ( !jQuery.support.appendChecked ) {if ( elem[0] && typeof (len = elem.length) === "number" ) {for ( j = 0; j < len; j++ ) {findInputs( elem[j] );}} else {findInputs( elem );}}if ( elem.nodeType ) {ret.push( elem );} else {ret = jQuery.merge( ret, elem );}}//元素为HTML代码片段if ( fragment ) {//去除掉script标签部分,其他标签部分插入文档流}return ret;
}

在clean函数源码中,主要是按照参数elems每个元素的类型来分类的,主要分了四大类:

对于数值类型: 转成字符串;
对于值为空的情况: 不做处理;
对于字符串,比较复杂,又要细分;
对于代码片段,将script标签部分分离出来,其他的标签插入到文档中;

下面主要分析参数为字符串时的源码:

if ( typeof elem === "string" ) {//正则表达式rhtml = /<|&#?\w+;/匹配标签,字符代码或字符代码if ( !rhtml.test( elem ) ) {//不是标签,创建文本节点elem = context.createTextNode( elem );} else {//匹配封闭标签//正则表达式rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig匹配除了自不封闭标签之外的标签//并调用字符串的replace方法,用匹配到的第一个分组和第二个分组来替换//比如:'<div/>'会被换成'<div></div>'elem = elem.replace(rxhtmlTag, "<$1></$2>");// 去除空格等,得到标签名var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),//获取最外层包裹元素,对option,legend,thead,tr,td,col,area特殊处理了//wrap 的格式是一个数组,[ 层级数 , 标签名(包含自带的上级标签), 封闭标签 ]wrap = wrapMap[ tag ] || wrapMap._default,depth = wrap[0], div = context.createElement("div"),safeChildNodes = safeFragment.childNodes,remove;// 将创建的div元素追加到html文档片段中if ( context === document ) {safeFragment.appendChild( div );} else {createSafeFragment( context ).appendChild( div );}// 将参数中的标签设为div的HTML文本,这样就将标签转换成了相应的DOM元素div.innerHTML = wrap[1] + elem + wrap[2];// 当elem为tr,td,option,legend等有默认父级标签时while ( depth-- ) {div = div.lastChild;}// 考虑IE浏览器中的tbody不兼容问题,这个暂时没有深究if ( !jQuery.support.tbody ) {var hasBody = rtbody.test(elem),tbody = tag === "table" && !hasBody ?div.firstChild && div.firstChild.childNodes :wrap[1] === "<table>" && !hasBody ?div.childNodes :[];for ( j = tbody.length - 1; j >= 0 ; --j ) {if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {tbody[ j ].parentNode.removeChild( tbody[ j ] );}}}if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );}elem = div.childNodes;//先获取到divd的子节点//再将初始创建的div元素删除掉,只保留后面追加的DOM元素if ( div ) {div.parentNode.removeChild( div );// Guard against -1 index exceptions in FF3.6if ( safeChildNodes.length > 0 ) {remove = safeChildNodes[ safeChildNodes.length - 1 ];if ( remove && remove.parentNode ) {remove.parentNode.removeChild( remove );}}}
}
}

今天主要就整理这么多,其中有些细节并没有完全掌握,不过对于buildFragment函数整体的框架已经有了了解。对于其中不具体或者不正确的部分,希望大家可以指出,欢迎批评指正!

这篇关于jQuery源码阅读(七)--init()遗留部分buildFragment()函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/959395

相关文章

Vue和React受控组件的区别小结

《Vue和React受控组件的区别小结》本文主要介绍了Vue和React受控组件的区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录背景React 的实现vue3 的实现写法一:直接修改事件参数写法二:通过ref引用 DOMVu

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

Python Counter 函数使用案例

《PythonCounter函数使用案例》Counter是collections模块中的一个类,专门用于对可迭代对象中的元素进行计数,接下来通过本文给大家介绍PythonCounter函数使用案例... 目录一、Counter函数概述二、基本使用案例(一)列表元素计数(二)字符串字符计数(三)元组计数三、C

Python中的filter() 函数的工作原理及应用技巧

《Python中的filter()函数的工作原理及应用技巧》Python的filter()函数用于筛选序列元素,返回迭代器,适合函数式编程,相比列表推导式,内存更优,尤其适用于大数据集,结合lamb... 目录前言一、基本概念基本语法二、使用方式1. 使用 lambda 函数2. 使用普通函数3. 使用 N

MySQL中REPLACE函数与语句举例详解

《MySQL中REPLACE函数与语句举例详解》在MySQL中REPLACE函数是一个用于处理字符串的强大工具,它的主要功能是替换字符串中的某些子字符串,:本文主要介绍MySQL中REPLACE函... 目录一、REPLACE()函数语法:参数说明:功能说明:示例:二、REPLACE INTO语句语法:参数

python中update()函数的用法和一些例子

《python中update()函数的用法和一些例子》update()方法是字典对象的方法,用于将一个字典中的键值对更新到另一个字典中,:本文主要介绍python中update()函数的用法和一些... 目录前言用法注意事项示例示例 1: 使用另一个字典来更新示例 2: 使用可迭代对象来更新示例 3: 使用