前言
内容主要是跟着慕课网上的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()函数的作用其实就是把混杂的代码中的脚本单独抽出来进行处理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。