本文目錄一覽:
js的模塊化編程有哪些方式
基礎
我們首先簡單地概述一下,自從三年前Eric Miraglia(YUI的開發者)第一次發表博客描述模塊化模式以來的一些模塊化模式。如果你已經對於這些模塊化模式非常熟悉了,大可以直接跳過本節,從「進階模式」開始閱讀。
匿名閉包
這是一種讓一切變為可能的基本結構,同時它也是Javascript最棒的特性。我們將簡單地創建一個匿名函數並立即執行它。所有的代碼將跑在這個函數內,生存在一個提供私有化的閉包中,它足以使得這些閉包中的變數能夠貫穿我們的應用的整個生命周期。
複製代碼 代碼如下:
(function () {
// … all vars and functions are in this scope only
// still maintains access to all globals
}());
注意這對包裹匿名函數的最外層括弧。因為Javascript的語言特性,這對括弧是必須的。在js中由關鍵詞function開頭的語句總是會被認為是函數聲明式。把這段代碼包裹在括弧中就可以讓解釋器知道這是個函數表達式。
全局變數導入
Javascript有一個特性叫做隱式全局變數。無論一個變數名在哪兒被用到了,解釋器會根據作用域鏈來反向找到這個變數的var聲明語句。如果沒有找到var聲明語句,那麼這個變數就會被視為全局變數。如果這個變數用在一句賦值語句中,同時這個變數又不存在時,就會創建出一個全局變數。這意味著在匿名閉包中使用或創建全局變數是很容易的。不幸的是,這會導致寫出的代碼極難維護,因為對於人的直觀感受來說,一眼根本分不清那些是全局的變數。
幸運的是,我們的匿名函數提供了簡單的變通方法。只要將全局變數作為參數傳遞到我們的匿名函數中,就可以得到比隱式全局變數更清晰又快速的代碼了。下面是示例:
複製代碼 代碼如下:
(function ($, YAHOO) {
// now have access to globals jQuery (as $) and YAHOO in this code
}(jQuery, YAHOO));
模塊導出
有時你不僅想要使用全局變數,你還想要聲明它們,以供反覆使用。我們可以很容易地通過導出它們來做到這一點——通過匿名函數的返回值。這樣做將會完成一個基本的模塊化模式雛形,接下來會是一個完整的例子:
複製代碼 代碼如下:
var MODULE = (function () {
var my = {},
privateVariable = 1;
function privateMethod() {
// …
}
my.moduleProperty = 1;
my.moduleMethod = function () {
// …
};
return my;
}());
注意我們已經聲明了一個叫做MODULE的全局模塊,它擁有2個公有的屬性:一個叫做MODULE.moduleMethod的方法和一個叫做MODULE.moduleProperty的變數。另外,它還維護了一個利用匿名函數閉包的、私有的內置狀態。同時,我們可以很容易地導入需要的全局變數,並像之前我們所學到的那樣來使用這個模塊化模式。
進階模式
上面一節所描述的基礎已經足以應對許多情況,現在我們可以將這個模塊化模式進一步的發展,創建更多強大的、可擴展的結構。讓我們從MODULE模塊開始,一一介紹這些進階模式。
放大模式
整個模塊必須在一個文件中是模塊化模式的一個限制。任何一個參與大型項目的人都會明白將js拆分多個文件的價值。幸運的是,我們擁有一個很棒的實現來放大模塊。首先,我們導入一個模塊,並為它添加屬性,最後再導出它。下面是一個例子——從原本的MODULE中放大它:
複製代碼 代碼如下:
var MODULE = (function (my) {
my.anotherMethod = function () {
// added method…
};
return my;
}(MODULE));
我們用var關鍵詞來保證一致性,雖然它在此處不是必須的。在這段代碼執行完之後,我們的模塊就已經擁有了一個新的、叫做MODULE.anotherMethod的公有方法。這個放大文件也會維護它自己的私有內置狀態和導入的對象。
寬放大模式
我們的上面例子需要我們的初始化模塊最先被執行,然後放大模塊才能執行,當然有時這可能也不一定是必需的。Javascript應用可以做到的、用來提升性能的、最棒的事之一就是非同步執行腳本。我們可以創建靈活的多部分模塊並通過寬放大模式使它們可以以任意順序載入。每一個文件都需要按下面的結構組織:
複製代碼 代碼如下:
var MODULE = (function (my) {
// add capabilities…
return my;
}(MODULE || {}));
在這個模式中,var表達式使必需的。注意如果MODULE還未初始化過,這句導入語句會創建MODULE。這意味著你可以用一個像LABjs的工具來並行載入你所有的模塊文件,而不會被阻塞。
緊放大模式
寬放大模式非常不錯,但它也會給你的模塊帶來一些限制。最重要的是,你不能安全地覆蓋模塊的屬性。你也無法在初始化的時候,使用其他文件中的屬性(但你可以在運行的時候用)。緊放大模式包含了一個載入的順序序列,並且允許覆蓋屬性。這兒是一個簡單的例子(放大我們的原始MODULE):
複製代碼 代碼如下:
var MODULE = (function (my) {
var old_moduleMethod = my.moduleMethod;
my.moduleMethod = function () {
// method override, has access to old through old_moduleMethod…
};
return my;
}(MODULE));
我們在上面的例子中覆蓋了MODULE.moduleMethod的實現,但在需要的時候,可以維護一個對原來方法的引用。
克隆與繼承
複製代碼 代碼如下:
var MODULE_TWO = (function (old) {
var my = {},
key;
for (key in old) {
if (old.hasOwnProperty(key)) {
my[key] = old[key];
}
}
var super_moduleMethod = old.moduleMethod;
my.moduleMethod = function () {
// override method on the clone, access to super through super_moduleMethod
};
return my;
}(MODULE));
這個模式可能是最缺乏靈活性的一種選擇了。它確實使得代碼顯得很整潔,但那是用靈活性的代價換來的。正如我上面寫的這段代碼,如果某個屬性是對象或者函數,它將不會被複制,而是會成為這個對象或函數的第二個引用。修改了其中的某一個就會同時修改另一個(譯者註:因為它們根本就是一個啊!)。這可以通過遞歸克隆過程來解決這個對象克隆問題,但函數克隆可能無法解決,也許用eval可以解決吧。因此,我在這篇文章中講述這個方法僅僅是考慮到文章的完整性。
跨文件私有變數
把一個模塊分到多個文件中有一個重大的限制:每一個文件都維護了各自的私有變數,並且無法訪問到其他文件的私有變數。但這個問題是可以解決的。這裡有一個維護跨文件私有變數的、寬放大模塊的例子:
複製代碼 代碼如下:
var MODULE = (function (my) {
var _private = my._private = my._private || {},
_seal = my._seal = my._seal || function () {
delete my._private;
delete my._seal;
delete my._unseal;
},
_unseal = my._unseal = my._unseal || function () {
my._private = _private;
my._seal = _seal;
my._unseal = _unseal;
};
// permanent access to _private, _seal, and _unseal
return my;
}(MODULE || {}));
所有文件可以在它們各自的_private變數上設置屬性,並且它理解可以被其他文件訪問。一旦這個模塊載入完成,應用程序可以調用MODULE._seal()來防止外部對內部_private的調用。如果這個模塊需要被重新放大,在任何一個文件中的內部方法可以在載入新的文件前調用_unseal(),並在新文件執行好以後再次調用_seal()。我如今在工作中使用這種模式,而且我在其他地方還沒有見過這種方法。我覺得這是一種非常有用的模式,很值得就這個模式本身寫一篇文章。
子模塊
我們的最後一種進階模式是顯而易見最簡單的。創建子模塊有許多優秀的實例。這就像是創建一般的模塊一樣:
複製代碼 代碼如下:
MODULE.sub = (function () {
var my = {};
// …
return my;
}());
雖然這看上去很簡單,但我覺得還是值得在這裡提一提。子模塊擁有一切一般模塊的進階優勢,包括了放大模式和私有化狀態。
js函數化模塊模式該怎麼應用?
Function.prototype.method = function (name, func) {//在Function的原型上增加一個函數叫做「method」。該方法會在「類」的原型上增加指定函數名的函數。
if (!this.prototype[name]) {
this.prototype[name] = func;
}
};
Object.method(‘superior’, function (name) {//增加一個超級方法。該方法保存它從原型上繼承來方法。用來模擬面向對象中的super關鍵字。
var that = this,
method = that[name];
return function ( ) {
return method.apply(that, arguments);
};
});
var mammal = function (spec) {//定義一個哺乳動物類型。
var that = {};
that.get_name = function ( ) {//獲取動物名字。
return spec.name;
};
that.says = function ( ) {//動物打招呼的方法。
return spec.saying || ”;
};
return that;
};
//var myMammal = mammal({name: ‘Herb’});//創建一個哺乳動物的實例。
var cat = function (spec) {//定義一個「貓」的類型。
spec.saying = spec.saying || ‘meow’;//為這個類型指定一個「叫聲」,這裡是「喵」
var that = mammal(spec);//創建一個哺乳動物實例。
that.purr = function (n) {//為這個哺乳動物創建一個貓的咕嚕咕嚕聲音的方法「purr」
var i, s = ”;
for (i = 0; i n; i += 1) {
if (s) {
s += ‘-‘;
}
s += ‘r’;
}
return s;
};
that.get_name = function ( ) {//獲取「貓」的名字。貓的叫聲(meow) + 貓的名字 + 貓的叫聲(meow)
return that.says( ) + ‘ ‘ + spec.name +
‘ ‘ + that.says( );
}
return that;
};
var myCat = cat({name: ‘Henrietta’});//創建一隻貓的實例。名字叫Henrietta
var coolcat = function (spec) {//創建一個「酷貓」的類型
var that = cat(spec),//創建一隻普通貓。
super_get_name = that.superior(‘get_name’);//保存普通貓的「獲取名字」的方法。
that.get_name = function (n) {//酷貓有自己的「獲取名字」的方法,「like」 + 普通貓的「獲取名字方法」 + 「baby」
return ‘like ‘ + super_get_name( ) + ‘ baby’;
};
return that;
};
var myCoolCat = coolcat({name: ‘Bix’});//創建一隻酷貓。
var name = myCoolCat.get_name( );//酷貓的名字是like meow Bix meow baby
// ‘like meow Bix meow baby’
上面採用prototype(原型擴展)的方式來實現「類」的繼承,如,酷貓 繼承 貓 繼承 哺乳動物。
而下面採用了另外一種思路:使用工廠模式來創建對象,不是通過復用原型。而是拆分零部件。每個部件都是可拆卸,可組裝的方式,來達到另一種代碼復用的效果。
function createCar(numberOfDoors){//造車廠,指定門的數量,並製造一輛汽車。
var numberOfWheels = 4;//輪子的數量是4.
function describe(){//描述一下車輛。
return “I have ” + numberOfWheels + ” wheels and ” + numberOfDoors + ” doors.”;
}
return {//返回製造完成的汽車。
describe: describe
};
}
function createOdometer(){//加工車載里程計。
var mileage = 0;
function increment(numberOfMiles){ mileage += numberOfMiles;}//里程計跑表的方法
function report(){ return mileage; }//報告當前的里程數
return {//返回製造完成的里程計
increment: increment,
report: report
}
}
function createCarWithOdometer(numberOfDoors){//製造帶有里程計的汽車
var odometer = createOdometer();//製造一個里程計
var car = createCar(numberOfDoors);//製造一輛車
car.drive = function(numberOfMiles){//將里程計安裝到汽車上,當汽車行駛的過程中,調用里程計的跑表方法。
odometer.increment(numberOfMiles);
}
car.mileage = function(){//報告汽車的里程,通過已安裝的里程計的報告方法,獲取里程。
return “car has driven ” + odometer.report() + ” miles”;
}
return car;//返回製造並組裝完畢的汽車。
}
var twoFn=createCarWithOdometer(100);//創造一輛帶有100個門的汽車(當然不可能。。Σ( ° △ °|||)︴)
console.log(twoFn);
上述兩種方式,體現了javascript的精妙之處。
在面向對象語言中:
採用了類繼承的方式,實現代碼復用。
在javascript中,並沒有真正的類。而是用「function」來代表一種類型,
比如:
function Person(){};
我們可以認為,Person就是所謂的javascript中的「類」的概念。我們可以這樣
var p = new Person();
alert(p instanceof Person);//結果是true,因為p就是Person類型的一個實例。
alert(p instanceof Object);//結果也是true,因為所有實例終歸都是Object。
而每個類型是如果擴展自己的新方法的呢?就是通過類型的prototype這個屬性。
只有function才有prototype屬性。
比如:
function Person(){}
Person.prototype.say = function(){alert(‘hello’);};//人這種類型有打招呼的方法
var p1 = new Person();
var p2 = new Person();
p1.say();//hello
p2.say();//hello
這樣,所有實例就都可以用打招呼的方法了。
當我們創建一個新的類型「男人」。想要繼承Person的話。就像下面這樣:
function Man(){}
var proto = Man.prototype = new Person();//男人也是人啊,男人的原型就是以一個普通人為標準來參考的,所以我們創建一個人類,賦值給男人,讓男人來學習,參考人的特性。
proto.fight = function(){//男人比起人來,脾氣大,因此有個打架的方法。
alert(‘I\’ll kick your ass!!’);
}
var m = new Man();
m.say();//hello
m.fight();//I’ll kick your ass.這就是傳說中的一句話說不上來就開打。。。
原型繼承的核心就是這樣的。
而另外一種思路就是工廠模式。
小汽車廠
輪子廠
里程計廠
卡車廠
小汽車廠需要輪子和里程計,分別向「輪子廠」和「里程計廠」要來了兩個現成的產品,直接安裝好,就完成了一輛小汽車。
卡車長也是一樣的。所以這種方式也是一種很好的代碼結構。
常用NodeJS中的模塊
五大核心模塊:
http—-開啟一個Web服務,給瀏覽器提供服務
url—-給瀏覽器發送請求用,還可以傳遞參數(GET)
querystring—-處理瀏覽器通過GET/POST發送過來的參數
path—-查找文件的路徑
fs—-在伺服器端讀取文件用的
自定義模塊:
一個.js文件就可以看成一個模塊,如果我們想把模塊中定義的變數,方法,對象給外面的js使用,就必須提供module將我們需要給外面用的東西,導出去。
導入模塊用 require,在模塊中導出 使用module.exports。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/285872.html