外观(Facade)模式为对象提供了一个方便的高层次接口,能够隐藏其底层的真实复杂性,通常是为了提高可用性。
外观模式非常适合于浏览器脚本处理,可以将浏览器之间的差异隐藏在外观之后。比如说事件API、AJAX等,IE低版本浏览器和W3C标准存在一定差异,这时候可以提供一个“外观”给外部调用,将处理细节隐藏起来,同时也能将细节处理的代码从逻辑代码中解耦出来。
以下内容通过提供一个跨浏览器事件的接口来实现外观模式。
通过创建一个工具类utils,主要包含addEventListener和removeEventListener,通过检查特性的存在来提供一个更安全的、跨浏览器的API。代码如下:
var utils = {
addEventListener: function (el, ev, handler) {
if (el.addEventListener) {
el.addEventListener(ev, handler, false);
} else if (el.attachEvent) {
el.attachEvent('on' + ev, handler);
} else {
el['on' + ev] = handler;
}
},
removeEventListener: function (el, ev, handler) {
if (el.addEventListener) {
el.removeEventListener(ev, handler, false);
} else if (el.attachEvent) {
el.detachEvent('on' + ev, handler);
} else {
el['on' + ev] = null;
}
}
};
这就是一个外观模式。但这段代码存在一个问题,执行效率比较低下。每次在调用utils.addEventListener()或utils.removeEventListener()时,都会重复执行相同的检查。那么该如何优化这段代码?
可以在脚本初始化时,一次性探测出浏览器的特征,并重新定义函数的运行。优化后的代码如下:
var utils = {
addEventListener: null,
removeEventListener: null
}
//初始化
if (window.addEventListener) {
utils.addEventListener = function(el, ev, handler) {
el.addEventListener(ev, handler, false);
};
utils.removeEventListener = function(el, ev, handler) {
el.removeEventListener(ev, handler, false);
}
} else if (document.attachEvent) {
utils.addEventListener = function(el, ev, handler) {
el.attachEvent('on' + ev, handler);
};
utils.removeEventListener = function(el, ev, handler) {
el.detachEvent('on' + ev, handler);
};
} else {
utils.addEventListener = function(el, ev, handler) {
el['on' + ev] = handler;
};
utils.removeEventListener = function(el, ev, handler) {
el['on' + ev] = null;
}
}
这样子,utils.addEventListener或utils.removeEventListener在执行时就不会执行重复的检查了。
外观模式,有时候可以跟模块模式一起使用,模块模式的实例包含很多已经定义的私有方法,然后通过外观模式提供一个更简单的API来访问。
在utils的基础上,增加stop函数来阻止默认行为,同时阻止事件冒泡。结合模块模式改写utils对象,代码如下:
var utils = (function() {
var addEventListener = null,
removeEventListener = null,
preventDefault = null,
stopPropagation = null;
if (window.addEventListener) {
addEventListener = function(el, ev, handler) {
el.addEventListener(ev, handler, false);
};
removeEventListener = function(el, ev, handler) {
el.removeEventListener(ev, handler, false);
};
} else if (document.attachEvent) {
addEventListener = function(el, ev, handler) {
el.attachEvent('on' + ev, handler);
};
removeEventListener = function(el, ev, handler) {
el.detachEvent('on' + ev, handler);
};
} else {
addEventListener = function(el, ev, handler) {
el['on' + ev] = handler;
};
removeEventListener = function(el, ev, handler) {
el['on' + ev] = null;
};
}
//通过首次运行重写preventDefault,防止后续重复检测
//阻止浏览器的默认行为
preventDefault = function(e) {
if (typeof e.preventDefault === 'function') {
e.preventDefault();
preventDefault = function(e) {
e.preventDefault();
};
return;
}
if (typeof e.returnValue === 'boolean') {
e.returnValue = false;
preventDefault = function(e) {
e.returnValue = false;
};
return;
}
};
//阻止事件冒泡
stopPropagation = function(e) {
if (typeof e.stopPropagation === 'function') {
e.stopPropagation();
stopPropagation = function(e) {
e.stopPropagation();
};
return;
}
if (typeof e.cancelBubble === 'boolean') {
e.cancelBubble = true;
stopPropagation = function(e) {
e.cancelBubble = true;
};
return;
}
};
//阻止默认行为和阻止事件冒泡
function stop(e) {
preventDefault(e);
stopPropagation(e);
}
return {
addEventListener: addEventListener,
removeEventListener: removeEventListener,
stop: stop
};
}());
调用utils.stop()实际上会触发内部一系列的行为,但用户无需关心,这样子就达到了代码解耦以及细节隐藏。
外观模式对于重构的方面也有帮助,比如说需要替换一个具有不同实现的对象时,可以首先考虑新对象的API,然后在原有对象的前面创建一个外观。这样子,在替换原有对象的时候,仅需修改更少的客户端代码。
代码地址:https://github.com/wengeek/design-patterns/tree/master/Facade