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

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

May 16, 2016 pm 05:28 PM
javascript 封裝 模組化 繼承 閉包

雖然 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

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1667
14
CakePHP 教程
1426
52
Laravel 教程
1328
25
PHP教程
1273
29
C# 教程
1255
24
C++ 函式繼承詳解:如何在繼承中使用「基底類別指標」和「衍生類別指標」? C++ 函式繼承詳解:如何在繼承中使用「基底類別指標」和「衍生類別指標」? May 01, 2024 pm 10:27 PM

在函數繼承中,使用「基底類別指標」和「衍生類別指標」來理解繼承機制:基底類別指標指向派生類別物件時,執行向上轉型,只存取基底類別成員。派生類別指標指向基底類別物件時,執行向下轉型(不安全),必須謹慎使用。

C++ lambda 表達式中閉包的意思是什麼? C++ lambda 表達式中閉包的意思是什麼? Apr 17, 2024 pm 06:15 PM

在C++中,閉包是能夠存取外部變數的lambda表達式。若要建立閉包,請擷取lambda表達式中的外部變數。閉包提供可重複使用性、資訊隱藏和延遲求值等優點。它們在事件處理程序等實際情況中很有用,其中即使外部變數被銷毀,閉包仍然可以存取它們。

集邦諮詢:英偉達 Blackwell 平台產品帶動台積電今年 CoWoS 產能提升 150% 集邦諮詢:英偉達 Blackwell 平台產品帶動台積電今年 CoWoS 產能提升 150% Apr 17, 2024 pm 08:00 PM

本站4月17日消息,集邦諮詢(TrendForce)近日發布報告,認為英偉達Blackwell新平台產品需求看漲,預估帶動台積電2024年CoWoS封裝總產能提升逾150%。英偉達Blackwell新平台產品包含B系列的GPU,以及整合英偉達自家GraceArmCPU的GB200加速卡等。集邦諮詢確認為供應鏈目前非常看好GB200,預估2025年出貨量預計超過百萬片,在英偉達高階GPU中的佔比達到40-50%。在英偉達計畫下半年交付GB200以及B100等產品,但上游晶圓封裝方面須進一步採用更複

C++ 函式中閉包的優點和缺點是什麼? C++ 函式中閉包的優點和缺點是什麼? Apr 25, 2024 pm 01:33 PM

閉包是一種巢狀函數,它能存取外層函數作用域的變量,優點包括資料封裝、狀態保持和靈活性。缺點包括記憶體消耗、效能影響和調試複雜性。此外,閉包還可以建立匿名函數,並將其作為回調或參數傳遞給其他函數。

C++ Lambda 表達式如何實作閉包? C++ Lambda 表達式如何實作閉包? Jun 01, 2024 pm 05:50 PM

C++Lambda表達式支援閉包,即保存函數作用域變數並供函數存取。語法為[capture-list](parameters)->return-type{function-body}。 capture-list定義要捕獲的變量,可以使用[=]按值捕獲所有局部變量,[&]按引用捕獲所有局部變量,或[variable1,variable2,...]捕獲特定變量。 Lambda表達式只能存取捕獲的變量,但無法修改原始值。

C++ 函式繼承詳解:如何偵錯繼承中出現的錯誤? C++ 函式繼承詳解:如何偵錯繼承中出現的錯誤? May 02, 2024 am 09:54 AM

繼承錯誤調試技巧:確保正確的繼承關係。使用偵錯器逐步執行程式碼,檢查變數值。確保正確使用virtual修飾符。檢查隱藏的繼承帶來的菱形繼承問題。檢查抽象類別中未實現的純虛函數。

AMD 'Strix Halo” FP11 封裝尺寸曝光:和英特爾 LGA1700 相當,比 Phoenix 大 60% AMD 'Strix Halo” FP11 封裝尺寸曝光:和英特爾 LGA1700 相當,比 Phoenix 大 60% Jul 18, 2024 am 02:04 AM

本站7月9日訊息,AMDZen5架構「Strix」系列處理器會有兩種封裝方案,其中較小的StrixPoint將採用FP8封裝,而StrixHalo將會採用FP11封裝。圖源:videocardz訊息源@Olrak29_最新曝料稱StrixHalo的FP11封裝尺寸為37.5mm*45mm(1687平方毫米),和英特爾AlderLake、RaptorLakeCPU的LGA-1700封裝尺寸相同。 AMD最新的PhoenixAPU採用FP8封裝方案,尺寸為25*40mm,這意味著StrixHalo的F

C++ 函式如何透過封裝程式碼來提高 GUI 開發的效率? C++ 函式如何透過封裝程式碼來提高 GUI 開發的效率? Apr 25, 2024 pm 12:27 PM

透過封裝程式碼,C++函數可以提高GUI開發效率:程式碼封裝:函數將程式碼分組到獨立單元,使程式碼易於理解和維護。可重複使用性:函數可建立通用功能供應用程式中重複使用,減少重複編寫和錯誤。簡潔程式碼:封裝程式碼讓主邏輯簡潔,方便閱讀和除錯。

See all articles