JavaScript面向對象輕鬆入門之封裝(demo by ES5、ES6、TypeScript)

来源:http://www.cnblogs.com/elonhu/archive/2017/06/18/7044975.html
-Advertisement-
Play Games

封裝(Encapsulation)就是把對象的內部屬性和方法隱藏起來,外部代碼訪問該對象只能通過特定的介面訪問,這也是面向介面編程思想的一部分。 ...


  本章預設大家已經看過作者的前一篇文章 JavaScript面向對象輕鬆入門之抽象》

為什麼要封裝?

  封裝(Encapsulation)就是把對象的內部屬性和方法隱藏起來,外部代碼訪問該對象只能通過特定的介面訪問,這也是面向介面編程思想的一部分。

  封裝是面向對象編程里非常重要的一部分,讓我們來看看沒有封裝的代碼是什麼樣的:

1     function Dog(){
2         this.hairColor = '白色';//string
3         this.breed = '貴賓';//string
4         this.age = 2;//number
5     }
6     var dog = new Dog();
7     console.log(dog.breed);//log: '貴賓'

 

  看似沒有什麼問題,但如果breed屬性名修改了怎麼辦?比如換成this.type = ‘貴賓’,那所有使用Dog類的代碼都要改變。

  如果類的代碼和使用類的代碼都是你寫的,並且使用這個類的地方不多,你這麼寫無所謂。

  但如果使用這個類的地方比較多,或者協同開發時其它人還要使用你的類,那這樣做就會讓代碼很難維護,正確的做法是:

 1     function Dog(){
 2         this.hairColor = '白色';//string
 3         this.age = 2;//number
 4         this._breed = '貴賓';//string
 5     }
 6     Dog.prototype.getBreed = function(){
 7         return this._breed;
 8     }
 9     Dog.prototype.setBreed = function(val){
10         this._breed = val;
11     }
12     var dog = new Dog();
13     console.log(dog.getBreed());//log: '貴賓'
14     dog.setBreed('土狗');

 

  getBreed()就是介面,如果內部的屬性變化了,比如breed換成了type ,那隻需要改變getBreed()里的代碼就可以了,並且你可以監聽到所有獲取這個屬性的操作。

  所以封裝有很多好處:

  1、只要介面不改變,內部的實現可以任意改變;

  2、使用者使用起來很方便,不用關係內部是如何實現;

  3、降低代碼之間的耦合;

  4、滿足大型應用程式和多人協同開發;

getter/setter來封裝私有屬性

  其實還有另一種封裝屬性的方法,那就是用getter/setter,如下demo,本章不講原理,只講使用,原理可自行查資料:

 1     function Dog(){
 2         this.hairColor = '白色';//string
 3         this.age = 2;//number
 4         this._breed = '貴賓';//string
 5         Object.defineProperty(this, 'breed', {//傳入this和屬性名
 6             get : function () {
 7                 console.log('監聽到了有人調用這個get breed')
 8                 return this._breed;
 9             },
10             set : function (val) {
11                 this._breed = val;
12                 /*
13                 如果不設置setter的話預設這個屬性是不可設置的
14                 但有點讓人詬病的是,瀏覽器並不會報錯
15                 所以即使你想讓breed是只讀的,你也應該設置一個setter讓其拋出錯誤:
16                 throw 'attribute "breed"  is read only!';
17                 */
18             }
19         });
20     }
21     var dog = new Dog();
22     console.log(dog.breed);
23     /*log:
24         '監聽到了有人調用這個get breed介面'
25         '貴賓'
26     */
27     dog.breed = '土狗';
28     console.log(dog.breed);
29     /*log:
30         '監聽到了有人調用這個get breed介面'
31         '土狗'
32     */

 

  但這種方法寫起來比較繁瑣,作者一般是用getBreed()這種方法,getter/setter一般用在readonly的屬性和一些比較重要的介面,以及重構沒有封裝介面的屬性操作。

  還可以用閉包封裝私有屬性,是最安全的,但會產生額外的記憶體開銷,所以作者不是很喜歡用,大家可自行瞭解。

公有/私有概念

  前兩小節我們簡單的瞭解了下封裝,但這些肯定是不夠用的,下麵的我們先來瞭解下幾個概念:

  私有屬性:即只能在類的內部調獲取、修改的屬性,不允許外部訪問。

  私有方法:僅供類內部調用的方法,禁止外部調用。

  公有屬性:可供類外部獲取、修改的屬性。理論上講類的所有屬性都應該是私有屬性,只能通過封裝的介面訪問,但一些比較小的類,或者使用次數比較少的類,你覺得比較方便的話也可以不封裝介面。

  公有方法:可供外部調用的方法,實現介面的方法如getBreed()就是公有方法,以及對外暴露的行為方法。

  靜態屬性、靜態方法:類本身的屬性和方法。這個就沒必要區分公有私有了,所有的靜態屬性、靜態方法都必須是私有的,一定要通過封裝介面訪問,這也是上一章中作者為什麼要用getInstanceNumber()來訪問Dog.instanceNumber屬性。

    ES5 demo如下

 1     function Dog(){
 2         /*公有屬性*/
 3         this.hairColor = null;//string
 4         this.age = null;//number
 5         /*私有屬性,人們共同約定私有屬性、私有方法前面加上_以便區分*/
 6         this._breed = null;//string
 7         this._init();
 8         /*屬性的初始化最好放一個私有方法里,構造函數最好只用來聲明類的屬性和調用方法*/
 9         Dog.instanceNumber++;
10     }
11     /*靜態屬性*/
12     Dog.instanceNumber = 0;
13     /*私有方法,只能類的內部調用*/
14     Dog.prototype._init = function(){
15         this.hairColor = '白色';
16         this.age = 2;
17         this._breed = '貴賓';
18     }
19     /*公有方法:獲取屬性的介面方法*/
20     Dog.prototype.getBreed = function(){
21         console.log('監聽到了有人調用這個getBreed()介面')
22         return this._breed;
23     }
24     /*公有方法:設置屬性的介面方法*/
25     Dog.prototype.setBreed = function(breed){
26         this._breed = breed;
27         return this;
28         /*這是一個小技巧,可以鏈式調用方法,只要公有方法沒有返回值都建議返回this*/
29     }
30     /*公有方法:對外暴露的行為方法*/
31     Dog.prototype.gnawBone = function() {
32         console.log('這是本狗最幸福的時候');
33         return this;
34     }
35     /*公有方法:對外暴露的靜態屬性獲取方法*/
36     Dog.prototype.getInstanceNumber = function() {
37         return Dog.instanceNumber;//也可以this.constructor.instanceNumber
38     }
39     var dog = new Dog();
40     console.log(dog.getBreed());
41     /*log:
42         '監聽到了有人調用這個getBreed()介面'
43         '貴賓'
44     */
45     /*鏈式調用,由於getBreed()不是返回this,所以getBreed()後面就不可以鏈式調用了*/
46     var dogBreed = dog.setBreed('土狗').gnawBone().getBreed();
47     /*log:
48         '這是本狗最幸福的時候'
49         '監聽到了有人調用這個getBreed()介面'
50     */
51     console.log(dogBreed);//log: '土狗'
52     console.log(dog);

 

  ES6 demo(新手可不看ES6和TypeScrpt實現部分):

 1     class Dog{
 2         constructor(){
 3             this.hairColor = null;//string
 4             this.age = null;//number
 5             this._breed = null;//string
 6             this._init();
 7             Dog.instanceNumber++;
 8         }
 9         _init(){
10             this.hairColor = '白色';
11             this.age = 2;
12             this._breed = '貴賓';
13         }
14         get breed(){
15             /*其實就是通過getter實現的,只是ES6寫起來更簡潔*/
16             console.log('監聽到了有人調用這個get breed介面');
17             return this._breed;
18         }
19         set breed(breed){
20             /*跟ES5一樣,如果不設置的話預設breed無法被修改,而且不會報錯*/
21             console.log('監聽到了有人調用這個set breed介面');
22             this._breed = breed;
23             return this;
24         }
25         gnawBone() {
26             console.log('這是本狗最幸福的時候');
27             return this;
28         }
29         getInstanceNumber() {
30             return Dog.instanceNumber;
31         }
32     }
33     Dog.instanceNumber = 0;
34     var dog = new Dog();
35     console.log(dog.breed);
36     /*log:
37         '監聽到了有人調用這個get breed介面'
38         '貴賓'
39     */
40     dog.breed = '土狗';//log:'監聽到了有人調用這個set breed介面'
41     console.log(dog.breed);
42     /*log:
43         '監聽到了有人調用這個get breed介面'
44         '土狗'
45     */

 

  ES5ES6中雖然我們把私有屬性和方法用“_”放在名字前面以區分,但外部還是可以訪問到屬性和方法的。

  TypeScrpt中就比較規範了,可以聲明私有屬性,私有方法,並且外部是無法訪問私有屬性、私有方法的:

 

 1     class Dog{
 2         public hairColor: string;
 3         readonly age: number;//可聲明只讀屬性
 4         private _breed: string;//雖然聲明瞭private,但還是建議屬性名加_以區分
 5         static instanceNumber: number = 0;//靜態屬性
 6         constructor(){
 7             this._init();
 8             Dog.instanceNumber++;
 9         }
10         private _init(){
11             this.hairColor = '白色';
12             this.age = 2;
13             this._breed = '貴賓';
14         }
15         get breed(){
16             console.log('監聽到了有人調用這個get breed介面');
17             return this._breed;
18         }
19         set breed(breed){
20             console.log('監聽到了有人調用這個set breed介面');
21             this._breed = breed;
22         }
23         public gnawBone() {
24             console.log('這是本狗最幸福的時候');
25             return this;
26         }
27         public getInstanceNumber() {
28             return Dog.instanceNumber;
29         }
30     }
31     let dog = new Dog();
32     console.log(dog.breed);
33     /*log:
34         '監聽到了有人調用這個get breed介面'
35         '貴賓'
36     */
37     dog.breed = '土狗';//log:'監聽到了有人調用這個set breed介面'
38     console.log(dog.breed);
39     /*log:
40         '監聽到了有人調用這個get breed介面'
41         '土狗'
42     */
43     console.log(dog._breed);//報錯,無法通過編譯
44     dog._init();//報錯,無法通過編譯

 

註意事項:

  1、暴露給別人的類,多個類組合成一個類時,所有屬性一定都要封裝起來;

  2、如果你來不及封裝屬性,可以後期用getter/setter彌補;

  3、每個公有方法,最好註釋一下含義;

  4、在重要的類前面最好用註釋描述所有的公有方法;

後話

  如果你喜歡作者的文章,記得收藏,你的點贊是對作者最大的鼓勵;

  作者會儘量每周更新一章,下一章是講繼承;

  大家有什麼疑問可以留言或私信作者,作者儘量第一時間回覆大家;

  如果老司機們覺得那裡可以有不恰當的,或可以表達的更好的,歡迎指出來,我會儘快修正、完善。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ::before與::after兩個偽元素其實是CSS3中的內容,然而實際上在CSS2中就已經有了這兩者的身影,只不過CSS2中是前面加一個冒號來表示(:before和:after)。今天主要講講這兩個偽元素該如何使用。 一、與普通元素一樣可以給其添加樣式 比如說我想在文字前面添加一個圖標,如果我用 ...
  • 本文提到的網站性能指網站的響應速度,這也符合絕大部分人對於網站性能的理解:訪問快速的網站性能好,反之,訪問速度越慢,則網站性能越差。本文總結的優化方法是巨集觀的工程層面的方法,並不包含微觀的語言語法層面的方法,例如,JS、CSS的語法優化,這一部分同樣影響網站的性能,但語言語法層面的優化更多的是取決於 ...
  • 雖然時間軸早已不是什麼新鮮事物了,個人只是感興趣所以就研究一下,最近從網上搜索了一個個人感覺比較好的時間軸demo,下載下來研究了一下並做了下修改.具體的效果如下圖:(該demo實現的是滾動載入圖片) 代碼地址:http://files.cnblogs.com/files/cby-love/html ...
  • Canvas繪圖環境中有些屬於立即繪製圖形方法,有些繪圖方法是基於路徑的。 立即繪製圖形方法僅有兩個strokeRect(),fillRect(),雖然strokezText(),fillText()方法也是立即繪製的,但是文本不算是圖形。 基於路徑的繪製系統 大多數繪製系統,如:SVG(Scala ...
  • string對象 string對象的兩種創建 var a="hello"; var b=new String("hello"); //下麵是方法 //charAt()根據下標 找字元串中的字元 alert(a.charAt(3));//根據下標返回字元串某個字元 alert(a.charAt(10) ...
  • web存儲分類 客戶端和服務端 認識web存儲 隨著web應用的發展,是的客戶端存儲的用途越來越多,然而實現客戶端端存儲的方式也是越來越多樣化。最簡單最相容的方式就是cookie,但作為真正的客戶端存儲cookie還是存在著許許多多的弊端的。同時,各種瀏覽器也有屬於自己的存儲方式。例如,IE6以及以 ...
  • 問題:js這麼語言的能力取決於什麼?(運行平臺) 答:現在的js語言可以在瀏覽器運行(js如果是在瀏覽器端運行則不能操作磁碟文件),也可以在伺服器端運行nodeJs(js在伺服器端能夠操作文件)。 問題:js是否能夠操作文件(一般不能),ajax能否操作文件(一般不能)? 答: a. js一般不能, ...
  • 渲染性能 頁面不僅要快速載入,而且要順暢地運行;滾動應與手指的滑動一樣快,並且動畫和交互應如絲綢般順滑。 60fps 與設備刷新率 60fps 與設備刷新率 目前大多數設備的屏幕刷新率為 60 次/秒。因此,如果在頁面中有一個動畫或漸變效果,或者用戶正在滾動頁面,那麼瀏覽器渲染動畫或頁面的每一幀的速 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...