jQuery源码-dom操作之jQuery.fn.html
写在前面
前面陆陆续续写了jQuery源码的一些分析,尽可能地想要cover里面的源码细节,结果导致进度有些缓慢。jQuery的源码本来就比较晦涩,里面还有很多为了解决兼容问题很引入的神代码,如果不google的话压根不知道那一段段代码为什么会存在于人世。
于是就一直在重复坐着这么件事情,到处谷歌或者请教别人,这段兼容代码是为解决神马问题引入的。好不容易把所有的源码细节搞清楚,喝着咖啡对着电脑欣赏自己的劳动成果,内心却闪过一丝奇怪的感觉:我花了这么长的时间究竟做了什么?就为了搞清楚这段常理无法解释的代码?而在这之前已经有无数仁人志士在这上面浪费了自己多少宝贵的时间。
当然,学习jQuery源码对于我这种老菜鸟还是很有助益的,只不过需要换种方式,不再去抠一些无谓的细节了,有些比较难理解的东西就直接扔出来看下园里的朋友们帮忙解答下了。社会化写作似乎是更好的方式,之前也想过把系列文章扔github让别人来帮忙完善,不过显然对于大部分人来说这种方式成本还是太高,而自己写的东西暂时也没有说让人家去fork然后pull request的价值,就作罢了。
技术无关的内容就此打住,还是老老实实开始我的源码分析。里面标疑惑的地方,是还没来得及去抠的细节(一般就是一些正则神马的),围观的群众如果能够帮忙解答下那是真真的好~~
简单例子
jQuery.fn.html()同样属于使用频率比较高的接口,从它的接口文档http://api.jquery.com/html/,可知有如下几种用法,假设有如下HTML片段
<div id="casper"> <span>name:</span> <span>casper</span> </div>
读取:$(selector).html()
运行下面代码
console.log( $('#casper').html() );
输出:
<span>name:</span>
<span>casper</span>
设置一:$(selector).html(value)
还是上面的HTML,运行下面脚本
$('#casper').html('<p>大家好,我是第一段文本</p><p>大家好,我是第二段文本</p>');
原本的HTML变成
<div id="casper"><p>大家好,我是第一段文本</p><p>大家好,我是第二段文本</p></div>
设置二:$(selector).html(callback)
在上面例子中,HTML变成如下
<div id="casper"><p>大家好,我是第一段文本</p><p>大家好,我是第二段文本</p></div>
运行如下代码,参数 index、html 分别代码什么,见下面输出即可,不赘述
$('#casper p').html(function(index, html){ return index + '、原本的内容:'+ html; });
结果原来的HTML变成(为方便查看进行了适当格式化)
<div id="casper"> <p>0、原本的内容:大家好,我是第一段文本</p> <p>1、原本的内容:大家好,我是第二段文本</p> </div>
源码分析
下面湿jQuery.fn.html 的源码,就直接贴上来了,一点都不意外,又见到了全知全能的jQuery.access方法。。。这里我们先不立即展开,下文慢慢分析
html: function( value ) { return jQuery.access( this, function( value ) { var elem = this[0] || {}, i = 0, l = this.length; if ( value === undefined ) { return elem.nodeType === 1 ? elem.innerHTML.replace( rinlinejQuery, "" ) : undefined; } // See if we can take a shortcut and just use innerHTML if ( typeof value === "string" && !rnoInnerhtml.test( value ) && ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { value = value.replace( rxhtmlTag, "<$1></$2>" ); try { for (; i < l; i++ ) { // Remove element nodes and prevent memory leaks elem = this[i] || {}; if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } elem = 0; // If using innerHTML throws an exception, use the fallback method } catch(e) {} } if ( elem ) { this.empty().append( value ); } }, null, value, arguments.length ); }
把传入jQuery.access的会掉方法的方法体去掉,我们看到如下代码,就是调用了下jQuery.access而已(这里我们还不知道jQuery.access那噩梦般的参数究竟是干嘛的):
html: function( value ) { return jQuery.access( this, function( value ) { // 先隐藏掉方法体内的细节 }, null, value, arguments.length ); },
实在不知道如何下手分析jQuery.access,就直接跳过jQuery.access运行的内部细节,给出各种情况下的运行分支
源码分析之:fn被调用的各种情况
假设在jQuery.fn.html方法中,第二个传入的回调方法为fn
读取:$(selector).html()
html: function( value ) { return fn.call(this); },
设置一:$(selector).html(value)
html: function( value ) { fn.call( this, value ); return this; },
设置二:$(selector).html(callback)
这里可能比较费解一点,得结合设置一的代码来看(其实结合了业不容易看懂),可以看到,内部比较曲折,最终将$(selector).html(callback)转成了$(selector).html(value)来实现,这种转换手法在jQuery源码里很常见。
html: function( callback ) { var elems = this, i = 0, length = elems.length; var bulk = fn; // elem为dom元素,value为该dom元素最初的innerHTML fn = function( elem, value ) { return bulk.call( jQuery( elem ), value ); }; for ( ; i < length; i++ ) { // 第一步:fn( elems[i], key ) ) 返回elem[i]的innerHTML,相当于 $(elem[i]).html() // 第二步:callback.call( elems[i], i, fn( elems[i], key ) ),就是将 i、$(elem[i]).html()当参数传给 callback // 第三步:fn( elems[i], XXXX ) 到了这一步,其实就是 $(elem[i]).html(value)的等价形式了,因为第三步中已经返回了一段html文本 fn( elems[i], callback.call( elems[i], i, fn( elems[i], key ) ) ); } return this; }
源码分析之:fn各种情况下内部的分支逻辑
接下来到大头,上面说的fn的源码了,下面列出各种情况下,fn内部的处理逻辑
读取:$(selector).html()
fn里面的处理逻辑,很简单,将$(selector)选中的第一个dom元素的innerHTML属性返回。
疑问:rinlinejQuery这个正则是干嘛?为什么还要将elem.innerHTML先替换一下再返回?
var elem = this[0] || {}, // this为选中的jQuery对象 i = 0, l = this.length; if ( value === undefined ) { return elem.nodeType === 1 ? elem.innerHTML.replace( rinlinejQuery, "" ) : undefined; }
设置一:$(selector).html(value)
代码也不算复杂,就是遍历下$(selector)选中的元素,将它们的innerHTML分别设置成value。这里麻烦点在于,value存在,还得符合一堆条件才能走进这个if,原谅我偷懒了
疑问:
!rnoInnerhtml.test( value ):判断神马的
( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ):判断神马的
( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ):判断神马的
!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ]:判断神马的
value = value.replace( rxhtmlTag, "<$1></$2>" ); 作用是神马
// See if we can take a shortcut and just use innerHTML if ( typeof value === "string" && !rnoInnerhtml.test( value ) && ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { value = value.replace( rxhtmlTag, "<$1></$2>" ); try { for (; i < l; i++ ) { // Remove element nodes and prevent memory leaks elem = this[i] || {}; if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } elem = 0; // If using innerHTML throws an exception, use the fallback method } catch(e) {} }
设置二:$(selector).html(callback)
这里直接略过,因为fn内部的逻辑是跟$(selector).html(value)一样的
写在后面
本文有些偷懒,凑合着看吧。。里面提到的疑惑,如果能够帮忙解答下就更好了。。。
github博客:https://github.com/chyingp/blog
新浪微博:http://weibo.com/chyingp
站酷主页:http://www.zcool.com.cn/u/346408/