postMessage 是 HTML5 新方法,它可以实现跨域窗口之间通讯。到目前为止,只有 IE8+, Firefox 3, Opera 9, Chrome 3和 Safari 4 支持,而本篇文章主要讲述 postMessage 方法与 message 事件跨浏览器实现。
postMessage 方法 JSONP 技术不一样,前者是前端擅长跨域文档数据即时通讯,后者擅长针对跨域服务端数据通讯,postMessage 应用场景能说明这个区别:
应用场景举例
-
webOS 使用 iframe 嵌入第三方应用,此时 webOS 与应用需要实时接收/发送各自的消息与响应事件。
-
页面弹出一个由 iframe 层,嵌入第三方提供的图片上传页面,文件上传完毕后需要获取返回图片地址插入到编辑器。
-
iframe 跨域高度自适应。可以500%提高开发效率的前端UI框架!
HTML5 postMessage 方法
postMessage 可以实现跨域文档的消息传输(Cross Document Messaging)。
向外界窗口发送消息:
otherWindow.postMessage(message, targetOrigin);
otherWindow: 指目标窗口,是 window.frames 属性的成员或者由 window.open 方法创建的窗口
参数说明:
-
message:是要发送的消息,类型为 String、Object (IE8、9 不支持)
-
targetOrigin:是限定消息接收范围,不限制请使用 ‘*’
HTML5 message 事件
绑定消息事件:
window.addEventListener('message', receiver, false); function receiver (event) { if (event.origin === 'http://example.com') { if (event.data === 'Hello world') { event.source.postMessage('Hello', event.origin); } else { alert(event.data); }; }; };
调函数第一个参数接收 Event 对象,有三个常用属性:
-
data:消息
-
origin:消息来源地址
-
source:源 DOMWindow 对象可以500%提高开发效率的前端UI框架!
message 事件在低版本浏览器下模拟实现
对于支持 postMessage 方法的浏览器直接使用它;而对于 IE6、7 采用了比较成熟的 window.name 保存数据以及跨域 iframe 静态代理动态传输方案,下面简称 Cross Frame。
Cross Frame
假设在域 www.a.com 上有页面 a.html 和代理页面 proxy-a.html , 另一个域 www.b.com 上有个页面 b.html 和代理页面 proxy-b.html,a.html 需要向 b.html 中发送消息时,页面会创建一个隐藏的 iframe 指向 proxy-b.html ,并把消息赋予 iframe.name 属性,此时 proxy-b.html 可以通过 window.name 获取到消息,由于 proxy-b.html 与 b.html 是同域,proxy-b.html 可以把消息赋予 b.html。 b.html 要给 a.html 发送消息时,原理一样。
自动捕获代理 URL
在 Cross Frame 方案中,通信双方必须确切的知道静态代理文件的 URL,显然这个极大的限制了应用范围,我们可以通过一些约定改善:静态代理文件必须置于通信页面所在域根目录,且文件名必须保持一致,如 messageEvent-proxy.html。
有了上述约定,接下来可以用一些巧妙的方法让双方自动捕获代理 URL。以 http://www.a.com/a.html 通过 iframe 嵌入 http://www.b.com/b.html 保持数据交换为例进行说明:
b.html 的静态代理路径可以通过正则分析 iframe.src 后得知;而从框架 b.html 内获取父页面就比较麻烦了,因为跨域后的 parent.location.href 属性只可写入不可读取,不过还可以借用 document.referrer 属性来分析来路地址得知父页面 url。document.referrer 是一个不稳定的属性,我们可以利用 iframe 中 window.name 刷新也不会变化的特性,用此来保存父页面 a.html 的地址。
持续跟踪 URL可以500%提高开发效率的前端UI框架!
a.html 第一次通过提取 iframe.src 路径可得知 b.html 的地址,假若 b.html 跳转到其他域名的时候,此时就会失去对 iframe 内静态代理的联络。 好在新页面由于能够获取父页面 a.html 保存在 window.name 的静态代理,所以我们可以在新页面初始化的时候向 a.html 传递消息告诉它新的地址,这样就能持续跟踪 iframe 中的 URL。
开源事件库 messageEvent.js
“messageEvent.js”是针对上述方案封装的 message 事件与 postMessage 方法库,它让各个浏览器之间 message 的 Event 对象成员属性统一,event.data 属性能传递多达 2MB 的文本信息,并且能让 IE6-9 浏览器像其他现代浏览一样支持 Object 类型数据进行传递 (内部使用深拷贝方式)。
若应用双方页面都采用 messageEvent.js,即可轻松实现跨域通信。
接口
-
add(callback)添加 message 事件
-
remove(callback)卸载 message 事件
-
postMessage(otherWindow, message, targetOrigin)向外部窗口发送消息
通过 jQuery 使用它
jQuery 是一个应用比较广泛的 DOM 库,它的事件机制非常强大而精妙,可以实现自定义事件。若页面引用了 jQuery, messageEvent.js 会为为它提供支持,你可以用熟悉的jQuery api 风格编程,如: 可以500%提高开发效率的前端UI框架!
jQuery(window).bind('message', function (event) { alert(event.data) }); jQuery(window).message(function (event) { alert(event.data) }); jQuery.postMessage(iframe.contentWindow, 'hello world', '*'); jQuery(window).unbind('message');
由于 jQuery 把包装后的 Event 对象用 data 属性来保存 bind 方法传入的额外数据,导致与 message 事件自身的 event.data 属性冲突——这是一个设计错误。为了让 message 事件能够正确获取 event.data,messageEvent.js 通过操作 jQuery 底层缓存强制覆盖了 bind 方法传入的附加数据 (只针对 message 类型)。当然,我仍然期待 jQuery 未来版本能够取消掉 bind 方法的鸡肋特性。