網頁制作Webjx文章簡介:全局變量是魔鬼,這句話在JavaScript存在的地方應該就是成立的,當然Firefox擴展也不例外.
全局變量是魔鬼,這句話在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);
}};
現在,然我們把前面的代碼連起來,看看全局擴展對象是如何定義和初始化的。
網頁制作Webjx文章簡介:全局變量是魔鬼,這句話在JavaScript存在的地方應該就是成立的,當然Firefox擴展也不例外.
下面這些代碼是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。
總結一下,這個對象要實現下面幾個方法:
當然這段代碼也會確保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的角度來縱觀整個架構。
大家可以在 這裡 下載本文提到的演示擴展。