当前位置: 首页 > 图文教程 > 网络编程 > Javascript > JS应用在Firebug中的扩展架构模式

Javascript
用JavaScript实现浏览器地震效果
Javascript实例教程(8) 利用Javascript基于浏览器类型的重定向
Javascript实例教程(14) 鼠标触发窗口
使主页呈现“飞舞”特效
Javascript实例教程(21) OLE Automation(1)
Javascript实例教程(13) 鼠标移过时报警
Javascript实例教程(10) 随机显示图片
Javascript实例教程(2) 创建弹出式窗口
JavaScript 小技巧(第十集)
Javascript实例教程(3) 创建折叠式导航菜单
Javascript实例教程(7) 利用Javascript进行密码保护
完美解决一个事件激活多个函数(2)
Javascript模拟游戏中的弹出菜单效果
Javascript实例教程(6) 在一个表单中设置和检查Cookies
JavaScript 小技巧(第八集)
Javascript实例教程(4) 探测浏览器插件
JavaScript 小技巧(第九集)
JavaScript学习:基础继承机制
初学Javascript之cookie篇(译)
让弹出窗口变得“体贴”一些(javascript)

Javascript 中的 JS应用在Firebug中的扩展架构模式


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-09-28   浏览: 138 ::
收藏到网摘: n/a

全局变量是魔鬼,这句话在JavaScript存在的地方应该就是成立的,当然Firefox扩展也不例外,如果大家把多于一个的对象置于全局命名空间下,和其他扩展的冲突是很容易发生的,而且发现这种冲突引起的错误是很困难的,因为每个人的扩展列表都不一样啊。避免全局名字污染已经成了一个基本原则,本文从这点引申,介绍了一个应用在Firebug中的扩展架构模式,非常值得推荐。

【原文】Firefox Extensions: Global Namespace Pollution
【作者】Jan Odvarko
【译文】http://cuimingda.com/2009/01/
【译者】明达

以下是对原文的翻译

最近有几个开发者向我咨询如何设计Firefox扩展的架构,第一个显现在我脑海中的答案就是要合理定义那些在ChromeWindow作用域下的全局变量。

不合理的定义全局变量,可以轻易的引发不同扩展之间的冲突,而这些完全是应该避免的(这也是AMO审阅的步骤之一),因为冲突所引发的问题是很难被发现的。就目前的开发环境来说,全局变量就是魔鬼,尤其是采用OOP开发模式的时候。

我不想重复介绍如何从头开始开发一个Firefox扩展,对于这方面已经有很多非常详细的文章。本文的重点放在如何设计一个更加易于维护的Firefox扩展架构。

如果你对前面的介绍感兴趣,那就接着看吧。。。

命名空间架构

扩展之间发生冲突的重要原因就是因为定义了不合理的全局变量。我认为对每个扩展来说,只有一个全局变量已经很足够了(可以根据扩展的信息来定义这个唯一的全局变量的名字,比如可以是扩展的名字、域名、地址等),不仅可以满足我们的开发,而且可以避免那些令人讨厌的冲突。

Firebug使用的命名空间架构,基本建立在著名的Module Pattern基础上(这种模式最早由Douglas Crockfod定义)下的。这种模式简单而清晰,但其实我在很长时间里都不是很明确这种模式究竟是如何工作的(I hadn’t understand how it actually works for a long time)。我相信每个开发者都可以充分利用这个方法。

基本的思路是将每个JavaScript脚本文件放进自己的作用域,这是通过一个函数来实现的,没有定义任何全局变量,比如下面这段代码:

function() {
  // TODO: 脚本文件中的全部代码
}

我管这个函数就叫做命名空间。摆在眼前的第一个问题是,如何确定这个函数的内容会在正确的时间被调用。第二个问题是,如何在多个脚本文件中共享对象(这个会在后面的章节解答)。 Firebug通过将所有的命名空间进行注册,并在Firefox chrome UI加载的时候调用来解决第一个问题,也就是下面这段代码:

myExtension.ns(function()
{
  // TODO: 脚本文件中的全部代码
});

命名空间(就是原来定义的那个函数)为当作myExtension.ns函数的一个参数,而myExtension对象是这个扩展中定义的唯一全局变量。这个对象代表着整个扩展。不用担心这个名字太长,我们可以为他建立个快捷方式(在实际开发中,这个名字可能会类似 comSoftwareIsHardMyExtension这个样子)。

ns函数比较简单,就是把所有的方法都添加到一个数组中。

var namespaces = [];
this.ns = function(fn)
{
  var ns = {};
  namespaces.push(fn, ns);
  return ns;
};

执行已注册命名空间的函数,不可以命名为apply,别的什么名字都可以。

this.initialize = function() {
  for (var i = 0; i < namespaces.length; i += 2) {
      var fn = namespaces[i];
      var ns = namespaces[i + 1];
      fn.apply(ns);
  }};

现在,然我们把前面的代码连起来,看看全局扩展对象是如何定义和初始化的。

下面这些代码是browserOverlay.js文件的内容,这个脚本文件会在一个界面文件(browserOverlay.xul)中被引用。

// 扩展对应的唯一全局变量
var myExtension = {};
(function() { // 注册命名空间
  var namespaces = [];
  this.ns = function(fn) {
      var ns = {};
      namespaces.push(fn, ns);
      return ns;
  };

  // 初始化
  this.initialize = function() {
      for (var i = 0; i < namespaces.length; i += 2) {
          var fn = namespaces[i];
          var ns = namespaces[i + 1];
          fn.apply(ns);
      }
  };

  // 收尾的清理工作
  this.shutdown = function() {
      window.removeEventListener("load", myExtension.initialize, false);
      window.removeEventListener("unload", myExtension.shutdown, false);
  };

  // 注册两个事件处理程序,维护扩展的生存期
  window.addEventListener("load", myExtension.initialize, false);
  window.addEventListener("unload", myExtension.shutdown, false);
}).apply(myExtension);

正如我前文所述,这里只有一个全局对象myExtension。

总结一下,这个对象要实现下面几个方法:

  • ns - 注册一个新的命名空间。
  • initialize - 初始化所有的命名空间。
  • shutdown - 收尾的清理工作。

当然这段代码也会确保initialize和shutdown方法会在正确的时间被调用,这也是两个事件处理程序的作用。

browserOverlay.xul现在看起来可能会是下面这个样子:

<?xml version="1.0"?>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/
there.is.only.xul">
  <script src="chrome://namespace/content/browserOverlay.js" type="application/x-javascript"/>
  <script src="chrome://namespace/content/Module1.js" type="application/x-javascript"/>
  <script src="chrome://namespace/content/Module2.js" type="application/x-javascript"/>
</overlay>

在这里,Module1.js和Module2.js两个文件是一模一样的。

myExtension.ns(function() {
  // TODO: 脚本内的全部代码
});

在不同的模块间共享数据

我们已经把所有的脚本置于本地的作用域下,现在让我们来回答上面提到的第二个问题,就是在不同的命名空间下如何共享函数和数据。基本的思路当然是要利用我们唯一的全局对象啦,也就是myExtension。

首先,让我们先来看看下面这段代码(都在lib.js文件中)

myExtension.LIB = {
  // 共享函数接口
  getCurrentURI: function() {
      return window.location.href;
  },

  // 扩展对象的快捷方式
  theApp: myExtension,

  // XPCOM组件的快捷方式
  Cc: Components.classes,
  Ci: Components.interfaces,

  // 等等。。。
};

你可以注意到,这段代码在全局的myExtension对象下建立了一个新的LIB属性,这个属性定义了一个函数库,是要在扩展所有的模块中共享的。你应该在Java的包结构中看到过相同的做法,所有的命名空间呈树状结构分布在一个唯一的对象下面,YUI也是这样子做的。

lib.js文件也在browserOverlay.xul中引入,紧随browserOverlay.js的后面。

<?xml version="1.0"?>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/
there.is.only.xul">
  <script src="chrome://myextension/content/browserOverlay.js" type="application/x-javascript"/>
  <script src="chrome://myextension/content/lib.js" type="application/x-javascript"/>
  <script src="chrome://myextension/content/Module1.js" type="application/x-javascript"/>
  <script src="chrome://myextension/content/Module2.js" type="application/x-javascript"/>
</overlay>

让我们对模块内的脚本也做一些改进。

myExtension.ns(function() {
  with(myExtension.LIB) {
      // TODO: 脚本内的全部代码
      var moduleVariable = "Accessible only from withing this module";
      dump("myExtension.Module initialization " + getCurrentURI() + "\n");
  }
});

通过利用with语句,我们可以方便的访问所有的库函数,就像访问全局变量一样。

既然我们要访问全局对象,还可以像下面这样利用theApp这个快捷方式(尤其是命名空间名字太长的时候)

myExtension.ns(function() {
  with(myExtension.LIB) {
      // TODO: 脚本内的全部代码
      theApp.sharedValue = "A new shared property";
  }
});

下面这个图是从UML的角度来纵观整个架构。

大家可以在 这里 下载本文提到的演示扩展。