国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 編程 > JavaScript > 正文

JavaScript的模塊化:封裝(閉包),繼承(原型) 介紹

2019-11-20 22:30:08
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

雖然 JavaScript 天生就是一副隨隨便便的樣子,但是隨著瀏覽器能夠完成的事情越來(lái)越多,這門語(yǔ)言也也越來(lái)越經(jīng)常地?cái)[出正襟危坐的架勢(shì)。在復(fù)雜的邏輯下, JavaScript 需要被模塊化,模塊需要封裝起來(lái),只留下供外界調(diào)用的接口。閉包是 JavaScript 中實(shí)現(xiàn)模塊封裝的關(guān)鍵,也是很多初學(xué)者難以理解的要點(diǎn)。最初,我也陷入迷惑之中。現(xiàn)在,我自信對(duì)這個(gè)概念已經(jīng)有了比較深入的理解。為了便于理解,文中試圖封裝一個(gè)比較簡(jiǎn)單的對(duì)象。

我們?cè)噲D在頁(yè)面上維護(hù)一個(gè)計(jì)數(shù)器對(duì)象 ticker ,這個(gè)對(duì)象維護(hù)一個(gè)數(shù)值 n 。隨著用戶的操作,我們可以增加一次計(jì)數(shù)(將數(shù)值 n 加上 1 ),但不能減少 n 或直接改變 n 。而且,我們需要時(shí)不時(shí)查詢這個(gè)數(shù)值。

門戶大開的 JSON 風(fēng)格模塊化

一種門戶大開的方式是:

復(fù)制代碼 代碼如下:

var ticker = {
    n:0,
    tick:function(){
        this.n++;
    },
};

這種方式書寫自然,而且確實(shí)有效,我們需要增加一次計(jì)數(shù)時(shí),就調(diào)用 ticker.tick() 方法,需要查詢次數(shù)時(shí),就訪問(wèn) ticker.n 變量。但是其缺點(diǎn)也是顯而易見的:模塊的使用者被允許自由地改變 n ,比如調(diào)用 ticker.n-- 或者 ticker.n=-1 。我們并沒(méi)有對(duì) ticker 進(jìn)行封裝, n 和 tick() 看上去是 ticker 的“成員”,但是它們的可訪問(wèn)性和 ticker 一樣,都是全局性的(如果 ticker 是全局變量的話)。在封裝性上,這種模塊化的方式比下面這種更加可笑的方式,只好那么一點(diǎn)點(diǎn)(雖然對(duì)有些簡(jiǎn)單的應(yīng)用來(lái)說(shuō),這一點(diǎn)點(diǎn)也足夠了)。

復(fù)制代碼 代碼如下:

var ticker = {};
var tickerN = 0;
var tickerTick = function(){
    tickerN++;
}

tickerTick();

值得注意的是,在 tick() 中,我訪問(wèn)的是 this.n ――這并不是因?yàn)?n 是 ticker 的成員,而是因?yàn)檎{(diào)用 tick() 的是 ticker 。事實(shí)上這里寫成 ticker.n 會(huì)更好,因?yàn)槿绻{(diào)用 tick() 的不是 ticker ,而是其他什么東西,比如:

復(fù)制代碼 代碼如下:

var func = ticker.tick;
func();

這時(shí),調(diào)用 tick() 的其實(shí)是 window ,而函數(shù)執(zhí)行時(shí)會(huì)試圖訪問(wèn) window.n 而出錯(cuò)。

事實(shí)上,這種“門戶大開”型的模塊化方式,往往用來(lái)組織 JSON 風(fēng)格的數(shù)據(jù),而不是程序。比如,我們可以將下面這個(gè) JSON 對(duì)象傳給 ticker 的某個(gè)函數(shù),來(lái)確定 ticker 從 100 開始計(jì)數(shù),每次遞進(jìn) 2 。

復(fù)制代碼 代碼如下:

var config = {
    nStart:100,
    step:2
}

作用域鏈和閉包
來(lái)看下面的代碼,注意我們已經(jīng)實(shí)現(xiàn)了傳入 config 對(duì) ticker 進(jìn)行自定義。

復(fù)制代碼 代碼如下:

function ticker(config){
    var n = config.nStart;
    function tick(){
        n += config.step;
    }
}
console.log(ticker.n); // ->undefined

你也許會(huì)疑惑,怎么 ticker 從對(duì)象變成了函數(shù)了?這是因?yàn)?JavaScript 中只有函數(shù)具有作用域,從函數(shù)體外無(wú)法訪問(wèn)函數(shù)內(nèi)部的變量。 ticker() 外訪問(wèn) ticker.n 獲得 undefined ,而 tick() 內(nèi)訪問(wèn) n 卻沒(méi)有問(wèn)題。從 tick() 到 ticker() 再到全局,這就是 JavaScript 中的“作用域鏈”。

可是還有問(wèn)題,那就是――怎么調(diào)用 tick() ? ticker() 的作用域?qū)?tick() 也掩蓋了起來(lái)。解決方法有兩種:

•1)將需要調(diào)用方法作為返回值,正如我們將遞增 n 的方法作為 ticker() 的返回值;
•2)設(shè)定外層作用域的變量,正如我們?cè)?ticker() 中設(shè)置 getN 。

復(fù)制代碼 代碼如下:

var getN;
function ticker(config){
    var n = config.nStart;
    getN = function(){
        return n;
    };
    return function(){
        n += config.step;
    };
}

var tick = ticker({nStart:100,step:2});
tick();
console.log(getN()); // ->102

請(qǐng)看,這時(shí),變量 n 就處在“閉包”之中,在 ticker() 外部無(wú)法直接訪問(wèn)它,但是卻可以通過(guò)兩個(gè)方法來(lái)觀察或操縱它。

在本節(jié)第一段代碼中, ticker() 方法執(zhí)行之后, n 和 tick() 就被銷毀了,直到下一次調(diào)用該函數(shù)時(shí)再創(chuàng)建;但是在第二段代碼中, ticker() 執(zhí)行之后, n 不會(huì)被銷毀,因?yàn)?tick() 和 getN() 可能訪問(wèn)它或改變它,瀏覽器會(huì)負(fù)責(zé)維持n。我對(duì)“閉包”的理解就是:用以保證 n 這種處在函數(shù)作用域內(nèi),函數(shù)執(zhí)行結(jié)束后仍需維持,可能被通過(guò)其他方式訪問(wèn)的變量 不被銷毀的機(jī)制。

可是,我還是覺(jué)得不大對(duì)勁?如果我需要維持兩個(gè)具有相同功能的對(duì)象 ticker1 和 ticker2 ,那該怎么辦? ticker() 只有一個(gè),總不能再寫一遍吧?

new 運(yùn)算符與構(gòu)造函數(shù)
如果通過(guò) new 運(yùn)算符調(diào)用一個(gè)函數(shù),就會(huì)創(chuàng)建一個(gè)新的對(duì)象,并使用該對(duì)象調(diào)用這個(gè)函數(shù)。在我的理解中,下面的代碼中 t1 和 t2 的構(gòu)造過(guò)程是一樣的。

復(fù)制代碼 代碼如下:

function myClass(){}
var t1 = new myClass();
var t2 = {};
t2.func = myClass;
t2.func();
t2.func = undefined;

t1 和 t2 都是新構(gòu)造的對(duì)象, myClass() 就是構(gòu)造函數(shù)了。類似的, ticker() 可以重新寫成。

復(fù)制代碼 代碼如下:

function TICKER(config){
    var n = config.nStart;
    this.getN = function(){
        return n;
    };
    this.tick = function(){
        n += config.step;
    }
}

var ticker1 = new TICKER({nStart:100,step:2});
ticker1.tick();
console.log(ticker1.getN()); // ->102
var ticker2 = new TICKER({nStart:20,step:3});
ticker2.tick();
ticker2.tick();
console.log(ticker2.getN()); // ->26

習(xí)慣上,構(gòu)造函數(shù)采用大寫。注意, TICKER() 仍然是個(gè)函數(shù),而不是個(gè)純粹的對(duì)象(之所以說(shuō)“純粹”,是因?yàn)楹瘮?shù)實(shí)際上也是對(duì)象, TICKER() 是函數(shù)對(duì)象),閉包依舊有效,我們無(wú)法訪問(wèn) ticker1.n 。

原型 prototype 與繼承
上面這個(gè) TICKER() 還是有缺陷,那就是, ticker1.tick() 和 ticker2.tick() 是互相獨(dú)立的!請(qǐng)看,每使用 new 運(yùn)算符調(diào)用 TICKER() ,就會(huì)生成一個(gè)新的對(duì)象并生成一個(gè)新的函數(shù)綁定在這個(gè)新的對(duì)象上,每構(gòu)造一個(gè)新的對(duì)象,瀏覽器就要開辟一塊空間,存儲(chǔ) tick() 本身和 tick() 中的變量,這不是我們所期望的。我們期望 ticker1.tick 和 ticker2.tick 指向同一個(gè)函數(shù)對(duì)象。

這就需要引入原型。

JavaScript 中,除了 Object 對(duì)象,其他對(duì)象都有一個(gè) prototype 屬性,這個(gè)屬性指向另一個(gè)對(duì)象。這“另一個(gè)對(duì)象”依舊有其原型對(duì)象,并形成原型鏈,最終指向 Object 對(duì)象。在某個(gè)對(duì)象上調(diào)用某方法時(shí),如果發(fā)現(xiàn)這個(gè)對(duì)象沒(méi)有指定的方法,那就在原型鏈上一次查找這個(gè)方法,直到 Object 對(duì)象。

函數(shù)也是對(duì)象,因此函數(shù)也有原型對(duì)象。當(dāng)一個(gè)函數(shù)被聲明出來(lái)時(shí)(也就是當(dāng)函數(shù)對(duì)象被定義出來(lái)時(shí)),就會(huì)生成一個(gè)新的對(duì)象,這個(gè)對(duì)象的 prototype 屬性指向 Object 對(duì)象,而且這個(gè)對(duì)象的 constructor 屬性指向函數(shù)對(duì)象。

通過(guò)構(gòu)造函數(shù)構(gòu)造出的新對(duì)象,其原型指向構(gòu)造函數(shù)的原型對(duì)象。所以我們可以在構(gòu)造函數(shù)的原型對(duì)象上添加函數(shù),這些函數(shù)就不是依賴于 ticker1 或 ticker2 ,而是依賴于 TICKER 了。

你也許會(huì)這樣做:

復(fù)制代碼 代碼如下:

function TICKER(config){
    var n = config.nStart;
}
TICKER.prototype.getN = function{
    // attention : invalid implementation
    return n;
};
TICKER.prototype.tick = function{
    // attention : invalid implementation
    n += config.step;
};

請(qǐng)注意,這是無(wú)效的實(shí)現(xiàn)。因?yàn)樵蛯?duì)象的方法不能訪問(wèn)閉包中的內(nèi)容,也就是變量 n 。 TICK() 方法運(yùn)行之后無(wú)法再訪問(wèn)到 n ,瀏覽器會(huì)將 n 銷毀。為了訪問(wèn)閉包中的內(nèi)容,對(duì)象必須有一些簡(jiǎn)潔的依賴于實(shí)例的方法,來(lái)訪問(wèn)閉包中的內(nèi)容,然后在其 prototype 上定義復(fù)雜的公有方法來(lái)實(shí)現(xiàn)邏輯。實(shí)際上,例子中的 tick() 方法就已經(jīng)足夠簡(jiǎn)潔了,我們還是把它放回到 TICKER 中吧。下面實(shí)現(xiàn)一個(gè)復(fù)雜些的方法 tickTimes() ,它將允許調(diào)用者指定調(diào)用 tick() 的次數(shù)。

復(fù)制代碼 代碼如下:

function TICKER(config){
    var n = config.nStart;
    this.getN = function(){
        return n;
    };
    this.tick = function(){
        n += config.step;
    };
}
TICKER.prototype.tickTimes = function(n){
    while(n>0){
        this.tick();
        n--;
    }
};
var ticker1 = new TICKER({nStart:100,step:2});
ticker1.tick();
console.log(ticker1.getN()); // ->102
var ticker2 = new TICKER({nStart:20,step:3});
ticker2.tickTimes(2);
console.log(ticker2.getN()); // ->26

這個(gè) TICKER 就很好了。它封裝了 n ,從對(duì)象外部無(wú)法直接改變它,而復(fù)雜的函數(shù) tickTimes() 被定義在原型上,這個(gè)函數(shù)通過(guò)調(diào)用實(shí)例的小函數(shù)來(lái)操作對(duì)象中的數(shù)據(jù)。

所以,為了維持對(duì)象的封裝性,我的建議是,將對(duì)數(shù)據(jù)的操作解耦為盡可能小的單元函數(shù),在構(gòu)造函數(shù)中定義為依賴于實(shí)例的(很多地方也稱之為“私有”的),而將復(fù)雜的邏輯實(shí)現(xiàn)在原型上(即“公有”的)。

最后再說(shuō)一些關(guān)于繼承的話。實(shí)際上,當(dāng)我們?cè)谠蜕隙x函數(shù)時(shí),我們就已經(jīng)用到了繼承! JavaScript 中的繼承比 C++ 中的更……呃……簡(jiǎn)單,或者說(shuō)簡(jiǎn)陋。在 C++ 中,我們可能會(huì)定義一個(gè) animal 類表示動(dòng)物,然后再定義 bird 類繼承 animal 類表示鳥類,但我想討論的不是這樣的繼承(雖然這樣的繼承在 JavaScript 中也可以實(shí)現(xiàn));我想討論的繼承在 C++ 中將是,定義一個(gè) animal 類,然后實(shí)例化了一個(gè) myAnimal 對(duì)象。對(duì),這在 C++ 里就是實(shí)例化,但在 JavaScript 中是作為繼承來(lái)對(duì)待的。

JavaScript 并不支持類,瀏覽器只管當(dāng)前有哪些對(duì)象,而不會(huì)額外費(fèi)心思地去管,這些對(duì)象是什么 class 的,應(yīng)該具有怎樣的結(jié)構(gòu)。在我們的例子中, TICKER() 是個(gè)函數(shù)對(duì)象,我們可以對(duì)其賦值(TICKER=1),將其刪掉(TICKER=undefined),但是正因?yàn)楫?dāng)前有 ticker1 和 ticker2 兩個(gè)對(duì)象是通過(guò) new 運(yùn)算符調(diào)用它而來(lái)的, TICKER() 就充當(dāng)了構(gòu)造函數(shù)的作用,而 TICKER.prototype 對(duì)象,也就充當(dāng)了類的作用。

以上就是我所了解的 JavaScript 模塊化的方法,如果您也是初學(xué)者,希望能對(duì)您有所幫助。如果有不對(duì)的地方,也勞駕您指出。

作者:一葉齋主人
出處:www.cnblogs.com/yiyezhai

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 吉林市| 周至县| 钟祥市| 大姚县| 北碚区| 平定县| 汝城县| 积石山| 宁安市| 柘城县| 交城县| 新昌县| 北票市| 泉州市| 桂东县| 塘沽区| 固原市| 张家川| 赤壁市| 崇义县| 政和县| 申扎县| 海宁市| 黄石市| 土默特右旗| 巴彦淖尔市| 耒阳市| 开鲁县| 朔州市| 泾川县| 大宁县| 集安市| 泰宁县| 灵丘县| 南开区| 临武县| 合作市| 呼伦贝尔市| 合水县| 日土县| 宜川县|