【笔记】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正则解析图如下:
【笔记】jQuery源码(文档处理)
可以看出是用于去除脚本代码里的空白字符以及注释和CDATA标记的。
另外补充clean()函数的作用:

clean() 中会动态产生一个div, 将div 的innerHTML设为传入的字符串,再用getElementsByTagName('script') 的方式把所有的script 抓出来另行储存。clean()执行完毕回到domManip() 中, domManip() 再将script 们一一拿出来执行。

代码中没有模拟实现,但是看得出clean()函数的作用其实就是把混杂的代码中的脚本单独抽出来进行处理。

相关推荐