首頁  >  文章  >  web前端  >  JavaScript的模組化:封裝(閉包),繼承(原型) 介紹_javascript技巧

JavaScript的模組化:封裝(閉包),繼承(原型) 介紹_javascript技巧

WBOY
WBOY原創
2016-05-16 17:28:171051瀏覽

雖然 JavaScript 天生就是一副隨便的樣子,但是隨著瀏覽器能夠完成的事情越來越多,這門語言也越來越經常地擺出正襟危坐的架勢。在複雜的邏輯下, JavaScript 需要被模組化,模組需要封裝起來,只留下供外界呼叫的介面。閉包是 JavaScript 中實作模組封裝的關鍵,也是許多初學者難以理解的要點。最初,我也陷入迷惑之中。現在,我自信對這個概念已經有了比較深入的理解。為了便於理解,文中試圖封裝一個比較簡單的物件。

我們試著在頁面上維護一個計數器物件 ticker ,這個物件維護一個數值 n 。隨著使用者的操作,我們可以增加一次計數(將數值 n 加上 1 ),但不能減少 n 或直接改變 n 。而且,我們需要時不時查詢這個數值。

門戶大開的 JSON 風格模組化

一個入口網站大開的方式是:

複製程式碼 程式碼如下:


程式碼如下:



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

 >

這種方式書寫自然,而且確實有效,我們需要增加一次計數時,就調用 ticker.tick() 方法,需要查詢次數時,就訪問 ticker.n 變數。但其缺點也是顯而易見的:模組的使用者被允許自由地改變 n ,例如呼叫 ticker.n-- 或 ticker.n=-1 。我們並沒有對 ticker 進行封裝, n 和 tick() 看上去是 ticker 的“成員”,但是它們的可訪問性和 ticker 一樣,都是全局性的(如果 ticker 是全局變量的話)。在封裝性上,這種模組化的方式比下面這種更可笑的方式,只好那麼一點點(雖然對有些簡單的應用來說,這一點點也足夠了)。

程式碼如下:


var ticker = {};


var ticker = {};

; 0;
var tickerTick = function(){

    tickerN ;}

tickerTick();

複製程式碼


程式碼如下:

var func = ticker.tick;

; );

複製程式碼


程式碼如下:

var config = { 🎜>    step:2

}

作用域鍊和閉包來看下面的程式碼,注意我們已經實作了傳入 config 對 ticker 進行自訂。
複製程式碼


程式碼如下:


function tipconfig){


function tipconfig){
  config.nStart;    function tick(){        n = config.step;    }}cons.log(ticker.n); 🎜>

你或許會疑惑,怎麼 ticker 從物件變成函數了?這是因為 JavaScript 中只有函數具有作用域,從函數體外無法存取函數內部的變數。 ticker() 外訪問 ticker.n 獲得 undefined ,而 tick() 內訪問 n 卻沒有問題。從 tick() 到 ticker() 再到全域,這就是 JavaScript 中的「作用域鏈」。

可是還有問題,那就是--怎麼呼叫 tick() ? ticker() 的作用域將 tick() 也掩蓋了起來。解決方法有兩種:

•1)將需要呼叫方法作為傳回值,正如我們將遞增n 的方法作為ticker() 的回傳值;
•2)設定外層作用域的變量,正如我們在ticker()中設定getN 。

複製程式碼 程式碼如下:

var getN;function cker(config)(config)
    var n = config.nStart;
    getN = function(){
        return n; };
}

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



請看,這時,變數 n 就處在「閉包」之中,在 ticker() 外部無法直接存取它,但是卻可以透過兩個方法來觀察或操縱它。

在本節第一段程式碼中, ticker() 方法執行之後, n 和tick() 就被銷毀了,直到下一次呼叫該函數時再建立;但是在第二段程式碼中,ticker()執行之後, n 不會被銷毀,因為tick() 和getN() 可能存取它或改變它,瀏覽器會負責維持n。我對「閉包」的理解就是:用以確保 n 這種處在函數作用域內,函數執行結束後仍需維持,可能被透過其他方式存取的變數 不被銷毀的機制。

可是,我還是覺得不大對勁?如果我需要維持兩個具有相同功能的物件 ticker1 和 ticker2 ,那該怎麼辦? ticker() 只有一個,總不能再寫一次吧?

new 運算子與建構子

如果透過 new 運算子呼叫一個函數,就會建立一個新的對象,並使用該物件呼叫這個函數。在我的理解中,下面的程式碼中 t1 和 t2 的構造過程是一樣的。


複製程式碼

程式碼如下:function myClass(){} new myClass();
var t2 = {};
t2.func = myClass;
t2.func();
t2.func = undefined;




t1 和 t2 都是新建構的對象, myClass() 就是建構子了。類似的, ticker() 可以重新寫成。

複製程式碼 程式碼如下:function TICKER(config){

function TICKER(config){
  config.nStart;
    this.getN = function(){
        return 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.tick();
ticker2.tick();
console.log(ticker2.getN()); // ->26

習慣上,建構函式採用大寫。注意, TICKER() 仍然是個函數,而不是個純粹的對象(之所以說“純粹”,是因為函數實際上也是對象, TICKER() 是函數對象),閉包依舊有效,我們無法訪問ticker1.n 。

原型 prototype 與繼承
上面這個 TICKER() 還是有缺陷,那就是, ticker1.tick() 和 ticker2.tick() 是互相獨立的!請看,每使用new 運算子呼叫TICKER() ,就會產生一個新的物件並產生一個新的函式綁定在這個新的物件上,每建構一個新的對象,瀏覽器就要開闢一塊空間,儲存tick() 本身和tick() 中的變量,這不是我們所期望的。我們期望 ticker1.tick 和 ticker2.tick 指向同一個函數物件。

這就需要引進原型。

JavaScript 中,除了 Object 對象,其他物件都有一個 prototype 屬性,這個屬性指向另一個物件。這「另一個對象」依舊有其原型對象,並形成原型鏈,最終指向 Object 對象。在某個物件上呼叫某方法時,如果發現這個物件沒有指定的方法,那就在原型鏈上一次找這個方法,直到 Object 物件。

函數也是對象,因此函數也有原型對象。當一個函數被宣告出來時(也就是當函數物件被定義出來時),就會產生一個新的對象,這個物件的 prototype 屬性指向 Object 對象,而且這個物件的 constructor 屬性指向函數物件。

透過建構函式建構出的新對象,其原型指向建構函式的原型對象。所以我們可以在建構函數的原型物件上加入函數,這些函數就不是依賴 ticker1 或 ticker2 ,而是依賴 TICKER 了。

你也許會這樣做:

複製程式碼 程式碼如下:

function TICKER(config){


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

請注意,這是無效的實作。因為原型物件的方法不能存取閉包中的內容,也就是變數 n 。 TICK() 方法運作之後無法再存取 n ,瀏覽器會將 n 銷毀。為了存取閉包中的內容,物件必須有一些簡潔的依賴實例的方法,來存取閉包中的內容,然後在其 prototype 上定義複雜的公有方法來實現邏輯。實際上,例子中的 tick() 方法就已經夠簡潔了,我們還是把它放回 TICKER 中吧。下面實作一個複雜些的方法 tickTimes() ,它將允許呼叫者指定呼叫 tick() 的次數。

複製程式碼
程式碼如下:


function TICKER(config){


function TICKER(config){
  config.nStart;
    this.getN = function(){
        return n;
    };    } ;
}
TICKER.prototype.tickTimes = function(n){
    while(n>0){
       🎜>};
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

這個 TICKER 就很好了。它封裝了 n ,從物件外部無法直接改變它,而複雜的函數 tickTimes() 被定義在原型上,這個函數透過呼叫實例的小函數來操作物件中的資料。

所以,為了維持物件的封裝性,我的建議是,將對資料的操作解耦為盡可能小的單元函數,在建構函數中定義為依賴實例的(很多地方也稱之為“私有」的),而將複雜的邏輯實現在原型上(即「公有」的)。

最後再說一些關於繼承的話。實際上,當我們在原型上定義函數時,我們就已經使用了繼承! JavaScript 中的繼承比 C 中的更……呃……簡單,或者說簡陋。在C 中,我們可能會定義一個animal 類別表示動物,然後再定義bird 類別繼承animal 類別表示鳥類,但我想討論的不是這樣的繼承(雖然這樣的繼承在JavaScript 中也可以實現);我想討論的繼承在C 中將是,定義一個animal 類,然後實例化了一個myAnimal 物件。對,這在 C 裡就是實例化,但在 JavaScript 中是被當作繼承來對待的。

JavaScript 並不支援類別,瀏覽器只管目前有哪些對象,而不會額外費心去管,這些對像是什麼 class 的,應該具有怎樣的結構。在我們的例子中, TICKER() 是個函數對象,我們可以對其賦值(TICKER=1),將其刪除(TICKER=undefined),但是正因為目前有ticker1 和ticker2 兩個物件是透過new 運算符呼叫它而來的, TICKER() 就扮演了建構子的作用,而TICKER.prototype 對象,也就扮演了類別的作用。

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

作者:一葉齋主人
來源:www.cnblogs.com/yiyezhai

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn