JavaScript面向對象輕鬆入門之封裝(demo by ES五、ES六、TypeScript)

  本章默認你們已經看過做者的前一篇文章 JavaScript面向對象輕鬆入門之抽象》html

爲何要封裝?

  封裝(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()裏的代碼就能夠了,而且你能夠監聽到全部獲取這個屬性的操做。this

  因此封裝有不少好處:spa

  一、只要接口不改變,內部的實現能夠任意改變;prototype

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

  三、下降代碼之間的耦合;

  四、知足大型應用程序和多人協同開發;

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();//報錯,沒法經過編譯

 

注意事項:

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

  二、若是你來不及封裝屬性,能夠後期用getter/setter彌補;

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

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

後話

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

  做者會盡可能每週更新一章,下一章是講繼承;

  你們有什麼疑問能夠留言或私信做者,做者儘可能第一時間回覆你們;

  若是老司機們以爲那裏能夠有不恰當的,或能夠表達的更好的,歡迎指出來,我會盡快修正、完善。

相關文章
相關標籤/搜索