抽象的概念
狹義的抽象,也就是代碼里的抽象,就是把一些相關聯的業務邏輯分離成屬性和方法(行為),這些屬性和方法就可以構成一個對象。
這種抽象是為了把難以理解的代碼歸納成與現實世界關聯的概念,比如小狗這樣一個對象:屬性可以歸納出“毛色”、“品種”、“年齡”等等;方法(行為)可以歸納出“叫”、“跑”、“啃骨頭”等。
注意:這里的抽象不是指抽象類,抽象類我認為放封裝一節講比較合適。
類的概念和實現
Javascript里創建一個對象有很多種方法,也非常簡單,就以小狗這個對象為例:
1 var dog = { 2 hairColor: '白色', 3 breed: '貴賓', 4 age: 2, 5 shout: function() { 6 console.log('汪!汪!汪!'); //這里是你的業務邏輯代碼,這里我就簡單用這個來代替 7 }, 8 run: function() { 9 console.log('吃我灰吧,哈哈!'); 10 }, 11 gnawBone: function() { 12 console.log('這是本狗最幸福的時候'); 13 } 14 };
非常便捷,但這時候有個問題:我要創建很多只dog怎么辦?每創建一只dog我都var一遍嗎?
於是這時候我們就引入了類(class)的概念,類即為類似,具有相同特征的對象的原型,它的作用是創建對象(實例),類本身並不存在內存中,當運行類的代碼時,一個對象(實例/instance)就被創建在內存中了,可以簡單的理解類為創造對象的工廠。
Javascript(ES5)里沒有類(class)這東西,它是通過構造函數來實現類的:
1 /*類的創建*/ 2 function Dog() { 3 //構造函數:人們一致協定把構造函數的名字(即類名),首字母大寫,以便區分 4 this.hairColor = '白色'; 5 /*this指向被創造的對象(實例),如果不明白可以簡單的理解為給對象(this)賦予hairColor這個屬性 6 */ 7 this.breed = '貴賓'; 8 this.age = 2; 9 this.runSpeed = null; //string 10 /*屬性的聲明一定要放在構造函數的最頂部; 11 有的屬性可能一開始沒有初始值,會在方法里才賦值,但你一定要在構造函數里聲明一下 12 有必要的話再聲明一下屬性的類型 13 */ 14 } 15 Dog.prototype.shout = function() { 16 /*我們把方法追加到構造函數的prototype屬性,而不是直接在構造函數里用this.shout = function(){}; 17 這樣的好處是會讓Dog創造的所有對象都共享一個方法,從而節約內存; 18 一般來說屬性在構造函數里賦予,方法在prototype里賦予; 19 更多prototype的知識就看書去吧,這里不會深講,作者要保持本章知識的封裝性; 20 */ 21 console.log('汪!汪!汪!我是一只' + this.age + '歲的' + this.hairColor + this.breed); 22 //方法里通過this可以訪問屬性 23 } 24 Dog.prototype.run = function() { 25 this.runSpeed = '10m/s'; 26 console.log('吃我灰吧,哈哈!本狗的速度可是有' + this.runSpeed); 27 } 28 Dog.prototype.gnawBone = function() { 29 console.log('這是本狗最幸福的時候'); 30 } 31 /*對象(實例)的創建與使用*/ 32 var dog1 = new Dog(); // 33 console.log(dog1.breed); //log: '貴賓' 34 dog1.shout(); //log: '汪!汪!汪!我是一只2歲的白色貴賓' 35 var dog2 = new Dog(); //創建多只dog(對象/實例) 36 var dog3 = new Dog(); 37 /*dog1、dog2、dog3這些對象的屬性是各自的,但方法是共享的*/ 38 dog1.hairColor = '黑色'; //修改dog1的屬性 39 console.log(dog1.hairColor); //log: '黑色';dog1屬性已被修改; 40 console.log(dog2.hairColor); //'白色';其它對象不受影響; 41 console.log(dog3.hairColor); //log: '白色' 42 console.log(dog1.shout === dog2.shout); //log: true;dog1的shout方法和dog2的是同一個方法;
但新的問題又來了:我想創建一個棕色的泰迪怎么辦呢?創建的時候傳遞參數給類的構造函數就可以解決這問題。
上個案例中說了,類創建的各個對象(實例)的方法的共享的,屬性是各自的,但我就是想創建一個共享的屬性怎么辦呢?比如說我想創建一個instanceNumber屬性來記錄程序中dog對象(實例)的個數。
這時候就可以給這個Dog類創建一個靜態屬性,靜態屬性是屬於類的,所以它是不會隨對象(實例)的變化而變化,可以用來設置該類的全局變量,全局參數配置等。
下面是新代碼:
1 function Dog(hairColor, breed, age) { 2 this.hairColor = hairColor; //string,這種依賴參數的屬性最好聲明下類型或接口; 3 this.breed = breed; //string 4 this.age = age; //number 5 this.runSpeed = null; //string 6 Dog.instanceNumber++; 7 } 8 Dog.instanceNumber = 0; //創建靜態屬性 9 Dog.prototype.shout = function() { 10 console.log('汪!汪!汪!我是一只' + this.age + '歲的' + this.hairColor + this.breed); 11 } 12 Dog.prototype.run = function() { 13 this.runSpeed = '10m/s'; 14 console.log('吃我灰吧,哈哈!本狗的速度可是有' + this.runSpeed); 15 } 16 Dog.prototype.gnawBone = function() { 17 console.log('這是本狗最幸福的時候'); 18 } 19 Dog.prototype.getInstanceNumber = function() { //為訪問靜態屬性封裝方法 20 return Dog.instanceNumber; 21 } 22 var dog1 = new Dog('白色', '貴賓', 2); 23 console.log(Dog.instanceNumber); //log: 1;雖然可以這樣訪問靜態屬性,並且還可以修改它,但堅決不推薦這樣做 24 console.log(dog1.getInstanceNumber()); //log: 1;正確的做法!為什么要這樣做,在封裝一節會詳細講 25 var dog2 = new Dog('棕色', '泰迪', 1); 26 console.log(dog1.getInstanceNumber()); //log: 2; 27 var dog3 = new Dog('黑色', '土狗', 3); 28 console.log(dog1.getInstanceNumber()); //log: 3; 29 dog1.shout(); //log: '汪!汪!汪!我是一只2歲的白色貴賓' 30 dog2.shout(); //log: '汪!汪!汪!我是一只1歲的棕色泰迪' 31 dog3.shout(); //log: '汪!汪!汪!我是一只3歲的黑色土狗'
接下來是ES6的類的創建方法,這段代碼可以直接在Chrome瀏覽器運行,新手可不用管這部分,包括再下面的TypeScript代碼。
1 class Dog { 2 constructor(hairColor, breed, age) { //代表這個類的構造函數 3 this.hairColor = hairColor; //string 4 this.breed = breed; //string 5 this.age = age; //number 6 this.runSpeed = null; //string 7 Dog.instanceNumber++; 8 } 9 shout() { 10 console.log('汪!汪!汪!我是一只' + this.age + '歲的' + this.hairColor + this.breed); 11 } 12 run() { 13 this.runSpeed = '10m/s'; 14 console.log('吃我灰吧,哈哈!本狗的速度可是有' + this.runSpeed); 15 } 16 gnawBone() { 17 console.log('這是本狗最幸福的時候'); 18 } 19 getInstanceNumber() { 20 return Dog.instanceNumber; 21 } 22 }//ES6類的創建就比較舒服了,class把整個類用{}包裹在一起,寫法也比較方便。 23 Dog.instanceNumber = 0;//遺憾的是ES6里也沒有規范靜態屬性,還是跟ES5一樣的用法,據說ES7已有一個靜態屬性的提案 24 let dog1 = new Dog('白色', '貴賓', 2); 25 let dog2 = new Dog('棕色', '泰迪', 1); 26 let dog3 = new Dog('黑色', '土狗', 3); 27 dog1.shout(); //log: '汪!汪!汪!我是一只2歲的白色貴賓' 28 dog2.shout(); //log: '汪!汪!汪!我是一只1歲的棕色泰迪' 29 dog3.shout(); //log: '汪!汪!汪!我是一只3歲的黑色土狗' 30 console.log(dog1.getInstanceNumber()); //log: 3;
TypeScript創建對象,TypeScript還有許多有用的特性,但本章不方便介紹更多。
class Dog { hairColor: string;//class的所有屬性必須在頂部全部聲明,否則無法通過編譯,這讓類的創建更加規范; breed: string;//並且可以聲明屬性類型,如果給屬性賦值的時候類型不正確也無法通過編譯,當然,你也可以不聲明類型或聲明為any任何類型; age: number; runSpeed: string; static instanceNumber: number = 0;//TS靜態變量的聲明就比較舒服了,在class的{}里面,保證了代碼的干凈 constructor(hairColor, breed, age) { this.hairColor = hairColor; this.breed = breed; this.age = age; Dog.instanceNumber++; } shout() { console.log('汪!汪!汪!我是一只' + this.age + '歲的' + this.hairColor + this.breed); } run() { this.runSpeed = '10m/s'; console.log('吃我灰吧,哈哈!本狗的速度可是有' + this.runSpeed); } gnawBone() { console.log('這是本狗最幸福的時候'); } getInstanceNumber() { return Dog.instanceNumber; } } let dog1 = new Dog('白色', '貴賓', 2); let dog2 = new Dog('棕色', '泰迪', 1); let dog3 = new Dog('黑色', '土狗', 3); dog1.shout();//log: '汪!汪!汪!我是一只2歲的白色貴賓' dog2.shout();//log: '汪!汪!汪!我是一只1歲的棕色泰迪' dog3.shout();//log: '汪!汪!汪!我是一只3歲的黑色土狗' console.log(dog1.getInstanceNumber());//log: 3;
類的抽象
上面的例子是我們明確知道要創建一個對象(實例)dog,但實際開發當中是沒有人告訴我們需要創建哪些對象的,領導給我們的只有需求,所以我們要分析需求的業務邏輯,把需求分解成一個個對象。
比如說現在領導給我們一個需求:做一個超市收銀系統,分為兩種角色:收銀員和管理員,收銀員可以查詢物品信息、統計價格、錄入賬單信息、打印小票,管理員可以查看賬單信息、統計賬單信息。
注意需求中的名詞:收銀員、管理員、物品信息、賬單、小票,這些就是天然的對象,這是最初步的抽象。
讓我們再注意動詞:查詢、統計、錄入、打印,我們是不是也可以抽象成對象?查詢器?統計器?
然后我們開始coding吧,不要糾結自己的抽象是否完美,作者很贊同Facebook的一句標語:Done is better than perfect(比完美更重要的是完成).
當某個對象的代碼不斷膨脹,慢慢超出控制的時候(作者自己的的標准是一個對象盡量不超過300行代碼),這時候你就得考慮更深層次的抽象了,查找這個對象里代碼比較多的屬性、方法、然后抽象成另一個對象,把新對象作為原先對象的成員(屬性)。
function Dog(){ this._tail = new Tail();//把尾巴tail抽象成另一個對象,作為dog的一個屬性; }
當然,你成了老司機后可以一開始就把一個對象再抽象出許多成員對象,隨你喜歡。
后話
如果你喜歡作者的文章,記得收藏,你的點贊是對作者最大的鼓勵;
作者會盡量每周更新一章,下一章是講封裝;
大家有什么疑問可以留言或私信作者,作者盡量第一時間回復大家;
如果老司機們覺得那里可以有不恰當的,或可以表達的更好的,歡迎指出來,作者會盡快修正、完善。