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

相关文章

在React中引入Tailwind CSS的完整指南

《在React中引入TailwindCSS的完整指南》在现代前端开发中,使用UI库可以显著提高开发效率,TailwindCSS是一个功能类优先的CSS框架,本文将详细介绍如何在Reac... 目录前言一、Tailwind css 简介二、创建 React 项目使用 Create React App 创建项目

vue使用docxtemplater导出word

《vue使用docxtemplater导出word》docxtemplater是一种邮件合并工具,以编程方式使用并处理条件、循环,并且可以扩展以插入任何内容,下面我们来看看如何使用docxtempl... 目录docxtemplatervue使用docxtemplater导出word安装常用语法 封装导出方

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Mysql删除几亿条数据表中的部分数据的方法实现

《Mysql删除几亿条数据表中的部分数据的方法实现》在MySQL中删除一个大表中的数据时,需要特别注意操作的性能和对系统的影响,本文主要介绍了Mysql删除几亿条数据表中的部分数据的方法实现,具有一定... 目录1、需求2、方案1. 使用 DELETE 语句分批删除2. 使用 INPLACE ALTER T

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Vue中组件之间传值的六种方式(完整版)

《Vue中组件之间传值的六种方式(完整版)》组件是vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用,针对不同的使用场景,如何选择行之有效的通信方式... 目录前言方法一、props/$emit1.父组件向子组件传值2.子组件向父组件传值(通过事件形式)方

css中的 vertical-align与line-height作用详解

《css中的vertical-align与line-height作用详解》:本文主要介绍了CSS中的`vertical-align`和`line-height`属性,包括它们的作用、适用元素、属性值、常见使用场景、常见问题及解决方案,详细内容请阅读本文,希望能对你有所帮助... 目录vertical-ali

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程