js自定义消息机制研究学习(续)关于iframe间消息传递的讨论邮件
(写了几篇《js自定义消息机制研究学习》后,有网友询问iframe之间的消息调用,列出一些邮件摘录。文章最后附有我写的一个简单示例)
邮件摘录一:
iframe通信机制
关于iframe的通信也许我的实践不是最佳实践,我知道yahoo在这方面的研究是比较深入的,当初ajax兴起之时,yahoo邮箱是基于大量的iframe完成的。但是我没有研究过,你可以看看他们的代码。
我这里的iframe通信主要是指同域iframe与父窗口,同级iframe之间互传消息。
附件有一个实现及示例(请看iframe.html),同样还是基于monitor对象
简单描述一下:
我设置一个顶级对象:delivery用于处理iframe之间的通信
他有两个方法:
parent:获得父窗口的delivery对象
pipeline:管道,负责投递同级iframe之间的信息
目前我写了一个临时的pipeline处理(delivery.bind("pipeline",function……
在iframe.js中),是根据iframe的name属性匹配的,具体到应用的时候,可以根据你自己的需求定制,比如根据iframe内页面的title,href等等进行匹配
我觉得这样做的好处:
1. 易于单页面调试,parent,pipeline的调用不会因为指定窗口的不存在而弹出错误,parent函数在父窗口不存在的情况下会创建一个新的delivery实例
2.
消息(事件or数据,看你怎么定义)传递出去,页面js 的任务就结束,降低了页面之间的依赖。比如pipeline并不会因为指定的iframe页面不存在就影响下边的js执行
像层级架构,父页面(或目标页)不需要知道iframe子页面(或要调用它的页面)是否存在,他只提供消息处理(就像一个接口)。
3.
单一消息口,使用delivery一个对象来处理所有从其他页面传过来的消息。降低了复杂度,以前有一些做法是写一些函数,或者定义一些变量,由要调用的页面来操作,这样增加了很多的页面依赖及复杂度。
当然,它页有一些弊端,比如回调,比如单一的delivery需要注意消息名称冲突等等
(附代码:)
View Code
var monitor = (function(){
function bind(b){
var queue = this.__MSG_QS__ = this.__MSG_QS__ || {};
if (!queue[b]) {
queue[b] = []
}
for (var a = 1, X = arguments.length, Y; a < X; a++) {
queue[b].push(arguments[a])
}
}
function live(b){
var queue = this.prototype.__STATIC_MSG_QS__;
if (!queue[b]) {
queue[b] = []
}
for (var a = 1, X = arguments.length, Y; a < X; a++) {
queue[b].push(arguments[a])
}
}
function trigger(Y){
var queue = [];
var qs = this.__MSG_QS__ || {};
var sqs = this.__STATIC_MSG_QS__ || {};
queue = queue.concat(qs[Y.type] || []);
queue = queue.concat(sqs[Y.type] || []);
for (var a = 0, X = queue.length; a < X; a++) {
if (queue[a].handler) {
queue[a].handler(Y, this)
}
else {
queue[a].call(this, Y, this);
}
}
}
return {
ini: function(X){
if (Object.prototype.toString.call(X) == "[object Function]") {
var proto = X.prototype;
proto.__STATIC_MSG_QS__ = {};
proto.bind = bind;
proto.trigger = trigger;
X.live = live;
}
X.bind = bind;
X.trigger = trigger;
return X
}
}
})();
//顶级通讯对象
var delivery = (function(){
var __parent = null; //父窗通信对象
//父窗口通信对象
function parent(){
if(__parent)return __parent;
if (window.parent && window.parent.delivery) {
__parent = window.parent.delivery;
__parent = __parent === delivery ? new delivery() : __parent;
}
if(!__parent)__parent=new delivery();
return __parent;
}
//管道通信,向同级iframe发送消息,
function pipeline(target,evt,data)
{
if (target && evt) {
//向父窗口传递消息,由父窗口来处理
this.parent().trigger({type:"pipeline",target: target, evt: evt, data: data || {} });
}
}
function Constructor()
{
}
Constructor.prototype={
parent:parent,
pipeline:pipeline
}
Constructor.parent=parent;
Constructor.pipeline=pipeline;
return monitor.ini(Constructor);
})();
//处理子窗口间通信
delivery.bind("pipeline",function(arg){
var tframe=frames[arg.target];
if(tframe && tframe.delivery)
{
tframe.delivery.trigger({type:arg.evt,data:arg.data});
}
})
邮件摘录二:
由于是工作时间,我只能简单看了下你的代码。提一些个人的见解:
我看到了两行代码
parent.document.getElementById("Tli_brainpower").style.display="none";
parent.IFrameReturnValue(thiss.getTds());
网上有很多文章教我们这么做,这也是一个基本的iframe调用方式,相类似的还有直接修改某一公用变量值,如parent.xxxx=0等等
在一些很简单的页面中,我不反对这么做(偷懒的时候我也这么干)
但这里有一些风险:
1.
直接操作了父页面元素,这首先造成一种强制,父页面必须有这个元素,比如上面的代码id为Tli_brainpower的dom、IFrameReturnValue函数,在父页面必须存在,否则程序无法运行。这肯定限制了父页面的改动。
你在改动这个页面的时候,你得不停的关心你会调用那些iframe页面,而内部的iframe页面又引用哪些元素等等。结果就是,你改了一个id,它就会留下一个隐患,让你的程序崩掉。
当只有你一个人操作这套程序的时候,也许记性会帮你(但用处也不大,我就是一个善忘的人),但当很多人和你一起开发,有UI帮你调整页面,甚至你会被要求改版等等……
简单来说,只要你有改动,你就会很痛苦。
2.
混乱的调用,直接调用的方式潜在的允许,你的iframe内的页面可以操作所有页面元素,同时,我们也许会出现要调用很多不同的iframe页面,他们又要实现很多相同或不同的对父页面的操作。
也许是这样的,iframe1要修改div1 div2 div3 div4的样式,同时它会调用fun1 fun2 fun3
fun4的父页面函数,iframe2要修改div5 div6 div7 div8的样式,同时它会调用fun5 fun6 fun7
fun8的父页面函数。
有时,我们还要iframe1调用一些iframe2的数据(我们这边现在就有这样的需求,两个iframe页面间大量数据交互)
最终,他们是一个网状结构,要维护他们,或者要看懂他们你需要花费很多的精力和时间(之前维护过一个系统,他的调用是parent.parent.frames,frames["main"].frames[""]遍布页面。很让人郁闷)
parent.很便利,但是不建议在页面里大量使用这种方式。我们用编程中高内聚低耦合的观点来设计这些页面交互。
我们换一种思路来考虑问题,把每一个页面当做是独立体、一个对象。
如果把一个页面当做是对象,那么一个对象是有自己属性,自己方法的一个独立体。
比如这样形容:
index页面:setMember()设置会员 addRow()添加一行数据
iframe1页面(会员选择iframe页面):selectMember()选中会员
iframe2页面(数据选择):selectData()选中数据
这样就比较清晰了
这里,我们首先要实现页面的内聚,简单来说,对于这个页面元素的控制只允许本页面(当然包括它所引用的js代码)自己来控制。我们可以把所有页面元素(实际上也包括存在js中的数据)当做是私有的,决不允许其他页面代码直接控制,这样来提高页面js代码的聚合性。
parent.document这样代码会让对index这个页面操作代码散落各处,违背了高内聚的准则,最好封装成一个方法供别的页面调用。
接下来讨论一下页面间的低耦合
假设在index里用iframe调用了iframe1,iframe2页面,那么调用方式是这样的:
iframe1:
var member=selectMember();
parent.setMember(member);
iframe2:
var data=selectData();
parent.addRow(data);
这是一种直接的方案,虽然比直接操作父窗口元素、数据耦合度低,但耦合度依然较高。
用一个词来形容:“依赖”,他造成iframe1、iframe2的依赖于index(或者说依赖于具有setMember,addRow方法的页面)
也许我们那天要再写一个页面index2,要用到iframe1,iframe2,那么我们必须按照调用写setMember,addRow
但是index2实际上要执行的是deleteMember,deleteData呢?
如何解除依赖(或者降低依赖)?
一些设计原则教我们,面对频繁变动的需求、代码,我们开发的东西最好依赖于稳定的对象(依赖于抽象、依赖于接口),而不是依赖于频繁变化的东西(比如变量,固定的方法,这里包括父窗口的dom元素)
之前我写的示例中:delivery就是比较稳定的一个对象(只有在开发前期我们会好好设计并修改它)。
那么以delivery的模式来解除依赖的关系,如:
iframe1:
selectMember(){…… delivery.parent.trigger({type:"selectMember",member:member})
}
iframe2:
selectData(){…… delivery.parent.trigger({type:"selectData",data:data})
}
当然它也有依赖,比如依赖于我示例写的delivery对象,但是只依赖于一个对象(且是全局统一的一个对象),要好于直接依赖于方法名(尤其是当你需要调用多个方法名的时候或不确定方法名得时候)。
那么主页面就会如下:
index:
delivery.bind("selectMember",function(data){ setMember(data.memeber)
})
index1:
delivery.bind("selectMember",function(data){ deleteMember(data.memeber)
})
通过委托delivery对象负责传递数据,子页面不再关心父页面做什么,父页面也不关心,是谁触发的selectMember(因为你可以在当前页面内用delivery.trigger触发调用,而不是通过iframe内的页面来触发)
大多时候,他们并不感知iframe、parent页面的存在,他们只与稳定的delivery对象打交道。他们之间的耦合降低了
所有的页面依赖于功能稳定的delivery对象,而不是一个不知谁开发、不知何时调用、不知有什么内容的一个页面(或js)
高内聚低耦合的准则适用很多场景,记住这个基本准则并应用可以良好的解决很多问题。比如有很多页面的时候,我们要像设计面向对象那样设计页面的职能,抽象它的行为。
delivery的模式也许在初期会带来很多麻烦和困扰,比如iframe1发了信息,index却没有正确处理。
这是因为原来直接的setMember调用,修改成了复杂的bind
,trigger-》function-》setMember模式,中间环节只要有一点问题,数据就无法准确抵达。
复杂的方式是应对复杂的问题,复杂的方式也会带来一点性能损伤,具体要根据你的需求来决定。
三种方案:
1.parent.domcument parent.xxx(变量)
使用iframe很少发生交互的情况下使用,比如几百个页面,偶尔一个页面有这个需求,我们就不必大费周章用什么模式了。
2.parent.fun 少数页面,交互只涉及很少几个函数。
3.delivery
很多的页面、很多的交互。依赖于稳定的对象(比如delivery),要好于直接依赖于变化多端的页面及js代码。
示例代码:下载
ozdoo技术系列文章 转载请署名