【笔记】jQuery源码(文档处理)
前言
内容主要是跟着慕课网上的jQuery源码解析系列课程以及自己的实践+理解来写的,可能会有错误,欢迎指出^_^。
以往笔记:
【笔记】jQuery源码(第一天):https://segmentfault.com/a/11...
第二天:文档处理
这里相关的方法主要有:
jQuery.fn.extend({ text: function() {}, append: function() {}, prepend: function() {}, before: function() {}, after: function() {}, clone: function() {}, html: function() {}, replaceWith: function() {}, domManip: function() {}, })
domManip方法
我们在使用appendChild()等方法添加内容的时候,必须是元素类型。但是在jQuery里面我们可以传入字符串来添加内容,还有添加script等操作。domManip方法就是一个处理中间过渡的函数。
所以针对所有接口的操作,jQuery会抽象出一种参数的处理方案,domManip方法主要做的事情如下:
1:解析参数,字符串,函数,对象。
2:针对大数据引入文档碎片处理。
3:如果参数中包含script的处理。
以及一些细节问题:
IE下面innerHTML会忽略没作用域元素,no-scope element(script,link,style,meta,noscript)等,所以这类通过innerHTML创建需要加前缀。
innerHTML在IE下面会对字符串进行trimLeft操作,去掉空白。
innerHTML不会执行脚本的代码,如果支持defer除外。
很多标签不能作为div的子元素、td、tr, tbody等等 jQuery是合集对象,文档碎片的与事件的复制问题。
模拟简单的append()
function buildFragment(elems, context) { //创建Document的文档碎片 var fragment = context.createDocumentFragment(), nodes = [], i = 0, elem, l = elems.length; for (; i < l; i++) { elem = elems[i]; //创建一个元素div做为容器 tmp = fragment.appendChild(context.createElement("div")); //放到文档碎片中 tmp.innerHTML = elem; } //在这个例子里,运行到此处fragment相当于<div><div>慕课网</div></div> return fragment; } /* * parentEles:被塞内容的容器->数组 * target:要添加的内容 * callback:创建成功后调用的函数 */ function domManip(parentEles, target, callback) { var l = parentEles.length; if (l) { //创建一个碎片,以document形式获取父元素中的第一个 //ownerDocument获取元素所属的Document var fragment = buildFragment([target], parentEles[0].ownerDocument); //获取创建成功的DOM、也就是我们需要添加的对象 first = fragment.childNodes[0]; //把结果返回给回调函数处理 if (first) { callback.call(parentEles, first); } } } function append(parentEles, target) { /** * parentEles:被塞内容的容器 * elem: 要添加的内容 */ return domManip([parentEles], target, function(elem) { //回调函数获取经过domManip处理后的DOM节点,可以直接添加 parentEles.appendChild(elem) }); } append(document.getElementById('test'),'<div>通过append加入慕课网</div>')
Tip:这里的代码我稍微修改了一下,因为似乎有一些并没有用,可能是老师在放的时候是侧重讲这个点的,其他的没删干净。而且这个结果其实是向test添加了一个内容是<div><div>通过append加入慕课网</div></div>,按照常理应该没有外面包裹的div,但是childNodes[0]也好firstchild也好,都只能获取到这个结果,如果div里面有多个div要添加,直接再获取它下面的节点好像也不太好,所以我觉得这里应该是一个粗略的模拟。
处理script
div.innerHTML = "<script>alert('慕课网')";
不会被执行,但是$('div').append("<script>alert('慕课网')")
可以。它类似的函数调用过程是:.append()-> .domManip() -> buildFragment() ->clean()。
我们假设要进行append(document.querySelectorAll('div')[0],"<script>alert('慕课网')" )
操作,改进原来的domManip方法:
//关闭脚本执行 function disableScript(elem) { elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; //"<script type="false/">alert('慕课网') return elem; } //还原脚本 function restoreScript(elem) { elem.removeAttribute("type"); return elem; } function domManip(parentEles, target, callback) { var l = parentEles.length; var scripts; var hasScripts; if (l) { var fragment = buildFragment([target], parentEles[0].ownerDocument); //first内容是<script>alert('慕课网') var first = fragment.firstChild.firstChild if (first) { //标记为有脚本 hasScripts = true //增加false标记,这样script不会马上执行,并把script加入到文档中 scripts = disableScript(first); callback.call(parentEles, scripts); } //执行脚本加载 if(hasScripts){ //去掉type的false锁定 restoreScript(scripts); var code = scripts.textContent.replace(/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g, ""); //开始执行脚本 eval(code) } } }
这里最后的/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g
正则解析图如下:
可以看出是用于去除脚本代码里的空白字符以及注释和CDATA标记的。
另外补充clean()函数的作用:
clean() 中会动态产生一个div, 将div 的innerHTML设为传入的字符串,再用getElementsByTagName('script') 的方式把所有的script 抓出来另行储存。clean()执行完毕回到domManip() 中, domManip() 再将script 们一一拿出来执行。
代码中没有模拟实现,但是看得出clean()函数的作用其实就是把混杂的代码中的脚本单独抽出来进行处理。