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

相关文章

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更

PostgreSQL中rank()窗口函数实用指南与示例

《PostgreSQL中rank()窗口函数实用指南与示例》在数据分析和数据库管理中,经常需要对数据进行排名操作,PostgreSQL提供了强大的窗口函数rank(),可以方便地对结果集中的行进行排名... 目录一、rank()函数简介二、基础示例:部门内员工薪资排名示例数据排名查询三、高级应用示例1. 每

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

MySQL 中的 CAST 函数详解及常见用法

《MySQL中的CAST函数详解及常见用法》CAST函数是MySQL中用于数据类型转换的重要函数,它允许你将一个值从一种数据类型转换为另一种数据类型,本文给大家介绍MySQL中的CAST... 目录mysql 中的 CAST 函数详解一、基本语法二、支持的数据类型三、常见用法示例1. 字符串转数字2. 数字

Python内置函数之classmethod函数使用详解

《Python内置函数之classmethod函数使用详解》:本文主要介绍Python内置函数之classmethod函数使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 类方法定义与基本语法2. 类方法 vs 实例方法 vs 静态方法3. 核心特性与用法(1编程客

Python函数作用域示例详解

《Python函数作用域示例详解》本文介绍了Python中的LEGB作用域规则,详细解析了变量查找的四个层级,通过具体代码示例,展示了各层级的变量访问规则和特性,对python函数作用域相关知识感兴趣... 目录一、LEGB 规则二、作用域实例2.1 局部作用域(Local)2.2 闭包作用域(Enclos

MySQL count()聚合函数详解

《MySQLcount()聚合函数详解》MySQL中的COUNT()函数,它是SQL中最常用的聚合函数之一,用于计算表中符合特定条件的行数,本文给大家介绍MySQLcount()聚合函数,感兴趣的朋... 目录核心功能语法形式重要特性与行为如何选择使用哪种形式?总结深入剖析一下 mysql 中的 COUNT

MySQL 中 ROW_NUMBER() 函数最佳实践

《MySQL中ROW_NUMBER()函数最佳实践》MySQL中ROW_NUMBER()函数,作为窗口函数为每行分配唯一连续序号,区别于RANK()和DENSE_RANK(),特别适合分页、去重... 目录mysql 中 ROW_NUMBER() 函数详解一、基础语法二、核心特点三、典型应用场景1. 数据分