小談js原型鏈和繼承

原型(prototype)在js中但是擔當着舉足輕重的做用,原型的實現則是在原型鏈的基礎上,理解原型鏈的原理後,對原型的使用會更加自如,也能體會到js語言的魅力。函數

 

本文章會涉及的內容this

  • 原型及原型對象
  • 原型鏈(JavaScript核心部分)
  • 類的繼承
  • instanceof
  • constructor

 

咱們先用一個構造器來實現一個構造函數:spa

function A(){
    this.mark = "A";
    this.changeMark = function(){
        this.mark += "_changed";
    }
}

A.prototype.mark2 = "A2";
A.prototype.changeMark2 = function(){
    this.mark2 += "_changed";
}

var a = new A();
var a2 = new A();


//下面則說明構造函數實例化後,分配着不一樣的實例對象,互不相關
console.log(a.mark);  //"A"
console.log(a2.mark); //"A"
    a.changeMark();   //使用實例對象中的方法
console.log(a.mark);  //"A_changed"
console.log(a2.mark); //"A"

//下面則說明了new操做符的一項做用,即將原型中的this指向當前對象,
//在a.changeMark2執行時,changMark2中的方法先找 this.mark2 的值,
//可是實例對象this中沒有mark2值,則在原型鏈(後面會介紹)向上尋找,獲得A原型對象中的mark2值,
//在賦值時,將修改後的值添加在了a實例中。
//總:雖然調用的是prototype方法,可是不會對prototype屬性作修改,只會說是在實例中新增屬性,可是在使用時,會最使用最近獲得的屬性(在後面原型鏈中能夠加以理解)
console.log(a.mark2);  //"A2"
console.log(a2.mark2); //"A2"
    a.changeMark2();   //使用原型鏈中的方法
console.log(a.mark2);  //"A2_changed"
console.log(a2.mark2); //"A2"

 

 

爲何a可使原型中的changeMark2方法?這就和js巧妙的原型鏈相關,在Firefox中咱們能夠打印出對象並可查看到對象下面的__proto__。prototype

咱們把上面的過程用流程圖來表示:code

 

 

只有構造函數纔會有prototype屬性,而實例化出來的對象會擁有__proto__,而不會有prototype對象

 

就像上圖畫的那樣,兩個實例化的對象都經過__proto__屬性指向了A.prototype(即構造函數的原型對象blog

而原型對象的__proto__指向Object對象,就像a.toString()的toString方法就是存在於Object原型對象(Object.prototype)中。繼承

 

so:當使用對象的方法或屬性時,對象會在一步一步經過__proto__向上尋找,找到最近的則是最終的獲取到的方法或屬性ip

  ————這就是js中的原型鏈原型鏈

 

就像圖上看到的同樣,全部對象的原型鏈最終都指向了Object對象,而Object的原型對象(Object.prototype)是爲數很少的不繼承自任何屬性的對象,即Object.prototype沒有__proto__,是原型鏈的頂峯。

 

經過上面咱們能夠了解到,當咱們對A.prototype或Object.prototype添加屬性或方法時,在a和a2實例中都會查看到該屬性或方法,由於這兩個實例都經過原型鏈與A和Object的原型對象相連。

 

 再來看看原型對象和原型鏈在繼承方面的實現:

再構造一個函數A和一個函數B,並讓B繼承A,以下:

function A(mark){
    this.mark = mark;
}
A.prototype.getMark = function(){
    return this.mark;
}

function B(mark){
  this.mark = mark
}

//var temp = new A("A");
//B.prototype = temp;
//上面語句和下語句做用相同

B.prototype = new A("A"); //實例化一個A,其賦值於B.prototype
                //咱們知道了原型鏈,這樣作就是將B的原型對象與A的原型對象經過原型鏈(__proto__)鏈接起來
                //以實現繼承
var b = new B("B"); console.log(b.mark); //B, 結果如上面原型鏈分析的那樣,向上找到最近的屬性,則爲b實例中的mark:"B"

 

 

 

其中的結構示意大概以下圖:

 

 

這時咱們能夠看到,在B.prototype中是沒有constructor的,由於B.prototype只是簡單的new A("A")對象賦值的結果。

在js中的constructor有什麼做用呢?如:

var arr = new Array();
arr instanceof Array;      //true
arr.constructor === Array; //true


function TEMP(){
}
var temp = new TEMP();
temp instanceof TEMP;      //true
temp.constructor === TEMP; //true

 

 

 引用《JavaScript權威指南》中的對於constructor的解釋爲:對象一般繼承的constructor均指代它們的構造函數,而構造函數是類的「公共標識」。constructor可用來判斷對象所屬的類

 

在上面的小例子中,用instanceof也可判斷對象的類,可是有自身的缺陷,instanceof的實現方法爲:

instanceof不會去檢查temp是否是由TEMP()構造函數初始化的,面是判斷temp是否繼承自TEMP.prototype,這樣,範圍就寬了不少。

如在上面的大例中,使用

b instaceof B //true 由於在b的原型鏈中能夠找到B.prototype對象

b instaceof A //true 在b的原型鏈中也能夠找到A.prototype對象

 

能夠說instanceof是用來檢測繼承關係的。

而當

console.log(b.constructor) //function A()
//由於在b的原型鏈中,最近的constructor就是A.prototype中有constructor指向了構造函數A();

 

但咱們知道的b是屬於B類的,那最後因此要作的就是:

B.prototype.constructor = B; //將constructor指向自身的構造函數


var new_b = new B("B");
console.log(new_b.constructor) //function B() 

 這樣,一個完整的類繼承才完成了。

 

 

最後附上一個完整繼承後的結果圖: 

 

 

第一次寫長文,若有問題,還請你們指出。

轉載請註明出處,謝謝。

 

Finish.

相關文章
相關標籤/搜索