JavaScript原型和麪向對象

原型(prototype)的來源:

  雖然JavaScript和Java並沒什麼關係,但是JavaScript在開始創建的時候想要模仿Java 通過new 操作符實現對一些方法和屬性的共享。但是在es6 之前JavaScript並沒有真正類 (class)的概念,所以JavaScript借鑑了其他語言(self , io, lua等)的原型機制。 簡單點說就是在JavaScript中每個函數都可以是一個構造器,每個函數被創建的時候都會有一個prototype ,而函數通過new 操作符可以產生自己的實例對象,這個實例對象會繼承父類prototype 上的所有方法 。

原型的構成:

  原型的本質其實是個對象字面量,主要由三部分構成 : constructor (構造器) , 原型方法 ,_ proto_ (原型鏈)

這裏寫圖片描述

  上圖是以數組爲例,因爲所有的數組都是Array的實例產出,所以所有的數組都能用上邊那些原型方法。

  constructor :構造器,指向的是當前擁有這個原型的那個類(函數)。

  _ proto_ : 原型鏈索引,指向自己的父類,也就是說指向的是當前這個原型的爸爸。 一個被new 出來的實例對象到底能使用什麼方法不只是取決是它的爸爸,還有他的爺爺,太爺爺….. , 也就是說如果只要是老祖宗給它繼承下來的東西它都是能用的 。_ proto_這個東西就是用來翻族譜的,兒子的 _ proto_會指向爸爸,爸爸的_ proto_會指向爺爺,以此類推。下邊來看個例子。如果實例對象某個自定義的方法或屬性跟prototype 上重名的話,會以這個實例對象自定義的方法或屬性返回。所以這也是一種非常靈活的改寫。

這裏寫圖片描述

  在頁面創建了一個div dom實例對象

  看一下這個實例對象的 _ proto_ ,找到了它的爸爸 HTMLDivElement

這裏寫圖片描述

展開HTMLDivElement這個對象的_ proto_,找到了它的爺爺 HTMLElement ,

這裏寫圖片描述

往下翻翻發現它爺爺居然也有個 _ proto_ 指向了Element

這裏寫圖片描述

  接着往下找下去你會發現還有 Node 、 EventTarget 不過最後指向的是Object 到這裏纔算結束了。 這個原型上的方法我們的 box 實例對象都能使用,因爲是繼承關係。咱們這個往上級找爸爸的過程形成了一條鏈路就是原型鏈。總結來說,當訪問一個對象的屬性的時候,JavaScript會從對象本身開始往上遍歷整個原型鏈,如果到達了原型鏈的頂部,也就是Object.prototype仍然未發現需要查找的屬性,那麼JavaScript就會返回undefined .

JavaScript 面向對象:

  不管是任何語言中面向對象有三個要素: 封裝、繼承 、多態。 作爲一個以函數作爲一等對象的語言,封裝在JavaScript中隨處可見,看一個封裝的例子,順便了解幾個概念:

var Book = function(id,name){
  // 私有屬性
 var num=1;
//私有方法
function()  checkId{
}
// 特權方法
this.setName = fucntion(name){}
//特權屬性
this.type = "book"
}

Book.method2 = function(){}   // 類方法 
Book.name  = "hello"  //類屬性

  上邊的私有屬性和方法就是一個簡單的封裝,只有內部可以訪問到。
多態 :就是同一種方法多種調用方式,簡單的例子是根據參數不同執行不同代碼,或者根據this類型不同,執行不同代碼,不展開了。
繼承是大家問到比較多的地方,這裏要敲一下黑板的:
上邊已經說過了JavaScript開始創建的時候就考慮到了一些方法和屬性共享的情景,並且創建了原型。所以我們實現繼承的方式必然和原型脫不開聯繫。

  現在有兩個函數 A 和B ,如果B想繼承A的原型方法應該怎麼做?

情景一 :簡單暴力的
function A(){}
A.prototype = {
  aaa:1
}
function B(){}
B.prototype = A.prototype ;
var  b = new B();
b.aaa  // 1
A.prototype.bbb = 2;
b.bbb  // 2

這個例子暴露出來最大的問題是不靈活,因爲上邊說過prototype是個對象字面量,直接賦值會傳遞引用地址。好比兩個人共用了一個u盤,其中一個人對裏邊的文件進行了操作另一個使用時也會發現變化。所以這是不可取的

情景二 : 模仿一下

  既然直接賦值原型不行。那我們只把別人原型上的方法和屬性拷貝過來不就可以了嗎?就是說我們再拿一個u盤,把裏邊的文件拷貝一份出來,大家自己用自己的這樣就能互不干擾了。

function A(){}  
A.prototype= {aaa:1 }
fucntion B(){}
for (var property in A.prototype){
  B.prototype[property] = A.prototype[property]
}
var b = new B();
A.prototype.aaa = 2;
b.aaa;     // 1

  這樣看起來似乎就比較靈活了,但是這樣似乎只繼承了A的原型方法,假如A擁有特權方法和屬性怎麼辦? 例如下面這種的

function A (name){
   this.name =name;
   this.sayHey =fucntion(){
        console.log("hello");
}
}

  所以還得把特權方法和特權屬性繼承過來; 這就需要用到兩個方法 call 和apply。
這兩個方法功能相似,調用的對象都是個函數,第一個參數都代表執行調用函數的對象,後邊傳入的參數代表調用函數的參數;區別主要在於call 方法參數是單個傳入,apply 方法可以直接傳入一個數組 。
要在B函數內執行A函數裏的代碼。

function  A(name,age){
   this.name= name;
   this.age = age;
}
function B(name,age){
   A.call(this,name,age)   或  A.apply(this,arguments)
// 執行上邊這行代碼相當於
// this.name = name;
// this.age = age;
}

  所以要想實現特權屬性、方法和原型屬性、方法都繼承就得結合一下


function B(){
 A.apply(this,arguments);
}
for (var property in A.prototype){
  B.prototype[property] = A.prototype[property]
}
情景三 :並不完美

  似乎一切看起來都妥了,我們說過原型是由三部分構成的,其中有個比較關鍵的部分叫constructor (構造器), 它會指向當前這個原型的所屬函數。還有個_ proto_會指向自己的父類原型。 採用直接複製的方式會把constructor和_ proto_屬性也複製過來,雖然並不影響功能使用,但是會使得_ proto_在追溯自己的爸爸的時候迷失方向的,有可能會認賊作父。所以採用上邊那種方式,在複製完後還需要將constructor和_ protp_重新調整一下。

  好在有個牛人(道格拉斯. 克羅克福)發明了更簡單的方法- 原型鏈繼承 (上邊那種叫拷貝繼承)

// 父類
function Parent (name,age){
this.name = name;
this.age = age;
}
//子類
fuction Children(){}
//中間類
function F(){}
F.prototype = Parent.prototype ;
Children.prototype = new F();

  在進行new 操作的時候實際是將 Children的 _ proto_指向F.prototype,而F.prototype又和Parent.prototype相同。所以Children的實例在使用方法和屬性的時候能使用到Parent類的方法和屬性。 你可能會疑問爲什麼不直接使用 Children.prototype =new Parent() 呢? 這是因爲在 調用new Parent()的時候會產生一個Parent類的實例,這可能並不是我們想要的。而F作爲中間傳遞者,沒有任何的私有方法、屬性或特權方法、屬性。所以在實例化的時候產生的負面影響幾乎沒有。
當然,最後不能忘了我們還有特權方法和屬性要繼承,所有完美的繼承方式應該是這樣的:

// 父類
function Parent (name,age){
 // 特權屬性
this.name = name;
this.age = age;
this.show =fucntion(){ }
}
//子類
fuction Children(name,age){
   Parent.apply(this,arguments)
}
//中間類
function F(){}
F.prototype = Parent.prototype ;
Children.prototype = new F();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章