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方法") } }
IE6 | IE8 | FF3.6 | safari4.0 | opera10.50 | chrome5.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方法") } }
IE6 | IE8 | firefox3.6 | safari4.0.4 | opera10.50 | chrome5.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方法") } }
IE6 | IE8 | firefox3.6 | safari4.0.4 | opera10.50 | chrome5.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方法") } }
IE6 | IE8 | firefox3.6 | safari4.0.4 | opera10.50 | chrome5.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)方法") } }
IE6 | IE8 | firefox3.6 | safari4.0.4 | opera10.50 | chrome5.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);
IE6 | IE8 | firefox3.6 | safari4.0.4 | opera10.50 | chrome5.0 | |
---|---|---|---|---|---|---|
能否创建文档 | ● | ● | ● | ● | ● | ● |
doc.documentElement为HTML标签 | ● | ● | ● | ● | ● | ● |
是否支持title属性 | ● | ● | ● | ● | ● | ● |
是否支持body属性 | ● | ● | ● | ● | ● | ● |
元素节点是否支持innerHTML | ● | ● | ● | ● | ● | ● |
元素节点是否支持outerHTML | ● | ● | × | ● | ● | ● |
是否支持动态解析脚本 | ● | ● | ● | ● | ● | ● |
是否支持xpath | × | × | ● | ● | ● | ● |
这种技术我算毫无保留地公开出来了,如果有谁知道怎样利用它创建一个不污染本地数组的数组类,也请不吝赐教!