首页 > 开发 > JS > 正文

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

2024-09-06 12:40:55
字体:
来源:转载
供稿:网友

全局变量是魔鬼,这句话在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的角度来纵观整个架构。

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表