JavaScript设计模式之外观模式

July 5, 2015

596

外观(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

设计模式 」相关文章

Wen's Blog

文章归档 » 文章标签 » 博主:吴文伟,Web开发爱好者,专注于前端开发,该博客用于记录和分享平时遇到的一些问题以及知识。

订阅

联系方式

链接