Ruby's Louvre

每天学习一点点算法

导航

javascript 跨文档调用技术

这是一种很有前途的技术,可惜生不逢时,IE刚打垮网景,火狐又冒出来了。这种技术是我在设计超级数组对象时发现的。由于直接继承原生数组问题多,我只有用Object与Array的原型方法构建一个新类。不用说,效率不太理想。直到我已把超级数组臻至完美的今天,我还在不断寻找新的替代方案,于是我就想到用另一个文档的数组对象来构建。经测试,IE下是完美的。后来我又发现早在2006年全知全能的DE大神早已做了这样的探索,在《How To Subclass The JavaScript Array Object》中应用这种技术,我只不过是重新把它发挖出来而已。但是,以后就没有下文了,DE大神在他的Base2类库构建Array2对象时也没有应用这种技术。不明真相的我一头扎下去,哎呀!DE大神你真坏,有陷阱也不通报声!

下面就拿DE大神的例子改一下,演示究竟出了什么状况?!

 window.onload = function(){
        //创建一个iframe
        var iframe = document.createElement("iframe");
        iframe.style.display = "none";
        document.body.appendChild(iframe);
        // 取得iframe文档的数组对象
        frames[frames.length - 1].document.write(
        "<script>parent.Array2 = Array;<\/script>");
        var a = Array2(1,2,3,4),
        b = a.slice(2)
        alert(b instanceof Array)//万恶的safari与firefox总是试图把沙箱文档的数组实例转化为本地文档的数组实例
        a.push(5);
        alert(a instanceof Array)//同上,safari与firefox受影响的方法是所有返回数组的方法……晕
      }

为了方便,我把iframe中的文档对象的javascript环境中的数组对象称之为沙箱数组,意即,它不受本地数组对象影响。对,本地数组的原型进行扩展,不会殃及沙箱数组。俗话说,龙生龙,凤生风,老鼠的儿子会打洞!事实也应该如此!看,firefox与safari做了什么好事!沙箱数组变成原生数组了,我们在沙箱数组上做的扩展成了废物了!而且现在是linux大多数系统捆绑firefox,Mac捆绑safari,真不好办……

别以为跨文档调用技术只能干这事,只是它的潜力还有待发掘而已。现在再解释一下上面那段用到的长长的定语。什么叫做“iframe中的文档对象的javascript环境……”?由于框架技术的发展,一个页面并不只有一个文档对象(在HTML5中,有关框架的标签只死剩iframe了)。最顶层的我们称之为本地文档,iframe与frame的文档我称之为沙箱文档。由于它们的相对独立性,人们最喜欢用iframe做富文本编辑器,省得调用document.execCommand( "BackColor", "", "red" )命令,整个页面都红了。不过,本地文档也好,iframe文档也好,它们都属于HTML文档。另,创建HTML文档也不单止iframe标签(frame标签我向来无视),还有ActiveXObject与createDocument,DOMParser等方法。

ActiveXObject是指ActiveXObject("htmlfile"),谷歌的gtalk就是用它结合其他技术实现push的技术。ActiveXObject("htmlfile")创建的文档是一个完美的HTML文档,它拥有document.title,document.body等HTML DOM专有的属性,还能运行javascript。全局变量this就是我们要找的全局对象,它还拥有我们想要的一切,Array,Boolean,String,Date,Object等等。我们需要调用它们为我们做事,问题是标准浏览器能有像ctiveXObject("htmlfile")这样便捷的方法创建另一个文档吗?这些文档能拥有独立的javascript运行环境吗?这正是我们下面要讲的。

w3c还真是实现了一些创建文档的方法,不过都比较偏~~~

利用createHTMLDocument(title)创建文档

title参数为tilte元素的innerText。

      window.onload = function(){
        try{    
          var doc = document.implementation.createHTMLDocument('跨文本调用技术 by 司徒正美');
          var html = doc.documentElement
          alert(html)        //测试是否存在HTML元素
          alert(html.tagName)//注意大小写,HTML文档会把元素节点的tagName与nodeName大写化,按理应该会返回“HTML”
          var body = doc.body
          alert(body);      //测试document.body
          if("title" in doc){
            alert(doc.title)//测试document.title
          }
          var head = doc.getElementsByTagName("head")[0]
          alert(head);
          if(head && "innerHTML" in head){
            alert("head.innerHTML = "+head.innerHTML)//测试innerHTML,HTML文档的元素节点都会支持这属性,及head与title是否存在套嵌关系
            alert("html.innerHTML = "+html.innerHTML)
          }
          if("outerHTML" in html){  //outerHTML也是HTML5的标准API
            alert("html.outerHTML = "+html.outerHTML)
          }
          var script = doc.createElement("script");
          html.insertBefore(script,null);
          script.appendChild(doc.createTextNode("alert('能调用javascript')"))//测试是否能动态解析脚本,注意这里是否弹出
          var xpath = doc.evaluate && doc.evaluate('//title', doc, null, 7, null);
          alert("测试xpath")
          alert(xpath && xpath.snapshotItem(0) )
        }catch(e){
          alert("不支持createHTMLDocument方法")
        }
      }

IE6IE8FF3.6safari4.0 opera10.50chrome5.0
能否创建文档 × × ×
doc.documentElement为HTML标签 × × ×
是否支持title属性 × × ×
是否支持body属性 × × ×
元素节点是否支持innerHTML × × ×
元素节点是否支持outerHTML × × ×
是否支持动态解析脚本 × × × × × ×
是否支持xpath × × ×

有关这方法更多的资料可点这里这里,很可能成为HTML5的标准API。

利用createDocument(namespaceURI , qualified , doctype )创建文档

第一个参数为字符串,指定命名空间,第二个参数为字符串,指定第一个元素的标签类型(tagName),第三个参数为对象,这个不用说吧,见字明义。

      window.onload = function(){
        try{
          var doc = document.implementation.createDocument(null, 'html', null);
          var html = doc.documentElement
          alert("doc.documentElement = "+html)        //测试是否存在HTML元素
          alert("html.tagName = "+html.tagName)//注意大小写,HTML文档会把元素节点的tagName与nodeName大写化,按理应该会返回“HTML”
          var body = doc.body
          alert("doc.body = "+body);      //测试document.body
          if("title" in doc){
            doc.title = "跨文档调用 by 司徒正美 "
            var title = doc.getElementsByTagName("title")[0]
            alert("title元素是否存在 = "+ title)
            alert("doc.title = "+doc.title)//测试document.titl
          }
          var head = doc.getElementsByTagName("head")[0]
          alert("是否有head元素 = " + head);
         if(head && "innerHTML" in head){
            alert("head.innerHTML = "+head.innerHTML)//测试innerHTML,HTML文档的元素节点都会支持这属性,及head与title是否存在套嵌关系
            alert("html.innerHTML = "+html.innerHTML)
          }
          if("outerHTML" in html){  //outerHTML也是HTML5的标准API
            alert("html.outerHTML = "+html.outerHTML)
          }
          var script = doc.createElement("script");
          html.insertBefore(script,null);
          script.appendChild(doc.createTextNode("alert('能调用javascript')"))//测试是否能动态解析脚本,注意这里是否弹出
          alert("测试xpath = "+ doc.evaluate )
        }catch(e){
          alert("不支持createDocument方法")
        }
      }

IE6IE8firefox3.6safari4.0.4 opera10.50chrome5.0
能否创建文档 × ×
doc.documentElement为HTML标签 × ×
是否支持title属性 × × × × × ×
是否支持body属性 × × × × × ×
元素节点是否支持innerHTML × × × × × ×
元素节点是否支持outerHTML × × × × × ×
是否支持动态解析脚本 × × × × × ×
是否支持xpath × ×

▲是因为标准浏览器返回的是 [object Element],而非正确的[object HTMLHtmlElement],证明它生成的是XML文档。

有关这方法更多的资料可点这里

利用命名空间与createDocument创建文档

      window.onload = function(){
        try{
          var namespace = 'http://www.w3.org/1999/xhtml';
          var doc = document.implementation.createDocument(namespace, 'html', null);
          var html = doc.documentElement
          alert("doc.documentElement = "+html)        //测试是否存在HTML元素
          alert("html.tagName = "+html.tagName)//注意大小写,HTML文档会把元素节点的tagName与nodeName大写化,按理应该会返回“HTML”
          if("title" in doc){
            doc.title = "跨文档调用 by 司徒正美 "
            var title = doc.getElementsByTagName("title")[0]
            if(!!title){
              alert("本来就存在title元素 "+ +doc.title)//测试document.title
            }else{
              alert("开始创建元素")
              try{
                html.innerHTML = "<head><title>由innerHTML创建的title</title></head><body></body>"
              }catch(e){
                var head = doc.createElement("head");
                html.appendChild(head);
                var title = doc.createElement("title");
                head.appendChild(title);
                title.appendChild(doc.createTextNode("由DOM API创建的title"));
                var body = doc.createElement("body");
                html.appendChild(body);
              }
              alert("doc.title = "+ doc.title)
            }
          }
          if("innerHTML" in html){
            alert("html.innerHTML = "+html.innerHTML)
          }
          if("outerHTML" in html){
            alert("html.outerHTML = "+html.outerHTML)
          }
          alert("doc.body = "+doc.body)
          var script = doc.createElement("script");
          html.insertBefore(script,null);
          script.appendChild(doc.createTextNode("alert('能调用javascript')"))//测试是否能动态解析脚本,注意这里是否弹出
          alert("测试xpath = "+ doc.evaluate )
        }catch(e){
          alert("不支持createHTMLDocument方法")
        }
      }

IE6IE8firefox3.6safari4.0.4 opera10.50chrome5.0
能否创建文档 × ×
doc.documentElement为HTML标签 × ×
是否支持title属性 × × ×
是否支持body属性 × × × × ×
元素节点是否支持innerHTML × ×
元素节点是否支持outerHTML × × ×
是否支持动态解析脚本 × × × × × ×
是否支持xpath × ×

有几点需要注意的:

  • 上述方法创建的是只有一个元素节点的HTML文档,由于是使用XHTML的命名空间,因此准确来说是XTHML文档。
  • XHTML的元素节点的tagName与nodeName是区分大小写的。
  • XHTML文档是没有实现body属性,opera的情况是特殊。

利用文档类型与createDocument创建文档

 window.onload = function(){
        try{
          var doctype = document.implementation.createDocumentType('html',
          '-//W3C//DTD HTML 4.01//EN', 'http://www.w3.org/TR/html4/strict.dtd');
          var doc = document.implementation.createDocument(null, 'html', doctype);
          var html = doc.documentElement
          alert("doc.documentElement = "+html)        //测试是否存在HTML元素
          alert("html.tagName = "+html.tagName)//注意大小写,HTML文档会把元素节点的tagName与nodeName大写化,按理应该会返回“HTML”
          if("title" in doc){
            doc.title = "跨文档调用 by 司徒正美 "
            var title = doc.getElementsByTagName("title")[0]
            if(!!title){
              alert("本来就存在title元素 "+ +doc.title)//测试document.title
            }else{
              alert("开始创建元素")
              try{
                if("innerHTML" in html)
                  html.innerHTML = "<head><title>由innerHTML创建的title</title></head><body></body>"
              }catch(e){
                var head = doc.createElement("head");
                html.appendChild(head);
                var title = doc.createElement("title");
                head.appendChild(title);
                title.appendChild(doc.createTextNode("由DOM API创建的title"));
                var body = doc.createElement("body");
                html.appendChild(body);
              }
              alert("doc.title = "+ doc.title)
            }
          }
          var head = document.getElementsByName("head")[0];
          alert("head = "+head)
          if("innerHTML" in html){
            alert("html.innerHTML = "+html.innerHTML)
          }
          if("outerHTML" in html){
            alert("html.outerHTML = "+html.outerHTML)
          }
          alert("doc.body = "+doc.body)
          var script = doc.createElement("script");
          html.insertBefore(script,null);
          script.appendChild(doc.createTextNode("alert('能调用javascript')"))//测试是否能动态解析脚本,注意这里是否弹出
          alert("测试xpath = "+ doc.evaluate )
        }catch(e){
          alert("不支持createHTMLDocument方法")
        }
      }

IE6IE8firefox3.6safari4.0.4 opera10.50chrome5.0
能否创建文档 × ×
doc.documentElement为HTML标签 × ×
是否支持title属性 × × × × × ×
是否支持body属性 × × × × × ×
元素节点是否支持innerHTML × × × × × ×
元素节点是否支持outerHTML × × × × × ×
是否支持动态解析脚本 × × × × × ×
是否支持xpath × ×

利用document.cloneNode(true)创建文档

      window.onload = function(){
        try{
          var doc = document.cloneNode(true);
          var html = doc.documentElement
          alert("doc.documentElement = "+html)        //测试是否存在HTML元素
          alert("html.tagName = "+html.tagName);
          alert("html.html = "+html.innerHTML)
          if(doc.title){
            alert("doc.title = "+doc.title);
          }else{
            doc.title = "司徒正美";
            var title = doc.getElementsByTagName("title")[0];
            alert("title.innerHTML = "+title.innerHTML);
          }
          alert("doc.body = "+doc.body)
          var script = doc.createElement("script");
          doc.body.appendChild(script);
          if(!+"\v1"){
            script.text = "alert('能调用javascript')"
          }else{
            script.appendChild(doc.createTextNode("alert('能调用javascript')"));
          }
          alert("测试xpath = "+ doc.evaluate )
        }catch(e){
          alert("不支持document.cloneNode(true)方法")
        }
      }

IE6IE8firefox3.6safari4.0.4 opera10.50chrome5.0
能否创建文档 × × ×
doc.documentElement为HTML标签 × × ×
是否支持title属性 × × ×
是否支持body属性 × × ×
元素节点是否支持innerHTML × × ×
元素节点是否支持outerHTML × × × ×
是否支持动态解析脚本 × × × ×
是否支持xpath × × × × ×

注意:IE下,document.cloneNode(true)不能复制title元素的innerHTML!

到目前,我们发现w3c实现的那一堆方法实在太屎了,竟然不支持创建另一个javascript运行环境。因此想得到拥有javascript运行环境的文档对象,我们还是得靠iframe与ActiveXObject("htmlfile")!

/*
get another HTMLDocument which can run javascript!
Copyright 2010
Dual licensed under the MIT or GPL Version 2 licenses.
author "司徒正美(cheng)"
http://www.cnblogs.com/rubylouvre/
 */
      (function(global) {
        var expando = '__dom__' + (new Date - 0),
        mode = (function(){
          if (global.ActiveXObject && global.location && global.location.protocol !== 'file:') {
            try {
              return new ActiveXObject('htmlfile') && 1;
            } catch (e) {}
          }
          return 2;
        })(),
        getSandboxDocument = (function(){
          // 获取另一个全局对象
          if (mode === 1){
            return function() {
              var htmlfile = new ActiveXObject('htmlfile');
              htmlfile.open();
              htmlfile.write('<script>this.author ="司徒正美";document.global = this;<\/script>');
              htmlfile.close();
              return htmlfile.global;
            };
          }else  if (mode === 2){
            return function() {
              var idoc, iframe, result,
              doc = global.document,
              parentNode = doc.body || doc.documentElement,
              name  = '__iframe__' + expando  ;
              try {
                iframe = doc.createElement('<iframe name="' + name + '">');
              } catch (e) {
                (iframe = doc.createElement('iframe')).name = name;
              }
              iframe.style.display = 'none';
              parentNode.insertBefore(iframe, parentNode.firstChild);
              try {
                (idoc = global.frames[name].document).open();
                var str = '<html><head><title>iframe</title><script>this.author ="司徒正美";parent.' +
                  expando + ' = this;<\/script></head><body></body></html>'
                idoc.write(str);
                idoc.close();
              } catch (e) {
                //opera9不支持在document.documentElement中插入iframe
                throw new Error('Creating a sandbox by iframe is fail.');
              }
              result = global[expando];
              try{
                delete global[expando]
              }catch(e){//IE下失败!
                global[expando] = undefined
              }
              return result;
            }
          }else{
            return function() {
              throw new Error('Creating a sandbox is fail.');
            };
          }
        })();
        global.getSandboxDocument = getSandboxDocument
      })(this);

IE6IE8firefox3.6safari4.0.4 opera10.50chrome5.0
能否创建文档
doc.documentElement为HTML标签
是否支持title属性
是否支持body属性
元素节点是否支持innerHTML
元素节点是否支持outerHTML ×
是否支持动态解析脚本
是否支持xpath × ×

这种技术我算毫无保留地公开出来了,如果有谁知道怎样利用它创建一个不污染本地数组的数组类,也请不吝赐教!

2ch

posted on 2010-03-11 21:43  司徒正美  阅读(3495)  评论(16编辑  收藏  举报