本章默認你們已經看過做者的前一篇文章 《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,以下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 */
ES5、ES6中雖然咱們把私有屬性和方法用「_」放在名字前面以區分,但外部仍是能夠訪問到屬性和方法的。
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彌補;
三、每一個公有方法,最好註釋一下含義;
四、在重要的類前面最好用註釋描述全部的公有方法;
若是你喜歡做者的文章,記得收藏,你的點贊是對做者最大的鼓勵;
做者會盡可能每週更新一章,下一章是講繼承;
你們有什麼疑問能夠留言或私信做者,做者儘可能第一時間回覆你們;
若是老司機們以爲那裏能夠有不恰當的,或能夠表達的更好的,歡迎指出來,我會盡快修正、完善。