JavaScript面向對象

知識內容:python

1.理解對象app

2.建立對象函數

3.繼承this

 

參考資料:《JavaScript高級程序設計》spa

 

 

 

1.理解對象prototype

JavaScript中的面向對象和其餘語言中的面向對象不同,JavaScript中沒有類的概念,ECMA-262把對象定義爲無序屬性的集合,其屬性能夠包含基本值、對象或函數設計

JavaScript中的對象都是基於一個引用類型建立的,這個引用類型能夠是原生類型,也能夠是開發人員定義的類型指針

簡單建立一個對象:code

 1 var person  = new Object()
 2 // 定義屬性
 3 person.name = "wyb"
 4 person.age = 21
 5 person.job = "Software Enginner"
 6 
 7 // 定義方法
 8 person.sayName = function (){
 9     console.log(this.name)
10 }

上述建立對象也能夠這樣寫:對象

 1 var person = {
 2     // 定義屬性
 3     name: "wyb",
 4     age: 21,
 5     job: "Software Enginner",
 6     
 7     // 定義方法
 8     sayName: function(){
 9          console.log(this.name)
10     }   
11 }

 

 

2.建立對象

雖然使用上述兩種方法均可以建立對象,可是這些方式有個明顯的缺點:使用同一個接口建立不少對象,會產生大量的重複代碼;爲解決這些問題,因而提出了一下的方式:

(1)工廠模式

工廠模式抽象建立具體對象的過程,解決了建立多個類似對象的問題的,可是沒有解決對象識別的問題(即如何知道一個對象的類型)

 1 function createPerson(name, age, job){
 2      var o = new Object()
 3      o.name = name
 4      o.age = age
 5      o.job = job
 6      o.sayName = function() {
 7          console.log(this.name)
 8      }
 9      return o
10 }
11 
12 var person1 = createPerson("wyb", 21, "IT")
13 var person2 = createPerson("alex", 29, "boss")

注:關於this,this代指對象,this至關於python中的self

 

(2)構造函數模式

使用構造函數的主要問題就是每一個方法都要在每一個實例上從新建立一遍,例如像下面構造函數中的sayName沒有必要重複定義,這個重複定義的問題能夠經過後面的原型模式解決

 1 // 構造函數:
 2 function Person(name, age, job){
 3     this.name = name
 4     this.age = age
 5     this.job = job
 6     this.sayName = function() {
 7          console.log(this.name);
 8     }
 9 }
10 
11 var person1 = new Person("wyb", 21, "IT") 
12 var person2 = new Person("alex", 29, "boss")

 

(3)原型模式

prototype就是經過構造函數而建立的那個對象實例的原型對象,使用原型對象的好處就是可讓全部對象實例共享它所包含的屬性和方法(不用在構造函數中定義對象實例的信息而是把這些信息直接添加到原型對象中)

prototype基本示例:

 1 function Person(){
 2 }
 3 
 4 // 讓全部對象實例共享的屬性和方法:
 5 Person.prototype.name = "wyb"
 6 Person.prototype.age = 21
 7 Person.prototype.job = "IT"
 8 Person.prototype.sayName = function() {
 9     console.log(this.name)
10 }
11 
12 var person1 = new Person()
13 var person2 = new Person()
14 person1.sayName()  // wyb
15 person2.sayName()  // wyb
16 console.log(person1.sayName() == person2.sayName())  // true

注:上述person1和person2中的屬性和方法均是原型對象中的屬性和方法,是全部對象實例通用的

關於prototype:

  • 不使用prototype屬性定義的對象方法,是靜態方法,只能直接用類名進行調用!另外,此靜態方法中沒法使用this變量來調用對象其餘的屬性!
  • 使用prototype屬性定義的對象方法,是非靜態方法,只有在實例化後才能使用!其方法內部能夠this來引用對象自身中的其餘屬性!

 

原型實現面向對象:

 1 //  構造函數
 2 function Foo (name, age) {
 3     this.Name = name;
 4     this.Age = age;
 5 }
 6 
 7 //  指定原型
 8 Foo.prototype.getInfo = function(){
 9      return this.Name + this.Age
10 }
11 Foo.prototype.func = function(arg){
12      return this.Name + arg
13 }
14 
15 obj1 = new Foo("wyb", 21)
16 obj2 = new Foo("xxx", 23)
17 console.log(obj1.getInfo())  // wyb21
18 console.log(obj2.getInfo())  // xxx23

上面的代碼和下面的代碼同理:

 1 //  構造函數
 2 function Foo (name, age) {
 3     this.Name = name
 4     this.Age = age
 5 }
 6 
 7 //  指定原型
 8 Foo.prototype = {
 9     getInfo: function(){
10          return this.Name + this.Age
11     },
12     func: function(arg){
13         return this.Name + arg
14     },   
15 }
16 
17 obj1 = new Foo("wyb", 21)
18 obj2 = new Foo("xxx", 23)
19 console.log(obj1.getInfo())  // wyb21
20 console.log(obj2.getInfo())  // xxx23

以上實現過程底層原理:

 

 

3.繼承

(1)兩種繼承

大多OO語言都支持兩種繼承方式: 接口繼承和實現繼承 ,而ECMAScript中沒法實現接口繼承(因爲函數沒有簽名),ECMAScript只支持實現繼承,並且其實現繼承主要是依靠原型鏈來實現

  • 接口繼承:
  • 實現繼承:

後面是JavaScript實現繼承的幾種方法的詳細說明

 

(2)原型鏈

核心: 將父類的實例做爲子類的原型

基本思想:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法

構造函數,原型,實例之間的關係:每一個構造函數都有一個原型對象,原型對象包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針

原型鏈實現繼承例子:

 1 var SuperType = function() {
 2     this.property = true;
 3 };
 4 
 5 SuperType.prototype.getSuperValue = function() {
 6     return this.property;
 7 }
 8 
 9 function SubType() {
10     this.subproperty = false;
11 }
12 
13 //繼承了SuperType
14 SubType.prototype = new SuperType();
15 SubType.prototype.getSubValue = function (){
16   return this.subproperty;
17 }
18
19 var instance = new SubType();
20 console.log(instance.getSuperValue()); // true
21
console.log(instance.getSubValue()); // false

說明:

上述繼承是經過建立SuperType的實例,並將該實例賦給SubType.prototype實現,實現的本質上重寫原型(實例都包含一個指向對象內部的指針,把實例賦給原型就是用實例對象來重寫原型)

換句話說,原來存在於SuperType的實例中的全部屬性和方法如今也存在於SubType.prototype中了。以後在肯定繼承關係以後,咱們給SubType.prototype添加了一個方法,而後用Subtype來建立對象,這個實例對象不只僅繼承了SuperType的屬性和方法,也添加了一個新方法,在這個例子中的實例以及構造函數和原型之間的關係以下:

特色:

  • 很是純粹的繼承關係,實例是子類的實例,也是父類的實例
  • 父類新增原型方法/原型屬性,子類都能訪問到
  • 簡單,易於實現

缺點:

  • 要想爲子類新增屬性和方法,必需要在new SuperType()這樣的語句以後執行,不能放到構造器中(不能使用字面量來添加新屬性和新方法,這樣會引發原型鏈斷裂,切斷SubType和SuperType的鏈接)
  • 沒法實現多繼承
  • 來自原型對象的引用屬性是全部實例共享的,這會致使對一個實例的修改會影響另外一個實例(詳細請看下面的代碼實例)
  • 建立子類實例時,沒法向父類構造函數傳參
 1 function SuperType(){
 2       this.colors=["red", "blue", "green"];
 3 }
 4 
 5 function SubType(){
 6 }
 7 
 8 //繼承了SuperType
 9 SubType.prototype=new SuperType();
10 
11 var instance1=new SubType();
12 instance1.colors.push("black");
13 console.log(instance1.colors);   //red,blue,green,black
14 
15 var instance2=new SubType();
16 console.log(instance2.colors);    //red,blue,green,black

 

(3)借用構造函數

在解決原型中包含引用類型值所帶來的問題中,使用借用構造函數技術來解決,另外借用構造函數還能夠在子類型構造函數中向基類構造函數傳遞參數

借用構造函數的基本思想,即在子類型構造函數的內部調用超類型構造函數。函數只不過是在特定環境中執行代碼的對象,所以經過使用apply()和call()方法能夠在新建立的對象上執行構造函數

實例:

 1 function SuperType(){
 2       this.colors=["red", "blue", "green"]
 3 }
 4 
 5 function SubType(){
 6       // 繼承SuperType
 7       SuperType.call(this)      // 調用SuperType的構造函數
 8 }
 9 
10 var instance1=new SubType()
11 instance1.colors.push("black")
12 console.log(instance1.colors)      // red,bllue,green,black
13 
14 var instance2=new SubType()
15 console.log(instance2.colors)      // red,blue,green

傳遞參數:

 1 function SuperType(name){
 2       this.name=name
 3 }
 4 function SubType(){
 5       // 繼承了SuperType,同時還傳遞了參數
 6       SuperType.call(this,"wyb")
 7       // 實例屬性
 8       this.age=22
 9 }
10 var instance=new SubType()
11 console.log(instance.name)       //wyb
12 console.log(instance.age)        //22

借用構造函數存在兩個問題:

  • 沒法避免構造函數模式存在的問題,方法都在構造函數中定義,所以沒法複用函數
  • 在超類型的原型中定義的方法,對子類型而言是不可見的。所以這種技術不多單獨使用

 

(4)組合繼承

組合繼承:將原型鏈和借用構造函數的技術組合到一塊兒

思路:使用原型鏈實現對原型方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。這樣,既經過在原型上定義方法實現了函數的複用,又可以保證每一個實例都有它本身的屬性

實例: 

 1 function SuperType(name){
 2       this.name = name
 3       this.colors = ["red", "blue", "green"]
 4 }
 5 
 6 SuperType.prototype.sayName = function(){
 7       console.log(this.name)
 8 }
 9 
10 function SubType(name, age){
11       //繼承屬性    使用借用構造函數實現對實例屬性的繼承
12       SuperType.call(this,name)
13       this.age = age
14 }
15 
16 //繼承方法     使用原型鏈實現
17 SubType.prototype = new SuperType()
18 SubType.prototype.constructor = SubType
19 SubType.prototype.sayAge = function(){
20       console.log(this.age)
21 }
22 
23 var instance1 = new SubType("wyb", 21)
24 instance1.colors.push("black")
25 console.log(instance1.colors)   // red,blue,green,black
26 instance1.sayName()  // wyb
27 instance1.sayAge()  // 21
28 
29 var instance2 = new SubType("greg", 25)
30 console.log(instance2.colors)   // red,blue,green
31 instance2.sayName()  // greg
32 instance2.sayAge()   // 25

說明:上述代碼中的兩個實例既分別擁有本身的屬性,包括colors屬性,又可使用相同的方法

組合繼承避免了原型鏈和借用構造函數的缺點,融合了他們的優勢,是JavaScript中最經常使用的繼承模式

 

(5)原型式繼承

基本想法:藉助原型能夠基於已有的對象建立新對象,同時還沒必要須所以建立自定義的類型。

原型式繼承的思想可用如下函數來講明:

1 function object(o) {
2     function F(){}
3     F.prototype = o;
4     return new F();
5 }

實例(下面的代碼須要先運行上面的代碼):

 1 var person = {
 2     name: "EvanChen",
 3     friends: ["Shelby","Court","Van"];
 4 }
 5 
 6 var anotherPerson = object(person)
 7 anotherPerson.name = "Greg"
 8 anotherPerson.friends.push("Rob")
 9 
10 var yetAnotherPerson = object(person)
11 yetAnotherPerson.name = "Linda"
12 yetAnotherPerson.friends.push("Barbie")
13 console.log(person.friends)  // "Shelby","Court","Van","Rob","Barbie"

ECMAScript5經過新增Object.create()方法規範化了原型式繼承,這個方法接收兩個參數:一個用做新對象原型的對象和一個做爲新對象定義額外屬性的對象(可選),實例以下:

 1 var person = {
 2     name: "EvanChen",
 3     friends: ["Shelby","Court","Van"]
 4 }
 5 
 6 var anotherPerson = Object.create(person)
 7 anotherPerson.name = "Greg"
 8 anotherPerson.friends.push("Rob")
 9 
10 var yetAnotherPerson = Object.create(person)
11 yetAnotherPerson.name = "Linda"
12 yetAnotherPerson.friends.push("Barbie")
13 console.log(person.friends)  // "Shelby","Court","Van","Rob","Barbie"

缺點:和原型繼承同樣,來自原型對象的引用屬性是全部實例始終都會共享的值

 

(6)寄生式繼承

基本思想:建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後再像真正是它作了全部工做同樣返回對象。 

實例:

 1 function createAnother(original) {
 2     var clone = object(original)         // 經過調用函數建立新對象
 3     clone.sayHi = function () {          // 以某種方式來增長對象
 4         console.log("hi")
 5 }
 6     return clone                               // 返回對象
 7 }
 8 
 9 var person = {
10     name: "EvanChen",
11     friends: ["Shelby","Court","Van"]
12 }
13 
14 var anotherPerson = createAnother(person);
15 anotherPerson.sayHi()  // "hi"

說明:上述實例中的代碼中基於person返回了一個新對象anotherPerson,新對象不只具備person中的全部屬性和方法,並且還有本身的sayHi方法

缺點:不能作到函數複用,這一點和借用構造函數來實現繼承相似

 

(7)寄生組合式繼承 -> 集寄生式繼承和組合繼承的優勢於一身,是實現基於類型繼承的最有效方式

基本思想:經過借用函數來繼承屬性,經過原型鏈的混成形式來繼承方法

基本模型:

1 function inheritProperty(subType, superType) {
2     var prototype = object(superType.prototype)  // 建立對象
3     prototype.constructor = subType  // 加強對象
4     subType.prototype = prototype    // 指定對象
5 }

實例:

 1 function SuperType(name){
 2     this.name = name
 3     this.colors = ["red","blue","green"]
 4 }
 5 SuperType.prototype.sayName = function (){
 6     console.log(this.name)
 7 }
 8 
 9 function SubType(name,age){    
10     SuperType.call(this,name)
11     this.age = age
12 }
13 inheritProperty(SubType,SuperType)
14 SubType.prototype.sayAge = function() {
15     console.log(this.age)
16 }

說明:上述例子的高效率體如今它只調用了一次SuperType構造函數,而且所以避免了在SubType.prototype上建立沒必要要的 多餘的屬性.與此同時,原型鏈還能保持不變.所以,還可以正常使用instanceof 和isPrototypeOf肯定繼承關係

相關文章
相關標籤/搜索