自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

淺談JavaScript的面向對象和它的封裝、繼承、多態(tài)

開發(fā) 前端
既然是淺談,就不會從原理上深度分析,只是幫助我們更好地理解...

[[252486]]

寫在前面

既然是淺談,就不會從原理上深度分析,只是幫助我們更好地理解...

面向對象與面向過程

面向對象和面向過程是兩種不同的編程思想,剛開始接觸編程的時候,我們大都是從面向過程起步的,畢竟像我一樣,大家接觸的***門計算機語言大概率都是C語言,C語言就是一門典型的面向過程的計算機語言。

面向過程主要是以動詞為主,解決問題的方式是按照順序一步一步調用不同的函數(shù)。

面向對象是以名詞為主,將問題抽象出具體的對象,而這個對象有自己的屬性和方法,在解決問題的時候,是將不同的對象組合在一起使用。 

  1. //面向過程裝大象  
  2. 1.開(冰箱)  
  3. 2.(大象)裝進(冰箱)  
  4. 3.關(冰箱)  
  1. //面向對象裝大象  
  2. 1. 冰箱.開門()  
  3. 2. 冰箱.裝進(大象)  
  4. 3. 冰箱.關門() 

從這個例子可以看出,面向對象是以主謂為主,將主謂堪稱一個一個的對象,然后對象有自己的屬性和方法。

面向對象是以功能來劃分問題的,而不是步驟。功能上的統(tǒng)一保證了面向對象設計的可擴展性,解決了代碼重用性的問題。

這也是在漫長的程序設計的發(fā)展過程中得到的驗證結果,面向對象的編程思想較之于面向過程較好一點

封裝

面向對象有封裝、繼承和多態(tài)三大特性。

封裝:就是把事物封裝成類,隱藏事物的屬性和方法的實現(xiàn)細節(jié),僅對外公開接口。

在ES5中,并沒有class的概念,但是由于js的函數(shù)級作用域(函數(shù)內部的變量函數(shù)外訪問不到)。所以我們可以模擬class。在es5中,類其實就是保存了一個函數(shù)的變量,這個函數(shù)有自己的屬性和方法。將屬性和方法組成一個類的過程就是封裝。

1.通過構造函數(shù)添加

JavaScript提供了一個構造函數(shù)(Constructor)模式,用來在創(chuàng)建對象時初始化對象。構造函數(shù)其實就是普通的函數(shù),只不過有以下的特點 

  1. ①首字母大寫(建議構造函數(shù)首字母大寫,即使用大駝峰命名,非構造函數(shù)首字母小寫)  
  2. ②內部使用this  
  3. ③使用new生成實例 

通過構造函數(shù)添加屬性和方法實際上也就是通過this添加的屬性和方法。因為this總是指向當前對象的,所以通過this添加的屬性和方法只在當前對象上添加,是該對象自身擁有的。所以我們實例化一個新對象的時候,this指向的屬性和方法都會得到相應的創(chuàng)建,也就是會在內存中復制一份,這樣就造成了內存的浪費。 

  1. function Cat(name,color){  
  2.     this.name = name;  
  3.     this.color = color;  
  4.     this.eat = (() => {  
  5.         console.log("fish!")  
  6.     })  
  7.  
  8. //生成實例  
  9. var cat1 = new Cat("tom", "gray") 

通過this定義的屬性和方法,我們實例化對象的時候斗湖重新復制一份

2.通過原型prototype封裝

在類上通過this的方式添加屬性和方法會導致內存浪費的現(xiàn)象,有什么辦法可以讓實例化的類所使用的屬性和方法 直接使用指針 指向同一個屬性和方法。

這就是原型的方法 

  1. JavaScript規(guī)定,每一個構造函數(shù)都有一個prototype屬性,指向另一個對象。這個對象的所有屬性和方法,都會被構造函數(shù)的實例繼承。  
  2. 也就是說,對于那些不變的屬性和方法,我們可以直接將其添加在類的prototype對象上。  
  1. function Cat(name,color){  
  2.             this.name = name;  
  3.             this.color = color;  
  4.         }  
  5.         Cat.prototype.type = "英短" 
  6.         Cat.prototype.eat = ( () => {  
  7.             alert("fish!")  
  8.         } )       
  9.  
  10.         //生成實例  
  11.         var cat1 = new Cat('Tom', 'gray');  
  12.         var cat2 = new Cat('Kobe', 'purple');  
  13.         console.log(cat1.type); //英短  
  14.         cat2.eat(); //fish! 

這時所有實例的type屬性和eat()方法,其實都是同一個內存地址,指向prototype對象,因此就提高了運行效率。

但是這樣做也有弊端,因為實例化的對象的原型都是指向同一內存地址,改動其中一個對象的屬性可能會影響到其他的對象

es6中的類和封裝

es6聲明一個類

①構造器:構造器內創(chuàng)建自有屬性

②方法:聲明類實例具有的方法 

  1. class Cat {  
  2.     //等價于Cat構造器  
  3.     constructor(name) {  
  4.         this.name = name;  
  5.     }  
  6.     //更加簡單的聲明類的內部函數(shù)  
  7.     //等價于 Cat.prototype.eat  
  8.     eat() {  
  9.         console.log("fish!");  
  10.     }  
  11.  
  12. //生成實例  
  13. var cat1 = new Cat("tom");  
  14. cat1.eat(); //fish!  
  15. console.log(cat1 instanceof Cat); //true  
  16. console.log(cat1 instanceof Object); //true  
  17. console.log(typeof Cat); //function  
  18. console.log(typeof Cat.prototype.eat); //function 

從上面class聲明的Cat為例:Cat類是一個具有構造函數(shù)行為的函數(shù),其中內部方法eat實際上就是Cat.prototype.eat()

所以說es6的class封裝類,本質上是es5實現(xiàn)方式的語法糖

最主要的區(qū)別在于,class類的屬性是不可重新賦值和不可枚舉的,Cat.prototype就是一個只讀屬性

class和自定義類型的區(qū)別

(1)class的聲明不會提升,與let類似

(2)class的聲明自動運行于嚴格模式之下

(3)class聲明的方法不可枚舉

(4)class的內部方法沒有 constructor 屬性,無法new

(5)調用class的構造函數(shù)必須new

(6)class內部方法不能同名

class類的使用

class作為js中的一級公民,可以被當作值來直接使用 

  1. //1.類名作為參數(shù)傳入函數(shù)  
  2. function createObj (ClassName) {  
  3.     return new ClassName()  
  4.  
  5. //2.立即執(zhí)行,實現(xiàn)單例模式  
  6. let cat1 = new class{  
  7.     constructor (name) {  
  8.         this.name = name  
  9.     }  
  10.     eat() {  
  11.         console.log("fish!")  
  12.     }  
  13. }("tom”)  
  14. cat1.eat() //fish! 

繼承

繼承就是子類可以使用父類的所有功能,并且對這些功能進行擴展。繼承的過程,就是從一般到特殊的過程。

1.類式繼承

所謂的類式繼承就是使用的原型的方式,將方法添加在父類的原型上,然后子類的原型是父類的一個實例化對象。 

  1. //聲明父類  
  2. var SuperClass = function(){  
  3.     let id = 1 
  4.     this.name = ['java'];  
  5.     this.superValue = function() {  
  6.         console.log('this is superValue!')  
  7.     } 
  8.  
  9. //為父類添加共有方法  
  10. SuperClass.prototype.getSuperValue = function () {  
  11.     return this.superValue();  
  12. };  
  13. //聲明子類  
  14. var SubClass = function() {  
  15.     this.subValue = (() => {  
  16.         console.log('this is subValue!')  
  17.     })  
  18.  
  19. //繼承父類  
  20. SubClass.prototype = new SuperClass();  
  21. //為子類添加共有方法  
  22. SubClass.prototype.getSubValue = function() {  
  23.     return this.subValue()  
  24.  
  25. //生成實例  
  26. var sub1 = new SubClass();  
  27. var sub2 = new SubClass();  
  28. sub1.getSuperValue(); //this is superValue!  
  29. sub1.getSubValue(); //this is subValue!  
  30. console.log(sub1.id); //undefined  
  31. console.log(sub1.name); //["java"]  
  32. sub1.name.push("php");   
  33. console.log(sub1.name); //["java", "php"]  
  34. console.log(sub2.name); //["java", "php"] 

其中最核心的是SubClass.prototype = new SuperClass();

類的原型對象prototype對象的作用就是為類的原型添加共有的方法的,但是類不能直接訪問這些方法,只有將類實例化之后,新創(chuàng)建的對象復制了父類構造函數(shù)的屬性和方法,并將原型 proto 指向了父類的原型對象。這樣子類就可以訪問父類的屬性和方法,同時,父類中定義的屬性和方法不會被子類繼承。

but使用類繼承的方法,如果父類的構造函數(shù)中有引用數(shù)據(jù)類型,就會在子類中被所有實例共用,因此一個子類的實例如果更改了這個引用數(shù)據(jù)類型,就會影響到其他子類的實例。

構造函數(shù)繼承

為了克服類繼承的缺點,才有了構造函數(shù)繼承,構造函數(shù)繼承的核心思想就是SuperClass.call(this, id),直接改變this的指向,使通過this創(chuàng)建的屬性和方法在子類中復制一份,因為是單獨復制的,所以各個實例化的子類互不影響。but會造成內存浪費的問題 

  1. //構造函數(shù)繼承  
  2. //聲明父類  
  3. var SuperClass = function(id){  
  4.     var name = 'java'  
  5.     this.languages = ['java', 'php', 'ruby'];  
  6.     this.id = id   
  7.  
  8. //聲明子類  
  9. var SubClass = function(id){  
  10.     SuperClass.call(this, id)  
  11.  
  12. //生成實例  
  13. var sub1 = new SubClass(1);  
  14. var sub2 = new SubClass(2);  
  15. console.log(sub2.id); // 2  
  16. console.log(sub1.name); //undefined  
  17. sub1.languages.push("python");  
  18. console.log(sub1.languages); // ['java', 'php', 'ruby', 'python']  
  19. console.log(sub2.languages); // ['java', 'php', 'ruby'] 

組合式繼承

組合式繼承是汲取了兩者的優(yōu)點,既避免了內存浪費,又使得每個實例化的子類互不影響。 

  1. //組合式繼承  
  2. //聲明父類  
  3. var SuperClass = function(name){  
  4.     this.languages = ['java', 'php', 'ruby'];  
  5.     this.name = name;  
  6.  
  7.  //聲明父類原型方法  
  8. SuperClass.prototype.showLangs = function () {  
  9.     console.log(this.languages);  
  10.  
  11. //聲明子類  
  12. var SubClass = function(name){  
  13.     SuperClass.call(this, name)  
  14.  
  15. //子類繼承父類(鏈式繼承)  
  16. SubClass.prototype = new SuperClass();  
  17. //生成實例  
  18. var sub1 = new SubClass('python');  
  19. var sub2 = new SubClass('go');  
  20. sub2.showLangs(); //['java', 'php', 'ruby']  
  21. sub1.languages.push(sub1.name);  
  22. console.log(sub1.languages);//["java", "php", "ruby", "python"]  
  23. console.log(sub2.languages);//['java', 'php', 'ruby'] 

but警告:組合式繼承方法固然好,但是會導致一個問題,父類的構造函數(shù)會被創(chuàng)建兩次(call()的時候一遍,new的時候又一遍)

寄生組合繼承

組合式繼承的缺點的關鍵是 父類的構造函數(shù)在類繼承和構造函數(shù)繼承的組合形式被創(chuàng)建了兩邊,但是在類繼承中我們并不需要創(chuàng)建父類的構造函數(shù),我們只要子類繼承父類的原型即可。

所以我們先給父類的原型創(chuàng)建一個副本,然后修改子類的 constructor 屬性,***在設置子類的原型就可以了 

  1. //原型式繼承  
  2. //原型式繼承其實就是類式繼承的封裝,實現(xiàn)的功能返回一個實例,該實例的原型繼承了傳入的o對象  
  3. function inheritObject(o) {  
  4.     //聲明一個過渡函數(shù)  
  5.     function F() {}  
  6.     //過渡對象的原型鏈繼承父對象  
  7.     F.prototype = o;  
  8.     //返回一個過渡對象的實例,該實例的原型繼承了父對象  
  9.     return new F();  
  10.  
  11. //寄生式繼承  
  12. //寄生式繼承就是對原型繼承的第二次封裝,使得子類的原型等于父類的原型。并且在第二次封裝的過程中對繼承的對象進行了擴展  
  13. function inheritPrototype(subClass, superClass){  
  14.     //復制一份父類的原型保存在變量中,使得p的原型等于父類的原型  
  15.     var p = inheritObject(superClass.prototype);  
  16.     //修正因為重寫子類原型導致子類constructor屬性被修改  
  17.     p.constructor = subClass 
  18.     //設置子類的原型  
  19.     subClass.prototype = p;  
  20.  
  21. //定義父類  
  22. var SuperClass = function(name) {  
  23.     this.name = name;  
  24.     this.languages = ["java", "php", "python"]  
  25.  
  26. //定義父類原型方法  
  27. SuperClass.prototype.showLangs = function() {  
  28.     console.log(this.languages);  
  29.  
  30. //定義子類  
  31. var SubClass = function(name) {  
  32.     SuperClass.call(this,name)  
  33.  
  34. inheritPrototype(SubClass, SuperClass);  
  35. var sub1 = new SubClass('go'); 

es6中的繼承 

  1. class SuperClass {  
  2.     constructor(name) {  
  3.         this.name = name  
  4.         this.languages = ['java', 'php', 'go'];   
  5.     }  
  6.     showLangs() {  
  7.         console.log(this.languages);  
  8.     }  
  9.  
  10. class SubClass extends SuperClass {  
  11.     constructor(name) {  
  12.         super(name)  
  13.     }  
  14.     //重寫父類中的方法  
  15.     showLangs() {  
  16.         this.languages.push(this.name)  
  17.         console.log(this.languages);  
  18.     }  
  19.  
  20. //生成實例  
  21. var sub = new SubClass('韓二虎');  
  22. console.log(sub.name); //韓二虎  
  23. sub.showLangs(); //["java", "php", "go", "韓二虎"] 

多態(tài)

多態(tài)實際上是不同對象作用與同一操作產(chǎn)生不同的效果。多態(tài)的思想實際上是把 “想做什么” 和 “誰去做” 分開。

多態(tài)的好處在于,你不必再向對象詢問“你是什么類型”后根據(jù)得到的答案再去調用對象的某個行為。你盡管去調用這個行為就是了,其他的一切可以由多態(tài)來負責。規(guī)范來說,多態(tài)最根本的作用就是通過吧過程化的條件語句轉化為對象的多態(tài)性,從而消除這些條件分支語句。

由于JavaScript中提到的關于多態(tài)的詳細介紹并不多,這里簡單的通過一個例子來介紹就好 

  1. //非多態(tài)       
  2. var hobby = function(animal){  
  3.     if(animal == 'cat'){  
  4.         cat.eat()  
  5.     }else if(animal == 'dog'){  
  6.         dog.eat()  
  7.     }  
  8.  
  9. var cat = {  
  10.     eat: function() {  
  11.         alert("fish!")  
  12.     }  
  13.  
  14. var dog = {  
  15.     eat: function() {  
  16.         alert("meat!")  
  17.     }  
  18.  
  19. console.log(123);  
  20. hobby('cat'); //fish!  
  21. hobby('dog'); //meat! 

從上面的例子能看到,雖然 hobby 函數(shù)目前保持了一定的彈性,但這種彈性很脆弱的,一旦需要替換或者增加成其他的animal,必須改動hobby函數(shù),繼續(xù)往里面堆砌條件分支語句。我們把程序中相同的部分抽象出來,那就是吃某個東西。然后再重新編程。 

  1. //多態(tài)       
  2. var hobby = function(animal){  
  3.     if(animal.eat instanceof Function){  
  4.         animal.eat();  
  5.     }  
  6.  
  7. var cat = {  
  8.     eat: function() {  
  9.         alert("fish!")  
  10.     }  
  11.  
  12. var dog = {  
  13.     eat: function() {  
  14.         alert("meat!")  
  15.     } 

現(xiàn)在來看這段代碼中的多態(tài)性。當我們向兩種 animal 發(fā)出 eat 的消息時,會分別調用他們的 eat 方法,就會產(chǎn)生不同的執(zhí)行結果。對象的多態(tài)性提示我們,“做什么” 和 “怎么去做”是可以分開的,這樣代碼的彈性就增強了很多。即使以后增加了其他的animal,hobby函數(shù)仍舊不會做任何改變。 

  1. //多態(tài)       
  2. var hobby = function(animal){  
  3.     if(animal.eat instanceof Function){  
  4.         animal.eat();  
  5.     }  
  6.  
  7. var cat = {  
  8.     eat: function() {  
  9.         alert("fish!")  
  10.     }  
  11.  
  12. var dog = {  
  13.     eat: function() {  
  14.         alert("meat!")  
  15.     }  
  16.  
  17. var aoteman = {  
  18.     eat: function(){  
  19.         alert("lil-monster!")  
  20.     }  
  21.  
  22. hobby(cat); //fish!  
  23. hobby(dog); //meat!  
  24. hobby(aoteman); //lil-monster! 

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2024-09-18 08:13:01

C#封裝繼承

2023-11-01 10:49:50

Python面向對象

2009-06-10 22:06:29

JavaScript面向對象

2011-05-25 11:15:02

Javascript繼承

2011-05-25 10:59:26

Javascript繼承

2011-05-25 10:21:44

Javascript

2010-10-08 09:13:15

oop模式JavaScript

2009-01-04 09:08:30

面向對象繼承接口

2021-10-21 18:47:37

JavaScript面向對象

2024-02-26 18:23:29

C++封裝代碼

2014-12-12 14:57:11

Objective-C封裝

2012-12-25 10:51:39

IBMdW

2009-06-29 10:50:18

VB.NET面向對象能力

2017-04-21 09:07:39

JavaScript對象編程

2012-01-17 09:34:52

JavaScript

2010-03-05 14:44:36

Python繼承

2019-09-18 18:56:34

JavascriptOOP前端

2023-05-09 12:42:51

Java繼承多態(tài)

2011-07-08 10:25:55

JavaScript

2023-09-27 23:28:28

Python編程
點贊
收藏

51CTO技術棧公眾號