Do what you do

由莫名其妙的错误开始---浅谈jquery的dom节点创建

本来没准备今天发博客的,在一个前端群中,一位老大哥风自由提出了一个简单却很奇特的问题:

有一个字符串是这样的:var s = '<html lang="en" class="js no-touch discourse-no-touch">'+ '<head><meta name="csrf-token" content="Oul7WqVh4FBVse2yGeY8ZkqoN5/9/2ImxohJvUYEJYc="/></head><body></body></html>';

怎么通过jquery得到其中content的值?

本来觉得这个问题蛮简单的,既可以通过正则获取,也可以通过jquery的$将这个字符串变成一个jq对象来获取。但是也正是后来这个方法,却带来了问题。

以我的理解,定位content的方法应该为$(s).find("meta").attr("content"),但是这么去获取结果确并不正确。

于是做了简单的尝试:

var s = '<html lang="en" class="js no-touch discourse-no-touch">'+ '<head><meta name="csrf-token" content="Oul7WqVh4FBVse2yGeY8ZkqoN5/9/2ImxohJvUYEJYc="/></head><body></body></html>';
$(s);//[<meta name=​"csrf-token" content=​"Oul7WqVh4FBVse2yGeY8ZkqoN5/​9/​2ImxohJvUYEJYc=">​]

得到的并不是所详细的结果,而是meta标签。

这就有点奇怪了,风大猜测是因为外面有<html></html>,可能jquery不能把<html>标签变为dom。而在字符串外层套一层div则确实能够正确的获取结果。
但是,这个结果真的正确么?
又做了一个简单的尝试:
var s = '<div><html lang="en" class="js no-touch discourse-no-touch">'+
  '<head><meta name="csrf-token" content="Oul7WqVh4FBVse2yGeY8ZkqoN5/9/2ImxohJvUYEJYc="/></head><body></body></html></div>';
$(s);//[<div>​<meta name=​"csrf-token" content=​"Oul7WqVh4FBVse2yGeY8ZkqoN5/​9/​2ImxohJvUYEJYc=">​</div>​]

结果并不是我们所想象的那样,而是仍旧去除了html标签,只不过因为套用了一层div,使$(s).find("meta").attr("content")去正确找寻了节点,并不是保护了html标签。

这个问题的原因,相信只有jq的源码能够给我答案了。写了几行简单的测试代码,去通过断点解读一下jq的处理机制:

(function() {
        debugger;
        $('<html class="123"></html>');
        $('<div></div>');
        $('<html></html>');
    })();

事实证明,除了第一种方式不能正确的获得结果以外,后两种都能正确的获取结果。通过研读jq的源码中对jq对象创建的代码,终于弄清楚了这个原因。

jq对传入的字符串会进行一次正则:

var parsed = rsingleTag.exec( data );//rsingleTag=/^<(\w+)\s*\/?>(?:<\/\1>|)$/

解释一下这个正则吧:

^<(\w+)\s*\/?
以<开头,至少跟着一个字符和任意个空白字符,之后出现0或1次/
>
这个就不说了,前面这些加起来就是<div >这样或者<meta />这样的
(?:<\/\1>|)$
可以匹配<、一个/或者空白并以之为结尾
这些就是</div>或者跟着<br />这种自闭合标签后面的空

这样如果没有任何属性和子节点的字符串(比如'<html></html>'或者'<div></div>'这样)会通过正则的匹配,当通过正则的匹配后则会通过传入的上下文直接创建一个节点:

if ( parsed ) {
            return [ context.createElement( parsed[1] ) ];
        }//context为上下文

而未通过节点的字符串,则通过创建一个div节点,将字符串置入div的innerHTML:

tmp = tmp || safe.appendChild( context.createElement("div") );//创建divdom节点
tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2];//将字符串置入div对象的innerHTML

而浏览器是不允许div内直接包含<html>的,因此会将html标签过滤。

当过滤了html之后,刚才的字符串变成了:

<head><meta name="csrf-token" content="Oul7WqVh4FBVse2yGeY8ZkqoN5/9/2ImxohJvUYEJYc="/></head><body></body>

jq会从两端向中间去寻找节点的头尾,在这里闭合标签则被寻找为meta标签。

这就是这次所得到的所有结论,总结一下:

如果你的标签是没有子节点和属性的标签,jq会通过正则判定后在你传入的上下文环境直接创建这个节点。
如果你的标签带有子节点或者属性,jq的正则不过,然后会创建一个div,把你的字符串作为div的innerHTML传入,再对div内部的dom节点遍历属性和节点,去获取class啊id啊之类的,然后再创建这个真正的节点。
但是浏览器不允许任何非frame类元素内部有html标签,会将之过滤。
在过滤了html标签后,head标签和body标签就成了两个标签,而jq是从外向内寻找包裹的标签对的,所以这两个标签没有被识别,而meta标签因为是自闭合标签,而且也是从外往内被包裹的最外层闭合标签,因此就成了获取这个标签。
大概就是这样了。

感谢阅读~同时感谢风大~

posted @ 2013-03-21 20:43  key yao  阅读(1869)  评论(0编辑  收藏  举报
学会做事 学会做人